-
Notifications
You must be signed in to change notification settings - Fork 86
Studio CLI: Add bundled Node binary and fix Windows popup #2303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
fredrikekelund
merged 16 commits into
dev/studio-cli-i2
from
stu-1148-investigate-site-create-performance
Jan 2, 2026
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
df70cf0
add bundled node
bcotrim dd6bce1
change node download script
bcotrim d1af8fe
Fix cli/node_modules copy pattern for Windows compatibility
bcotrim 25b53bd
fix node command on Windows
bcotrim 53af43f
hide windows on child processes
bcotrim 7ac84b7
ensure nvrm version is used
bcotrim 09ab0f4
logs
bcotrim c2016e4
fix lint and tests
bcotrim 216e890
attempt to fix windows hide
bcotrim 6fd7336
trigger tests
bcotrim 17e2ead
Refine download-node-binary script
fredrikekelund 4e77d33
Use bundled node binary in ./bin scripts
fredrikekelund 0280050
Flip the logic for distinguishing node environments
fredrikekelund e284836
process.env is already the default
fredrikekelund f0b9873
Use system-level node binary in development
fredrikekelund 16814d1
Use Electron node binary in unit tests
fredrikekelund File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -127,3 +127,7 @@ artifacts | |
| .cursor/ | ||
| .claude/ | ||
| CLAUDE.md | ||
|
|
||
| # Bundled Node binary (downloaded during build) | ||
| bin/node | ||
| bin/node.exe | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| diff --git a/node_modules/ps-man/lib/index.js b/node_modules/ps-man/lib/index.js | ||
| index 1234567..abcdefg 100644 | ||
| --- a/node_modules/ps-man/lib/index.js | ||
| +++ b/node_modules/ps-man/lib/index.js | ||
| @@ -42,7 +42,7 @@ var listProcesses = function(options, done) { | ||
| options = options || {}; | ||
| } | ||
|
|
||
| - var ps = spawn(operations.ps.command, operations.ps.arguments); | ||
| + var ps = spawn(operations.ps.command, operations.ps.arguments, { windowsHide: true }); | ||
| var processList = ''; | ||
| var processErr = ''; | ||
| var options = { | ||
| @@ -137,7 +137,7 @@ var killProcesses = function(options, done) { | ||
| }); | ||
| } | ||
|
|
||
| - var kill = spawn(operations.kill.command, killArguments); | ||
| + var kill = spawn(operations.kill.command, killArguments, { windowsHide: true }); | ||
| var processErr = ''; | ||
|
|
||
| kill.on('error', done); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,166 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Download Node.js binary for bundling with Studio | ||
| * Usage: node scripts/download-node-binary.js <platform> <arch> | ||
| * Example: node scripts/download-node-binary.js darwin arm64 | ||
| */ | ||
|
|
||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import os from 'os'; | ||
| import { extract } from 'tar'; | ||
| import unzipper from 'unzipper'; | ||
|
|
||
| const LTS_FALLBACK = 'v22.12.0'; | ||
|
|
||
| function getNodeVersion() { | ||
| const nvmrcPath = path.join( import.meta.dirname, '..', '.nvmrc' ); | ||
| if ( fs.existsSync( nvmrcPath ) ) { | ||
| const version = fs.readFileSync( nvmrcPath, 'utf-8' ).trim(); | ||
| return version.startsWith( 'v' ) ? version : `v${ version }`; | ||
| } | ||
| console.log( `.nvmrc not found, using fallback version ${ LTS_FALLBACK }` ); | ||
| return LTS_FALLBACK; | ||
| } | ||
|
|
||
| const NODE_VERSION = getNodeVersion(); | ||
|
|
||
| const platform = process.argv[ 2 ] || process.platform; | ||
| const arch = process.argv[ 3 ] || process.arch; | ||
|
|
||
| // Map platform names to nodejs.org download naming | ||
| const platformMap = { | ||
| darwin: 'darwin', | ||
| win32: 'win', | ||
| }; | ||
|
|
||
| // Map architecture names to nodejs.org download naming | ||
| const archMap = { | ||
| arm64: 'arm64', | ||
| x64: 'x64', | ||
| }; | ||
|
|
||
| const nodePlatform = platformMap[ platform ]; | ||
| const nodeArch = archMap[ arch ]; | ||
|
|
||
| if ( ! nodePlatform ) { | ||
| console.error( `Unsupported platform: ${ platform }` ); | ||
| process.exit( 1 ); | ||
| } | ||
|
|
||
| if ( ! nodeArch ) { | ||
| console.error( `Unsupported architecture: ${ arch }` ); | ||
| process.exit( 1 ); | ||
| } | ||
|
|
||
| const binDir = path.join( import.meta.dirname, '..', 'bin' ); | ||
| const tmpDir = os.tmpdir(); | ||
|
|
||
| if ( ! fs.existsSync( binDir ) ) { | ||
| fs.mkdirSync( binDir, { recursive: true } ); | ||
| } | ||
|
|
||
| const isWindows = nodePlatform === 'win'; | ||
| // nodejs.org provides different archive formats depending on the target platform | ||
| const ext = isWindows ? 'zip' : 'tar.gz'; | ||
| const filename = `node-${ NODE_VERSION }-${ nodePlatform }-${ nodeArch }.${ ext }`; | ||
| const url = `https://nodejs.org/dist/${ NODE_VERSION }/${ filename }`; | ||
| const downloadPath = path.join( tmpDir, filename ); | ||
|
|
||
| async function download( downloadUrl, dest ) { | ||
| console.log( `Downloading Node.js ${ NODE_VERSION } for ${ nodePlatform }-${ nodeArch }...` ); | ||
|
|
||
| const response = await fetch( downloadUrl ); | ||
|
|
||
| if ( ! response.ok ) { | ||
| throw new Error( `Failed to download: HTTP ${ response.status }` ); | ||
| } | ||
|
|
||
| const file = fs.createWriteStream( dest ); | ||
| const reader = response.body.getReader(); | ||
|
|
||
| try { | ||
| while ( true ) { | ||
| const { done, value } = await reader.read(); | ||
| if ( done ) { | ||
| break; | ||
| } | ||
| file.write( value ); | ||
| } | ||
| } finally { | ||
| reader.releaseLock(); | ||
| } | ||
|
|
||
| await new Promise( ( resolve, reject ) => { | ||
| file.on( 'finish', resolve ); | ||
| file.on( 'error', reject ); | ||
| file.end(); | ||
| } ); | ||
|
|
||
| console.log( 'Download complete.' ); | ||
| } | ||
|
|
||
| async function extractTarGz( archivePath, destDir, binaryName ) { | ||
| console.log( 'Extracting node binary...' ); | ||
|
|
||
| const extractDir = path.join( tmpDir, `node-${ NODE_VERSION }-${ nodePlatform }-${ nodeArch }` ); | ||
|
|
||
| await extract( { | ||
| file: archivePath, | ||
| cwd: tmpDir, | ||
| } ).then( () => { | ||
| // Do nothing. We just need the `.then` chaining to be able to await the promise | ||
| } ); | ||
|
|
||
| const sourcePath = path.join( extractDir, 'bin', 'node' ); | ||
| const destPath = path.join( destDir, binaryName ); | ||
|
|
||
| fs.copyFileSync( sourcePath, destPath ); | ||
| fs.chmodSync( destPath, 0o755 ); | ||
| fs.rmSync( extractDir, { recursive: true } ); | ||
| } | ||
|
|
||
| async function extractZip( archivePath, destDir, binaryName ) { | ||
| console.log( 'Extracting node.exe...' ); | ||
|
|
||
| const extractDir = path.join( tmpDir, `node-${ NODE_VERSION }-${ nodePlatform }-${ nodeArch }` ); | ||
|
|
||
| await fs | ||
| .createReadStream( archivePath ) | ||
| .pipe( unzipper.Extract( { path: tmpDir } ) ) | ||
| .promise(); | ||
|
|
||
| const sourcePath = path.join( extractDir, 'node.exe' ); | ||
| const destPath = path.join( destDir, binaryName ); | ||
|
|
||
| fs.copyFileSync( sourcePath, destPath ); | ||
| fs.rmSync( extractDir, { recursive: true } ); | ||
| } | ||
|
|
||
| try { | ||
| await download( url, downloadPath ); | ||
|
|
||
| const binaryName = isWindows ? 'node.exe' : 'node'; | ||
|
|
||
| if ( isWindows ) { | ||
| await extractZip( downloadPath, binDir, binaryName ); | ||
| } else { | ||
| await extractTarGz( downloadPath, binDir, binaryName ); | ||
| } | ||
|
|
||
| fs.unlinkSync( downloadPath ); | ||
|
|
||
| console.log( `\nNode.js binary installed to ${ binDir }` ); | ||
|
|
||
| const files = fs.readdirSync( binDir ); | ||
| console.log( '\nBin directory contents:' ); | ||
| for ( const file of files ) { | ||
| const filePath = path.join( binDir, file ); | ||
| const stats = fs.statSync( filePath ); | ||
| const size = ( stats.size / 1024 / 1024 ).toFixed( 2 ); | ||
| console.log( ` ${ file } (${ size } MB)` ); | ||
| } | ||
| } catch ( error ) { | ||
| console.error( 'Error:', error.message ); | ||
| process.exit( 1 ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be safer to remove fallback and fail the job/build if there is no version in
.nvmrc?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can even consider have a separate version for the bundled Node.
Keeping the Node version in sync with the recommended version from Playground CLI should be the best approach. cc @fredrikekelund