44 * Run with: bun run generate
55 */
66import { execSync } from 'node:child_process' ;
7- import { mkdirSync , writeFileSync } from 'node:fs' ;
7+ import { mkdirSync , readFileSync , writeFileSync } from 'node:fs' ;
88
99const SPEC_URL = 'https://roxyapi.com/api/v2/openapi.json' ;
1010const SPEC_PATH = 'specs/openapi.json' ;
@@ -30,8 +30,28 @@ async function fetchSpec(url: string, attempts = 5): Promise<string> {
3030 }
3131}
3232
33- console . log ( 'Fetching OpenAPI spec from' , SPEC_URL ) ;
34- const spec = JSON . parse ( await fetchSpec ( SPEC_URL ) ) ;
33+ /**
34+ * Load the spec from disk when `ROXYAPI_SPEC_FILE` is set, from the API otherwise.
35+ *
36+ * @remarks
37+ * Reading from a file keeps generation offline and byte-reproducible, which is what the codegen
38+ * drift check in CI relies on.
39+ */
40+ async function loadSpec ( ) : Promise < string > {
41+ const file = process . env . ROXYAPI_SPEC_FILE ;
42+ if ( file ) {
43+ console . log (
44+ 'Reading OpenAPI spec from' ,
45+ file ,
46+ '(offline, ROXYAPI_SPEC_FILE)' ,
47+ ) ;
48+ return readFileSync ( file , 'utf8' ) ;
49+ }
50+ console . log ( 'Fetching OpenAPI spec from' , SPEC_URL ) ;
51+ return fetchSpec ( SPEC_URL ) ;
52+ }
53+
54+ const spec = JSON . parse ( await loadSpec ( ) ) ;
3555
3656// Patch server URL to absolute production URL so the generated default client
3757// works out of the box without users specifying baseUrl
0 commit comments