66 *
77 * Options:
88 * --help, -h Show this help message
9- * --branch, -b Specify branch/tag to sync from (default: v24.x-staging)
9+ * --branch, -b Specify branch/tag to sync from. When omitted, resolves
10+ * the highest vNN.x-staging branch on nodejs/node via the
11+ * GitHub API (falls back to FALLBACK_STAGING_BRANCH below
12+ * on API failure).
1013 * --repo, -r Specify GitHub repository (default: nodejs/node)
1114 * --dry-run Show what files would be downloaded without actually downloading
1215 *
@@ -26,6 +29,13 @@ import { githubFetch } from "./github-api";
2629
2730const execAsync = promisify ( exec ) ;
2831
32+ // Last-resort default when the GitHub API is unreachable (rate limit, offline,
33+ // etc.). Bump this when a new Node.js major's staging branch becomes the
34+ // supported compatibility target. We sync from `vNN.x-staging` rather than
35+ // `main` so we stay aligned with the stabilized view of the current release
36+ // line; `main` includes the next major's in-flight work.
37+ const FALLBACK_STAGING_BRANCH = "v25.x-staging" ;
38+
2939const __filename = fileURLToPath ( import . meta. url ) ;
3040const __dirname = path . dirname ( __filename ) ;
3141const packageRoot = path . join ( __dirname , ".." ) ;
@@ -67,9 +77,15 @@ const filesToSync: SyncedFile[] = [
6777
6878// Parse command line arguments
6979function parseArgs ( ) {
70- const args = {
80+ const args : {
81+ help : boolean ;
82+ branch : string | null ;
83+ repo : string ;
84+ dryRun : boolean ;
85+ force : boolean ;
86+ } = {
7187 help : false ,
72- branch : "v25.x-staging" ,
88+ branch : null ,
7389 repo : "nodejs/node" ,
7490 dryRun : false ,
7591 force : false ,
@@ -121,6 +137,45 @@ function parseArgs() {
121137 return args ;
122138}
123139
140+ /**
141+ * Resolve the `vNN.x-staging` branch corresponding to the latest RELEASED
142+ * Node.js major. We deliberately don't pick the highest existing staging
143+ * branch: Node.js creates `vNN.x-staging` well before `vNN.0.0` actually
144+ * ships, so a naive "highest branch" pick would drag us into in-flight
145+ * next-major work. Our compat promise is against released Node.js, so we
146+ * follow the staging branch for the current stable line.
147+ *
148+ * Returns FALLBACK_STAGING_BRANCH if the API call fails (rate limit,
149+ * network, unexpected response shape).
150+ */
151+ async function resolveLatestStagingBranch ( repo : string ) : Promise < string > {
152+ try {
153+ // /releases/latest returns the newest non-prerelease, non-draft release
154+ // across all release lines. For nodejs/node that's effectively the
155+ // latest "current" release (not LTS), which is what our compat target
156+ // tracks.
157+ const url = `https://api.github.com/repos/${ repo } /releases/latest` ;
158+ const res = await githubFetch ( url , { logRateLimit : false } ) ;
159+ if ( ! res . ok ) {
160+ throw new Error ( `HTTP ${ res . status } : ${ res . statusText } ` ) ;
161+ }
162+ const release = ( await res . json ( ) ) as { tag_name ?: string } ;
163+ const match = release . tag_name ?. match ( / ^ v ( \d + ) \. / ) ;
164+ if ( ! match ) {
165+ throw new Error (
166+ `Unexpected latest-release tag shape: ${ release . tag_name } ` ,
167+ ) ;
168+ }
169+ return `v${ match [ 1 ] } .x-staging` ;
170+ } catch ( err : any ) {
171+ console . log (
172+ `Warning: Could not resolve latest staging branch (${ err . message } ); ` +
173+ `falling back to ${ FALLBACK_STAGING_BRANCH } ` ,
174+ ) ;
175+ return FALLBACK_STAGING_BRANCH ;
176+ }
177+ }
178+
124179function showHelp ( ) {
125180 const help = `
126181Sync Node.js SQLite implementation files from GitHub into this package.
@@ -130,7 +185,9 @@ Usage:
130185
131186Options:
132187 --help, -h Show this help message
133- --branch, -b Specify branch/tag to sync from (default: v24.x-staging)
188+ --branch, -b Specify branch/tag to sync from. When omitted, resolves
189+ the highest vNN.x-staging branch on nodejs/node via the
190+ GitHub API (fallback: ${ FALLBACK_STAGING_BRANCH } ).
134191 --repo, -r Specify GitHub repository (default: nodejs/node)
135192 --dry-run Show what files would be downloaded without actually downloading
136193 --force, -f Force sync even if current version is newer
@@ -319,9 +376,14 @@ async function main() {
319376 return ;
320377 }
321378
379+ const branch = args . branch ?? ( await resolveLatestStagingBranch ( args . repo ) ) ;
380+ if ( ! args . branch ) {
381+ console . log ( `Resolved default branch: ${ branch } ` ) ;
382+ }
383+
322384 console . log ( `Syncing Node.js SQLite files from GitHub` ) ;
323385 console . log ( `Repository: ${ args . repo } ` ) ;
324- console . log ( `Branch/Tag: ${ args . branch } ` ) ;
386+ console . log ( `Branch/Tag: ${ branch } ` ) ;
325387 console . log ( `Package root: ${ packageRoot } ` ) ;
326388
327389 if ( args . dryRun ) {
@@ -338,7 +400,7 @@ async function main() {
338400 // Fetch Node.js version and commit info
339401 try {
340402 // Get commit SHA using authenticated fetch
341- const commitUrl = `https://api.github.com/repos/${ args . repo } /commits/${ args . branch } ` ;
403+ const commitUrl = `https://api.github.com/repos/${ args . repo } /commits/${ branch } ` ;
342404 const commitResponse = await githubFetch ( commitUrl ) ;
343405
344406 if ( commitResponse . ok ) {
@@ -349,12 +411,12 @@ async function main() {
349411 // Only parse node_version.h for release tags (e.g., v25.8.1), not staging
350412 // branches — staging branches have version numbers bumped ahead of the
351413 // actual release, so the parsed version would be misleading.
352- const isReleaseTag = / ^ v \d + \. \d + \. \d + $ / . test ( args . branch ) ;
414+ const isReleaseTag = / ^ v \d + \. \d + \. \d + $ / . test ( branch ) ;
353415
354416 if ( isReleaseTag ) {
355- nodeVersion = args . branch ;
417+ nodeVersion = branch ;
356418 } else {
357- const versionRef = nodeCommitSha || args . branch ;
419+ const versionRef = nodeCommitSha || branch ;
358420 const versionUrl = `https://raw.githubusercontent.com/${ args . repo } /${ versionRef } /src/node_version.h` ;
359421 const versionResponse = await fetch ( versionUrl ) ;
360422 if ( versionResponse . ok ) {
@@ -372,7 +434,7 @@ async function main() {
372434 if ( majorMatch && minorMatch && patchMatch ) {
373435 // Use the branch name, not the (potentially unreleased) version
374436 // from the header. The commit SHA suffix provides traceability.
375- nodeVersion = args . branch ;
437+ nodeVersion = branch ;
376438 }
377439 }
378440 }
@@ -383,13 +445,13 @@ async function main() {
383445 }
384446
385447 console . log (
386- `Node.js version: ${ nodeVersion || "unknown" } (${ nodeCommitSha ?. substring ( 0 , 7 ) || args . branch } )` ,
448+ `Node.js version: ${ nodeVersion || "unknown" } (${ nodeCommitSha ?. substring ( 0 , 7 ) || branch } )` ,
387449 ) ;
388450
389451 // Check if we should skip the entire sync based on SHA
390452 if (
391453 nodeCommitSha &&
392- shouldSkipSync ( args . repo , args . branch , nodeCommitSha , args . force )
454+ shouldSkipSync ( args . repo , branch , nodeCommitSha , args . force )
393455 ) {
394456 console . log ( "✅ No sync needed - files are already up to date" ) ;
395457 return ;
@@ -399,7 +461,7 @@ async function main() {
399461
400462 for ( const file of filesToSync ) {
401463 // Use commit SHA for consistency, fallback to branch if SHA not available
402- const ref = nodeCommitSha || args . branch ;
464+ const ref = nodeCommitSha || branch ;
403465 const url = `https://raw.githubusercontent.com/${ args . repo } /${ ref } /${ file . src } ` ;
404466 const destPath = path . join ( packageRoot , file . dest ) ;
405467
@@ -434,7 +496,7 @@ async function main() {
434496 // Always update Node.js version if we have it
435497 try {
436498 const nodeVersionString =
437- ( nodeVersion || args . branch ) +
499+ ( nodeVersion || branch ) +
438500 ( nodeCommitSha ? `@${ nodeCommitSha . substring ( 0 , 7 ) } ` : "" ) ;
439501 await execAsync ( `npm pkg set versions.nodejs="${ nodeVersionString } "` , {
440502 cwd : packageRoot ,
@@ -479,7 +541,7 @@ async function main() {
479541
480542 // Update sync cache with the current SHA
481543 if ( nodeCommitSha ) {
482- updateSyncCache ( args . repo , args . branch , nodeCommitSha ) ;
544+ updateSyncCache ( args . repo , branch , nodeCommitSha ) ;
483545 }
484546
485547 console . log ( "\n✅ Sync complete!" ) ;
0 commit comments