feat: add dry-run options and retry helper
This commit is contained in:
@@ -179,6 +179,7 @@ Examples:
|
|||||||
.option("--path <path>", "Webhook path", "/webhook/whatsapp")
|
.option("--path <path>", "Webhook path", "/webhook/whatsapp")
|
||||||
.option("--verbose", "Log inbound and auto-replies", false)
|
.option("--verbose", "Log inbound and auto-replies", false)
|
||||||
.option("-y, --yes", "Auto-confirm prompts when possible", false)
|
.option("-y, --yes", "Auto-confirm prompts when possible", false)
|
||||||
|
.option("--dry-run", "Print planned actions without starting server", false)
|
||||||
.addHelpText(
|
.addHelpText(
|
||||||
"after",
|
"after",
|
||||||
`
|
`
|
||||||
@@ -220,6 +221,7 @@ With Tailscale:
|
|||||||
.option("--path <path>", "Webhook path", "/webhook/whatsapp")
|
.option("--path <path>", "Webhook path", "/webhook/whatsapp")
|
||||||
.option("--verbose", "Verbose logging during setup/webhook", false)
|
.option("--verbose", "Verbose logging during setup/webhook", false)
|
||||||
.option("-y, --yes", "Auto-confirm prompts when possible", false)
|
.option("-y, --yes", "Auto-confirm prompts when possible", false)
|
||||||
|
.option("--dry-run", "Print planned actions without touching network", false)
|
||||||
// istanbul ignore next
|
// istanbul ignore next
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
setVerbose(Boolean(opts.verbose));
|
setVerbose(Boolean(opts.verbose));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { RuntimeEnv } from "../runtime.js";
|
|||||||
import { waitForever as defaultWaitForever } from "../cli/wait.js";
|
import { waitForever as defaultWaitForever } from "../cli/wait.js";
|
||||||
|
|
||||||
export async function upCommand(
|
export async function upCommand(
|
||||||
opts: { port: string; path: string; verbose?: boolean; yes?: boolean },
|
opts: { port: string; path: string; verbose?: boolean; yes?: boolean; dryRun?: boolean },
|
||||||
deps: CliDeps,
|
deps: CliDeps,
|
||||||
runtime: RuntimeEnv,
|
runtime: RuntimeEnv,
|
||||||
waiter: typeof defaultWaitForever = defaultWaitForever,
|
waiter: typeof defaultWaitForever = defaultWaitForever,
|
||||||
@@ -15,6 +15,13 @@ export async function upCommand(
|
|||||||
|
|
||||||
await deps.ensurePortAvailable(port);
|
await deps.ensurePortAvailable(port);
|
||||||
const env = deps.readEnv(runtime);
|
const env = deps.readEnv(runtime);
|
||||||
|
if (opts.dryRun) {
|
||||||
|
runtime.log(`[dry-run] would enable funnel on port ${port}`);
|
||||||
|
runtime.log(`[dry-run] would start webhook at path ${opts.path}`);
|
||||||
|
runtime.log(`[dry-run] would update Twilio sender webhook`);
|
||||||
|
const publicUrl = `https://dry-run${opts.path}`;
|
||||||
|
return { server: undefined, publicUrl, senderSid: undefined, waiter };
|
||||||
|
}
|
||||||
await deps.ensureBinary("tailscale", undefined, runtime);
|
await deps.ensureBinary("tailscale", undefined, runtime);
|
||||||
await deps.ensureFunnel(port, undefined, runtime);
|
await deps.ensureFunnel(port, undefined, runtime);
|
||||||
const host = await deps.getTailnetHostname();
|
const host = await deps.getTailnetHostname();
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export async function webhookCommand(
|
|||||||
throw new Error("Port must be between 1 and 65535");
|
throw new Error("Port must be between 1 and 65535");
|
||||||
}
|
}
|
||||||
await deps.ensurePortAvailable(port);
|
await deps.ensurePortAvailable(port);
|
||||||
|
if (opts.reply === "dry-run") {
|
||||||
|
runtime.log(`[dry-run] would start webhook on port ${port} path ${opts.path}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
const server = await deps.startWebhook(
|
const server = await deps.startWebhook(
|
||||||
port,
|
port,
|
||||||
opts.path,
|
opts.path,
|
||||||
|
|||||||
18
src/infra/retry.ts
Normal file
18
src/infra/retry.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export async function retryAsync<T>(
|
||||||
|
fn: () => Promise<T>,
|
||||||
|
attempts = 3,
|
||||||
|
initialDelayMs = 300,
|
||||||
|
): Promise<T> {
|
||||||
|
let lastErr: unknown;
|
||||||
|
for (let i = 0; i < attempts; i += 1) {
|
||||||
|
try {
|
||||||
|
return await fn();
|
||||||
|
} catch (err) {
|
||||||
|
lastErr = err;
|
||||||
|
if (i === attempts - 1) break;
|
||||||
|
const delay = initialDelayMs * 2 ** i;
|
||||||
|
await new Promise((r) => setTimeout(r, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw lastErr;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user