Improved the dev CLI output

This commit is contained in:
Eric Allam
2024-03-27 18:36:52 +00:00
parent 2dea8dec35
commit 5b745dc1a4
12 changed files with 196 additions and 108 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Vastly improved dev command output
+1
View File
@@ -113,6 +113,7 @@
"node-fetch": "^3.3.0",
"npm-check-updates": "^16.12.2",
"object-hash": "^3.0.0",
"p-debounce": "^4.0.0",
"p-throttle": "^6.1.0",
"partysocket": "^0.0.17",
"proxy-agent": "^6.3.0",
+16 -29
View File
@@ -8,7 +8,6 @@ import {
detectDependencyVersion,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import chalk from "chalk";
import { watch } from "chokidar";
import { Command } from "commander";
import { BuildContext, Metafile, context } from "esbuild";
@@ -18,7 +17,7 @@ import { createHash } from "node:crypto";
import fs, { readFileSync } from "node:fs";
import { ClientRequestArgs } from "node:http";
import { basename, dirname, join } from "node:path";
import pThrottle from "p-throttle";
import pDebounce from "p-debounce";
import { WebSocket } from "partysocket";
import React, { Suspense, useEffect } from "react";
import { ClientOptions, WebSocket as wsWebSocket } from "ws";
@@ -27,10 +26,10 @@ import * as packageJson from "../../package.json";
import { CliApiClient } from "../apiClient";
import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js";
import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../utilities/build";
import { chalkPurple } from "../utilities/colors";
import { chalkGrey, chalkPurple, chalkWorker } from "../utilities/cliOutput";
import { readConfig } from "../utilities/configFiles";
import { readJSONFile } from "../utilities/fileSystem";
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
import { printDevBanner, printStandloneInitialBanner } from "../utilities/initialBanner.js";
import {
detectPackageNameFromImportPath,
parsePackageName,
@@ -104,6 +103,7 @@ async function startDev(
}
await printStandloneInitialBanner(true);
printDevBanner();
logger.debug("Starting dev session", { dir, options, authorization });
@@ -339,7 +339,7 @@ function useDev({
let firstBuild = true;
logger.log(chalk.dim(" Building background worker..."));
logger.log(chalkGrey(" Building background worker"));
ctx = await context({
stdin: {
@@ -375,7 +375,7 @@ function useDev({
}
if (!firstBuild) {
logger.log(chalk.dim("⎔ Rebuilding background worker..."));
logger.log(chalkGrey("○ Building background worker"));
}
const metaOutputKey = join("out", `stdin.js`);
@@ -406,9 +406,8 @@ function useDev({
const contentHash = md5Hasher.digest("hex");
if (latestWorkerContentHash === contentHash) {
logger.log(chalk.dim(" No changes detected, skipping build..."));
logger.log(chalkGrey(" No changes detected, skipping build"));
logger.debug(`No changes detected, skipping build`);
return;
}
@@ -509,19 +508,13 @@ function useDev({
backgroundWorker.metadata = backgroundWorkerRecord.data;
if (firstBuild) {
logger.log(
chalk.green(
`Background worker started (${backgroundWorkerRecord.data.version})`
)
);
} else {
logger.log(
chalk.dim(
`Background worker rebuilt (${backgroundWorkerRecord.data.version})`
)
);
}
logger.log(
`${chalkGrey(
`○ Background worker started -> ${chalkWorker(
backgroundWorkerRecord.data.version
)}`
)}`
);
firstBuild = false;
@@ -555,13 +548,7 @@ function useDev({
await ctx.watch();
}
const throttle = pThrottle({
limit: 1,
interval: 1000,
strict: true,
});
const throttledRebuild = throttle(runBuild);
const throttledRebuild = pDebounce(runBuild, 250, { before: true });
const taskFileWatcher = watch(
config.triggerDirectories.map((triggerDir) => `${triggerDir}/*.ts`),
@@ -570,7 +557,7 @@ function useDev({
}
);
taskFileWatcher.on("change", async (path) => {
taskFileWatcher.on("add", async (path) => {
throttledRebuild().catch((error) => {
logger.error(error);
});
+21 -4
View File
@@ -13,7 +13,7 @@ import {
tracer,
wrapCommandAction,
} from "../cli/common.js";
import { chalkLink } from "../utilities/colors.js";
import { chalkLink } from "../utilities/cliOutput.js";
import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js";
import { getVersion } from "../utilities/getVersion.js";
import { printInitialBanner } from "../utilities/initialBanner.js";
@@ -75,7 +75,14 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
const authConfig = readAuthConfigProfile(options?.profile);
if (authConfig && authConfig.accessToken) {
const whoAmIResult = await whoAmI({ profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel }, opts.embedded);
const whoAmIResult = await whoAmI(
{
profile: options?.profile ?? "default",
skipTelemetry: !span.isRecording(),
logLevel: logger.loggerLevel,
},
opts.embedded
);
if (!whoAmIResult.success) {
throw new Error(whoAmIResult.error);
@@ -175,9 +182,19 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
getPersonalAccessTokenSpinner.stop(`Logged in with token ${indexResult.obfuscatedToken}`);
writeAuthConfigProfile({ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl }, options?.profile);
writeAuthConfigProfile(
{ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl },
options?.profile
);
const whoAmIResult = await whoAmI({ profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel }, opts.embedded);
const whoAmIResult = await whoAmI(
{
profile: options?.profile ?? "default",
skipTelemetry: !span.isRecording(),
logLevel: logger.loggerLevel,
},
opts.embedded
);
if (!whoAmIResult.success) {
throw new Error(whoAmIResult.error);
+1 -1
View File
@@ -2,7 +2,7 @@ import { confirm, spinner } from "@clack/prompts";
import { RunOptions, run } from "npm-check-updates";
import path from "path";
import { z } from "zod";
import { chalkError, chalkSuccess } from "../utilities/colors.js";
import { chalkError, chalkSuccess } from "../utilities/cliOutput.js";
import { readJSONFileSync, writeJSONFile } from "../utilities/fileSystem.js";
import { installDependencies } from "../utilities/installDependencies.js";
+29 -21
View File
@@ -1,41 +1,45 @@
import { intro, note, spinner } from "@clack/prompts";
import { chalkLink } from "../utilities/colors.js";
import { chalkLink } from "../utilities/cliOutput.js";
import { logger } from "../utilities/logger.js";
import { isLoggedIn } from "../utilities/session.js";
import { Command } from "commander";
import { printInitialBanner } from "../utilities/initialBanner.js";
import { CommonCommandOptions, commonOptions, handleTelemetry, wrapCommandAction } from "../cli/common.js";
import {
CommonCommandOptions,
commonOptions,
handleTelemetry,
wrapCommandAction,
} from "../cli/common.js";
import { z } from "zod";
import { CliApiClient } from "../apiClient.js";
type WhoAmIResult =
| {
success: true;
data: {
userId: string;
email: string;
dashboardUrl: string;
};
}
success: true;
data: {
userId: string;
email: string;
dashboardUrl: string;
};
}
| {
success: false;
error: string;
};
success: false;
error: string;
};
const WhoamiCommandOptions = CommonCommandOptions;
type WhoamiCommandOptions = z.infer<typeof WhoamiCommandOptions>;
export function configureWhoamiCommand(program: Command) {
return commonOptions(program
.command("whoami")
.description("display the current logged in user and project details"))
.action(async (options) => {
await handleTelemetry(async () => {
await printInitialBanner(false);
await whoAmICommand(options);
});
return commonOptions(
program.command("whoami").description("display the current logged in user and project details")
).action(async (options) => {
await handleTelemetry(async () => {
await printInitialBanner(false);
await whoAmICommand(options);
});
});
}
export async function whoAmICommand(options: unknown) {
@@ -61,7 +65,11 @@ export async function whoAmI(
if (authentication.error === "fetch failed") {
loadingSpinner.stop("Fetch failed. Platform down?");
} else {
loadingSpinner.stop(`You must login first. Use \`trigger.dev login --profile ${options?.profile ?? "default"}\` to login.`);
loadingSpinner.stop(
`You must login first. Use \`trigger.dev login --profile ${
options?.profile ?? "default"
}\` to login.`
);
}
return {
@@ -0,0 +1,65 @@
import chalk from "chalk";
export const green = "#4FFF54";
export const purple = "#735BF3";
export function chalkGreen(text: string) {
return chalk.hex(green)(text);
}
export function chalkPurple(text: string) {
return chalk.hex(purple)(text);
}
export function chalkGrey(text: string) {
return chalk.hex("#878C99")(text);
}
export function chalkError(text: string) {
return chalk.hex("#E11D48")(text);
}
export function chalkWarning(text: string) {
return chalk.yellow(text);
}
export function chalkSuccess(text: string) {
return chalk.hex("#28BF5C")(text);
}
export function chalkLink(text: string) {
return chalk.underline.hex("#D7D9DD")(text);
}
export function chalkWorker(text: string) {
return chalk.hex("#FFFF89")(text);
}
export function chalkTask(text: string) {
return chalk.hex("#60A5FA")(text);
}
export function chalkRun(text: string) {
return chalk.hex("#A78BFA")(text);
}
export function logo() {
return `${chalk.hex(green).bold("Trigger")}${chalk.hex(purple).bold(".dev")}`;
}
// Mar 27 09:17:25.653
export function prettyPrintDate(date: Date = new Date()) {
let formattedDate = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false,
}).format(date);
// Append milliseconds
formattedDate += "." + ("00" + date.getMilliseconds()).slice(-3);
return formattedDate;
}
-32
View File
@@ -1,32 +0,0 @@
import chalk from "chalk";
export const green = "#4FFF54";
export const purple = "#735BF3";
export function chalkGreen(text: string) {
return chalk.hex(green)(text);
}
export function chalkPurple(text: string) {
return chalk.hex(purple)(text);
}
export function chalkGrey(text: string) {
return chalk.hex("#666")(text);
}
export function chalkError(text: string) {
return chalk.red(text);
}
export function chalkSuccess(text: string) {
return chalk.green(text);
}
export function chalkLink(text: string) {
return chalk.underline.blue(text);
}
export function logo() {
return `${chalk.hex(green).bold("Trigger")}${chalk.hex(purple).bold(".dev")}`;
}
@@ -4,7 +4,7 @@ import supportsColor from "supports-color";
import type { Result } from "update-check";
import checkForUpdate from "update-check";
import pkg from "../../package.json";
import { chalkGrey, green, logo } from "./colors.js";
import { chalkGrey, chalkRun, chalkTask, chalkWorker, green, logo } from "./cliOutput.js";
import { getVersion } from "./getVersion.js";
import { logger } from "./logger.js";
@@ -52,9 +52,16 @@ export async function printStandloneInitialBanner(performUpdateCheck = true) {
}
}
logger.log(text + "\n" + (supportsColor.stdout ? chalkGrey("-".repeat(54)) : "-".repeat(54)));
}
export function printDevBanner() {
logger.log(
text + "\n" + (supportsColor.stdout ? chalk.hex(green)("-".repeat(54)) : "-".repeat(54))
`${chalkGrey("Key:")} ${chalkWorker("Worker")} ${chalkGrey("|")} ${chalkTask(
"Task"
)} ${chalkGrey("|")} ${chalkRun("Run")}`
);
logger.log(chalkGrey("-".repeat(54)));
}
async function doUpdateCheck(): Promise<string | undefined> {
@@ -15,6 +15,7 @@ import {
ZodMessageSender,
childToWorkerMessages,
correctErrorStackTrace,
formatDurationMilliseconds,
workerToChildMessages,
} from "@trigger.dev/core/v3";
import chalk from "chalk";
@@ -27,6 +28,17 @@ import { safeDeleteFileSync } from "../../utilities/fileSystem.js";
import { installPackages } from "../../utilities/installPackages.js";
import { logger } from "../../utilities/logger.js";
import { UncaughtExceptionError } from "../common/errors.js";
import {
chalkError,
chalkGrey,
chalkLink,
chalkRun,
chalkSuccess,
chalkTask,
chalkWarning,
chalkWorker,
prettyPrintDate,
} from "../../utilities/cliOutput.js";
export type CurrentWorkers = BackgroundWorkerCoordinator["currentWorkers"];
export class BackgroundWorkerCoordinator {
@@ -146,16 +158,23 @@ export class BackgroundWorkerCoordinator {
const { execution } = payload;
// ○ Mar 27 09:17:25.653 -> View logs | 20240326.20 | create-avatar | run_slufhjdfiv8ejnrkw9dsj.1
const logsUrl = `${this.baseURL}/runs/${execution.run.id}`;
const link = chalk.bgBlueBright(terminalLink("view logs", logsUrl));
let timestampPrefix = chalk.gray(new Date().toISOString());
const workerPrefix = chalk.green(`[worker:${record.version}]`);
const taskPrefix = chalk.yellow(`[task:${execution.task.id}]`);
const runId = chalk.blue(execution.run.id);
const attempt = chalk.blue(`.${execution.attempt.number}`);
const pipe = chalkGrey("|");
const bullet = chalkGrey("○");
const link = chalkLink(terminalLink("View logs", logsUrl));
let timestampPrefix = chalkGrey(prettyPrintDate(payload.execution.attempt.startedAt));
const workerPrefix = chalkWorker(record.version);
const taskPrefix = chalkTask(execution.task.id);
const runId = chalkRun(`${execution.run.id}.${execution.attempt.number}`);
logger.log(`${timestampPrefix} ${workerPrefix}${taskPrefix} ${runId}${attempt} ${link}`);
logger.log(
`${bullet} ${timestampPrefix} ${chalkGrey(
"->"
)} ${link} ${pipe} ${workerPrefix} ${pipe} ${taskPrefix} ${pipe} ${runId}`
);
const now = performance.now();
@@ -163,20 +182,21 @@ export class BackgroundWorkerCoordinator {
const elapsed = performance.now() - now;
const retryingText =
const retryingText = chalkGrey(
!completion.ok && completion.skippedRetrying
? " (retrying skipped)"
: !completion.ok && completion.retry !== undefined
? ` (retrying in ${completion.retry.delay}ms)`
: "";
: ""
);
const resultText = !completion.ok
? completion.error.type === "INTERNAL_ERROR" &&
(completion.error.code === TaskRunErrorCodes.TASK_EXECUTION_ABORTED ||
completion.error.code === TaskRunErrorCodes.TASK_RUN_CANCELLED)
? chalk.yellow("cancelled")
: chalk.red(`error${retryingText}`)
: chalk.green("success");
? chalkWarning("Cancelled")
: `${chalkError("Error")}${retryingText}`
: chalkSuccess("Success");
const errorText = !completion.ok
? this.#formatErrorLog(completion.error)
@@ -184,12 +204,14 @@ export class BackgroundWorkerCoordinator {
? `retry in ${completion.retry}ms`
: "";
const elapsedText = chalk.dim(`(${elapsed.toFixed(2)}ms)`);
const elapsedText = chalkGrey(`(${formatDurationMilliseconds(elapsed, { style: "short" })})`);
timestampPrefix = chalk.gray(new Date().toISOString());
timestampPrefix = chalkGrey(prettyPrintDate());
logger.log(
`${timestampPrefix} ${workerPrefix}${taskPrefix} ${runId}${attempt} ${resultText} ${elapsedText} ${link}${errorText}`
`${bullet} ${timestampPrefix} ${chalkGrey(
"->"
)} ${link} ${pipe} ${workerPrefix} ${pipe} ${taskPrefix} ${pipe} ${runId} ${pipe} ${resultText} ${elapsedText}${errorText}`
);
this.onTaskCompleted.post({ completion, execution, worker, backgroundWorkerId: id });
@@ -201,13 +223,13 @@ export class BackgroundWorkerCoordinator {
return "";
}
case "STRING_ERROR": {
return `\n\n${error.raw}\n`;
return `\n\n${chalkError("X Error:")} ${error.raw}\n`;
}
case "CUSTOM_ERROR": {
return `\n\n${error.raw}\n`;
return `\n\n${chalkError("X Error:")} ${error.raw}\n`;
}
case "BUILT_IN_ERROR": {
return `\n\n${error.stackTrace}\n`;
return `\n\n${error.stackTrace.replace(/^Error: /, chalkError("X Error: "))}\n`;
}
}
}
+8
View File
@@ -1546,6 +1546,9 @@ importers:
object-hash:
specifier: ^3.0.0
version: 3.0.0
p-debounce:
specifier: ^4.0.0
version: 4.0.0
p-throttle:
specifier: ^6.1.0
version: 6.1.0
@@ -26134,6 +26137,11 @@ packages:
engines: {node: '>=12.20'}
dev: false
/p-debounce@4.0.0:
resolution: {integrity: sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==}
engines: {node: '>=12'}
dev: false
/p-event@4.2.0:
resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==}
engines: {node: '>=8'}
@@ -30,7 +30,7 @@ export const superChildTask = task({
date: new Date(),
regex: /foo/,
bigint: BigInt(123),
set: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]),
set: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
map: new Map([
["foo", "bar"],
["baz", "qux"],