@@ -7,6 +7,7 @@ export const examples: Example[] = [
77 [ "Preview a specific project directory" , "hyperframes preview ./my-video" ] ,
88 [ "Use a custom port" , "hyperframes preview --port 8080" ] ,
99 [ "Force a new server even if one is already running" , "hyperframes preview --force-new" ] ,
10+ [ "Start without opening the browser" , "hyperframes preview --no-open" ] ,
1011 [ "List all active preview servers" , "hyperframes preview --list" ] ,
1112 [ "Kill all active preview servers" , "hyperframes preview --kill-all" ] ,
1213] ;
@@ -46,6 +47,11 @@ export default defineCommand({
4647 description : "Kill all active preview servers and exit" ,
4748 default : false ,
4849 } ,
50+ open : {
51+ type : "boolean" ,
52+ default : true ,
53+ description : "Open browser automatically" ,
54+ } ,
4955 } ,
5056 async run ( { args } ) {
5157 const startPort = parseInt ( args . port ?? "3002" , 10 ) ;
@@ -100,31 +106,36 @@ export default defineCommand({
100106 }
101107 }
102108
109+ const noOpen = ! args . open ;
110+
103111 if ( isDevMode ( ) ) {
104- return runDevMode ( dir , projectName ) ;
112+ return runDevMode ( dir , { projectName, noOpen } ) ;
105113 }
106114
107115 // If @hyperframes /studio is installed locally, use Vite for full HMR
108116 if ( hasLocalStudio ( dir ) ) {
109- return runLocalStudioMode ( dir , projectName ) ;
117+ return runLocalStudioMode ( dir , { projectName, noOpen } ) ;
110118 }
111119
112120 const forceNew = ! ! args [ "force-new" ] ;
113- return runEmbeddedMode ( dir , startPort , projectName , forceNew ) ;
121+ return runEmbeddedMode ( dir , startPort , { projectName, forceNew, noOpen } ) ;
114122 } ,
115123} ) ;
116124
117125/**
118126 * Dev mode: spawn the studio dev server from the monorepo.
119127 */
120- async function runDevMode ( dir : string , projectName ?: string ) : Promise < void > {
128+ async function runDevMode (
129+ dir : string ,
130+ options ?: { projectName ?: string ; noOpen ?: boolean } ,
131+ ) : Promise < void > {
121132 // Find monorepo root by navigating from packages/cli/src/commands/
122133 const thisFile = fileURLToPath ( import . meta. url ) ;
123134 const repoRoot = resolve ( dirname ( thisFile ) , ".." , ".." , ".." , ".." ) ;
124135
125136 // Symlink project into the studio's data directory
126137 const projectsDir = join ( repoRoot , "packages" , "studio" , "data" , "projects" ) ;
127- const pName = projectName ?? basename ( dir ) ;
138+ const pName = options ?. projectName ?? basename ( dir ) ;
128139 const symlinkPath = join ( projectsDir , pName ) ;
129140
130141 mkdirSync ( projectsDir , { recursive : true } ) ;
@@ -181,8 +192,10 @@ async function runDevMode(dir: string, projectName?: string): Promise<void> {
181192 console . log ( ` ${ c . dim ( "Press Ctrl+C to stop" ) } ` ) ;
182193 console . log ( ) ;
183194
184- const urlToOpen = `${ frontendUrl } #project/${ pName } ` ;
185- import ( "open" ) . then ( ( mod ) => mod . default ( urlToOpen ) ) . catch ( ( ) => { } ) ;
195+ if ( ! options ?. noOpen ) {
196+ const urlToOpen = `${ frontendUrl } #project/${ pName } ` ;
197+ import ( "open" ) . then ( ( mod ) => mod . default ( urlToOpen ) ) . catch ( ( ) => { } ) ;
198+ }
186199
187200 child . stdout ?. removeListener ( "data" , handleOutput ) ;
188201 child . stderr ?. removeListener ( "data" , handleOutput ) ;
@@ -232,10 +245,13 @@ function hasLocalStudio(dir: string): boolean {
232245 * Local studio mode: spawn Vite using a locally installed @hyperframes/studio.
233246 * Provides full Vite HMR and the complete studio experience.
234247 */
235- async function runLocalStudioMode ( dir : string , projectName ?: string ) : Promise < void > {
248+ async function runLocalStudioMode (
249+ dir : string ,
250+ options ?: { projectName ?: string ; noOpen ?: boolean } ,
251+ ) : Promise < void > {
236252 const req = createRequire ( join ( dir , "package.json" ) ) ;
237253 const studioPkgPath = dirname ( req . resolve ( "@hyperframes/studio/package.json" ) ) ;
238- const pName = projectName ?? basename ( dir ) ;
254+ const pName = options ?. projectName ?? basename ( dir ) ;
239255
240256 // Symlink project into studio's data directory
241257 const projectsDir = join ( studioPkgPath , "data" , "projects" ) ;
@@ -279,7 +295,9 @@ async function runLocalStudioMode(dir: string, projectName?: string): Promise<vo
279295 console . log ( ) ;
280296 console . log ( ` ${ c . dim ( "Press Ctrl+C to stop" ) } ` ) ;
281297 console . log ( ) ;
282- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
298+ if ( ! options ?. noOpen ) {
299+ import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
300+ }
283301 }
284302 }
285303
@@ -315,12 +333,11 @@ async function runLocalStudioMode(dir: string, projectName?: string): Promise<vo
315333async function runEmbeddedMode (
316334 dir : string ,
317335 startPort : number ,
318- projectName ?: string ,
319- forceNew = false ,
336+ options ?: { projectName ?: string ; forceNew ?: boolean ; noOpen ?: boolean } ,
320337) : Promise < void > {
321338 const { createStudioServer, resolveStudioBundle } = await import ( "../server/studioServer.js" ) ;
322339
323- const pName = projectName ?? basename ( dir ) ;
340+ const pName = options ?. projectName ?? basename ( dir ) ;
324341 const studioBundle = resolveStudioBundle ( ) ;
325342
326343 clack . intro ( c . bold ( "hyperframes preview" ) ) ;
@@ -345,7 +362,7 @@ async function runEmbeddedMode(
345362
346363 let result : FindPortResult ;
347364 try {
348- result = await findPortAndServe ( app . fetch , startPort , dir , forceNew ) ;
365+ result = await findPortAndServe ( app . fetch , startPort , dir , ! ! options ?. forceNew ) ;
349366 } catch ( err : unknown ) {
350367 s . stop ( c . error ( "Failed to start studio" ) ) ;
351368 console . error ( ) ;
@@ -366,7 +383,9 @@ async function runEmbeddedMode(
366383 ` ${ c . dim ( "Reusing existing server. Use --force-new to start a fresh instance." ) } ` ,
367384 ) ;
368385 console . log ( ) ;
369- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
386+ if ( ! options ?. noOpen ) {
387+ import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
388+ }
370389 return ;
371390 }
372391
@@ -385,7 +404,9 @@ async function runEmbeddedMode(
385404 console . log ( ) ;
386405 console . log ( ` ${ c . dim ( "Press Ctrl+C to stop" ) } ` ) ;
387406 console . log ( ) ;
388- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
407+ if ( ! options ?. noOpen ) {
408+ import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
409+ }
389410
390411 // Block until Ctrl+C. Node would normally exit on SIGINT, but the listening
391412 // HTTP server keeps handles open, so the event loop stays alive after the
0 commit comments