@@ -7,6 +7,15 @@ import { fileURLToPath } from "url";
77const ROOT = path . resolve ( fileURLToPath ( import . meta. url ) , "../../" ) ;
88const TMP = path . join ( ROOT , ".tmp" ) ;
99const OUT = path . join ( ROOT , "docs/reference" ) ;
10+ const SCHEMAS_PUBLIC = path . join ( ROOT , "docs/public/schemas/anolis" ) ;
11+ const PIN_FILE = path . join ( ROOT , "schemas/anolis-version.json" ) ;
12+
13+ const pin = JSON . parse ( await fs . readFile ( PIN_FILE , "utf-8" ) ) ;
14+ const ANOLIS_VERSION = pin . anolis_version ;
15+ const ANOLIS_PROTOCOL_VERSION = pin . anolis_protocol_version ;
16+
17+ if ( ! ANOLIS_VERSION ) { console . error ( `ERROR: anolis_version missing in ${ PIN_FILE } ` ) ; process . exit ( 1 ) ; }
18+ if ( ! ANOLIS_PROTOCOL_VERSION ) { console . error ( `ERROR: anolis_protocol_version missing in ${ PIN_FILE } ` ) ; process . exit ( 1 ) ; }
1019
1120const repos = JSON . parse (
1221 await fs . readFile ( path . join ( ROOT , "data/repos.json" ) , "utf-8" )
@@ -53,13 +62,16 @@ function relPosix(base, absPath) {
5362 return path . relative ( base , absPath ) . split ( path . sep ) . join ( "/" ) ;
5463}
5564
56- function ensureCheckout ( repo ) {
57- const checkout = path . join ( TMP , repo . name ) ;
65+ function ensureCheckout ( repo , tag ) {
66+ const dirName = tag ? `${ repo . name } -${ tag } ` : repo . name ;
67+ const checkout = path . join ( TMP , dirName ) ;
5868 if ( ! existsSync ( checkout ) ) {
59- console . log ( `Reference: cloning ${ repo . repo } ` ) ;
60- execFileSync ( "git" , [ "clone" , "--depth=1" , `https://github.com/${ repo . repo } .git` , checkout ] , {
61- stdio : "inherit" ,
62- } ) ;
69+ const label = tag ? `${ repo . repo } @v${ tag } ` : repo . repo ;
70+ console . log ( `Reference: cloning ${ label } ` ) ;
71+ const cloneArgs = tag
72+ ? [ "clone" , "--depth=1" , "--branch" , `v${ tag } ` , `https://github.com/${ repo . repo } .git` , checkout ]
73+ : [ "clone" , "--depth=1" , `https://github.com/${ repo . repo } .git` , checkout ] ;
74+ execFileSync ( "git" , cloneArgs , { stdio : "inherit" } ) ;
6375 }
6476 return checkout ;
6577}
@@ -160,36 +172,23 @@ function protoSymbols(text) {
160172}
161173
162174async function generateAnolisRuntimeReference ( repo ) {
163- const repoRoot = ensureCheckout ( repo ) ;
164- const specRoot = path . join ( repoRoot , repo . specPath ) ;
165-
166- if ( ! ( await exists ( specRoot ) ) ) {
167- fail ( `Reference input missing for ${ repo . name } : ${ repo . specPath } ` ) ;
168- }
169-
170- const allFiles = await walkFiles ( repoRoot ) ;
171- const openApiCandidates = allFiles
172- . filter ( ( f ) => / \. ( y a ? m l | j s o n ) $ / i. test ( f ) )
173- . filter ( ( f ) => / o p e n a p i / i. test ( path . basename ( f ) ) )
174- . filter ( ( f ) => / s c h e m a s [ \\ / ] + h t t p | d o c s [ \\ / ] + h t t p / i. test ( f ) )
175- . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
175+ // Use the already-injected OpenAPI file rather than cloning anolis at floating
176+ // main. This keeps the reference page consistent with the pinned schema version
177+ // declared in schemas/anolis-version.json.
178+ const injectedOpenApi = path . join ( SCHEMAS_PUBLIC , "http" , "runtime-http.openapi.v0.yaml" ) ;
176179
177- if ( openApiCandidates . length === 0 ) {
180+ if ( ! ( await exists ( injectedOpenApi ) ) ) {
178181 fail (
179- `No OpenAPI file found for ${ repo . name } . Checked repo for filenames containing 'openapi' under HTTP-related paths.`
182+ `Injected OpenAPI file not found at ${ injectedOpenApi } .\n` +
183+ `Run scripts/inject-schemas.sh first (or check schemas/anolis-version.json).`
180184 ) ;
181185 }
182186
183- const openApiFile = openApiCandidates [ 0 ] ;
184- const openApiText = await fs . readFile ( openApiFile , "utf-8" ) ;
187+ const openApiText = await fs . readFile ( injectedOpenApi , "utf-8" ) ;
185188 const summary = parseOpenApi ( openApiText ) ;
186189
187- const specFiles = ( await walkFiles ( specRoot ) )
188- . filter ( ( f ) => / \. ( m d | y a ? m l | j s o n ) $ / i. test ( f ) )
189- . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
190-
191- const repoSha = shortSha ( repoRoot ) ;
192- const openApiRel = relPosix ( repoRoot , openApiFile ) ;
190+ const openApiRel = "schemas/http/runtime-http.openapi.v0.yaml" ;
191+ const releaseTag = `v${ ANOLIS_VERSION } ` ;
193192
194193 const endpointRows =
195194 summary . pathMethods . length === 0
@@ -209,29 +208,21 @@ async function generateAnolisRuntimeReference(repo) {
209208 ...summary . schemas . map ( ( s ) => `| \`${ s } \` |` ) ,
210209 ] . join ( "\n" ) ;
211210
212- const sourcesRows =
213- specFiles . length === 0
214- ? "_No files found under configured spec path._"
215- : [
216- "| Source File |" ,
217- "|---|" ,
218- ...specFiles . map ( ( f ) => {
219- const rel = relPosix ( repoRoot , f ) ;
220- return `| [\`${ rel } \`](https://github.com/${ repo . repo } /blob/main/${ rel } ) |` ;
221- } ) ,
222- ] . join ( "\n" ) ;
211+ const sourcesRows = `[\`${ openApiRel } \`](https://github.com/${ repo . repo } /blob/${ releaseTag } /${ openApiRel } )` ;
223212
224213 const body = [
225214 "# Runtime HTTP API Reference" ,
226215 "" ,
227- `Generated from [${ repo . repo } ](https://github.com/${ repo . repo } ) @ \`${ repoSha } \`. ` +
228- `Configured spec path: \`${ repo . specPath } \`.` ,
216+ `Schema version: [${ releaseTag } ](https://github.com/${ repo . repo } /releases/tag/${ releaseTag } ) — ` +
217+ `source [\`${ openApiRel } \`](https://github.com/${ repo . repo } /blob/${ releaseTag } /${ openApiRel } ).` ,
218+ "" ,
219+ `> Pin: \`schemas/anolis-version.json\` → \`anolis_version: ${ ANOLIS_VERSION } \`` ,
229220 "" ,
230221 "## OpenAPI Summary" ,
231222 "" ,
232223 `- Title: **${ summary . title } **` ,
233224 `- Version: **${ summary . version } **` ,
234- `- Source: [\` ${ openApiRel } \`](https://github.com/ ${ repo . repo } /blob/main/ ${ openApiRel } ) ` ,
225+ `- Source: ${ sourcesRows } ` ,
235226 "" ,
236227 "## Endpoints" ,
237228 "" ,
@@ -241,10 +232,6 @@ async function generateAnolisRuntimeReference(repo) {
241232 "" ,
242233 schemaRows ,
243234 "" ,
244- "## Reference Sources" ,
245- "" ,
246- sourcesRows ,
247- "" ,
248235 ] . join ( "\n" ) ;
249236
250237 return {
@@ -256,8 +243,11 @@ async function generateAnolisRuntimeReference(repo) {
256243}
257244
258245async function generateAdppReference ( repo ) {
259- const repoRoot = ensureCheckout ( repo ) ;
246+ // Clone at the pinned tag rather than floating main so the reference stays
247+ // consistent with schemas/anolis-version.json.
248+ const repoRoot = ensureCheckout ( repo , ANOLIS_PROTOCOL_VERSION ) ;
260249 const specRoot = path . join ( repoRoot , repo . specPath ) ;
250+ const releaseTag = `v${ ANOLIS_PROTOCOL_VERSION } ` ;
261251
262252 if ( ! ( await exists ( specRoot ) ) ) {
263253 fail ( `Reference input missing for ${ repo . name } : ${ repo . specPath } ` ) ;
@@ -297,14 +287,12 @@ async function generateAdppReference(repo) {
297287 } ) ;
298288 }
299289
300- const repoSha = shortSha ( repoRoot ) ;
301-
302290 const fileTable = [
303291 "| Proto File | Package | Messages | Enums | Services | RPCs |" ,
304292 "|---|---|---:|---:|---:|---:|" ,
305293 ...fileSummaries . map (
306294 ( f ) =>
307- `| [\`${ f . rel } \`](https://github.com/${ repo . repo } /blob/main /${ f . rel } ) | \`${ f . packageName } \` | ` +
295+ `| [\`${ f . rel } \`](https://github.com/${ repo . repo } /blob/${ releaseTag } /${ f . rel } ) | \`${ f . packageName } \` | ` +
308296 `${ f . messages . length } | ${ f . enums . length } | ${ f . services . length } | ${ f . rpcs . length } |`
309297 ) ,
310298 ] . join ( "\n" ) ;
@@ -315,8 +303,10 @@ async function generateAdppReference(repo) {
315303 const body = [
316304 "# ADPP Protocol Reference" ,
317305 "" ,
318- `Generated from [${ repo . repo } ](https://github.com/${ repo . repo } ) @ \`${ repoSha } \`. ` +
319- `Configured spec path: \`${ repo . specPath } \`.` ,
306+ `Schema version: [${ releaseTag } ](https://github.com/${ repo . repo } /releases/tag/${ releaseTag } ) — ` +
307+ `proto path \`${ repo . specPath } \`.` ,
308+ "" ,
309+ `> Pin: \`schemas/anolis-version.json\` → \`anolis_protocol_version: ${ ANOLIS_PROTOCOL_VERSION } \`` ,
320310 "" ,
321311 "## Overview" ,
322312 "" ,
@@ -389,6 +379,108 @@ async function generateGenericSpecReference(repo) {
389379 } ;
390380}
391381
382+ async function generateSchemasReference ( ) {
383+ // Read each injected schema file to extract $id, title, and description.
384+ const schemas = [
385+ {
386+ file : path . join ( SCHEMAS_PUBLIC , "runtime" , "runtime-config.schema.json" ) ,
387+ url : "/schemas/anolis/runtime/runtime-config.schema.json" ,
388+ label : "Runtime Config" ,
389+ fallbackDesc : "JSON Schema for anolis-runtime YAML config files." ,
390+ } ,
391+ {
392+ file : path . join ( SCHEMAS_PUBLIC , "machine" , "machine-profile.schema.json" ) ,
393+ url : "/schemas/anolis/machine/machine-profile.schema.json" ,
394+ label : "Machine Profile" ,
395+ fallbackDesc : "JSON Schema for machine package manifests." ,
396+ } ,
397+ {
398+ file : path . join ( SCHEMAS_PUBLIC , "telemetry" , "telemetry-timeseries.schema.v1.json" ) ,
399+ url : "/schemas/anolis/telemetry/telemetry-timeseries.schema.v1.json" ,
400+ label : "Telemetry Timeseries" ,
401+ fallbackDesc : "JSON Schema for telemetry export time-series records." ,
402+ } ,
403+ ] ;
404+
405+ const rows = [ ] ;
406+ for ( const s of schemas ) {
407+ let desc = s . fallbackDesc ;
408+ let id = s . url ;
409+ if ( await exists ( s . file ) ) {
410+ try {
411+ const raw = JSON . parse ( await fs . readFile ( s . file , "utf-8" ) ) ;
412+ if ( raw . description ) desc = raw . description ;
413+ if ( raw . $id ) id = raw . $id ;
414+ } catch {
415+ // keep fallback
416+ }
417+ } else {
418+ console . warn ( ` Warning: injected schema not found: ${ s . file } ` ) ;
419+ }
420+ rows . push ( { label : s . label , id, desc } ) ;
421+ }
422+
423+ const releaseTag = `v${ ANOLIS_VERSION } ` ;
424+ const baseUrl = "https://anolishq.github.io" ;
425+
426+ const tableRows = rows . map (
427+ ( r ) => `| [${ r . label } ](${ r . id } ) | \`${ r . id } \` | ${ r . desc } |`
428+ ) ;
429+
430+ const body = [
431+ "# Schema Reference" ,
432+ "" ,
433+ `Canonical JSON Schemas published from [anolishq/anolis](https://github.com/anolishq/anolis) ` +
434+ `[${ releaseTag } ](https://github.com/anolishq/anolis/releases/tag/${ releaseTag } ).` ,
435+ "" ,
436+ `> Pin: \`schemas/anolis-version.json\` → \`anolis_version: ${ ANOLIS_VERSION } \`` ,
437+ "" ,
438+ "These URLs are stable and resolve to the current pinned schema version:" ,
439+ "" ,
440+ "| Schema | \`$id\` / URL | Description |" ,
441+ "|---|---|---|" ,
442+ ...tableRows ,
443+ "" ,
444+ "## OpenAPI Contract" ,
445+ "" ,
446+ `| Contract | URL | Description |` ,
447+ `|---|---|---|` ,
448+ `| Runtime HTTP API | [${ baseUrl } /schemas/anolis/http/runtime-http.openapi.v0.yaml](${ baseUrl } /schemas/anolis/http/runtime-http.openapi.v0.yaml) | OpenAPI 3.x contract for the runtime \`/v0\` HTTP surface. |` ,
449+ "" ,
450+ "## Usage" ,
451+ "" ,
452+ "### VS Code / editor tooling" ,
453+ "" ,
454+ "Add a `yaml.schemas` entry to `.vscode/settings.json`:" ,
455+ "" ,
456+ "```json" ,
457+ "{" ,
458+ ` "yaml.schemas": {` ,
459+ ` "${ baseUrl } /schemas/anolis/runtime/runtime-config.schema.json": "config/anolis-runtime*.yaml",` ,
460+ ` "${ baseUrl } /schemas/anolis/machine/machine-profile.schema.json": "config/**/machine-profile.yaml"` ,
461+ " }" ,
462+ "}" ,
463+ "```" ,
464+ "" ,
465+ "### Python / jsonschema" ,
466+ "" ,
467+ "```python" ,
468+ "import urllib.request, json, jsonschema" ,
469+ `url = \"${ baseUrl } /schemas/anolis/runtime/runtime-config.schema.json\"` ,
470+ "schema = json.loads(urllib.request.urlopen(url).read())" ,
471+ "jsonschema.validate(instance=config, schema=schema)" ,
472+ "```" ,
473+ "" ,
474+ ] . join ( "\n" ) ;
475+
476+ return {
477+ fileName : "schemas.md" ,
478+ title : "Schemas" ,
479+ description : `JSON Schema and OpenAPI contracts published from anolishq/anolis ${ releaseTag } .` ,
480+ body,
481+ } ;
482+ }
483+
392484if ( specRepos . length === 0 ) {
393485 fail ( "No repositories with specPath configured in data/repos.json" ) ;
394486}
@@ -398,6 +490,10 @@ await fs.mkdir(OUT, { recursive: true });
398490
399491const pages = [ ] ;
400492
493+ // Schema reference is not repo-specific — built from injected files + pin.
494+ console . log ( "Reference: generating schemas page" ) ;
495+ pages . push ( await generateSchemasReference ( ) ) ;
496+
401497for ( const repo of specRepos ) {
402498 console . log ( `Reference: generating for ${ repo . name } ` ) ;
403499 if ( repo . name === "anolis" ) {
@@ -427,7 +523,8 @@ const indexBody = [
427523 "" ,
428524 "## Inputs" ,
429525 "" ,
430- ...specRepos . map ( ( r ) => `- \`${ r . name } \`: repo \`${ r . repo } \`, specPath \`${ r . specPath } \`` ) ,
526+ `- \`anolis\`: pinned to \`v${ ANOLIS_VERSION } \` via \`schemas/anolis-version.json\`` ,
527+ `- \`anolis-protocol\`: pinned to \`v${ ANOLIS_PROTOCOL_VERSION } \` via \`schemas/anolis-version.json\`` ,
431528 "" ,
432529] . join ( "\n" ) ;
433530
0 commit comments