Skip to content

Commit adeb92e

Browse files
na-naviAnoKno
andauthored
feat(cli): add --no-open flag to preview and play (#871)
Add --no-open boolean flag to both commands via citty's built-in boolean negation (--no-open sets args.open to false). - preview.ts: guard all 4 open() calls with args.open check - play.ts: guard the open() call with args.open check - Default is true (open browser), preserving existing behavior Closes #1 Co-authored-by: AnoKno <122017492+AnoKno@users.noreply.github.com>
1 parent 4bc2406 commit adeb92e

2 files changed

Lines changed: 46 additions & 17 deletions

File tree

packages/cli/src/commands/play.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const examples: Example[] = [
66
["Play the current project", "hyperframes play"],
77
["Play a specific project directory", "hyperframes play ./my-video"],
88
["Use a custom port", "hyperframes play --port 8080"],
9+
["Start without opening the browser", "hyperframes play --no-open"],
910
];
1011
import { resolve, dirname } from "node:path";
1112
import * as clack from "@clack/prompts";
@@ -17,6 +18,11 @@ export default defineCommand({
1718
args: {
1819
dir: { type: "positional", description: "Project directory", required: false },
1920
port: { type: "string", description: "Port to run the player server on", default: "3003" },
21+
open: {
22+
type: "boolean",
23+
default: true,
24+
description: "Open browser automatically",
25+
},
2026
},
2127
async run({ args }) {
2228
const project = resolveProject(args.dir);
@@ -145,7 +151,9 @@ export default defineCommand({
145151
console.log();
146152
console.log(` ${c.dim("Press Ctrl+C to stop")}`);
147153
console.log();
148-
import("open").then((mod) => mod.default(url)).catch(() => {});
154+
if (args.open) {
155+
import("open").then((mod) => mod.default(url)).catch(() => {});
156+
}
149157

150158
return new Promise<void>(() => {});
151159
},

packages/cli/src/commands/preview.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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
315333
async 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

Comments
 (0)