feat: add ability to use custom tunnel in dev cmd (#597)
* feat: add ability to use custom tunnel in dev cmd * Added a short flag -t for the tunnel flag * skip framework url resolution if tunnel-url is provided * remove type annotation --------- Co-authored-by: Eric Allam <eric@trigger.dev> Co-authored-by: Matt Aitken <matt@mattaitken.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@trigger.dev/cli": patch
|
||||
---
|
||||
|
||||
add ability to use custom tunnel in dev command
|
||||
@@ -59,6 +59,10 @@ program
|
||||
"The URI path to the API handler function to use for this project.",
|
||||
"/api/trigger"
|
||||
)
|
||||
.option(
|
||||
"-t, --tunnel <url>",
|
||||
"An optional custom tunnel URL. Use only if you already have an open tunnel to your local dev server."
|
||||
)
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.action(async (path, options) => {
|
||||
try {
|
||||
|
||||
@@ -28,6 +28,11 @@ export const DevCommandOptionsSchema = z.object({
|
||||
envFile: z.string().optional(),
|
||||
handlerPath: z.string(),
|
||||
clientId: z.string().optional(),
|
||||
tunnel: z
|
||||
.string()
|
||||
.url()
|
||||
.regex(/^(http|https).+/, "only http/https URLs are accepted")
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type DevCommandOptions = z.infer<typeof DevCommandOptionsSchema>;
|
||||
@@ -43,6 +48,29 @@ const formattedDate = new Intl.DateTimeFormat("en", {
|
||||
|
||||
let runtime: JsRuntime;
|
||||
|
||||
type TunnelUrl = {
|
||||
type: "tunnel";
|
||||
url: string;
|
||||
};
|
||||
|
||||
type ResolvedUrl = {
|
||||
type: "resolved";
|
||||
hostname: string;
|
||||
port: number;
|
||||
};
|
||||
|
||||
type ServerUrl = TunnelUrl | ResolvedUrl;
|
||||
|
||||
type TunnelEndpoint = TunnelUrl & {
|
||||
handlerPath: string;
|
||||
};
|
||||
|
||||
type ResolvedEndpoint = ResolvedUrl & {
|
||||
handlerPath: string;
|
||||
};
|
||||
|
||||
type ServerEndpoint = TunnelEndpoint | ResolvedEndpoint;
|
||||
|
||||
export async function devCommand(path: string, anyOptions: any) {
|
||||
telemetryClient.dev.started(path, anyOptions);
|
||||
|
||||
@@ -90,23 +118,23 @@ export async function devCommand(path: string, anyOptions: any) {
|
||||
logger.error(
|
||||
`✖ [trigger.dev] Your endpoint couldn't be verified. Make sure your app is running and try again. ${resolvedOptions.handlerPath}`
|
||||
);
|
||||
logger.info(` [trigger.dev] You can use -H to specify a hostname, or -p to specify a port.`);
|
||||
logger.info(
|
||||
` [trigger.dev] You can use -H to specify a hostname, or -p to specify a port, or -t to specify the tunnel-url pointing to the local dev server.`
|
||||
);
|
||||
telemetryClient.dev.failed("no_server_found", resolvedOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
const { hostname, port, handlerPath } = verifiedEndpoint;
|
||||
|
||||
telemetryClient.dev.serverRunning(path, resolvedOptions);
|
||||
|
||||
// Setup tunnel
|
||||
const endpointUrl = await resolveEndpointUrl(apiUrl, port, hostname);
|
||||
const endpointUrl = await resolveEndpointUrl(apiUrl, verifiedEndpoint);
|
||||
if (!endpointUrl) {
|
||||
telemetryClient.dev.failed("failed_to_create_tunnel", resolvedOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
const endpointHandlerUrl = `${endpointUrl}${handlerPath}`;
|
||||
const endpointHandlerUrl = `${endpointUrl}${verifiedEndpoint.handlerPath}`;
|
||||
telemetryClient.dev.tunnelRunning(path, resolvedOptions);
|
||||
|
||||
// Watch for changes to files and refresh endpoints
|
||||
@@ -306,6 +334,7 @@ async function resolveOptions(
|
||||
envFile: unresolvedOptions.envFile ?? ".env",
|
||||
handlerPath: unresolvedOptions.handlerPath,
|
||||
clientId: unresolvedOptions.clientId,
|
||||
tunnel: unresolvedOptions.tunnel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -318,6 +347,7 @@ async function resolveOptions(
|
||||
envFile: unresolvedOptions.envFile ?? envName ?? ".env",
|
||||
handlerPath: unresolvedOptions.handlerPath,
|
||||
clientId: unresolvedOptions.clientId,
|
||||
tunnel: unresolvedOptions.tunnel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -327,40 +357,15 @@ async function verifyEndpoint(
|
||||
apiKey: string,
|
||||
framework?: Framework
|
||||
) {
|
||||
//create list of hostnames to try
|
||||
const hostnames = [];
|
||||
if (resolvedOptions.hostname) {
|
||||
hostnames.push(resolvedOptions.hostname);
|
||||
}
|
||||
if (framework) {
|
||||
hostnames.push(...framework.defaultHostnames);
|
||||
} else {
|
||||
hostnames.push("localhost");
|
||||
}
|
||||
const serverUrls = findServerUrls(resolvedOptions, framework);
|
||||
|
||||
//create list of ports to try
|
||||
const ports = [];
|
||||
if (resolvedOptions.port) {
|
||||
ports.push(resolvedOptions.port);
|
||||
}
|
||||
if (framework) {
|
||||
ports.push(...framework.defaultPorts);
|
||||
} else {
|
||||
ports.push(3000);
|
||||
}
|
||||
|
||||
//create list of urls to try
|
||||
const urls: { hostname: string; port: number }[] = [];
|
||||
for (const hostname of hostnames) {
|
||||
for (const port of ports) {
|
||||
urls.push({ hostname, port });
|
||||
}
|
||||
}
|
||||
|
||||
//try each hostname
|
||||
for (const url of urls) {
|
||||
const { hostname, port } = url;
|
||||
const localEndpointHandlerUrl = `http://${hostname}:${port}${resolvedOptions.handlerPath}`;
|
||||
//try each url
|
||||
for (const serverUrl of serverUrls) {
|
||||
const url =
|
||||
serverUrl.type === "tunnel"
|
||||
? serverUrl.url
|
||||
: `http://${serverUrl.hostname}:${serverUrl.port}`;
|
||||
const localEndpointHandlerUrl = `${url}${resolvedOptions.handlerPath}`;
|
||||
|
||||
const spinner = ora(
|
||||
`[trigger.dev] Looking for your trigger endpoint: ${localEndpointHandlerUrl}`
|
||||
@@ -384,7 +389,8 @@ async function verifyEndpoint(
|
||||
}
|
||||
|
||||
spinner.succeed(`[trigger.dev] Found your trigger endpoint: ${localEndpointHandlerUrl}`);
|
||||
return { hostname, port, handlerPath: resolvedOptions.handlerPath };
|
||||
|
||||
return { ...serverUrl, handlerPath: resolvedOptions.handlerPath };
|
||||
} catch (err) {
|
||||
spinner.fail(`[trigger.dev] No server found (${localEndpointHandlerUrl}).`);
|
||||
}
|
||||
@@ -399,17 +405,61 @@ export function getEndpointId(runtime: JsRuntime, clientId?: string) {
|
||||
} else return runtime.getEndpointId();
|
||||
}
|
||||
|
||||
async function resolveEndpointUrl(apiUrl: string, port: number, hostname: string) {
|
||||
function findServerUrls(resolvedOptions: ResolvedOptions, framework?: Framework): ServerUrl[] {
|
||||
if (resolvedOptions.tunnel) {
|
||||
logger.info(` Using provided tunnel URL: ${resolvedOptions.tunnel}`);
|
||||
return [{ type: "tunnel", url: resolvedOptions.tunnel }];
|
||||
}
|
||||
|
||||
//create list of hostnames to try
|
||||
const hostnames = [];
|
||||
if (resolvedOptions.hostname) {
|
||||
hostnames.push(resolvedOptions.hostname);
|
||||
}
|
||||
if (framework) {
|
||||
hostnames.push(...framework.defaultHostnames);
|
||||
} else {
|
||||
hostnames.push("localhost");
|
||||
}
|
||||
|
||||
//create list of ports to try
|
||||
const ports = [];
|
||||
if (resolvedOptions.port) {
|
||||
ports.push(resolvedOptions.port);
|
||||
}
|
||||
if (framework) {
|
||||
ports.push(...framework.defaultPorts);
|
||||
} else {
|
||||
ports.push(3000);
|
||||
}
|
||||
|
||||
//create list of urls to try
|
||||
const urls: ResolvedUrl[] = [];
|
||||
for (const hostname of hostnames) {
|
||||
for (const port of ports) {
|
||||
urls.push({ type: "resolved", hostname, port });
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
async function resolveEndpointUrl(apiUrl: string, endpoint: ServerEndpoint) {
|
||||
// use tunnel URL if provided
|
||||
if (endpoint.type === "tunnel") {
|
||||
return endpoint.url;
|
||||
}
|
||||
|
||||
const apiURL = new URL(apiUrl);
|
||||
|
||||
//if the API is localhost and the hostname is localhost
|
||||
if (apiURL.hostname === "localhost" && hostname === "localhost") {
|
||||
return `http://${hostname}:${port}`;
|
||||
// if the API is localhost and the hostname is localhost
|
||||
if (apiURL.hostname === "localhost" && endpoint.hostname === "localhost") {
|
||||
return `http://${endpoint.hostname}:${endpoint.port}`;
|
||||
}
|
||||
|
||||
// Setup tunnel
|
||||
const tunnelSpinner = ora(`🚇 Creating tunnel`).start();
|
||||
const tunnelUrl = await createTunnel(hostname, port, tunnelSpinner);
|
||||
const tunnelUrl = await createTunnel(endpoint.hostname, endpoint.port, tunnelSpinner);
|
||||
|
||||
if (tunnelUrl) {
|
||||
tunnelSpinner.succeed(`🚇 Created tunnel: ${tunnelUrl}`);
|
||||
@@ -442,7 +492,7 @@ async function createTunnel(hostname: string, port: number, spinner: Ora) {
|
||||
error.message.includes("connect ECONNREFUSED 127.0.0.1:4041")
|
||||
) {
|
||||
spinner.fail(
|
||||
`Ngrok failed to create a tunnel for port ${port} because ngrok is already running`
|
||||
`Ngrok failed to create a tunnel for port ${port} because ngrok is already running.\n You may want to use -t flag to use an existing URL that points to the local dev server.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user