Skip to content

Commit 059c522

Browse files
committed
fix: sort generated import blocks in code-unit order so the drift check is the same on every Bun runtime
1 parent 0360498 commit 059c522

1 file changed

Lines changed: 47 additions & 1 deletion

File tree

‎scripts/generate.ts‎

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
* Run with: bun run generate
55
*/
66
import { 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

910
const SPEC_URL = 'https://roxyapi.com/api/v2/openapi.json';
1011
const 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 = /(?:^import\b[^;]*;\n)+/m.exec(src);
74+
if (!block) continue;
75+
const statements = block[0].split(/(?<=;\n)/).filter(Boolean);
76+
const key = (stmt: string) =>
77+
`${/from '([^']+)'/.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+
5499
const 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

66111
console.log('Running hey-api generator...');
67112
execSync('bunx openapi-ts', { stdio: 'inherit' });
113+
sortImportBlocks('src');
68114
console.log('SDK generated successfully.');
69115

70116
console.log('Syncing README.md and AGENTS.md from spec...');

0 commit comments

Comments
 (0)