@browserbasehq/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Browserbase CLI
2
+
3
+ Browserbase's Bun-native development repo for the `bb` CLI.
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ bun install
9
+ bun run check
10
+ bun run cli -- --help
11
+ ```
12
+
13
+ ## Build
14
+
15
+ ```bash
16
+ bun run build
17
+ ```
18
+
19
+ The published artifact is Node-compatible JavaScript in `dist/`, even though local
20
+ development stays Bun-native.
package/dist/cli.js ADDED
@@ -0,0 +1,54 @@
1
+ import { Command, CommanderError } from "commander";
2
+ import { attachBrowseCommand } from "./commands/browse.js";
3
+ import { attachContextsCommand } from "./commands/contexts.js";
4
+ import { attachDashboardCommand } from "./commands/dashboard.js";
5
+ import { attachExtensionsCommand } from "./commands/extensions.js";
6
+ import { attachFetchCommand } from "./commands/fetch.js";
7
+ import { attachFunctionsCommand } from "./commands/functions.js";
8
+ import { attachProjectsCommand } from "./commands/projects.js";
9
+ import { attachSessionsCommand } from "./commands/sessions.js";
10
+ export function buildProgram() {
11
+ const program = new Command();
12
+ program
13
+ .name("bb")
14
+ .description("Browserbase CLI for platform APIs, functions, and browse passthrough.")
15
+ .version("0.1.0")
16
+ .helpCommand(false)
17
+ .showHelpAfterError()
18
+ .showSuggestionAfterError()
19
+ .enablePositionalOptions()
20
+ .exitOverride();
21
+ attachDashboardCommand(program);
22
+ attachBrowseCommand(program);
23
+ attachFunctionsCommand(program);
24
+ attachSessionsCommand(program);
25
+ attachProjectsCommand(program);
26
+ attachContextsCommand(program);
27
+ attachExtensionsCommand(program);
28
+ attachFetchCommand(program);
29
+ return program;
30
+ }
31
+ export async function run(argv) {
32
+ const program = buildProgram();
33
+ process.exitCode = 0;
34
+ if (argv.length === 0) {
35
+ program.outputHelp();
36
+ return 0;
37
+ }
38
+ try {
39
+ await program.parseAsync(argv, { from: "user" });
40
+ return process.exitCode ?? 0;
41
+ }
42
+ catch (error) {
43
+ if (error instanceof CommanderError) {
44
+ const commanderError = error;
45
+ if (commanderError.code === "commander.helpDisplayed" ||
46
+ commanderError.code === "commander.version") {
47
+ return 0;
48
+ }
49
+ return commanderError.exitCode;
50
+ }
51
+ console.error(error instanceof Error ? error.message : String(error));
52
+ return error?.exitCode ?? 1;
53
+ }
54
+ }
@@ -0,0 +1,27 @@
1
+ import { fail } from "../lib/command.js";
2
+ import { findExecutable, spawnPassthrough } from "../lib/process.js";
3
+ export function attachBrowseCommand(program) {
4
+ program
5
+ .command("browse")
6
+ .description("Forward commands to the standalone @browserbasehq/browse-cli binary.")
7
+ .argument("[args...]", "Arguments to forward to browse")
8
+ .allowUnknownOption(true)
9
+ .allowExcessArguments(true)
10
+ .passThroughOptions()
11
+ .action(async (args) => {
12
+ const browsePath = await findExecutable("browse");
13
+ if (!browsePath) {
14
+ fail([
15
+ "`browse` is not installed.",
16
+ "Install it with:",
17
+ "npm install -g @browserbasehq/browse-cli",
18
+ "",
19
+ "Then rerun your `bb browse ...` command.",
20
+ ].join("\n"));
21
+ }
22
+ const exitCode = await spawnPassthrough(browsePath, args ?? []);
23
+ if (exitCode !== 0) {
24
+ process.exitCode = exitCode;
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,42 @@
1
+ import { addCommonApiOptions, createBrowserbaseClient, mergeProjectIdIntoBody, outputJson, parseOptionalJsonObjectArg, requestBrowserbase, requestBrowserbaseJson, resolveProjectId, } from "../lib/command.js";
2
+ export function attachContextsCommand(program) {
3
+ const contexts = program
4
+ .command("contexts")
5
+ .description("Create, inspect, update, and delete Browserbase contexts.")
6
+ .helpCommand(false);
7
+ contexts.action(() => {
8
+ contexts.outputHelp();
9
+ });
10
+ addCommonApiOptions(contexts
11
+ .command("create")
12
+ .description("Create a context.")
13
+ .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
14
+ const client = createBrowserbaseClient(options);
15
+ const body = mergeProjectIdIntoBody(resolveProjectId(options), parseOptionalJsonObjectArg(options.body, "body"));
16
+ outputJson(await client.contexts.create(body));
17
+ });
18
+ addCommonApiOptions(contexts
19
+ .command("get <id>")
20
+ .description("Get a context by ID.")).action(async (id, options) => {
21
+ const client = createBrowserbaseClient(options);
22
+ outputJson(await client.contexts.retrieve(id));
23
+ });
24
+ addCommonApiOptions(contexts
25
+ .command("update <id>")
26
+ .description("Refresh the upload URL for a context.")).action(async (id, options) => {
27
+ outputJson(await requestBrowserbaseJson(options, `/v1/contexts/${id}`, {
28
+ method: "PUT",
29
+ }));
30
+ });
31
+ addCommonApiOptions(contexts
32
+ .command("delete <id>")
33
+ .description("Delete a context.")).action(async (id, options) => {
34
+ await requestBrowserbase(options, `/v1/contexts/${id}`, {
35
+ method: "DELETE",
36
+ headers: {
37
+ Accept: "*/*",
38
+ },
39
+ });
40
+ outputJson({ ok: true, id });
41
+ });
42
+ }
@@ -0,0 +1,11 @@
1
+ import { openUrl } from "../lib/open.js";
2
+ export function attachDashboardCommand(program) {
3
+ program
4
+ .command("dashboard")
5
+ .description("Open Browserbase Overview in your local browser.")
6
+ .action(async () => {
7
+ const url = "http://browserbase.com/overview";
8
+ await openUrl(url);
9
+ console.log(`Opened ${url}`);
10
+ });
11
+ }
@@ -0,0 +1,35 @@
1
+ import { addCommonApiOptions, createBrowserbaseClient, outputJson, requestBrowserbase, resolveUploadableFile, } from "../lib/command.js";
2
+ export function attachExtensionsCommand(program) {
3
+ const extensions = program
4
+ .command("extensions")
5
+ .description("Upload and manage Browserbase extensions.")
6
+ .helpCommand(false);
7
+ extensions.action(() => {
8
+ extensions.outputHelp();
9
+ });
10
+ addCommonApiOptions(extensions
11
+ .command("upload <file>")
12
+ .description("Upload an extension ZIP file.")).action(async (file, options) => {
13
+ const client = createBrowserbaseClient(options);
14
+ outputJson(await client.extensions.create({
15
+ file: await resolveUploadableFile(file, "extension"),
16
+ }));
17
+ });
18
+ addCommonApiOptions(extensions
19
+ .command("get <id>")
20
+ .description("Get an extension by ID.")).action(async (id, options) => {
21
+ const client = createBrowserbaseClient(options);
22
+ outputJson(await client.extensions.retrieve(id));
23
+ });
24
+ addCommonApiOptions(extensions
25
+ .command("delete <id>")
26
+ .description("Delete an extension.")).action(async (id, options) => {
27
+ await requestBrowserbase(options, `/v1/extensions/${id}`, {
28
+ method: "DELETE",
29
+ headers: {
30
+ Accept: "*/*",
31
+ },
32
+ });
33
+ outputJson({ ok: true, id });
34
+ });
35
+ }
@@ -0,0 +1,35 @@
1
+ import { addCommonApiOptions, createBrowserbaseClient, outputJson, writeOutputFile, } from "../lib/command.js";
2
+ export function attachFetchCommand(program) {
3
+ addCommonApiOptions(program
4
+ .command("fetch <url>")
5
+ .description("Fetch a page with the Browserbase Fetch API.")
6
+ .option("--allow-insecure-ssl", "Bypass TLS certificate verification.")
7
+ .option("--allow-redirects", "Follow HTTP redirects.")
8
+ .option("--proxies", "Enable Browserbase proxy support.")
9
+ .option("--output <output>", "Write the fetched content to a file.")).action(async (url, options) => {
10
+ const client = createBrowserbaseClient(options);
11
+ const result = await client.fetchAPI.create({
12
+ url,
13
+ allowInsecureSsl: options.allowInsecureSsl,
14
+ allowRedirects: options.allowRedirects,
15
+ proxies: options.proxies,
16
+ });
17
+ if (options.output) {
18
+ await writeOutputFile(options.output, result.content);
19
+ if (options.json) {
20
+ outputJson({ ...result, outputPath: options.output });
21
+ return;
22
+ }
23
+ console.log(`Saved fetched content to ${options.output}`);
24
+ return;
25
+ }
26
+ if (options.json) {
27
+ outputJson(result);
28
+ return;
29
+ }
30
+ process.stdout.write(result.content);
31
+ if (!result.content.endsWith("\n")) {
32
+ process.stdout.write("\n");
33
+ }
34
+ });
35
+ }
@@ -0,0 +1,70 @@
1
+ import { Option } from "commander";
2
+ import { addFunctionsApiOptions } from "../lib/command.js";
3
+ import { initFunctionsProject } from "../lib/functions/init.js";
4
+ import { invokeFunction } from "../lib/functions/invoke.js";
5
+ import { startFunctionsDevServer } from "../lib/functions/dev.js";
6
+ import { publishFunction } from "../lib/functions/publish.js";
7
+ const packageManagers = ["npm", "pnpm"];
8
+ export function attachFunctionsCommand(program) {
9
+ const functions = program
10
+ .command("functions")
11
+ .description("Develop, publish, and invoke Browserbase Functions.")
12
+ .helpCommand(false);
13
+ functions.action(() => {
14
+ functions.outputHelp();
15
+ });
16
+ functions
17
+ .command("init [projectName]")
18
+ .description("Initialize a new Browserbase Functions project.")
19
+ .addOption(new Option("--package-manager <packageManager>", "Package manager to use.")
20
+ .choices([...packageManagers])
21
+ .default("pnpm"))
22
+ .action(async (projectName, options) => {
23
+ await initFunctionsProject({
24
+ projectName: projectName ?? "my-browserbase-function",
25
+ packageManager: options.packageManager ?? "pnpm",
26
+ });
27
+ });
28
+ addFunctionsApiOptions(functions
29
+ .command("dev <entrypoint>")
30
+ .description("Run the local Browserbase Functions development server.")
31
+ .option("--port <port>", "Port to listen on.", "14113")
32
+ .option("--host <host>", "Host to bind to.", "127.0.0.1"), { includeProjectId: true, includeVerbose: true }).action(async (entrypoint, options) => {
33
+ await startFunctionsDevServer({
34
+ entrypoint,
35
+ port: Number(options.port ?? "14113"),
36
+ host: options.host ?? "127.0.0.1",
37
+ apiKey: options.apiKey,
38
+ projectId: options.projectId,
39
+ apiUrl: options.apiUrl,
40
+ verbose: options.verbose ?? false,
41
+ });
42
+ });
43
+ addFunctionsApiOptions(functions
44
+ .command("publish <entrypoint>")
45
+ .description("Package and upload a Browserbase Function build.")
46
+ .option("--dry-run", "Show what would be published without uploading."), { includeProjectId: true }).action(async (entrypoint, options) => {
47
+ await publishFunction({
48
+ entrypoint,
49
+ apiKey: options.apiKey,
50
+ projectId: options.projectId,
51
+ apiUrl: options.apiUrl,
52
+ dryRun: options.dryRun ?? false,
53
+ });
54
+ });
55
+ addFunctionsApiOptions(functions
56
+ .command("invoke [functionId]")
57
+ .description("Invoke a deployed Browserbase Function or check invocation status.")
58
+ .option("--params <params>", "JSON params to pass to the function.")
59
+ .option("--no-wait", "Return immediately after creating the invocation.")
60
+ .option("--check-status <checkStatus>", "Invocation ID to inspect without creating a new invocation.")).action(async (functionId, options) => {
61
+ await invokeFunction({
62
+ functionId,
63
+ params: options.params,
64
+ apiKey: options.apiKey,
65
+ apiUrl: options.apiUrl,
66
+ noWait: options.wait === false,
67
+ checkStatus: options.checkStatus,
68
+ });
69
+ });
70
+ }
@@ -0,0 +1,28 @@
1
+ import { addCommonApiOptions, createBrowserbaseClient, outputJson } from "../lib/command.js";
2
+ export function attachProjectsCommand(program) {
3
+ const projects = program
4
+ .command("projects")
5
+ .description("List and inspect Browserbase projects.")
6
+ .helpCommand(false);
7
+ projects.action(() => {
8
+ projects.outputHelp();
9
+ });
10
+ addCommonApiOptions(projects
11
+ .command("list")
12
+ .description("List projects visible to the current API key.")).action(async (options) => {
13
+ const client = createBrowserbaseClient(options);
14
+ outputJson(await client.projects.list());
15
+ });
16
+ addCommonApiOptions(projects
17
+ .command("get <id>")
18
+ .description("Get a project by ID.")).action(async (id, options) => {
19
+ const client = createBrowserbaseClient(options);
20
+ outputJson(await client.projects.retrieve(id));
21
+ });
22
+ addCommonApiOptions(projects
23
+ .command("usage <id>")
24
+ .description("Get project usage.")).action(async (id, options) => {
25
+ const client = createBrowserbaseClient(options);
26
+ outputJson(await client.projects.usage(id));
27
+ });
28
+ }
@@ -0,0 +1,99 @@
1
+ import { Option } from "commander";
2
+ import { addCommonApiOptions, createBrowserbaseClient, fail, mergeProjectIdIntoBody, outputJson, parseOptionalJsonObjectArg, resolveProjectId, resolveUploadableFile, writeBinaryOutput, } from "../lib/command.js";
3
+ export function attachSessionsCommand(program) {
4
+ const sessions = program
5
+ .command("sessions")
6
+ .description("Create, inspect, and manage Browserbase sessions.")
7
+ .helpCommand(false);
8
+ sessions.action(() => {
9
+ sessions.outputHelp();
10
+ });
11
+ addCommonApiOptions(sessions
12
+ .command("list")
13
+ .description("List sessions.")
14
+ .option("--q <q>", "Session metadata query.")).action(async (options) => {
15
+ const client = createBrowserbaseClient(options);
16
+ outputJson(await client.sessions.list(options.q ? { q: options.q } : {}));
17
+ });
18
+ addCommonApiOptions(sessions
19
+ .command("get <id>")
20
+ .description("Get a session by ID.")).action(async (id, options) => {
21
+ const client = createBrowserbaseClient(options);
22
+ outputJson(await client.sessions.retrieve(id));
23
+ });
24
+ addCommonApiOptions(sessions
25
+ .command("create")
26
+ .description("Create a session.")
27
+ .option("--body <body>", "Optional JSON request body.")).action(async (options) => {
28
+ const client = createBrowserbaseClient(options);
29
+ const body = mergeProjectIdIntoBody(resolveProjectId(options), parseOptionalJsonObjectArg(options.body, "body"));
30
+ outputJson(await client.sessions.create(body));
31
+ });
32
+ addCommonApiOptions(sessions
33
+ .command("update <id>")
34
+ .description("Update a session.")
35
+ .addOption(new Option("--status <status>", "Session status update.")
36
+ .choices(["REQUEST_RELEASE"])
37
+ .default("REQUEST_RELEASE"))
38
+ .option("--body <body>", "Optional JSON request body. Merged with --status when provided.")).action(async (id, options) => {
39
+ const client = createBrowserbaseClient(options);
40
+ const sessionId = id || fail("Session ID is required.");
41
+ const body = mergeProjectIdIntoBody(resolveProjectId(options), {
42
+ ...parseOptionalJsonObjectArg(options.body, "body"),
43
+ status: options.status ?? "REQUEST_RELEASE",
44
+ });
45
+ outputJson(await client.sessions.update(sessionId, body));
46
+ });
47
+ addCommonApiOptions(sessions
48
+ .command("debug <id>")
49
+ .description("Get live debugger URLs for a session.")).action(async (id, options) => {
50
+ const client = createBrowserbaseClient(options);
51
+ outputJson(await client.sessions.debug(id));
52
+ });
53
+ addCommonApiOptions(sessions
54
+ .command("logs <id>")
55
+ .description("Get session logs.")).action(async (id, options) => {
56
+ const client = createBrowserbaseClient(options);
57
+ outputJson(await client.sessions.logs.list(id));
58
+ });
59
+ addCommonApiOptions(sessions
60
+ .command("recording <id>")
61
+ .description("Get rrweb session recording events.")).action(async (id, options) => {
62
+ const client = createBrowserbaseClient(options);
63
+ outputJson(await client.sessions.recording.retrieve(id));
64
+ });
65
+ const downloads = sessions
66
+ .command("downloads")
67
+ .description("Download session artifacts as a ZIP file.")
68
+ .helpCommand(false);
69
+ downloads.action(() => {
70
+ downloads.outputHelp();
71
+ });
72
+ addCommonApiOptions(downloads
73
+ .command("get <id>")
74
+ .description("Download session files as a ZIP archive.")
75
+ .option("--output <output>", "Path to write the ZIP file.")).action(async (id, options) => {
76
+ const client = createBrowserbaseClient(options);
77
+ const response = await client.sessions.downloads.list(id);
78
+ const defaultPath = `${id}-downloads.zip`;
79
+ const outputPath = options.output ?? defaultPath;
80
+ const bytes = new Uint8Array(await response.arrayBuffer());
81
+ await writeBinaryOutput(outputPath, bytes);
82
+ outputJson({ ok: true, outputPath, sizeBytes: bytes.byteLength });
83
+ });
84
+ const uploads = sessions
85
+ .command("uploads")
86
+ .description("Upload files to a running session.")
87
+ .helpCommand(false);
88
+ uploads.action(() => {
89
+ uploads.outputHelp();
90
+ });
91
+ addCommonApiOptions(uploads
92
+ .command("create <id> <file>")
93
+ .description("Upload a file to a session.")).action(async (id, file, options) => {
94
+ const client = createBrowserbaseClient(options);
95
+ outputJson(await client.sessions.uploads.create(id, {
96
+ file: await resolveUploadableFile(file, "session upload"),
97
+ }));
98
+ });
99
+ }
@@ -0,0 +1,162 @@
1
+ import Browserbase from "@browserbasehq/sdk";
2
+ import { createReadStream, constants } from "node:fs";
3
+ import { access, mkdir, writeFile } from "node:fs/promises";
4
+ import { dirname, resolve } from "node:path";
5
+ const defaultBrowserbaseApiUrl = "https://api.browserbase.com";
6
+ export class CommandFailure extends Error {
7
+ exitCode;
8
+ constructor(message, exitCode = 1) {
9
+ super(message);
10
+ this.name = "CommandFailure";
11
+ this.exitCode = exitCode;
12
+ }
13
+ }
14
+ export function fail(message, exitCode = 1) {
15
+ throw new CommandFailure(message, exitCode);
16
+ }
17
+ export function addCommonApiOptions(command) {
18
+ return command
19
+ .option("--api-key <apiKey>", "Override the Browserbase API key.")
20
+ .option("--project-id <projectId>", "Override the Browserbase project ID.")
21
+ .option("--base-url <baseUrl>", "Override the Browserbase API base URL.")
22
+ .option("--json", "Print JSON output.")
23
+ .option("--verbose", "Enable verbose logging.");
24
+ }
25
+ export function addFunctionsApiOptions(command, options = {}) {
26
+ const { includeProjectId = false, includeVerbose = false } = options;
27
+ command
28
+ .option("--api-key <apiKey>", "Override the Browserbase API key.")
29
+ .option("--api-url <apiUrl>", "Override the Browserbase API base URL.");
30
+ if (includeProjectId) {
31
+ command.option("--project-id <projectId>", "Override the Browserbase project ID.");
32
+ }
33
+ if (includeVerbose) {
34
+ command.option("--verbose", "Print verbose server and runtime logs.");
35
+ }
36
+ return command;
37
+ }
38
+ export function resolveApiKey(args) {
39
+ const apiKey = args.apiKey ?? process.env.BROWSERBASE_API_KEY;
40
+ return apiKey || fail("Missing Browserbase API key. Set BROWSERBASE_API_KEY or pass --api-key.");
41
+ }
42
+ export function resolveBaseUrl(args) {
43
+ return args.baseUrl ?? process.env.BROWSERBASE_BASE_URL;
44
+ }
45
+ export function resolveApiBaseUrl(args) {
46
+ return resolveBaseUrl(args) ?? defaultBrowserbaseApiUrl;
47
+ }
48
+ export function resolveProjectId(args, options = {}) {
49
+ const projectId = args.projectId ?? process.env.BROWSERBASE_PROJECT_ID;
50
+ if (options.required && !projectId) {
51
+ fail("Missing Browserbase project ID. Set BROWSERBASE_PROJECT_ID or pass --project-id.");
52
+ }
53
+ return projectId;
54
+ }
55
+ export function createBrowserbaseClient(args) {
56
+ return new Browserbase({
57
+ apiKey: resolveApiKey(args),
58
+ baseURL: resolveBaseUrl(args),
59
+ });
60
+ }
61
+ export function parseOptionalJsonObjectArg(rawValue, label) {
62
+ if (!rawValue) {
63
+ return {};
64
+ }
65
+ if (typeof rawValue !== "string") {
66
+ fail(`${label} must be provided as a JSON string.`);
67
+ }
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(rawValue);
71
+ }
72
+ catch (error) {
73
+ fail(`Invalid JSON for ${label}: ${error.message}`);
74
+ }
75
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
76
+ fail(`${label} must be a JSON object.`);
77
+ }
78
+ return parsed;
79
+ }
80
+ export function parseOptionalJsonValueArg(rawValue, label) {
81
+ if (!rawValue) {
82
+ return {};
83
+ }
84
+ if (typeof rawValue !== "string") {
85
+ fail(`${label} must be provided as a JSON string.`);
86
+ }
87
+ try {
88
+ return JSON.parse(rawValue);
89
+ }
90
+ catch (error) {
91
+ fail(`Invalid JSON for ${label}: ${error.message}`);
92
+ }
93
+ }
94
+ export function mergeProjectIdIntoBody(projectId, body) {
95
+ if (projectId && body.projectId === undefined) {
96
+ return { ...body, projectId };
97
+ }
98
+ return body;
99
+ }
100
+ export function outputJson(value) {
101
+ console.log(JSON.stringify(value, null, 2));
102
+ }
103
+ export async function resolveUploadableFile(filePath, label) {
104
+ const absolutePath = resolve(filePath);
105
+ try {
106
+ await access(absolutePath, constants.R_OK);
107
+ }
108
+ catch {
109
+ fail(`Could not read ${label} file: ${absolutePath}`);
110
+ }
111
+ return createReadStream(absolutePath);
112
+ }
113
+ export async function readBrowserbaseError(response) {
114
+ try {
115
+ const data = await response.json();
116
+ if (typeof data === "object" && data !== null) {
117
+ const message = data.message || data.error;
118
+ if (message) {
119
+ return message;
120
+ }
121
+ }
122
+ }
123
+ catch {
124
+ try {
125
+ const text = await response.text();
126
+ if (text) {
127
+ return text;
128
+ }
129
+ }
130
+ catch {
131
+ return `${response.status} ${response.statusText}`;
132
+ }
133
+ }
134
+ return `${response.status} ${response.statusText}`;
135
+ }
136
+ export async function requestBrowserbase(args, pathname, init = {}) {
137
+ const response = await fetch(new URL(pathname, resolveApiBaseUrl(args)), {
138
+ ...init,
139
+ headers: {
140
+ "x-bb-api-key": resolveApiKey(args),
141
+ ...(init.headers ?? {}),
142
+ },
143
+ });
144
+ if (!response.ok) {
145
+ fail(await readBrowserbaseError(response));
146
+ }
147
+ return response;
148
+ }
149
+ export async function requestBrowserbaseJson(args, pathname, init = {}) {
150
+ const response = await requestBrowserbase(args, pathname, init);
151
+ return (await response.json());
152
+ }
153
+ export async function writeOutputFile(pathname, contents) {
154
+ const absolutePath = resolve(pathname);
155
+ await mkdir(dirname(absolutePath), { recursive: true });
156
+ await writeFile(absolutePath, contents, "utf8");
157
+ }
158
+ export async function writeBinaryOutput(pathname, contents) {
159
+ const absolutePath = resolve(pathname);
160
+ await mkdir(dirname(absolutePath), { recursive: true });
161
+ await writeFile(absolutePath, contents);
162
+ }