2026-07-11 14:08:26 +08:00
/**
2026-07-14 00:40:36 +08:00
* Build the SDK runtime executables and Python node carrier. The fixed
* `@yao-pkg/pkg --sea` route, deploy flags, and artifact layout are owned by
2026-07-19 22:50:49 +08:00
* .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md.
2026-07-14 00:40:36 +08:00
* The staged closure is symlink-free, and whole-tree assets cover Cordis's
* runtime imports that pkg cannot discover statically.
2026-07-11 14:08:26 +08:00
*/
import { spawn } from 'node:child_process'
2026-07-30 01:29:18 +08:00
import { existsSync , statSync } from 'node:fs'
2026-08-10 20:55:02 +08:00
import { chmod , copyFile , cp , mkdir , readFile , rm , writeFile } from 'node:fs/promises'
2026-07-29 22:44:19 +08:00
import { basename , dirname , join , resolve , sep } from 'node:path'
2026-07-11 14:08:26 +08:00
import { parseArgs } from 'node:util'
const root = resolve ( import . meta . dirname , '..' )
2026-07-14 00:40:36 +08:00
/** The closure manifest whose dependencies define the executable. */
2026-07-11 14:08:26 +08:00
const DEPLOY_ROOT_PACKAGE = 'dsh-jsonrpc-agent-pkg'
2026-08-10 21:39:47 +08:00
/** The closed-runtime app entry inside the deployed closure. */
const ENTRY_BIN = 'node_modules/@deepseek-ai/dsh-jsonrpc-demo/lib/packaged-bin.js'
2026-07-11 14:08:26 +08:00
const OUTPUT_BASENAME = 'dsh-jsonrpc-agent-pkg'
2026-07-14 00:40:36 +08:00
/** Default Node major; SEA mode requires at least Node 22. */
2026-07-11 14:08:26 +08:00
const DEFAULT_NODE_RANGE = 'node24'
2026-07-14 00:40:36 +08:00
/** Pinned for reproducible builds. */
2026-07-11 14:08:26 +08:00
const PKG_SPEC = '@yao-pkg/pkg@6.21.0'
const OUT_DIR = 'dist-exe'
2026-07-14 00:40:36 +08:00
/** Python package destination; created when absent. */
2026-07-11 14:08:26 +08:00
const PYTHON_RUNTIME_DIR = 'python/sdk-runtime/src/deepseek_harness_runtime/runtime'
2026-07-14 00:40:36 +08:00
/** The deployed closure doubles as the node-mode carrier. */
2026-07-11 14:08:26 +08:00
const PYTHON_NODE_SUBDIR = 'node'
2026-08-10 20:55:02 +08:00
/** Legacy deploy may hoist peer-specialized workspace packages back here. */
const DEPLOY_SOURCE_NODE_MODULES = 'python/sdk-runtime/node_modules'
2026-07-14 00:40:36 +08:00
/** Documentation excluded from the generated runtime directory. */
2026-07-13 16:34:09 +08:00
const DEPLOY_ONLY_DOCS = [ 'README.md' , 'README.zh.md' , 'README.i18n.yaml' ]
2026-07-11 14:08:26 +08:00
/**
2026-07-14 00:40:36 +08:00
* Whole-tree assets cover Cordis's runtime bare-package imports, which pkg's
* static analysis cannot see. Package manifests are explicit because bare-name
* resolution depends on them.
2026-07-11 14:08:26 +08:00
*/
const ASSET_GLOBS = [
'package.json' ,
'node_modules/**/*.js' ,
'node_modules/**/*.cjs' ,
'node_modules/**/*.mjs' ,
'node_modules/**/package.json' ,
'node_modules/**/*.json' ,
'node_modules/**/*.node' ,
'node_modules/**/*.wasm' ,
]
const PLATFORMS = [ 'linux' , 'macos' ] as const
const ARCHES = [ 'x64' , 'arm64' ] as const
type Platform = ( typeof PLATFORMS ) [ number ]
type Arch = ( typeof ARCHES ) [ number ]
function isPlatform ( value : string ) : value is Platform {
return ( PLATFORMS as readonly string [ ] ) . includes ( value )
}
function isArch ( value : string ) : value is Arch {
return ( ARCHES as readonly string [ ] ) . includes ( value )
}
/**
2026-07-14 16:21:41 +08:00
* A parsed pkg target triple, constructed from `--targets` or the host.
2026-07-11 14:08:26 +08:00
*/
class Target {
private constructor (
2026-07-14 00:40:36 +08:00
/** pkg Node range (`node<major>`). */
2026-07-11 14:08:26 +08:00
readonly nodeRange : string ,
/**
* pkg platform tag. Windows is a documented non-goal
2026-07-19 22:50:49 +08:00
* (.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md).
2026-07-11 14:08:26 +08:00
*/
readonly platform : Platform ,
/** pkg CPU tag. */
readonly arch : Arch ,
) { }
/** The pkg `--targets` spec string `<nodeRange>-<platform>-<arch>`. */
get spec ( ) : string {
return ` ${ this . nodeRange } - ${ this . platform } - ${ this . arch } `
}
/**
2026-07-14 16:21:41 +08:00
* Parse one target spec, rejecting malformed triples and unsupported platform or architecture.
2026-07-11 14:08:26 +08:00
* @param spec - the raw triple, e.g. `node24-linux-x64`.
* @returns the parsed target.
*/
static parse ( spec : string ) : Target {
const parts = spec . split ( '-' )
const [ nodeRange , platform , arch ] = parts
if ( parts . length !== 3 || nodeRange === undefined || platform === undefined || arch === undefined ) {
throw new Error ( ` build-exe-for-python-sdk: target ${ JSON . stringify ( spec ) } must be <nodeRange>-<platform>-<arch>, e.g. node24-linux-x64. ` )
}
if ( ! /^node\d+$/ . test ( nodeRange ) ) {
throw new Error ( ` build-exe-for-python-sdk: target ${ JSON . stringify ( spec ) } : node range must look like node24, got ${ JSON . stringify ( nodeRange ) } . ` )
}
if ( ! isPlatform ( platform ) ) {
2026-07-14 00:40:36 +08:00
throw new Error ( ` build-exe-for-python-sdk: target ${ JSON . stringify ( spec ) } : platform must be one of ${ PLATFORMS . join ( ', ' ) } , got ${ JSON . stringify ( platform ) } . ` )
2026-07-11 14:08:26 +08:00
}
if ( ! isArch ( arch ) ) {
throw new Error ( ` build-exe-for-python-sdk: target ${ JSON . stringify ( spec ) } : arch must be one of ${ ARCHES . join ( ', ' ) } , got ${ JSON . stringify ( arch ) } . ` )
}
return new Target ( nodeRange , platform , arch )
}
/**
2026-07-14 00:40:36 +08:00
* Resolve the host-platform default on Node 24.
2026-07-11 14:08:26 +08:00
* @returns the host target; throws on an unsupported host platform or arch.
*/
static host ( ) : Target {
const platform = process . platform === 'darwin' ? 'macos' : process . platform === 'linux' ? 'linux' : undefined
if ( platform === undefined ) {
throw new Error ( ` build-exe-for-python-sdk: unsupported host platform ${ process . platform } ; pass --targets explicitly. ` )
}
const arch = process . arch === 'x64' || process . arch === 'arm64' ? process.arch : undefined
if ( arch === undefined ) {
throw new Error ( ` build-exe-for-python-sdk: unsupported host arch ${ process . arch } ; pass --targets explicitly. ` )
}
return new Target ( DEFAULT_NODE_RANGE , platform , arch )
}
}
/**
2026-07-14 00:40:36 +08:00
* Validated CLI configuration; construction owns help and parse-error exits.
2026-07-11 14:08:26 +08:00
*/
class BuildCli {
private constructor (
/** Build targets; defaults to the host platform only. */
readonly targets : readonly Target [ ] ,
/** Skip step 1 (`pnpm run build`); lib/ artifacts must already exist. */
readonly skipBuild : boolean ,
/** Print every command and config patch instead of executing. */
readonly dryRun : boolean ,
) { }
/**
2026-07-14 00:40:36 +08:00
* Parse argv. Help exits 0; malformed flags exit 1; invalid or colliding
* targets throw.
2026-07-11 14:08:26 +08:00
* @param argv - the raw arguments (`process.argv.slice(2)`).
* @returns the parsed, validated configuration.
*/
static parse ( argv : string [ ] ) : BuildCli {
let values : ReturnType < typeof BuildCli.parseRaw >
try {
values = BuildCli . parseRaw ( argv )
} catch ( error ) {
console . error ( ` build-exe-for-python-sdk: ${ error instanceof Error ? error.message : String ( error ) } \ n ` )
console . error ( BuildCli . usage ( ) )
process . exit ( 1 )
}
if ( values . help ) {
console . log ( BuildCli . usage ( ) )
process . exit ( 0 )
}
const targets = values . targets === undefined
? [ Target . host ( ) ]
: values . targets . split ( ',' ) . map ( part = > part . trim ( ) ) . filter ( part = > part !== '' ) . map ( spec = > Target . parse ( spec ) )
if ( targets . length === 0 ) throw new Error ( 'build-exe-for-python-sdk: --targets is empty.' )
const seen = new Set < string > ( )
for ( const target of targets ) {
const key = ` ${ target . platform } - ${ target . arch } `
if ( seen . has ( key ) ) {
throw new Error ( ` build-exe-for-python-sdk: duplicate platform-arch ${ key } in --targets; canonical product names would collide. ` )
}
seen . add ( key )
}
return new BuildCli ( targets , values [ 'skip-build' ] , values [ 'dry-run' ] )
}
private static parseRaw ( argv : string [ ] ) {
return parseArgs ( {
args : argv ,
options : {
'targets' : { type : 'string' } ,
'skip-build' : { type : 'boolean' , default : false } ,
'dry-run' : { type : 'boolean' , default : false } ,
'help' : { type : 'boolean' , default : false } ,
} ,
} ) . values
}
private static usage ( ) : string {
return [
'Usage: pnpm exec tsx scripts/build-exe-for-python-sdk.ts [flags]' ,
'' ,
' --targets=<t1,t2,...> pkg targets, e.g. node24-linux-x64,node24-linux-arm64,node24-macos-arm64.' ,
' Default: the host platform only (on node24).' ,
' --skip-build skip `pnpm run build` (lib/ artifacts must already exist).' ,
' --dry-run print every command and config patch without executing.' ,
' --help print this help.' ,
'' ,
2026-07-19 22:50:49 +08:00
` Build route: ${ PKG_SPEC } --sea; see .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md. ` ,
2026-07-14 00:40:36 +08:00
` Stages the node carrier in ${ PYTHON_RUNTIME_DIR } / ${ PYTHON_NODE_SUBDIR } and writes executables to ${ OUT_DIR } /. ` ,
2026-07-11 14:08:26 +08:00
] . join ( '\n' )
}
}
function pnpmBin ( ) : string {
return process . platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
}
/**
2026-07-14 00:40:36 +08:00
* Render a command for logs and errors, quoting arguments with spaces.
2026-07-11 14:08:26 +08:00
* @param command - the executable.
* @param args - its arguments.
* @returns the printable command line.
*/
function formatCommand ( command : string , args : string [ ] ) : string {
return [ command , . . . args ] . map ( part = > ( part . includes ( ' ' ) ? JSON . stringify ( part ) : part ) ) . join ( ' ' )
}
/**
2026-07-14 00:40:36 +08:00
* Sequential build pipeline. Subprocesses inherit stdio and errors include
* the command; dry runs print commands and filesystem changes.
2026-07-11 14:08:26 +08:00
*/
class SingleExeBuild {
/**
2026-07-14 00:40:36 +08:00
* The cleared deploy target, pkg input, and Python node-mode carrier. The
* checked-in default `cordis.yml` remains in its parent directory.
2026-07-11 14:08:26 +08:00
*/
readonly staging = resolve ( root , PYTHON_RUNTIME_DIR , PYTHON_NODE_SUBDIR )
private readonly outDir = resolve ( root , OUT_DIR )
constructor ( private readonly cli : BuildCli ) { }
2026-07-14 00:40:36 +08:00
/** Verify the closure before compiling or packaging. */
2026-07-13 16:34:09 +08:00
async verifyClosure ( ) : Promise < void > {
await this . run ( 'runtime dependency closure' , pnpmBin ( ) , [ 'run' , 'verify-runtime-closure' ] )
}
2026-07-14 00:40:36 +08:00
/** Build all package artifacts unless `--skip-build` was passed. */
2026-07-11 14:08:26 +08:00
async build ( ) : Promise < void > {
if ( this . cli . skipBuild ) {
console . log ( 'build-exe-for-python-sdk: skipping pnpm run build (--skip-build)' )
return
}
await this . run ( 'build' , pnpmBin ( ) , [ 'run' , 'build' ] )
}
2026-07-14 00:40:36 +08:00
/** Clear and deploy the runtime closure into the node carrier. */
2026-07-11 14:08:26 +08:00
async deployStaging ( ) : Promise < void > {
if ( this . staging === root || root . startsWith ( this . staging + sep ) ) {
throw new Error ( ` build-exe-for-python-sdk: refusing to clear staging dir ${ this . staging } : it contains the repo root. ` )
}
if ( this . cli . dryRun ) console . log ( ` build-exe-for-python-sdk: [dry-run] rm -rf ${ this . staging } ` )
else await rm ( this . staging , { recursive : true , force : true } )
await this . run ( 'deploy' , pnpmBin ( ) , [
'--filter' ,
DEPLOY_ROOT_PACKAGE ,
'deploy' ,
'--legacy' ,
'--prod' ,
'--config.node-linker=hoisted' ,
'--config.auto-install-peers=false' ,
'--config.link-workspace-packages=true' ,
this . staging ,
] )
2026-08-10 20:55:02 +08:00
await this . restoreLegacyHoists ( )
2026-07-13 16:34:09 +08:00
if ( this . cli . dryRun ) {
for ( const name of DEPLOY_ONLY_DOCS ) console . log ( ` build-exe-for-python-sdk: [dry-run] rm -f ${ join ( this . staging , name ) } ` )
} else {
await Promise . all ( DEPLOY_ONLY_DOCS . map ( name = > rm ( join ( this . staging , name ) , { force : true } ) ) )
}
2026-07-11 14:08:26 +08:00
}
2026-08-10 20:55:02 +08:00
/**
* Restore direct packages that pnpm's legacy hoister places beside the deploy
* source instead of in the target. The runtime manifest supplies every peer,
* so package-local node_modules trees are omitted to preserve one flat Cordis
* instance and a symlink-free packaged payload.
*/
private async restoreLegacyHoists ( ) : Promise < void > {
if ( this . cli . dryRun ) {
console . log ( 'build-exe-for-python-sdk: [dry-run] restore direct dependencies omitted by legacy deploy' )
return
}
const manifestPath = join ( this . staging , 'package.json' )
const manifest = JSON . parse ( await readFile ( manifestPath , 'utf8' ) ) as {
dependencies? : Record < string , string >
}
const sourceNodeModules = resolve ( root , DEPLOY_SOURCE_NODE_MODULES )
const restored : string [ ] = [ ]
for ( const dependency of Object . keys ( manifest . dependencies ? ? { } ) . sort ( ) ) {
const destination = join ( this . staging , 'node_modules' , dependency )
if ( existsSync ( destination ) ) continue
const source = join ( sourceNodeModules , dependency )
if ( ! existsSync ( source ) ) {
throw new Error (
` build-exe-for-python-sdk: deployed dependency ${ dependency } is absent from both ${ destination } and ${ source } . ` ,
)
}
await mkdir ( dirname ( destination ) , { recursive : true } )
const nestedNodeModules = join ( source , 'node_modules' )
await cp ( source , destination , {
recursive : true ,
filter : path = > path !== nestedNodeModules && ! path . startsWith ( nestedNodeModules + sep ) ,
} )
restored . push ( dependency )
}
const stillMissing = Object . keys ( manifest . dependencies ? ? { } )
. filter ( dependency = > ! existsSync ( join ( this . staging , 'node_modules' , dependency ) ) )
if ( stillMissing . length > 0 ) {
throw new Error ( ` build-exe-for-python-sdk: staged dependencies remain missing: ${ stillMissing . join ( ', ' ) } . ` )
}
if ( restored . length > 0 ) {
console . log ( ` build-exe-for-python-sdk: restored legacy deploy hoists: ${ restored . join ( ', ' ) } ` )
}
}
2026-07-14 00:40:36 +08:00
/** Add the executable entry and pkg assets to the staged manifest. */
2026-07-11 14:08:26 +08:00
async injectPkgConfig ( ) : Promise < void > {
const patch = { bin : ENTRY_BIN , pkg : { assets : ASSET_GLOBS } }
const manifestPath = join ( this . staging , 'package.json' )
if ( this . cli . dryRun ) {
console . log ( ` build-exe-for-python-sdk: [dry-run] patch ${ manifestPath } with ${ JSON . stringify ( patch ) } ` )
return
}
if ( ! existsSync ( manifestPath ) ) {
throw new Error ( ` build-exe-for-python-sdk: ${ manifestPath } missing — pnpm deploy did not produce a staged package. ` )
}
if ( ! existsSync ( join ( this . staging , ENTRY_BIN ) ) ) {
throw new Error ( ` build-exe-for-python-sdk: ${ join ( this . staging , ENTRY_BIN ) } missing — run without --skip-build so lib/ artifacts exist. ` )
}
const manifest = JSON . parse ( await readFile ( manifestPath , 'utf8' ) ) as Record < string , unknown >
await writeFile ( manifestPath , ` ${ JSON . stringify ( { . . . manifest , . . . patch } , null, 2)} \ n ` )
console . log ( ` build-exe-for-python-sdk: injected pkg config into ${ manifestPath } ` )
}
/**
2026-07-14 00:40:36 +08:00
* Package one target; SEA mode accepts one target per invocation.
2026-07-11 14:08:26 +08:00
* @param target - the pkg target triple to build.
2026-07-30 01:09:54 +08:00
* @returns the executable path and, on macOS, its helper path.
2026-07-11 14:08:26 +08:00
*/
2026-07-30 01:09:54 +08:00
async pack ( target : Target ) : Promise < string [ ] > {
2026-07-11 14:08:26 +08:00
const product = join ( this . outDir , ` ${ OUTPUT_BASENAME } - ${ target . platform } - ${ target . arch } ` )
2026-07-29 22:44:19 +08:00
await this . prepareNativePty ( target )
2026-07-30 01:29:18 +08:00
if ( ! this . cli . dryRun ) await mkdir ( this . outDir , { recursive : true } )
2026-07-11 14:08:26 +08:00
await this . run ( ` pkg ${ target . spec } ` , pnpmBin ( ) , [
'dlx' ,
PKG_SPEC ,
this . staging ,
'--sea' ,
'--targets' ,
target . spec ,
'--output' ,
product ,
] )
if ( ! this . cli . dryRun && ! existsSync ( product ) ) {
throw new Error ( ` build-exe-for-python-sdk: product ${ product } is missing after the pkg run; inspect ${ this . outDir } . ` )
}
2026-07-30 01:09:54 +08:00
if ( target . platform !== 'macos' ) return [ product ]
2026-07-30 01:29:18 +08:00
const spawnHelper = ` ${ product } -spawn-helper `
const source = join ( this . staging , 'node_modules' , 'node-pty' , 'prebuilds' , ` darwin- ${ target . arch } ` , 'spawn-helper' )
2026-07-29 14:12:27 +08:00
if ( this . cli . dryRun ) {
2026-07-30 01:29:18 +08:00
console . log ( ` build-exe-for-python-sdk: [dry-run] cp ${ source } ${ spawnHelper } ` )
2026-07-29 14:12:27 +08:00
} else {
await copyFile ( source , spawnHelper )
2026-07-30 01:29:18 +08:00
await chmod ( spawnHelper , 0 o755 )
2026-07-29 14:12:27 +08:00
}
2026-07-30 01:09:54 +08:00
return [ product , spawnHelper ]
2026-07-29 14:12:27 +08:00
}
2026-07-29 22:44:19 +08:00
/**
* Put the target node-pty addon in the staged closure. Linux npm installs
* build it from source, but legacy deploy omits that side-effect directory.
* @param target - the pkg target whose native addon is being staged.
*/
private async prepareNativePty ( target : Target ) : Promise < void > {
2026-07-30 01:29:18 +08:00
const stagedBuild = join ( this . staging , 'node_modules' , 'node-pty' , 'build' )
2026-07-29 22:44:19 +08:00
if ( this . cli . dryRun ) console . log ( ` build-exe-for-python-sdk: [dry-run] rm -rf ${ stagedBuild } ` )
else await rm ( stagedBuild , { recursive : true , force : true } )
2026-07-30 01:29:18 +08:00
if ( target . platform !== 'linux' ) return
2026-08-07 23:40:45 +08:00
const source = join ( root , 'packages' , 'subprocess' , 'subprocess-local' , 'node_modules' , 'node-pty' , 'build' , 'Release' , 'pty.node' )
2026-07-29 22:44:19 +08:00
const destination = join ( stagedBuild , 'Release' , 'pty.node' )
if ( this . cli . dryRun ) {
2026-07-30 01:29:18 +08:00
console . log ( ` build-exe-for-python-sdk: [dry-run] cp ${ source } ${ destination } ` )
2026-07-29 22:44:19 +08:00
return
}
const host = Target . host ( )
2026-07-30 01:29:18 +08:00
if ( target . platform !== host . platform || target . arch !== host . arch ) {
2026-07-29 22:44:19 +08:00
throw new Error (
2026-07-30 01:29:18 +08:00
'build-exe-for-python-sdk: build the Linux runtime on its target architecture; '
+ ` target ${ target . platform } - ${ target . arch } does not match host ${ host . platform } - ${ host . arch } . ` ,
2026-07-29 22:44:19 +08:00
)
}
await mkdir ( dirname ( destination ) , { recursive : true } )
await copyFile ( source , destination )
}
2026-07-11 14:08:26 +08:00
/**
2026-07-14 00:40:36 +08:00
* Print each product path and, outside dry-run mode, its size.
2026-07-11 14:08:26 +08:00
* @param products - the product paths returned by {@link pack}.
*/
2026-07-30 01:09:54 +08:00
printProducts ( products : string [ ] ) : void {
2026-07-11 14:08:26 +08:00
console . log ( this . cli . dryRun ? 'build-exe-for-python-sdk: [dry-run] would produce:' : 'build-exe-for-python-sdk: products:' )
2026-07-30 01:09:54 +08:00
for ( const path of products ) {
2026-07-11 14:08:26 +08:00
if ( this . cli . dryRun ) {
2026-07-30 01:09:54 +08:00
console . log ( ` ${ path } ` )
2026-07-11 14:08:26 +08:00
continue
}
2026-07-30 01:09:54 +08:00
const megabytes = statSync ( path ) . size / ( 1024 * 1024 )
console . log ( ` ${ path } ( ${ megabytes . toFixed ( 1 ) } MB) ` )
2026-07-11 14:08:26 +08:00
}
}
/**
2026-07-30 01:09:54 +08:00
* Copy each product into the Python runtime package. The deployed node
2026-07-14 00:40:36 +08:00
* carrier is already in place, and `dist-exe/` retains upload copies.
2026-07-11 14:08:26 +08:00
* @param products - the product paths returned by {@link pack}.
*/
2026-07-30 01:09:54 +08:00
async syncToPythonRuntime ( products : string [ ] ) : Promise < void > {
2026-07-11 14:08:26 +08:00
const destDir = resolve ( root , PYTHON_RUNTIME_DIR )
if ( this . cli . dryRun ) {
2026-07-30 01:09:54 +08:00
for ( const path of products ) {
console . log ( ` build-exe-for-python-sdk: [dry-run] cp ${ path } ${ join ( destDir , basename ( path ) ) } ` )
2026-07-11 14:08:26 +08:00
}
return
}
2026-07-30 01:29:18 +08:00
await mkdir ( destDir , { recursive : true } )
2026-07-30 01:09:54 +08:00
for ( const path of products ) {
const destination = join ( destDir , basename ( path ) )
await copyFile ( path , destination )
await chmod ( destination , statSync ( path ) . mode & 0 o777 )
console . log ( ` build-exe-for-python-sdk: synced ${ destination } ` )
2026-07-11 14:08:26 +08:00
}
}
/**
2026-07-14 00:40:36 +08:00
* Run one subprocess with inherited stdio. Spawn and non-zero-exit errors
* include the command; dry runs only print it.
2026-07-11 14:08:26 +08:00
* @param label - the step name used in logs and error messages.
* @param command - the executable.
* @param args - its arguments.
*/
private async run ( label : string , command : string , args : string [ ] ) : Promise < void > {
const printable = formatCommand ( command , args )
if ( this . cli . dryRun ) {
console . log ( ` build-exe-for-python-sdk: [dry-run] ${ printable } ` )
return
}
console . log ( ` build-exe-for-python-sdk: ${ label } : ${ printable } ` )
await new Promise < void > ( ( resolvePromise , reject ) = > {
2026-07-29 14:12:27 +08:00
const child = spawn ( command , args , {
cwd : root ,
stdio : 'inherit' ,
// Artifact builds must not mutate or validate a developer's Git hooks.
env : { . . . process . env , CI : 'true' } ,
} )
2026-07-11 14:08:26 +08:00
child . once ( 'error' , ( error ) = > {
reject ( new Error ( ` build-exe-for-python-sdk: ${ label } failed to spawn: ${ error . message } ( ${ printable } ) ` ) )
} )
child . once ( 'exit' , ( code , signal ) = > {
if ( code === 0 ) {
resolvePromise ( )
return
}
const cause = code === null ? ` signal ${ signal ? ? 'unknown' } ` : ` exit code ${ code } `
reject ( new Error ( ` build-exe-for-python-sdk: ${ label } failed ( ${ cause } ): ${ printable } ` ) )
} )
} )
}
}
async function main ( ) : Promise < void > {
const cli = BuildCli . parse ( process . argv . slice ( 2 ) )
const pipeline = new SingleExeBuild ( cli )
console . log ( ` build-exe-for-python-sdk: targets: ${ cli . targets . map ( target = > target . spec ) . join ( ', ' ) } ` )
console . log ( ` build-exe-for-python-sdk: staging: ${ pipeline . staging } ` )
2026-07-13 16:34:09 +08:00
await pipeline . verifyClosure ( )
2026-07-11 14:08:26 +08:00
await pipeline . build ( )
await pipeline . deployStaging ( )
await pipeline . injectPkgConfig ( )
2026-07-30 01:09:54 +08:00
const products : string [ ] = [ ]
for ( const target of cli . targets ) products . push ( . . . await pipeline . pack ( target ) )
2026-07-11 14:08:26 +08:00
pipeline . printProducts ( products )
await pipeline . syncToPythonRuntime ( products )
}
await main ( )