@@ -8,6 +8,7 @@ export const examples: Example[] = [
88 [ "Use a custom port" , "hyperframes preview --port 8080" ] ,
99 [ "Force a new server even if one is already running" , "hyperframes preview --force-new" ] ,
1010 [ "Start without opening the browser" , "hyperframes preview --no-open" ] ,
11+ [ "Open with a specific browser" , "hyperframes preview --browser-path /usr/bin/chromium" ] ,
1112 [ "List all active preview servers" , "hyperframes preview --list" ] ,
1213 [ "Kill all active preview servers" , "hyperframes preview --kill-all" ] ,
1314] ;
@@ -18,6 +19,7 @@ import { createRequire } from "node:module";
1819import * as clack from "@clack/prompts" ;
1920import { c } from "../ui/colors.js" ;
2021import { isDevMode } from "../utils/env.js" ;
22+ import { openBrowser } from "../utils/openBrowser.js" ;
2123import { lintProject } from "../utils/lintProject.js" ;
2224import { formatLintFindings } from "../utils/lintFormat.js" ;
2325import {
@@ -52,6 +54,14 @@ export default defineCommand({
5254 default : true ,
5355 description : "Open browser automatically" ,
5456 } ,
57+ "browser-path" : {
58+ type : "string" ,
59+ description : "Path to the browser executable to open" ,
60+ } ,
61+ "user-data-dir" : {
62+ type : "string" ,
63+ description : "Chromium-compatible user data directory (requires --browser-path)" ,
64+ } ,
5565 } ,
5666 async run ( { args } ) {
5767 const startPort = parseInt ( args . port ?? "3002" , 10 ) ;
@@ -106,19 +116,34 @@ export default defineCommand({
106116 }
107117 }
108118
119+ // Validation: --user-data-dir requires --browser-path
120+ if ( args [ "user-data-dir" ] && ! args [ "browser-path" ] ) {
121+ clack . log . error ( "--user-data-dir requires --browser-path" ) ;
122+ process . exitCode = 1 ;
123+ return ;
124+ }
125+
109126 const noOpen = ! args . open ;
127+ const browserPath = args [ "browser-path" ] as string | undefined ;
128+ const userDataDir = args [ "user-data-dir" ] as string | undefined ;
110129
111130 if ( isDevMode ( ) ) {
112- return runDevMode ( dir , { projectName, noOpen } ) ;
131+ return runDevMode ( dir , { projectName, noOpen, browserPath , userDataDir } ) ;
113132 }
114133
115134 // If @hyperframes /studio is installed locally, use Vite for full HMR
116135 if ( hasLocalStudio ( dir ) ) {
117- return runLocalStudioMode ( dir , { projectName, noOpen } ) ;
136+ return runLocalStudioMode ( dir , { projectName, noOpen, browserPath , userDataDir } ) ;
118137 }
119138
120139 const forceNew = ! ! args [ "force-new" ] ;
121- return runEmbeddedMode ( dir , startPort , { projectName, forceNew, noOpen } ) ;
140+ return runEmbeddedMode ( dir , startPort , {
141+ projectName,
142+ forceNew,
143+ noOpen,
144+ browserPath,
145+ userDataDir,
146+ } ) ;
122147 } ,
123148} ) ;
124149
@@ -127,7 +152,7 @@ export default defineCommand({
127152 */
128153async function runDevMode (
129154 dir : string ,
130- options ?: { projectName ?: string ; noOpen ?: boolean } ,
155+ options ?: { projectName ?: string ; noOpen ?: boolean ; browserPath ?: string ; userDataDir ?: string } ,
131156) : Promise < void > {
132157 // Find monorepo root by navigating from packages/cli/src/commands/
133158 const thisFile = fileURLToPath ( import . meta. url ) ;
@@ -194,7 +219,10 @@ async function runDevMode(
194219
195220 if ( ! options ?. noOpen ) {
196221 const urlToOpen = `${ frontendUrl } #project/${ pName } ` ;
197- import ( "open" ) . then ( ( mod ) => mod . default ( urlToOpen ) ) . catch ( ( ) => { } ) ;
222+ openBrowser ( urlToOpen , {
223+ browserPath : options ?. browserPath ,
224+ userDataDir : options ?. userDataDir ,
225+ } ) ;
198226 }
199227
200228 child . stdout ?. removeListener ( "data" , handleOutput ) ;
@@ -247,7 +275,7 @@ function hasLocalStudio(dir: string): boolean {
247275 */
248276async function runLocalStudioMode (
249277 dir : string ,
250- options ?: { projectName ?: string ; noOpen ?: boolean } ,
278+ options ?: { projectName ?: string ; noOpen ?: boolean ; browserPath ?: string ; userDataDir ?: string } ,
251279) : Promise < void > {
252280 const req = createRequire ( join ( dir , "package.json" ) ) ;
253281 const studioPkgPath = dirname ( req . resolve ( "@hyperframes/studio/package.json" ) ) ;
@@ -296,7 +324,10 @@ async function runLocalStudioMode(
296324 console . log ( ` ${ c . dim ( "Press Ctrl+C to stop" ) } ` ) ;
297325 console . log ( ) ;
298326 if ( ! options ?. noOpen ) {
299- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
327+ openBrowser ( `${ url } #project/${ pName } ` , {
328+ browserPath : options ?. browserPath ,
329+ userDataDir : options ?. userDataDir ,
330+ } ) ;
300331 }
301332 }
302333 }
@@ -333,7 +364,13 @@ async function runLocalStudioMode(
333364async function runEmbeddedMode (
334365 dir : string ,
335366 startPort : number ,
336- options ?: { projectName ?: string ; forceNew ?: boolean ; noOpen ?: boolean } ,
367+ options ?: {
368+ projectName ?: string ;
369+ forceNew ?: boolean ;
370+ noOpen ?: boolean ;
371+ browserPath ?: string ;
372+ userDataDir ?: string ;
373+ } ,
337374) : Promise < void > {
338375 const { createStudioServer, resolveStudioBundle } = await import ( "../server/studioServer.js" ) ;
339376
@@ -384,7 +421,10 @@ async function runEmbeddedMode(
384421 ) ;
385422 console . log ( ) ;
386423 if ( ! options ?. noOpen ) {
387- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
424+ openBrowser ( `${ url } #project/${ pName } ` , {
425+ browserPath : options ?. browserPath ,
426+ userDataDir : options ?. userDataDir ,
427+ } ) ;
388428 }
389429 return ;
390430 }
@@ -405,7 +445,10 @@ async function runEmbeddedMode(
405445 console . log ( ` ${ c . dim ( "Press Ctrl+C to stop" ) } ` ) ;
406446 console . log ( ) ;
407447 if ( ! options ?. noOpen ) {
408- import ( "open" ) . then ( ( mod ) => mod . default ( `${ url } #project/${ pName } ` ) ) . catch ( ( ) => { } ) ;
448+ openBrowser ( `${ url } #project/${ pName } ` , {
449+ browserPath : options ?. browserPath ,
450+ userDataDir : options ?. userDataDir ,
451+ } ) ;
409452 }
410453
411454 // Block until Ctrl+C. Node would normally exit on SIGINT, but the listening
0 commit comments