@@ -18,22 +18,42 @@ const JULIA_VERSION_PARTS = [1, 10, 10];
1818const JULIA_VERSION = JULIA_VERSION_PARTS . join ( '.' ) ;
1919const JULIA_VERSION_MINOR = JULIA_VERSION_PARTS . slice ( 0 , 2 ) . join ( '.' ) ;
2020
21- const JULIA_URL = `https://julialang-s3.julialang.org/bin/winnt/x64/${ JULIA_VERSION_MINOR } /julia-${ JULIA_VERSION } -win64.zip` ;
21+ // Detect platform
22+ const platform = process . platform ;
23+ const arch = process . arch === 'arm64' ? 'aarch64' : 'x64' ;
2224
23- const ZIP_NAME = `julia-${ JULIA_VERSION } -win64.zip` ;
24- const JULIA_DIR_NAME = `julia-${ JULIA_VERSION } ` ;
25+ let JULIA_URL , ZIP_NAME , JULIA_DIR_NAME , JULIA_EXECUTABLE ;
26+
27+ if ( platform === 'win32' ) {
28+ ZIP_NAME = `julia-${ JULIA_VERSION } -win64.zip` ;
29+ JULIA_URL = `https://julialang-s3.julialang.org/bin/winnt/x64/${ JULIA_VERSION_MINOR } /${ ZIP_NAME } ` ;
30+ JULIA_DIR_NAME = `julia-${ JULIA_VERSION } ` ;
31+ JULIA_EXECUTABLE = 'julia.exe' ;
32+ } else if ( platform === 'darwin' ) {
33+ // macOS
34+ const macArch = arch === 'aarch64' ? 'aarch64' : 'x64' ;
35+ ZIP_NAME = `julia-${ JULIA_VERSION } -mac${ arch === 'aarch64' ? 'aarch64' : '64' } .dmg` ;
36+ JULIA_URL = `https://julialang-s3.julialang.org/bin/mac/${ macArch } /${ JULIA_VERSION_MINOR } /${ ZIP_NAME } ` ;
37+ JULIA_DIR_NAME = `julia-${ JULIA_VERSION } ` ;
38+ JULIA_EXECUTABLE = 'julia' ;
39+ } else {
40+ // Linux
41+ throw new Error ( 'Linux is not supported' ) ;
42+ }
2543
2644const DEPOT_NAME = `julia_depot` ;
2745
2846const downloadJulia = async ( ) => {
29- const spinner = createSpinner ( `\tDownloading Julia ${ JULIA_VERSION } ` ) . start ( ) ;
47+ const spinner = createSpinner (
48+ `\tDownloading Julia ${ JULIA_VERSION } for ${ platform } ` ,
49+ ) . start ( ) ;
3050 const writer = fs . createWriteStream ( path . join ( assetPath , ZIP_NAME ) ) ;
3151
3252 const response = await axios . get ( JULIA_URL , {
3353 responseType : 'stream' ,
3454 onDownloadProgress : ( progressEvent ) => {
3555 const percentage = Math . round (
36- ( progressEvent . loaded * 100 ) / progressEvent . total
56+ ( progressEvent . loaded * 100 ) / progressEvent . total ,
3757 ) ;
3858 spinner
3959 . update ( { text : `\tDownloading Julia ${ JULIA_VERSION } ${ percentage } %` } )
@@ -56,17 +76,22 @@ const downloadJulia = async () => {
5676 reject ( e ) ;
5777 } ) ;
5878 writer . on ( 'finish' , ( args ) => {
59- spinner . success ( { text : '\tDownloaded Julia' , mark : '✓' } ) ;
79+ spinner . success ( {
80+ text : `\tDownloaded Julia (for ${ platform } ) (size: ${ ( response . data . length / 1024 / 1024 ) . toFixed ( 2 ) } MB)` ,
81+ mark : '✓' ,
82+ } ) ;
6083 resolve ( args ) ;
6184 } ) ;
6285 } ) ;
6386} ;
6487
6588const precompilePluto = async ( { julia_path } ) => {
89+ // TODO: You need to add PackageCompiler to some environment for this to work.
90+
6691 const SYSIMAGE_LOCATION = path . join (
6792 assetPath ,
6893 // TODO: auto version number
69- 'pluto.so'
94+ 'pluto.so' ,
7095 ) ;
7196
7297 // immediately return if the sysimage has already been compiled
@@ -77,7 +102,7 @@ const precompilePluto = async ({ julia_path }) => {
77102 const PRECOMPILE_SCRIPT_LOCATION = path . join ( assetPath , 'precompile.jl' ) ;
78103 const PRECOMPILE_EXECUTION_LOCATION = path . join (
79104 assetPath ,
80- 'precompile_execution.jl'
105+ 'precompile_execution.jl' ,
81106 ) ;
82107
83108 const res = spawn ( julia_path , [
@@ -124,7 +149,7 @@ const prepareJuliaDepot = async ({ julia_path }) => {
124149 env : {
125150 JULIA_DEPOT_PATH : DEPOT_LOCATION ,
126151 } ,
127- }
152+ } ,
128153 ) ;
129154
130155 res . stderr . on ( 'data' , ( data ) => {
@@ -146,35 +171,130 @@ const prepareJuliaDepot = async ({ julia_path }) => {
146171 recursive : true ,
147172 } ) ;
148173
174+ // Fix file permissions on macOS to prevent code signing issues
175+ if ( platform === 'darwin' ) {
176+ // (🤖🤖 This if block is AI written and not reviewed.)
177+ try {
178+ // First, make files writable so we can remove extended attributes
179+ execSync ( `find "${ DEPOT_LOCATION } " -type f -exec chmod u+w {} +` , {
180+ stdio : 'ignore' ,
181+ } ) ;
182+ execSync ( `find "${ DEPOT_LOCATION } " -type d -exec chmod u+w {} +` , {
183+ stdio : 'ignore' ,
184+ } ) ;
185+ // Remove extended attributes (quarantine, etc.) that can interfere with code signing
186+ execSync ( `xattr -rc "${ DEPOT_LOCATION } " 2>/dev/null || true` , {
187+ stdio : 'ignore' ,
188+ } ) ;
189+ // Set final correct permissions: 644 for files, 755 for directories
190+ execSync ( `find "${ DEPOT_LOCATION } " -type f -exec chmod 644 {} +` , {
191+ stdio : 'ignore' ,
192+ } ) ;
193+ execSync ( `find "${ DEPOT_LOCATION } " -type d -exec chmod 755 {} +` , {
194+ stdio : 'ignore' ,
195+ } ) ;
196+ console . info (
197+ 'Fixed file permissions and removed extended attributes in DEPOT' ,
198+ ) ;
199+ } catch ( error ) {
200+ console . warn ( 'Warning: Could not fix file permissions:' , error . message ) ;
201+ }
202+ }
203+
149204 console . info ( 'DEPOT preparation success' , DEPOT_LOCATION ) ;
150205} ;
151206
207+ /** (🤖🤖 This function is AI written and not reviewed.) */
208+ const extractJulia = async ( ) => {
209+ const spinner1 = createSpinner ( `\tExtracting: ${ ZIP_NAME } ` ) . start ( ) ;
210+ fs . rmSync ( path . join ( assetPath , JULIA_DIR_NAME ) , {
211+ force : true ,
212+ recursive : true ,
213+ } ) ;
214+
215+ if ( platform === 'win32' ) {
216+ // Windows: extract zip
217+ await unzip ( path . join ( assetPath , ZIP_NAME ) , { dir : assetPath } ) ;
218+ } else if ( platform === 'darwin' ) {
219+ // macOS: mount DMG and copy contents
220+ const dmgPath = path . join ( assetPath , ZIP_NAME ) ;
221+ const mountPoint = path . join ( assetPath , 'julia_mount' ) ;
222+
223+ // Create mount point if it doesn't exist
224+ if ( ! fs . existsSync ( mountPoint ) ) {
225+ fs . mkdirSync ( mountPoint , { recursive : true } ) ;
226+ }
227+
228+ // Mount the DMG
229+ execSync ( `hdiutil attach "${ dmgPath } " -mountpoint "${ mountPoint } " -quiet` ) ;
230+
231+ try {
232+ // Find the Julia app bundle in the mounted DMG
233+ const mountedContents = fs . readdirSync ( mountPoint ) ;
234+ const juliaApp = mountedContents . find ( ( item ) =>
235+ item . startsWith ( 'Julia-' ) ,
236+ ) ;
237+
238+ if ( ! juliaApp ) {
239+ throw new Error ( 'Could not find Julia app in DMG' ) ;
240+ }
241+
242+ const juliaAppPath = path . join ( mountPoint , juliaApp ) ;
243+ const juliaContentsPath = path . join (
244+ juliaAppPath ,
245+ 'Contents' ,
246+ 'Resources' ,
247+ 'julia' ,
248+ ) ;
249+ const targetPath = path . join ( assetPath , JULIA_DIR_NAME ) ;
250+
251+ // Copy Julia contents to target directory
252+ if ( fs . existsSync ( juliaContentsPath ) ) {
253+ // Copy the entire julia directory
254+ execSync ( `cp -R "${ juliaContentsPath } " "${ targetPath } "` ) ;
255+ } else {
256+ throw new Error ( 'Could not find Julia contents in app bundle' ) ;
257+ }
258+ } finally {
259+ // Unmount the DMG
260+ execSync ( `hdiutil detach "${ mountPoint } " -quiet` ) ;
261+ if ( fs . existsSync ( mountPoint ) ) {
262+ fs . rmSync ( mountPoint , { recursive : true , force : true } ) ;
263+ }
264+ }
265+ } else {
266+ throw new Error ( 'Linux is not supported' ) ;
267+ // Linux: extract tar.gz
268+ execSync ( `tar -xzf "${ path . join ( assetPath , ZIP_NAME ) } " -C "${ assetPath } "` ) ;
269+ }
270+
271+ fs . rmSync ( path . join ( assetPath , ZIP_NAME ) , {
272+ force : true ,
273+ } ) ;
274+ spinner1 . success ( { text : '\tExtracted!' , mark : '✓' } ) ;
275+ } ;
276+
152277exports . default = async ( context ) => {
153278 let files = fs . readdirSync ( assetPath ) ;
154279
155280 if ( ! files . includes ( JULIA_DIR_NAME ) ) {
156281 await downloadJulia ( ) ;
157- // files = fs.readdirSync(assetPath);
158-
159- const spinner1 = createSpinner ( `\tExtracting: ${ ZIP_NAME } ` ) . start ( ) ;
160- fs . rmSync ( path . join ( assetPath , JULIA_DIR_NAME ) , {
161- force : true ,
162- recursive : true ,
163- } ) ;
164- await unzip ( path . join ( assetPath , ZIP_NAME ) , { dir : assetPath } ) ;
165- fs . rmSync ( path . join ( assetPath , ZIP_NAME ) , {
166- force : true ,
167- } ) ;
168- spinner1 . success ( { text : '\tExtracted!' , mark : '✓' } ) ;
282+ await extractJulia ( ) ;
169283 }
170284
285+ const juliaPath = path . join (
286+ assetPath ,
287+ JULIA_DIR_NAME ,
288+ 'bin' ,
289+ JULIA_EXECUTABLE ,
290+ ) ;
291+
171292 await prepareJuliaDepot ( {
172- julia_path : path . join ( assetPath , JULIA_DIR_NAME , 'bin' , 'julia.exe' ) ,
293+ julia_path : juliaPath ,
173294 } ) ;
174295
175296 // NOT DOING THIS, see https://github.com/JuliaPluto/PlutoDesktop/issues/56
176- // maybe we are... (CB)
177- await precompilePluto ( {
178- julia_path : path . join ( assetPath , JULIA_DIR_NAME , 'bin' , 'julia.exe' ) ,
179- } ) ;
297+ // await precompilePluto({
298+ // julia_path: juliaPath,
299+ // });
180300} ;
0 commit comments