@@ -17,16 +17,20 @@ const sourceRoot = process.env.OPENWORK_ROOT?.trim()
1717 ? path . resolve ( process . env . OPENWORK_ROOT )
1818 : repoRoot ;
1919const runtimeDir = path . join ( packageDir , 'runtime' ) ;
20- const packageRoot = path . join ( runtimeDir , 'openwork' ) ;
20+ const finalPackageRoot = path . join ( runtimeDir , 'openwork' ) ;
2121const refreshChecksums = process . argv . indexOf ( '--refresh-checksums' ) ;
2222if ( refreshChecksums !== - 1 ) {
2323 const root = process . argv [ refreshChecksums + 1 ]
2424 ? path . resolve ( process . argv [ refreshChecksums + 1 ] )
25- : packageRoot ;
25+ : finalPackageRoot ;
2626 writeChecksums ( root ) ;
2727 console . log ( `Refreshed OpenWork runtime checksums at ${ root } ` ) ;
2828 process . exit ( 0 ) ;
2929}
30+ fs . mkdirSync ( runtimeDir , { recursive : true } ) ;
31+ recoverInterruptedRuntime ( ) ;
32+ const stagingRoot = fs . mkdtempSync ( path . join ( runtimeDir , '.prepare-' ) ) ;
33+ const packageRoot = path . join ( stagingRoot , 'openwork' ) ;
3034const libDir = path . join ( packageRoot , 'lib' ) ;
3135const nodeDir = path . join ( packageRoot , 'node' ) ;
3236const toolsDir = path . join ( packageRoot , 'tools' ) ;
@@ -93,48 +97,52 @@ for (const required of [
9397 }
9498}
9599
96- fs . rmSync ( runtimeDir , { recursive : true , force : true } ) ;
97- fs . mkdirSync ( libDir , { recursive : true } ) ;
98- fs . writeFileSync ( path . join ( packageRoot , '.gitkeep' ) , '' ) ;
99- fs . mkdirSync ( binDir , { recursive : true } ) ;
100- copyDirectory ( distDir , libDir ) ;
101- installRuntimeDependencies ( libDir , target ) ;
102- await installNodeRuntime ( nodeDir , target ) ;
103- copyDocumentTools ( ) ;
104- await installUvRuntime ( path . join ( toolsDir , 'uv' ) , target ) ;
105- writeLaunchers ( target ) ;
106- copyRequiredFile (
107- path . join ( sourceRoot , 'LICENSE' ) ,
108- path . join ( packageRoot , 'LICENSE' ) ,
109- ) ;
110- copyRequiredFile (
111- path . join ( packageDir , 'NOTICE' ) ,
112- path . join ( packageRoot , 'NOTICE' ) ,
113- ) ;
114- const nodeLicense = path . join ( nodeDir , 'LICENSE' ) ;
115- if ( ! fs . existsSync ( nodeLicense ) ) {
116- throw new Error ( `Bundled Node.js license is missing: ${ nodeLicense } ` ) ;
100+ try {
101+ fs . mkdirSync ( libDir , { recursive : true } ) ;
102+ fs . writeFileSync ( path . join ( packageRoot , '.gitkeep' ) , '' ) ;
103+ fs . mkdirSync ( binDir , { recursive : true } ) ;
104+ copyDirectory ( distDir , libDir ) ;
105+ installRuntimeDependencies ( libDir , target ) ;
106+ await installNodeRuntime ( nodeDir , target ) ;
107+ copyDocumentTools ( ) ;
108+ await installUvRuntime ( path . join ( toolsDir , 'uv' ) , target ) ;
109+ writeLaunchers ( target ) ;
110+ copyRequiredFile (
111+ path . join ( sourceRoot , 'LICENSE' ) ,
112+ path . join ( packageRoot , 'LICENSE' ) ,
113+ ) ;
114+ copyRequiredFile (
115+ path . join ( packageDir , 'NOTICE' ) ,
116+ path . join ( packageRoot , 'NOTICE' ) ,
117+ ) ;
118+ const nodeLicense = path . join ( nodeDir , 'LICENSE' ) ;
119+ if ( ! fs . existsSync ( nodeLicense ) ) {
120+ throw new Error ( `Bundled Node.js license is missing: ${ nodeLicense } ` ) ;
121+ }
122+ fs . writeFileSync (
123+ path . join ( packageRoot , 'manifest.json' ) ,
124+ `${ JSON . stringify (
125+ {
126+ name : '@openwork/desktop-shell' ,
127+ desktopVersion,
128+ qwenCodeVersion,
129+ qwenCodeCommit : process . env . QWEN_CODE_COMMIT || gitCommit ( sourceRoot ) ,
130+ target,
131+ node : `v${ process . versions . node } ` ,
132+ uv : uvVersion ,
133+ builtAt : new Date ( ) . toISOString ( ) ,
134+ } ,
135+ null ,
136+ 2 ,
137+ ) } \n`,
138+ ) ;
139+ writeChecksums ( ) ;
140+ replaceRuntime ( ) ;
141+ } finally {
142+ fs . rmSync ( stagingRoot , { recursive : true , force : true } ) ;
117143}
118- fs . writeFileSync (
119- path . join ( packageRoot , 'manifest.json' ) ,
120- `${ JSON . stringify (
121- {
122- name : '@openwork/desktop-shell' ,
123- desktopVersion,
124- qwenCodeVersion,
125- qwenCodeCommit : process . env . QWEN_CODE_COMMIT || gitCommit ( sourceRoot ) ,
126- target,
127- node : `v${ process . versions . node } ` ,
128- uv : uvVersion ,
129- builtAt : new Date ( ) . toISOString ( ) ,
130- } ,
131- null ,
132- 2 ,
133- ) } \n`,
134- ) ;
135- writeChecksums ( ) ;
136144console . log (
137- `Prepared OpenWork desktop runtime at ${ path . relative ( repoRoot , packageRoot ) } ` ,
145+ `Prepared OpenWork desktop runtime at ${ path . relative ( repoRoot , finalPackageRoot ) } ` ,
138146) ;
139147
140148async function installNodeRuntime ( destination , desktopTarget ) {
@@ -148,19 +156,40 @@ async function installNodeRuntime(destination, desktopTarget) {
148156 }
149157 const archiveName = nodeArchiveName ( nodeVersion , desktopTarget ) ;
150158 const downloadRoot = `https://nodejs.org/dist/v${ nodeVersion } ` ;
159+ const cacheRoot = process . env . QWEN_DESKTOP_NODE_CACHE_DIR
160+ ? path . resolve ( process . env . QWEN_DESKTOP_NODE_CACHE_DIR )
161+ : path . join ( os . tmpdir ( ) , 'qwen-desktop-node-cache' ) ;
162+ const cacheDir = path . join ( cacheRoot , `v${ nodeVersion } ` ) ;
163+ const cachedArchivePath = path . join ( cacheDir , archiveName ) ;
151164 const temporaryRoot = fs . mkdtempSync (
152165 path . join ( os . tmpdir ( ) , 'openwork-desktop-node-' ) ,
153166 ) ;
154167 try {
155168 const checksumsPath = path . join ( temporaryRoot , 'SHASUMS256.txt' ) ;
156169 const archivePath = path . join ( temporaryRoot , archiveName ) ;
157170 await download ( `${ downloadRoot } /SHASUMS256.txt` , checksumsPath ) ;
158- await download ( `${ downloadRoot } /${ archiveName } ` , archivePath ) ;
159- verifyChecksum (
160- archivePath ,
161- archiveName ,
162- fs . readFileSync ( checksumsPath , 'utf8' ) ,
163- ) ;
171+ const checksums = fs . readFileSync ( checksumsPath , 'utf8' ) ;
172+ if (
173+ copyValidCachedArchive (
174+ cachedArchivePath ,
175+ archivePath ,
176+ archiveName ,
177+ checksums ,
178+ )
179+ ) {
180+ console . log ( `Using cached Node.js runtime ${ archiveName } ` ) ;
181+ } else {
182+ await download ( `${ downloadRoot } /${ archiveName } ` , archivePath ) ;
183+ verifyChecksum ( archivePath , archiveName , checksums ) ;
184+ fs . mkdirSync ( cacheDir , { recursive : true } ) ;
185+ const temporaryCachePath = `${ cachedArchivePath } .${ process . pid } .tmp` ;
186+ try {
187+ fs . copyFileSync ( archivePath , temporaryCachePath ) ;
188+ fs . renameSync ( temporaryCachePath , cachedArchivePath ) ;
189+ } finally {
190+ fs . rmSync ( temporaryCachePath , { force : true } ) ;
191+ }
192+ }
164193 extractArchive ( archivePath , temporaryRoot ) ;
165194 const extractedRoot = path . join (
166195 temporaryRoot ,
@@ -175,6 +204,24 @@ async function installNodeRuntime(destination, desktopTarget) {
175204 }
176205}
177206
207+ function copyValidCachedArchive (
208+ cachedArchivePath ,
209+ archivePath ,
210+ archiveName ,
211+ checksums ,
212+ ) {
213+ if ( ! fs . existsSync ( cachedArchivePath ) ) return false ;
214+ try {
215+ fs . copyFileSync ( cachedArchivePath , archivePath ) ;
216+ verifyChecksum ( archivePath , archiveName , checksums ) ;
217+ return true ;
218+ } catch {
219+ fs . rmSync ( cachedArchivePath , { force : true } ) ;
220+ fs . rmSync ( archivePath , { force : true } ) ;
221+ return false ;
222+ }
223+ }
224+
178225function copyDocumentTools ( ) {
179226 const resources = path . join (
180227 sourceRoot ,
@@ -414,3 +461,30 @@ function copyDirectory(source, destination) {
414461 filter : ( entry ) => path . basename ( entry ) !== '.DS_Store' ,
415462 } ) ;
416463}
464+
465+ function recoverInterruptedRuntime ( ) {
466+ for ( const entry of fs . readdirSync ( runtimeDir ) ) {
467+ if ( ! entry . startsWith ( '.prepare-' ) ) continue ;
468+ const staleRoot = path . join ( runtimeDir , entry ) ;
469+ const previousRoot = path . join ( staleRoot , 'previous' ) ;
470+ if ( ! fs . existsSync ( finalPackageRoot ) && fs . existsSync ( previousRoot ) ) {
471+ fs . renameSync ( previousRoot , finalPackageRoot ) ;
472+ }
473+ fs . rmSync ( staleRoot , { recursive : true , force : true } ) ;
474+ }
475+ }
476+
477+ function replaceRuntime ( ) {
478+ const previousRoot = path . join ( stagingRoot , 'previous' ) ;
479+ if ( fs . existsSync ( finalPackageRoot ) ) {
480+ fs . renameSync ( finalPackageRoot , previousRoot ) ;
481+ }
482+ try {
483+ fs . renameSync ( packageRoot , finalPackageRoot ) ;
484+ } catch ( error ) {
485+ if ( fs . existsSync ( previousRoot ) ) {
486+ fs . renameSync ( previousRoot , finalPackageRoot ) ;
487+ }
488+ throw error ;
489+ }
490+ }
0 commit comments