@browserbasehq/sdk-functions 0.0.2 → 0.0.5
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/dist/cli.js +13 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/templates/.env.template +2 -2
- package/package.json +2 -3
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import a from 'chalk';
|
|
3
3
|
import * as L from 'fs';
|
|
4
4
|
import { existsSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
5
|
-
import * as
|
|
5
|
+
import * as _ from 'path';
|
|
6
6
|
import { dirname, resolve, join } from 'path';
|
|
7
7
|
import 'dotenv/config';
|
|
8
|
-
import
|
|
8
|
+
import Ge from 'archiver';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import { createServer } from 'http';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
@@ -15,20 +15,20 @@ import { execSync, spawn } from 'child_process';
|
|
|
15
15
|
import { createRequire } from 'module';
|
|
16
16
|
import { fileURLToPath } from 'url';
|
|
17
17
|
|
|
18
|
-
var Z=Object.defineProperty;var Ie=(o,e,n)=>e in o?Z(o,e,{enumerable:true,configurable:true,writable:true,value:n}):o[e]=n;var
|
|
19
|
-
`).map(i=>i.trim()).filter(i=>i&&!i.startsWith("#")).map(i=>i.endsWith("/")?`${i}**`:i);return [...n,...t]}catch(r){return console.warn(a.yellow(r,"Warning: Could not read .gitignore file, using defaults")),n}}async function de(o,e){return new Promise((n,r)=>{console.log(a.cyan("Creating archive..."));let t=
|
|
18
|
+
var Z=Object.defineProperty;var Ie=(o,e,n)=>e in o?Z(o,e,{enumerable:true,configurable:true,writable:true,value:n}):o[e]=n;var A=(o,e)=>()=>(o&&(e=o(o=0)),e);var Se=(o,e)=>{for(var n in e)Z(o,n,{get:e[n],enumerable:true});};var p=(o,e,n)=>Ie(o,typeof e!="symbol"?e+"":e,n);function ae(o){let e=process.env.BROWSERBASE_API_KEY;e||(console.error(a.red("Error: BROWSERBASE_API_KEY not found in environment variables.")),console.log(a.gray("Please set BROWSERBASE_API_KEY in your .env file or environment.")),process.exit(1));let n=process.env.BROWSERBASE_PROJECT_ID;n||(console.error(a.red("Error: BROWSERBASE_PROJECT_ID not found in environment variables.")),console.log(a.gray("Please set BROWSERBASE_PROJECT_ID in your .env file or environment.")),process.exit(1));let r=o.apiUrl||process.env.BROWSERBASE_API_BASE_URL||"https://api.browserbase.com",t=o.entrypoint||"main.ts",i=_.resolve(t);L.existsSync(i)||(console.error(a.red(`Error: Entrypoint file not found: ${i}`)),process.exit(1));let s=_.extname(t).toLowerCase();return [".ts",".js",".mjs",".mts"].includes(s)||(console.error(a.red(`Error: Invalid entrypoint extension: ${s}. Must be .ts, .js, .mjs, or .mts`)),process.exit(1)),{apiKey:e,projectId:n,apiUrl:r,entrypoint:t,workingDirectory:process.cwd()}}function ce(o){o.apiKey.startsWith("bb_")||console.warn(a.yellow("Warning: API key doesn't start with 'bb_'. Make sure you're using a valid Browserbase API key."));}var le=A(()=>{});function Ye(o){let e=_.join(o,".gitignore"),n=["node_modules/**",".git/**",".env",".env.*","*.log",".DS_Store","dist/**","build/**","*.zip","*.tar","*.tar.gz",".vscode/**",".idea/**"];if(!L.existsSync(e))return n;try{let t=L.readFileSync(e,"utf-8").split(`
|
|
19
|
+
`).map(i=>i.trim()).filter(i=>i&&!i.startsWith("#")).map(i=>i.endsWith("/")?`${i}**`:i);return [...n,...t]}catch(r){return console.warn(a.yellow(r,"Warning: Could not read .gitignore file, using defaults")),n}}async function de(o,e){return new Promise((n,r)=>{console.log(a.cyan("Creating archive..."));let t=Ge("tar",{gzip:true,gzipOptions:{level:9}}),i=[],s=0;t.on("data",c=>{i.push(c);}),t.on("entry",c=>{if(!c.stats?.isDirectory()&&(s++,e?.dryRun)){let u=_.relative(o,c.name);console.log(a.gray(` + ${u}`));}}),t.on("end",()=>{let c=Buffer.concat(i),u=(c.length/(1024*1024)).toFixed(2);console.log(a.green(`\u2713 Archive created: ${s} files, ${u} MB`)),n({buffer:c,size:c.length,fileCount:s});}),t.on("error",c=>{console.error(a.red(`Archive error: ${c.message}`)),r(c);}),t.on("warning",c=>{c.code==="ENOENT"?console.warn(a.yellow(`Warning: ${c.message}`)):r(c);});let d=Ye(o);e?.dryRun&&(console.log(a.gray(`
|
|
20
20
|
Ignoring patterns:`)),d.forEach(c=>{console.log(a.gray(` - ${c}`));}),console.log(a.gray(`
|
|
21
|
-
Including files:`))),t.glob("**/*",{cwd:o,ignore:d,dot:true,follow:false}),t.finalize();})}function pe(o,e=50){let n=o/1048576;n>e&&(console.error(a.red(`Error: Archive size (${n.toFixed(2)} MB) exceeds maximum allowed size (${e} MB)`)),console.log(a.gray("Consider adding more patterns to .gitignore to reduce archive size")),process.exit(1));}var ue=
|
|
21
|
+
Including files:`))),t.glob("**/*",{cwd:o,ignore:d,dot:true,follow:false}),t.finalize();})}function pe(o,e=50){let n=o/1048576;n>e&&(console.error(a.red(`Error: Archive size (${n.toFixed(2)} MB) exceeds maximum allowed size (${e} MB)`)),console.log(a.gray("Consider adding more patterns to .gitignore to reduce archive size")),process.exit(1));}var ue=A(()=>{});async function ge(o,e,n){if(n?.dryRun)return console.log(a.cyan(`
|
|
22
22
|
[Dry run] Would upload to:`)),console.log(a.gray(` URL: ${o.apiUrl}/v1/functions/builds`)),console.log(a.gray(` Project ID: ${o.projectId}`)),console.log(a.gray(` Entrypoint: ${o.entrypoint}`)),console.log(a.gray(` Archive size: ${(e.length/(1024*1024)).toFixed(2)} MB`)),{success:true,message:"Dry run completed successfully"};console.log(a.cyan(`
|
|
23
23
|
Uploading build...`));try{let r=new FormData,t={entrypoint:o.entrypoint,projectId:o.projectId};r.append("metadata",JSON.stringify(t));let i=new Blob([e],{type:"application/gzip"});r.append("archive",i,"archive.tar.gz");let s=`${o.apiUrl}/v1/functions/builds`;console.log(a.gray(`Uploading to: ${s}`));let d=await fetch(s,{method:"POST",headers:{"x-bb-api-key":o.apiKey},body:r});if(!d.ok){let u=`HTTP ${d.status}: ${d.statusText}`;try{let m=await d.json();if(typeof m=="object"&&m!==null&&("message"in m||"error"in m)){let h=m;u=h.message||h.error||u;}}catch{try{let m=await d.text();m&&(u=m);}catch{}}return console.error(a.red(`Upload failed: ${u}`)),{success:!1,message:u}}let c={};try{let u=await d.json();typeof u=="object"&&u!==null&&(c=u);}catch{}return c.id?(console.log(a.green("\u2713 Build uploaded successfully")),console.log(a.gray(`Build ID: ${c.id}`)),{success:!0,buildId:c.id,message:"Build uploaded successfully"}):(console.error(a.red("Upload failed: No build ID received in response")),{success:!1,message:"No build ID received in response"})}catch(r){let t=r instanceof Error?r.message:"Unknown error occurred";return console.error(a.red(`Upload error: ${t}`)),r&&typeof r=="object"&&"code"in r&&r.code==="ECONNREFUSED"&&console.log(a.yellow(`
|
|
24
|
-
Cannot connect to ${o.apiUrl}. Make sure the API server is running.`)),{success:false,message:t}}}async function
|
|
25
|
-
Waiting for build to complete...`)),console.log(a.gray("(Builds typically take around 1 minute to complete)"));for(let i=0;i<t;i++){let s=await
|
|
24
|
+
Cannot connect to ${o.apiUrl}. Make sure the API server is running.`)),{success:false,message:t}}}async function Ze(o,e){try{let n=`${o.apiUrl}/v1/functions/builds/${e}`,r=await fetch(n,{method:"GET",headers:{"x-bb-api-key":o.apiKey}});return r.ok?await r.json():(console.error(a.red(`Failed to get build status: HTTP ${r.status}`)),null)}catch(n){return console.error(a.red(`Error fetching build status: ${n instanceof Error?n.message:"Unknown error"}`)),null}}async function me(o,e,n){let r=2e3,t=100;console.log(a.cyan(`
|
|
25
|
+
Waiting for build to complete...`)),console.log(a.gray("(Builds typically take around 1 minute to complete)"));for(let i=0;i<t;i++){let s=await Ze(o,e);if(!s)return console.error(a.red("Failed to get build status")),null;if(process.stdout.write(`\r${a.gray(`Status: ${s.status}... (polling ${i+1}/${t})`)}`),s.status!=="RUNNING")return process.stdout.write("\r"+" ".repeat(60)+"\r"),s.status==="COMPLETED"?console.log(a.green("\u2713 Build completed successfully")):s.status==="FAILED"&&console.error(a.red("\u2717 Build failed")),s;await new Promise(d=>setTimeout(d,r));}return process.stdout.write("\r"+" ".repeat(60)+"\r"),console.error(a.yellow("Build is still running after maximum wait time (~3 minutes)")),console.error(a.yellow("Please check the dashboard for the current build status.")),null}var fe=A(()=>{});var ye={};Se(ye,{publishFunction:()=>Qe});function he(o){if(console.log(a.bold.cyan(`
|
|
26
26
|
\u{1F4E6} Build Details`)),console.log(a.gray("\u2500".repeat(50))),console.log(a.white(`Build ID: ${a.cyan(o.id)}`)),console.log(a.white(`Project ID: ${a.cyan(o.projectId)}`)),console.log(a.white(`Status: ${a.green(o.status)}`)),o.request?.entrypoint&&console.log(a.white(`Entrypoint: ${a.cyan(o.request.entrypoint)}`)),o.startedAt&&console.log(a.white(`Started: ${a.gray(new Date(o.startedAt).toLocaleString())}`)),o.endedAt&&(console.log(a.white(`Completed: ${a.gray(new Date(o.endedAt).toLocaleString())}`)),o.startedAt)){let e=new Date(o.endedAt).getTime()-new Date(o.startedAt).getTime(),n=Math.floor(e/1e3);console.log(a.white(`Duration: ${a.cyan(`${n} seconds`)}`));}o.expiresAt&&console.log(a.white(`Expires: ${a.gray(new Date(o.expiresAt).toLocaleString())}`)),o.builtFunctions&&o.builtFunctions.length>0?(console.log(a.bold.cyan(`
|
|
27
27
|
\u{1F680} Built Functions`)),console.log(a.gray("\u2500".repeat(50))),o.builtFunctions.forEach((e,n)=>{if(console.log(a.bold.white(`
|
|
28
28
|
${n+1}. ${e.name}`)),console.log(a.white(` Function ID: ${a.cyan(e.id)}`)),e.createdVersion&&(console.log(a.white(` Version ID: ${a.cyan(e.createdVersion.id)}`)),e.createdVersion.sessionCreateParams)){let r=e.createdVersion.sessionCreateParams;Object.keys(r).length>0&&(console.log(a.white(" Browser Settings:")),Object.entries(r).forEach(([i,s])=>{console.log(a.gray(` - ${i}: ${JSON.stringify(s)}`));}));}}),console.log(a.bold.cyan(`
|
|
29
29
|
\u2728 Next Steps`)),console.log(a.gray("\u2500".repeat(50))),console.log(a.white("Your functions are ready to be invoked! Invoke using cURL:")),o.builtFunctions.forEach(e=>{console.log(a.gray(`
|
|
30
30
|
curl --request POST \\`)),console.log(a.gray(` --url ${e.projectId?"https":"http"}://api.browserbase.com/v1/functions/${e.id}/invoke \\`)),console.log(a.gray(" --header 'Content-Type: application/json' \\")),console.log(a.gray(" --header 'x-bb-api-key: YOUR_API_KEY' \\")),console.log(a.gray(` --data '{"params": {}}'`));})):console.log(a.yellow(`
|
|
31
|
-
No functions were built. Please check your entrypoint and function exports.`));}async function
|
|
31
|
+
No functions were built. Please check your entrypoint and function exports.`));}async function Qe(o){console.log(a.bold.cyan(`
|
|
32
32
|
Browserbase Functions - Publish
|
|
33
33
|
`));try{let e={};o.entrypoint!==void 0&&(e.entrypoint=o.entrypoint),o.apiUrl!==void 0&&(e.apiUrl=o.apiUrl);let n=ae(e);ce(n),console.log(a.gray(`Working directory: ${n.workingDirectory}`)),console.log(a.gray(`Entrypoint: ${n.entrypoint}`)),console.log(a.gray(`API URL: ${n.apiUrl}`)),console.log(a.gray(`Project ID: ${n.projectId}`)),o.dryRun&&console.log(a.yellow(`
|
|
34
34
|
[Dry run mode - no files will be uploaded]
|
|
@@ -42,12 +42,12 @@ Build ID: ${s.buildId}`));let d=await me(n,s.buildId);d?.status==="COMPLETED"?(c
|
|
|
42
42
|
\u2717 Build failed during processing`)),d&&he(d),process.exit(1)):console.log(a.yellow(`
|
|
43
43
|
Build status could not be determined. Check the dashboard for updates.`));}else console.log(a.cyan(`
|
|
44
44
|
Your function will be available for invocation once the build is processed.`));}catch(e){console.error(a.red(`
|
|
45
|
-
\u2717 Publish failed: ${e.message??"unknown error"}`)),process.exit(1);}}var ve=
|
|
46
|
-
`)+a.red(` Please set
|
|
47
|
-
`)+a.gray(" Copy .env.example to .env and fill in your credentials.")),new Error("Missing Browserbase credentials");this.projectId=e,this.apiKey=n;}async initialize(){this.initialized||(this.browserbaseClient=new xe({apiKey:this.apiKey}),this.initialized=true,console.log(a.green("\u2713 Browserbase client initialized")));}async createSession(e){if(!this.browserbaseClient)throw new Error("Browser manager not initialized");console.log(a.cyan("Creating browser session..."));let n=await this.browserbaseClient.sessions.create({projectId:this.projectId,...e}),r={id:n.id,connectUrl:n.connectUrl};return console.log(a.green(`\u2713 Browser session created: ${r.id}`)),r}async closeSession(e){if(!this.browserbaseClient)throw new Error("Browser manager not initialized");try{console.log(a.cyan(`Closing browser session: ${e}...`)),await this.browserbaseClient.sessions.update(e,{projectId:this.projectId,status:"REQUEST_RELEASE"}),console.log(a.green(`\u2713 Browser session closed: ${e}`));}catch(n){console.warn(a.yellow(`\u26A0\uFE0F Could not close session ${e}:`),n instanceof Error?n.message:String(n));}}getProjectId(){return this.projectId}isInitialized(){return this.initialized}};var T=class{constructor(e){p(this,"manifests",new Map);p(this,"manifestsPath");this.manifestsPath=e||join(process.cwd(),".browserbase","functions","manifests");}loadManifests(){if(!existsSync(this.manifestsPath)){console.log(a.yellow(`\u26A0\uFE0F No ${this.manifestsPath} directory found`)),console.log(a.gray(" Run your entrypoint file first to generate manifests"));return}try{let n=readdirSync(this.manifestsPath).filter(r=>r.endsWith(".json"));for(let r of n){let t=join(this.manifestsPath,r),i=readFileSync(t,"utf-8"),s=JSON.parse(i);this.manifests.set(s.name,s),console.log(a.gray(` Loaded manifest for function: ${s.name}`));}this.manifests.size>0?console.log(a.green(`\u2713 Loaded ${this.manifests.size} function manifest(s)`)):console.log(a.yellow("\u26A0\uFE0F No function manifests found in .browserbase directory"));}catch(e){console.error(a.red("Failed to load function manifests:"),e);}}getManifest(e){return this.manifests.get(e)}getSize(){return this.manifests.size}hasManifest(e){return this.manifests.has(e)}getManifestNames(){return Array.from(this.manifests.keys())}};var
|
|
45
|
+
\u2717 Publish failed: ${e.message??"unknown error"}`)),process.exit(1);}}var ve=A(()=>{le();ue();fe();});var j=class{constructor(e=false){p(this,"nextConnection",null);p(this,"invokeConnection",null);p(this,"currentRequestId",null);p(this,"currentFunctionName",null);p(this,"currentSessionId",null);p(this,"sessionCleanupCallback",null);p(this,"verbose");p(this,"runtimeConnectedOnce",false);this.verbose=e;}setSessionCleanupCallback(e){this.sessionCleanupCallback=e;}holdNextConnection(e){this.nextConnection&&(this.nextConnection.response.writeHead(503,{"Content-Type":"application/json"}),this.nextConnection.response.end(JSON.stringify({error:"Another runtime connected"}))),this.nextConnection={response:e,timestamp:Date.now()},this.runtimeConnectedOnce=true,this.verbose&&console.log(a.cyan("\u{1F50C} Function runtime connected, ready for invocations"));}triggerInvocation(e,n,r,t){if(!this.nextConnection)return this.verbose&&console.log(a.yellow("\u26A0\uFE0F No runtime connected to handle invocation")),false;if(this.invokeConnection)return this.verbose&&console.log(a.yellow("\u26A0\uFE0F Another invocation is already in progress")),false;let i=randomUUID();this.currentRequestId=i,this.currentFunctionName=e,this.currentSessionId=r.session.id,this.invokeConnection={response:t,timestamp:Date.now()};let s={functionName:e,params:n,context:r};return this.nextConnection.response.writeHead(200,{"Content-Type":"application/json","Lambda-Runtime-Aws-Request-Id":i,"Lambda-Runtime-Deadline-Ms":String(Date.now()+3e5),"Lambda-Runtime-Invoked-Function-Arn":`arn:aws:lambda:us-east-1:000000000000:function:${e}`}),this.nextConnection.response.end(JSON.stringify(s)),this.nextConnection=null,console.log(a.blue(`\u{1F680} Invoking function '${e}' (request-id: ${i})`)),true}completeWithSuccess(e,n){return e!==this.currentRequestId?(this.verbose&&console.log(a.yellow(`\u26A0\uFE0F Request ID mismatch: expected ${this.currentRequestId}, got ${e}`)),false):this.invokeConnection?(this.invokeConnection.response.writeHead(200,{"Content-Type":"application/json"}),this.invokeConnection.response.end(JSON.stringify(n??{})),console.log(a.green(`\u2713 Function '${this.currentFunctionName}' completed successfully`)),this.sessionCleanupCallback&&this.currentSessionId&&this.sessionCleanupCallback(this.currentSessionId).catch(r=>{console.error(a.red("Failed to cleanup session:"),r);}),this.invokeConnection=null,this.currentRequestId=null,this.currentFunctionName=null,this.currentSessionId=null,true):(this.verbose&&console.log(a.yellow("\u26A0\uFE0F No active invocation to complete")),false)}completeWithError(e,n){return e!==this.currentRequestId?(this.verbose&&console.log(a.yellow(`\u26A0\uFE0F Request ID mismatch: expected ${this.currentRequestId}, got ${e}`)),false):this.invokeConnection?(this.invokeConnection.response.writeHead(500,{"Content-Type":"application/json"}),this.invokeConnection.response.end(JSON.stringify({error:{message:n.errorMessage,type:n.errorType,stackTrace:n.stackTrace}})),console.log(a.red(`\u2717 Function '${this.currentFunctionName}' failed: ${n.errorMessage}`)),this.sessionCleanupCallback&&this.currentSessionId&&this.sessionCleanupCallback(this.currentSessionId).catch(r=>{console.error(a.red("Failed to cleanup session:"),r);}),this.invokeConnection=null,this.currentRequestId=null,this.currentFunctionName=null,this.currentSessionId=null,true):(this.verbose&&console.log(a.yellow("\u26A0\uFE0F No active invocation to complete")),false)}isReady(){return this.nextConnection!==null&&this.invokeConnection===null}hasActiveInvocation(){return this.invokeConnection!==null}getCurrentRequestId(){return this.currentRequestId}isRuntimeConnected(){return this.runtimeConnectedOnce&&this.nextConnection!==null}};var F=class{constructor(){p(this,"browserbaseClient",null);p(this,"projectId");p(this,"apiKey");p(this,"initialized",false);let e=process.env.BROWSERBASE_PROJECT_ID,n=process.env.BROWSERBASE_API_KEY;if(!e||!n)throw console.error(a.red(`\u2717 Browserbase credentials not found.
|
|
46
|
+
`)+a.red(` Please set BROWSERBASE_PROJECT_ID and BROWSERBASE_API_KEY in your .env file.
|
|
47
|
+
`)+a.gray(" Copy .env.example to .env and fill in your credentials.")),new Error("Missing Browserbase credentials");this.projectId=e,this.apiKey=n;}async initialize(){this.initialized||(this.browserbaseClient=new xe({apiKey:this.apiKey}),this.initialized=true,console.log(a.green("\u2713 Browserbase client initialized")));}async createSession(e){if(!this.browserbaseClient)throw new Error("Browser manager not initialized");console.log(a.cyan("Creating browser session..."));let n=await this.browserbaseClient.sessions.create({projectId:this.projectId,...e}),r={id:n.id,connectUrl:n.connectUrl};return console.log(a.green(`\u2713 Browser session created: ${r.id}`)),r}async closeSession(e){if(!this.browserbaseClient)throw new Error("Browser manager not initialized");try{console.log(a.cyan(`Closing browser session: ${e}...`)),await this.browserbaseClient.sessions.update(e,{projectId:this.projectId,status:"REQUEST_RELEASE"}),console.log(a.green(`\u2713 Browser session closed: ${e}`));}catch(n){console.warn(a.yellow(`\u26A0\uFE0F Could not close session ${e}:`),n instanceof Error?n.message:String(n));}}getProjectId(){return this.projectId}isInitialized(){return this.initialized}};var T=class{constructor(e){p(this,"manifests",new Map);p(this,"manifestsPath");this.manifestsPath=e||join(process.cwd(),".browserbase","functions","manifests");}loadManifests(){if(!existsSync(this.manifestsPath)){console.log(a.yellow(`\u26A0\uFE0F No ${this.manifestsPath} directory found`)),console.log(a.gray(" Run your entrypoint file first to generate manifests"));return}try{let n=readdirSync(this.manifestsPath).filter(r=>r.endsWith(".json"));for(let r of n){let t=join(this.manifestsPath,r),i=readFileSync(t,"utf-8"),s=JSON.parse(i);this.manifests.set(s.name,s),console.log(a.gray(` Loaded manifest for function: ${s.name}`));}this.manifests.size>0?console.log(a.green(`\u2713 Loaded ${this.manifests.size} function manifest(s)`)):console.log(a.yellow("\u26A0\uFE0F No function manifests found in .browserbase directory"));}catch(e){console.error(a.red("Failed to load function manifests:"),e);}}getManifest(e){return this.manifests.get(e)}getSize(){return this.manifests.size}hasManifest(e){return this.manifests.has(e)}getManifestNames(){return Array.from(this.manifests.keys())}};var O={async parseJsonBody(o){return new Promise((e,n)=>{let r="";o.on("data",t=>{r+=t.toString();}),o.on("end",()=>{try{let t=r?JSON.parse(r):{};e(t);}catch(t){n(new Error("Invalid JSON body",{cause:t}));}}),o.on("error",n);})},async parseAndValidate(o,e){let n=await this.parseJsonBody(o);return e.parse(n)}};var y={sendJson(o,e,n){o.writeHead(e,{"Content-Type":"application/json"}),o.end(JSON.stringify(n));},sendSuccess(o,e,n=200){let r={status:"success",...e!==void 0&&{data:e}};this.sendJson(o,n,r);},sendError(o,e,n=500,r,t){let i={error:e};r&&(i.message=r),t&&(i.details=t),this.sendJson(o,n,i);},sendBadRequest(o,e,n){this.sendError(o,"Bad Request",400,e,n);},sendNotFound(o,e){this.sendError(o,"Not Found",404,e);},sendInternalError(o,e="An internal error occurred",n){this.sendError(o,"Internal Server Error",500,e,n);},sendServiceUnavailable(o,e){this.sendError(o,"Service Unavailable",503,e);},sendAccepted(o,e){let n=e||{status:"accepted"};this.sendJson(o,202,n);}};var ke=I.looseObject({id:I.string(),connectUrl:I.string()}),U=I.looseObject({session:ke});var Be=I.object({functionName:I.string().min(1),params:I.looseObject({}),context:U});I.object({requestId:I.string().min(1),event:Be});var X=I.object({errorMessage:I.string().min(1),errorType:I.string().min(1),stackTrace:I.array(I.string().min(1))});var q=class{constructor(e){p(this,"bridge");p(this,"browserManager");p(this,"manifestStore");this.bridge=e.bridge,this.browserManager=e.browserManager,this.manifestStore=e.manifestStore,this.bridge.setSessionCleanupCallback(async n=>{await this.cleanupSession(n);});}async handleInvocationNext(e,n){this.bridge.holdNextConnection(n);}async handleFunctionInvoke(e,n,r){try{let t=z$1.object({functionName:z$1.string().optional(),params:z$1.unknown().default({}),context:U.optional()}),i=await O.parseAndValidate(e,t),s=r||i.functionName;if(!s){y.sendBadRequest(n,"Function name is required");return}let d=this.manifestStore.getManifest(s);if(!d){console.error(a.red(`\u2717 Function "${s}" not found in registry`)),console.error(a.gray(" Make sure the function is defined in your entrypoint file")),y.sendNotFound(n,`Function "${s}" not found in registry. Make sure it is defined with defineFn() in your entrypoint file.`);return}let c;try{console.log(a.cyan(`Creating browser session for ${s}...`));let h=d?.config?.sessionConfig||{};c=await this.browserManager.createSession(h);}catch(h){console.error(a.red("Failed to create browser session:"),h),y.sendInternalError(n,"Failed to create browser session",h instanceof Error?h.message:String(h));return}let u=i.context||{invocation:{id:randomUUID(),region:"local"},session:c};if(u.session=c,!this.bridge.triggerInvocation(s,i.params,u,n)){await this.cleanupSession(c.id),y.sendServiceUnavailable(n,this.bridge.hasActiveInvocation()?"Another invocation is in progress":"No runtime connected");return}}catch(t){t instanceof z$1.ZodError?y.sendBadRequest(n,"Invalid request body",t):(console.error(a.red("Error handling invoke:"),t),y.sendInternalError(n));}}async handleInvocationResponse(e,n,r){try{let t=await O.parseJsonBody(e);if(!this.bridge.completeWithSuccess(r,t)){y.sendBadRequest(n,"No matching invocation or request ID mismatch");return}y.sendAccepted(n);}catch(t){console.error(a.red("Error handling response:"),t),y.sendInternalError(n);}}async handleInvocationError(e,n,r){try{let t=await O.parseAndValidate(e,X);if(!this.bridge.completeWithError(r,t)){y.sendBadRequest(n,"No matching invocation or request ID mismatch");return}y.sendAccepted(n);}catch(t){t instanceof z$1.ZodError?y.sendBadRequest(n,"Invalid error format",t):(console.error(a.red("Error handling error report:"),t),y.sendInternalError(n));}}async cleanupSession(e){try{await this.browserManager.closeSession(e);}catch(n){console.error(a.red(`Failed to cleanup session ${e}:`),n);}}};async function Ne(o,e,n){let{handlers:r}=n,t=new URL(o.url||"",`http://${o.headers.host}`),i=o.method||"GET",s=t.pathname;if(console.log(a.gray(`[${i}] ${s}`)),e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type"),i==="OPTIONS"){e.writeHead(200),e.end();return}try{if(i==="GET"&&s==="/"){e.writeHead(200,{"Content-Type":"application/json"}),e.end(JSON.stringify({ok:!0}));return}if(i==="GET"&&s==="/2018-06-01/runtime/invocation/next"){await r.handleInvocationNext(o,e);return}let d=s.match(/^\/v1\/functions\/([^/]+)\/invoke$/);if(i==="POST"&&d&&d[1]){let m=d[1];await r.handleFunctionInvoke(o,e,m);return}let c=s.match(/^\/2018-06-01\/runtime\/invocation\/([^/]+)\/response$/);if(i==="POST"&&c&&c[1]){let m=c[1];await r.handleInvocationResponse(o,e,m);return}let u=s.match(/^\/2018-06-01\/runtime\/invocation\/([^/]+)\/error$/);if(i==="POST"&&u&&u[1]){let m=u[1];await r.handleInvocationError(o,e,m);return}e.writeHead(404,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Not found"}));}catch(d){console.error(a.red("Server error:"),d),e.writeHead(500,{"Content-Type":"application/json"}),e.end(JSON.stringify({error:"Internal server error"}));}}async function ne(o){let{port:e,host:n,handlers:i}=o,s=createServer(async(d,c)=>{await Ne(d,c,{handlers:i});});return new Promise((d,c)=>{s.listen(e,n,()=>{d(s);}),s.on("error",u=>{u.code==="EADDRINUSE"?c(new Error(`Port ${e} is already in use`)):u.code==="EACCES"?c(new Error(`Permission denied to bind to port ${e}`)):c(u);});})}var z=class{constructor(e){p(this,"process",null);p(this,"entrypoint");p(this,"runtimeApiUrl");p(this,"verbose");p(this,"isShuttingDown",false);this.entrypoint=e.entrypoint,this.runtimeApiUrl=e.runtimeApiUrl,this.verbose=e.verbose;}async start(){if(this.process)throw new Error("Process is already running");this.verbose&&(console.log(a.gray("Starting runtime process...")),console.log(a.gray(` Command: tsx watch --clear-screen=false ${this.entrypoint}`)),console.log(a.gray(` Working directory: ${process.cwd()}`)),console.log(a.gray(` Runtime API: ${this.runtimeApiUrl}`)));let n=createRequire(import.meta.url).resolve("tsx/cli"),r=["watch","--clear-screen=false",this.entrypoint];if(this.process=spawn(process.execPath,[n,...r],{cwd:process.cwd(),env:{...process.env,AWS_LAMBDA_RUNTIME_API:this.runtimeApiUrl,BB_FUNCTIONS_PHASE:"runtime",NODE_ENV:"local"},stdio:["ignore","pipe","pipe"]}),this.process.stdout?.on("data",t=>{t.toString().trim().split(`
|
|
48
48
|
`).forEach(s=>{s.trim()&&console.log(a.blue("[Runtime]"),s);});}),this.process.stderr?.on("data",t=>{t.toString().trim().split(`
|
|
49
49
|
`).forEach(s=>{s.trim()&&(s.includes("Watching for file changes")?console.log(a.green("\u2713 Runtime watching for file changes")):s.includes("Restarting")?console.log(a.yellow("\u21BB Runtime restarting due to file change...")):console.error(a.red("[Runtime Error]"),s));});}),this.process.on("exit",(t,i)=>{this.isShuttingDown||(t!==0?(console.error(a.red(`\u2717 Runtime process exited unexpectedly with code ${t}`)),i&&console.error(a.red(` Signal: ${i}`))):console.log(a.gray("Runtime process exited")),this.process=null);}),this.process.on("error",t=>{t.code==="ENOENT"?console.error(a.red("\u2717 Failed to start runtime: tsx not found"),a.yellow(`
|
|
50
|
-
Make sure tsx is installed: npm install -g tsx or pnpm add tsx`)):console.error(a.red("\u2717 Failed to start runtime process:"),t),this.process=null;}),await new Promise(t=>setTimeout(t,100)),!this.process||this.process.exitCode!==null)throw new Error("Failed to start runtime process");console.log(a.green("\u2713 Runtime process started"));}async stop(){if(this.process)return this.isShuttingDown=true,this.verbose&&console.log(a.gray("Stopping runtime process...")),new Promise(e=>{if(!this.process){e();return}let n=setTimeout(()=>{this.process&&(console.log(a.yellow("\u26A0\uFE0F Force killing runtime process")),this.process.kill("SIGKILL"));},5e3);this.process.on("exit",()=>{clearTimeout(n),this.process=null,console.log(a.green("\u2713 Runtime process stopped")),e();}),this.process.kill("SIGTERM");})}isRunning(){return this.process!==null&&this.process.exitCode===null}};async function oe(o){let{entrypoint:e,port:n,host:r,verbose:t}=o;process.env.NODE_ENV==="production"&&console.warn(a.yellow("\u26A0\uFE0F Warning: Running dev server in production mode. This is not recommended."));let i=`${r}:${n}`;t&&console.log(a.gray(`Runtime API URL: ${i}`));let s=new
|
|
51
|
-
\u{1F4E6} Shutting down...`)),await m.stop(),new Promise(
|
|
50
|
+
Make sure tsx is installed: npm install -g tsx or pnpm add tsx`)):console.error(a.red("\u2717 Failed to start runtime process:"),t),this.process=null;}),await new Promise(t=>setTimeout(t,100)),!this.process||this.process.exitCode!==null)throw new Error("Failed to start runtime process");console.log(a.green("\u2713 Runtime process started"));}async stop(){if(this.process)return this.isShuttingDown=true,this.verbose&&console.log(a.gray("Stopping runtime process...")),new Promise(e=>{if(!this.process){e();return}let n=setTimeout(()=>{this.process&&(console.log(a.yellow("\u26A0\uFE0F Force killing runtime process")),this.process.kill("SIGKILL"));},5e3);this.process.on("exit",()=>{clearTimeout(n),this.process=null,console.log(a.green("\u2713 Runtime process stopped")),e();}),this.process.kill("SIGTERM");})}isRunning(){return this.process!==null&&this.process.exitCode===null}};async function oe(o){let{entrypoint:e,port:n,host:r,verbose:t}=o;process.env.NODE_ENV==="production"&&console.warn(a.yellow("\u26A0\uFE0F Warning: Running dev server in production mode. This is not recommended."));let i=`${r}:${n}`;t&&console.log(a.gray(`Runtime API URL: ${i}`));let s=new j(t),d=new F;await d.initialize();let c=new T;c.loadManifests();let u=new q({bridge:s,browserManager:d,manifestStore:c}),m=new z({entrypoint:e,runtimeApiUrl:i,verbose:t}),h=null;try{h=await ne({port:n,host:r,bridge:s,browserManager:d,handlers:u}),console.log(a.green(`\u2713 Development server listening on http://${r}:${n}`)),console.log(a.cyan("Starting runtime process...")),await m.start();let N=1e4,we=200,be=Date.now(),G=!1;for(;Date.now()-be<N;){if(s.isRuntimeConnected()){G=!0,console.log(a.green("\u2713 Runtime connected and ready")),c.loadManifests();break}await new Promise(W=>setTimeout(W,we));}G||(console.log(a.yellow("\u26A0\uFE0F Runtime is taking longer than expected to connect...")),c.loadManifests());let Y=async()=>(console.log(a.cyan(`
|
|
51
|
+
\u{1F4E6} Shutting down...`)),await m.stop(),new Promise(W=>{h?.close(()=>{console.log(a.green("\u2713 Server closed")),W();});}));process.on("SIGINT",async()=>{await Y(),process.exit(0);}),process.on("SIGTERM",async()=>{await Y(),process.exit(0);});}catch(N){throw console.error(a.red("Failed to start:"),N),m.isRunning()&&await m.stop(),h&&h.close(),N}}var Ue=fileURLToPath(import.meta.url),V=dirname(Ue);async function se(o){if(!Ke(o.projectName))throw new Error(`Invalid project name "${o.projectName}". Project names must start with a letter and contain only letters, numbers, hyphens, and underscores.`);let e=resolve(process.cwd(),o.projectName);if(existsSync(e))throw new Error(`Directory "${o.projectName}" already exists. Please choose a different name or delete the existing directory.`);console.log(a.cyan(`\u{1F680} Creating new Browserbase Functions project: ${a.bold(o.projectName)}`)),mkdirSync(e,{recursive:true});try{qe(),existsSync(join(e,".git"))?console.log(a.yellow("\u2713 Git repository already exists")):(console.log(a.gray("Initializing git repository...")),execSync("git init",{cwd:e,stdio:"pipe"}),console.log(a.green("\u2713 Git repository initialized"))),We(e),existsSync(join(e,"package.json"))?console.log(a.yellow("\u2713 package.json already exists")):(console.log(a.gray("Creating package.json...")),execSync("pnpm init",{cwd:e,stdio:"pipe"}),console.log(a.green("\u2713 package.json created")));let n=ze(o.packageManager);_e(e,n),console.log(a.gray("Installing dependencies...")),Je(e,n),console.log(a.green("\u2713 Dependencies installed")),Le(e),existsSync(join(e,"tsconfig.json"))?console.log(a.yellow("\u2713 TypeScript configuration already exists")):(console.log(a.gray("Initializing TypeScript configuration...")),execSync(`${n==="pnpm"?"pnpm":"npx"} tsc --init`,{cwd:e,stdio:"pipe"}),Ve(e),console.log(a.green("\u2713 TypeScript configuration created"))),He(e),console.log(""),console.log(a.green.bold("\u2728 Project initialized successfully!")),console.log(""),console.log(a.cyan("Next steps:")),console.log(a.gray("1. Navigate to your project:")),console.log(a.white(` cd ${o.projectName}`)),console.log(a.gray("2. Add your Browserbase API key and project ID to .env")),console.log(a.gray("3. Run your function locally:")),console.log(a.white(` ${n==="pnpm"?"pnpm":"npx"} bb dev index.ts`)),console.log(a.gray("4. When ready, publish your function:")),console.log(a.white(` ${n==="pnpm"?"pnpm":"npx"} bb publish index.ts`)),console.log(""),console.log(a.gray("Learn more at https://browserbase.com/docs"));}catch(n){console.error(a.red("\u274C Initialization failed:"),n instanceof Error?n.message:n),process.exit(1);}}function qe(){let o=[{command:"node --version",name:"Node.js"},{command:"pnpm --version",name:"pnpm"},{command:"git --version",name:"git"}];for(let{command:e,name:n}of o)try{execSync(e,{stdio:"pipe"});}catch{throw new Error(`${n} is not installed. Please install ${n} and try again.`)}}function ze(o){if(o)return o;let e=process.env.npm_config_user_agent;return e&&e.includes("pnpm"),"pnpm"}function _e(o,e){let n=join(o,"package.json"),r=JSON.parse(readFileSync(n,"utf-8")),t;try{e==="pnpm"?(t=execSync("pnpm --version",{stdio:"pipe"}).toString().trim(),r.packageManager=`pnpm@${t}`):(t=execSync("npm --version",{stdio:"pipe"}).toString().trim(),r.packageManager=`npm@${t}`);}catch{r.packageManager=e==="pnpm"?"pnpm@9.0.0":"npm@10.0.0";}r.type="module",writeFileSync(n,JSON.stringify(r,null,2)),console.log(a.green(`\u2713 Package manager set to ${r.packageManager}`));}function Je(o,e){let n=e==="pnpm"?"pnpm add":"npm install",r=e==="pnpm"?"pnpm add -D":"npm install --save-dev";console.log(a.gray(" Installing @browserbasehq/sdk-functions...")),execSync(`${n} @browserbasehq/sdk-functions`,{cwd:o,stdio:"pipe"}),console.log(a.gray(" Installing playwright-core...")),execSync(`${n} playwright-core`,{cwd:o,stdio:"pipe"}),console.log(a.gray(" Installing zod...")),execSync(`${n} zod`,{cwd:o,stdio:"pipe"}),console.log(a.gray(" Installing TypeScript and type definitions...")),execSync(`${r} typescript @types/node`,{cwd:o,stdio:"pipe"});}function Le(o){let e=join(o,".env");if(existsSync(e))console.log(a.yellow("\u2713 .env file already exists"));else {let n=join(V,"templates",".env.template");copyFileSync(n,e),console.log(a.green("\u2713 .env file created"));}}function We(o){let e=join(o,".gitignore");if(existsSync(e))console.log(a.yellow("\u2713 .gitignore file already exists"));else {let n=join(V,"templates",".gitignore.template");copyFileSync(n,e),console.log(a.green("\u2713 .gitignore file created"));}}function He(o){let e=join(o,"index.ts");if(existsSync(e))console.log(a.yellow("\u2713 index.ts already exists"));else {let n=join(V,"templates","starter-function.ts.template");copyFileSync(n,e),console.log(a.green("\u2713 Starter function created (index.ts)"));}}function Ve(o){let e=join(o,"tsconfig.json");try{let n=JSON.parse(readFileSync(e,"utf-8"));n.compilerOptions={...n.compilerOptions,target:"ES2022",module:"NodeNext",moduleResolution:"NodeNext",esModuleInterop:!0,forceConsistentCasingInFileNames:!0,strict:!0,skipLibCheck:!0,resolveJsonModule:!0},writeFileSync(e,JSON.stringify(n,null,2));}catch{console.log(a.yellow(" Using default TypeScript configuration"));}}function Ke(o){return /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(o)}var M=new Command;M.name("bb").description("Browserbase Functions CLI").version("0.0.4");var K=["npm","pnpm"];M.command("init <project-name>").description("Initialize a new Browserbase Functions project").option("-p, --package-manager <manager>",`Package manager to use (${K.join(" or ")})`,"pnpm").action(async(o,e)=>{try{let n=e.packageManager.toLowerCase();K.includes(n)||(console.error(a.red(`Error: Invalid package manager "${e.packageManager}"`)),console.error(a.gray(`Valid options are: ${K.join(", ")}`)),process.exit(1)),await se({projectName:o,packageManager:n});}catch(n){console.error(a.red("Initialization failed:"),n),process.exit(1);}});M.command("dev").description("Start a local development server for testing Browserbase Functions").argument("<entrypoint>","Path to the TypeScript/JavaScript file that imports all your functions").option("-p, --port <number>","Port to listen on","14113").option("-h, --host <string>","Host to bind to","127.0.0.1").action(async(o,e)=>{try{let n=await import('fs'),r=await import('path'),t=r.resolve(o);n.existsSync(t)||(console.error(a.red(`Error: Entrypoint file not found: ${t}`)),process.exit(1));let i=r.extname(t);[".ts",".tsx",".js",".jsx",".mjs",".cjs"].includes(i)||(console.error(a.red("Error: Invalid file extension. Expected .ts, .tsx, .js, .jsx, .mjs, or .cjs")),process.exit(1));let s=parseInt(e.port,10);(isNaN(s)||s<1||s>65535)&&(console.error(a.red("Error: Invalid port number. Must be between 1 and 65535.")),process.exit(1)),console.log(a.cyan("Starting Browserbase Functions development server...")),console.log(a.gray(`Entrypoint: ${t}`)),await oe({entrypoint:t,port:s,host:e.host,verbose:e.verbose});}catch(n){console.error(a.red("Failed to start development server:"),n),process.exit(1);}});M.command("publish").description("Publish your Browserbase Function to the cloud").argument("<entrypoint>","Path to the TypeScript/JavaScript file that imports all your functions").option("-u, --api-url <url>","API endpoint URL").option("--dry-run","Show what would be published without uploading").action(async(o,e)=>{try{let{publishFunction:n}=await Promise.resolve().then(()=>(ve(),ye));await n({entrypoint:o,apiUrl:e.apiUrl,dryRun:e.dryRun});}catch(n){console.error(a.red("Publish failed:"),n),process.exit(1);}});M.parse();
|
|
52
52
|
//# sourceMappingURL=cli.js.map
|
|
53
53
|
//# sourceMappingURL=cli.js.map
|