Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/tern-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"@clack/prompts": "latest",
"@hookflo/tern-dev": "latest"
"@hookflo/tern-dev": "latest",
"@clack/prompts": "latest"
},
"type": "module",
"engines": {
"node": ">=18"
}
}
6 changes: 4 additions & 2 deletions packages/tern-cli/src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export const GREEN = "\x1b[38;2;16;185;129m";
export const CYAN = "\x1b[38;2;6;182;212m";
/** ANSI yellow used for env variables. */
export const YELLOW = "\x1b[38;2;245;158;11m";
/** ANSI gray used for muted labels. */
export const GRAY = "\x1b[38;2;107;105;99m";
/** ANSI gray used for dark borders. */
export const GRAY = "\x1b[38;2;55;55;55m";
/** ANSI muted used for labels and secondary information. */
export const MUTED = "\x1b[38;2;75;75;75m";
/** ANSI white used for primary text. */
export const WHITE = "\x1b[38;2;240;237;232m";
/** ANSI red used for errors. */
Expand Down
3 changes: 0 additions & 3 deletions packages/tern-cli/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as clack from "@clack/prompts";
import { CYAN, RESET } from "./colors";

/** Writes tern.config.json using current wizard selections. */
export function createConfig(
Expand Down Expand Up @@ -45,5 +43,4 @@ export function createConfig(
`;

fs.writeFileSync(configPath, config, "utf8");
clack.log.success(`created ${CYAN}tern.config.json${RESET}`);
}
8 changes: 3 additions & 5 deletions packages/tern-cli/src/files.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as clack from "@clack/prompts";
import { CYAN, RESET } from "./colors";

/** Returns the target handler file path for a framework/platform pair. */
export function getFilePath(framework: string, platform: string): string {
Expand Down Expand Up @@ -45,20 +44,19 @@ export function getWebhookPath(platform: string): string {
export async function createHandlerFile(
filePath: string,
content: string,
): Promise<void> {
): Promise<boolean> {
const fullPath = path.join(process.cwd(), filePath);

if (fs.existsSync(fullPath)) {
const overwrite = await clack.confirm({
message: `${path.basename(fullPath)} already exists. overwrite?`,
});
if (clack.isCancel(overwrite) || !overwrite) {
clack.log.warn(`skipped ${filePath}`);
return;
return false;
}
}

fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content, "utf8");
clack.log.success(`created ${CYAN}${filePath}${RESET}`);
return true;
}
55 changes: 38 additions & 17 deletions packages/tern-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
#!/usr/bin/env node
import * as clack from "@clack/prompts";
import { GRAY, RESET } from "./colors";
import { createConfig } from "./config";
import { createHandlerFile, getFilePath, getWebhookPath } from "./files";
import { installTern } from "./install";
import { printEnvBox, printLogo } from "./print";
import { getTemplate } from "./templates";
import { startTunnel } from "./tunnel";
import { askQuestions, ENV_VARS, getPlatformLabel } from "./wizard";
import "./patch-clack.js";
import { GRAY, RESET } from "./colors.js";
import { createConfig } from "./config.js";
import { createHandlerFile, getFilePath, getWebhookPath } from "./files.js";
import { installTern } from "./install.js";
import { printEnvBlock, printLogo, printStep, printStepDone, printStepFile, printSummary } from "./print.js";
import { getTemplate } from "./templates.js";
import { startTunnel } from "./tunnel.js";
import { askQuestions, ENV_VARS, getPlatformLabel } from "./wizard.js";

function actionLabel(action: "both" | "handler" | "tunnel"): string {
if (action === "both") return "handler + local testing";
if (action === "handler") return "handler only";
return "local testing only";
}

/** CLI entrypoint for @hookflo/tern-cli. */
export async function main(): Promise<void> {
printLogo();
printLogo("v0.1.0");

const { platform, framework, action, port } = await askQuestions();
printSummary(getPlatformLabel(platform), framework === "nextjs" ? "Next.js" : framework, actionLabel(action), action === "handler" ? undefined : port);

if (action === "handler") {
await installTern();
Expand All @@ -25,16 +32,23 @@ export async function main(): Promise<void> {
envVar,
getPlatformLabel(platform),
);
await createHandlerFile(filePath, content);
if (envVar) printEnvBox(envVar);
clack.outro("handler ready · add the env variable above to get started");
printStep("creating webhook handler");
const created = await createHandlerFile(filePath, content);
if (created) {
printStepFile(filePath);
} else {
printStepDone(`skipped ${filePath}`);
}
if (envVar) printEnvBlock(envVar);
printStepDone("handler ready");
return;
}

if (action === "tunnel") {
const webhookPath = getWebhookPath(platform);
printStep("creating tern.config.json");
createConfig(port, webhookPath, platform, framework);
clack.log.step("connecting...");
printStepDone("created tern.config.json");
startTunnel(port, webhookPath, getPlatformLabel(platform));
return;
}
Expand All @@ -49,10 +63,17 @@ export async function main(): Promise<void> {
envVar ?? "",
getPlatformLabel(platform),
);
await createHandlerFile(filePath, content);
printStep("creating webhook handler");
const created = await createHandlerFile(filePath, content);
if (created) {
printStepFile(filePath);
} else {
printStepDone(`skipped ${filePath}`);
}
printStep("creating tern.config.json");
createConfig(port, webhookPath, platform, framework);
if (envVar) printEnvBox(envVar);
clack.log.step("connecting...");
printStepDone("created tern.config.json");
if (envVar) printEnvBlock(envVar);
startTunnel(port, webhookPath, getPlatformLabel(platform));
}

Expand Down
10 changes: 4 additions & 6 deletions packages/tern-cli/src/install.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { execSync } from "node:child_process";
import * as fs from "node:fs";
import * as clack from "@clack/prompts";
import { printStep, printStepDone } from "./print.js";

/** Detects the package manager install command from lockfiles. */
export function detectPackageManager(): string {
Expand All @@ -11,14 +11,12 @@ export function detectPackageManager(): string {

/** Installs @hookflo/tern and reports status in the wizard. */
export async function installTern(): Promise<void> {
const spinner = clack.spinner();
spinner.start("installing @hookflo/tern");
printStep("installing @hookflo/tern");
try {
const pm = detectPackageManager();
execSync(`${pm} @hookflo/tern`, { stdio: "pipe" });
spinner.stop("installed @hookflo/tern");
printStepDone("installed @hookflo/tern");
} catch {
spinner.stop("could not install @hookflo/tern");
clack.log.warn("run manually: npm install @hookflo/tern");
printStepDone("could not install @hookflo/tern · run manually: npm install @hookflo/tern");
}
}
24 changes: 24 additions & 0 deletions packages/tern-cli/src/patch-clack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { GREEN } from "./colors.js";

let clackPatched = false;

export function patchClackColors(): void {
if (clackPatched) return;
clackPatched = true;

process.env.FORCE_COLOR = "3";

const originalWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = ((chunk: string | Uint8Array, ...args: unknown[]) => {
if (typeof chunk === "string") {
chunk = chunk
.replace(/\x1b\[32m/g, GREEN)
.replace(/\x1b\[36m/g, GREEN)
.replace(/\x1b\[2;32m/g, GREEN);
}

return originalWrite(chunk as never, ...(args as never[]));
}) as typeof process.stdout.write;
}

patchClackColors();
Loading
Loading