1010 * binaries); only the rolling `channel-<name>` manifest differs per channel.
1111 *
1212 * Re-runs are idempotent via `gh release upload --clobber` (see gh-release.mjs).
13- * Optionally notifies BAILIAN_OSS_SYNC_WEBHOOK (see oss-sync-webhook.mjs).
13+ * After the GitHub upload the same assets are pushed straight to OSS from the
14+ * runner, HEAD-reconciled, and (stable only) release/manifest.json is updated —
15+ * all in-process, no external FC (see oss-direct-upload.mjs).
1416 *
1517 * Called by publish-stable.mjs / publish-channel.mjs.
1618 * Debug:
1719 * node tools/release/lib/binary-release.mjs --mode stable --dry-run
1820 * node tools/release/lib/binary-release.mjs --mode channel --channel beta --dry-run
1921 */
2022import { existsSync , readdirSync , readFileSync } from "node:fs" ;
21- import { join , resolve } from "node:path" ;
23+ import { basename , join , resolve } from "node:path" ;
2224import { fileURLToPath } from "node:url" ;
2325import { parseArgs as parseCliArgs } from "node:util" ;
2426import { ROOT , readPackageJson , PACKAGES } from "./packages.mjs" ;
2527import { buildBinaryArtifacts , matrixAssetNames } from "./binary-build.mjs" ;
2628import { channelManifestFileName , normalizeModeChannel } from "./binary-options.mjs" ;
2729import { ensureGh , GITHUB_REPOSITORY , upsertRelease } from "./gh-release.mjs" ;
28- import { notifyOssSyncWebhook } from "./oss-sync-webhook .mjs" ;
30+ import { maintainReleaseManifest , mirrorReleaseAssetsToOss } from "./oss-direct-upload .mjs" ;
2931
3032const DEFAULT_DIR = join ( ROOT , "dist-bin" ) ;
3133
@@ -124,14 +126,31 @@ function planDryRunWithoutArtifacts({ version, mode, channel }) {
124126 } ) ;
125127}
126128
129+ /**
130+ * Build the OSS mirror plan — the same tag/asset pairs as the GitHub Release
131+ * upload. `files == null` means dry-run planning without artifacts on disk,
132+ * so bare basenames stand in for real paths.
133+ */
134+ function ossMirrorPlans ( { dir, version, mode, channel, files } ) {
135+ const paths = files
136+ ? versionBinaryAssets ( dir , version , files )
137+ : [ ...matrixAssetNames ( version ) , "SHA256SUMS" ] ;
138+ const plans = [ { tag : `v${ version } ` , paths } ] ;
139+ if ( mode === "channel" ) {
140+ const manifest = channelManifestFileName ( channel ) ;
141+ plans . push ( { tag : `channel-${ channel } ` , paths : [ files ? join ( dir , manifest ) : manifest ] } ) ;
142+ }
143+ return plans ;
144+ }
145+
127146/**
128147 * Build (unless skipped / dry-run) and upload binary artifacts to GitHub Releases.
129148 * Called by publish-stable / publish-channel orchestrators.
130149 *
131150 * `--dry-run` never compiles; it plans gh release steps. Prebuilt `dist-bin` is
132151 * optional (used only to list real paths when present).
133152 */
134- export function releaseBinaryArtifacts ( rawOptions = { } ) {
153+ export async function releaseBinaryArtifacts ( rawOptions = { } ) {
135154 const { mode, channel } = normalizeModeChannel ( rawOptions . mode , rawOptions . channel ) ;
136155 const dir = rawOptions . dir ? resolve ( rawOptions . dir ) : DEFAULT_DIR ;
137156 const dryRun = Boolean ( rawOptions . dryRun ) ;
@@ -157,7 +176,15 @@ export function releaseBinaryArtifacts(rawOptions = {}) {
157176 if ( dryRun && ! existsSync ( dir ) ) {
158177 process . stdout . write ( `[dry-run] ${ dir } missing; planning expected assets\n` ) ;
159178 planDryRunWithoutArtifacts ( { version, mode, channel } ) ;
160- notifyOssSyncWebhook ( { version, mode, channel, dryRun } ) ;
179+ const plans = ossMirrorPlans ( { dir, version, mode, channel, files : null } ) ;
180+ await mirrorReleaseAssetsToOss ( { plans, dryRun : true } ) ;
181+ if ( mode === "stable" ) {
182+ await maintainReleaseManifest ( {
183+ tag : `v${ version } ` ,
184+ assetNames : plans [ 0 ] . paths . map ( ( path ) => basename ( path ) ) ,
185+ dryRun : true ,
186+ } ) ;
187+ }
161188 return { version, mode, channel, dryRun } ;
162189 }
163190
@@ -198,7 +225,18 @@ export function releaseBinaryArtifacts(rawOptions = {}) {
198225 uploadChannel ( { dir, version, channel, files, dryRun } ) ;
199226 }
200227
201- notifyOssSyncWebhook ( { version, mode, channel, dryRun } ) ;
228+ // Push the exact Release assets straight to OSS from the runner, then
229+ // HEAD-reconcile. Stable releases additionally maintain release/manifest.json
230+ // (newer-version guard). Throws on failure — CI is the only OSS writer.
231+ const plans = ossMirrorPlans ( { dir, version, mode, channel, files } ) ;
232+ const mirror = await mirrorReleaseAssetsToOss ( { plans, dryRun } ) ;
233+ if ( mode === "stable" && ! mirror . skipped ) {
234+ await maintainReleaseManifest ( {
235+ tag : `v${ version } ` ,
236+ assetNames : plans [ 0 ] . paths . map ( ( path ) => basename ( path ) ) ,
237+ dryRun,
238+ } ) ;
239+ }
202240
203241 return { version, mode, channel, dryRun } ;
204242}
@@ -223,7 +261,7 @@ if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
223261 process . stdout . write ( USAGE ) ;
224262 process . exit ( 0 ) ;
225263 }
226- releaseBinaryArtifacts ( {
264+ await releaseBinaryArtifacts ( {
227265 dir : values . dir ? resolve ( values . dir ) : undefined ,
228266 dryRun : values [ "dry-run" ] ,
229267 mode : values . mode ,
0 commit comments