44 * Run with: bun run generate
55 */
66import { execSync } from 'node:child_process' ;
7- import { mkdirSync , readFileSync , writeFileSync } from 'node:fs' ;
7+ import { mkdirSync , readdirSync , readFileSync , writeFileSync } from 'node:fs' ;
8+ import { join } from 'node:path' ;
89
910const SPEC_URL = 'https://roxyapi.com/api/v2/openapi.json' ;
1011const SPEC_PATH = 'specs/openapi.json' ;
@@ -51,6 +52,50 @@ async function loadSpec(): Promise<string> {
5152 return fetchSpec ( SPEC_URL ) ;
5253}
5354
55+ /**
56+ * Sort the leading import block of every generated file by module specifier in code-unit order.
57+ *
58+ * @remarks
59+ * hey-api orders imports with `localeCompare`, and for `./client.gen.js` against `./client/index.js`
60+ * Bun 1.3.14 and Bun 1.4.2 disagree (the same statement lands on either side of the other), so the
61+ * committed output depended on which Bun ran the generator and the hermetic drift check went red on
62+ * CI for a tree that was correct locally. A code-unit sort is the same on every runtime.
63+ */
64+ function sortImportBlocks ( dir : string ) : void {
65+ for ( const entry of readdirSync ( dir , { withFileTypes : true } ) ) {
66+ const path = join ( dir , entry . name ) ;
67+ if ( entry . isDirectory ( ) ) {
68+ sortImportBlocks ( path ) ;
69+ continue ;
70+ }
71+ if ( ! entry . name . endsWith ( '.gen.ts' ) ) continue ;
72+ const src = readFileSync ( path , 'utf8' ) ;
73+ const block = / (?: ^ i m p o r t \b [ ^ ; ] * ; \n ) + / m. exec ( src ) ;
74+ if ( ! block ) continue ;
75+ const statements = block [ 0 ] . split ( / (?< = ; \n ) / ) . filter ( Boolean ) ;
76+ const key = ( stmt : string ) =>
77+ `${ / f r o m ' ( [ ^ ' ] + ) ' / . exec ( stmt ) ?. [ 1 ] ?? '' } \u0000${ stmt } ` ;
78+ const sorted = statements
79+ . map ( ( stmt , index ) => ( { stmt, index } ) )
80+ . sort ( ( a , b ) =>
81+ key ( a . stmt ) < key ( b . stmt )
82+ ? - 1
83+ : key ( a . stmt ) > key ( b . stmt )
84+ ? 1
85+ : a . index - b . index ,
86+ )
87+ . map ( ( { stmt } ) => stmt )
88+ . join ( '' ) ;
89+ if ( sorted !== block [ 0 ] )
90+ writeFileSync (
91+ path ,
92+ src . slice ( 0 , block . index ) +
93+ sorted +
94+ src . slice ( block . index + block [ 0 ] . length ) ,
95+ ) ;
96+ }
97+ }
98+
5499const spec = JSON . parse ( await loadSpec ( ) ) ;
55100
56101// Patch server URL to absolute production URL so the generated default client
@@ -65,6 +110,7 @@ console.log('Spec saved to', SPEC_PATH);
65110
66111console . log ( 'Running hey-api generator...' ) ;
67112execSync ( 'bunx openapi-ts' , { stdio : 'inherit' } ) ;
113+ sortImportBlocks ( 'src' ) ;
68114console . log ( 'SDK generated successfully.' ) ;
69115
70116console . log ( 'Syncing README.md and AGENTS.md from spec...' ) ;
0 commit comments