Feat: added loading transition

This commit is contained in:
Harshith Mullapudi 2025-07-18 15:06:26 +05:30
parent 0776b8dbab
commit 1d8fab67b2
63 changed files with 3185 additions and 309 deletions

View File

@ -1,21 +1,27 @@
VERSION=0.1.6
# Nest run in docker, change host to database container name
DB_HOST=localhost
DB_PORT=5432
# POSTGRES
POSTGRES_USER=docker
POSTGRES_PASSWORD=docker
POSTGRES_DB=core
LOGIN_ORIGIN=http://localhost:3000
LOGIN_ORIGIN=http://localhost:3033
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=core"
# This sets the URL used for direct connections to the database and should only be needed in limited circumstances
# See: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields:~:text=the%20shadow%20database.-,directUrl,-No
DIRECT_URL=${DATABASE_URL}
REMIX_APP_PORT=3000
REMIX_APP_PORT=3033
APP_ENV=production
NODE_ENV=${APP_ENV}
APP_ORIGIN=http://localhost:3000
APP_ORIGIN=http://localhost:3033
SESSION_SECRET=27192e6432564f4788d55c15131bd5ac
ENCRYPTION_KEY=27192e6432564f4788d55c15131bd5ac
@ -41,5 +47,11 @@ MAGIC_LINK_SECRET=27192e6432564f4788d55c15131bd5ac
NEO4J_AUTH=neo4j/27192e6432564f4788d55c15131bd5ac
OLLAMA_URL=http://ollama:11434
EMBEDDING_MODEL=GPT41
MODEL=GPT41
## Trigger ##
TRIGGER_PROJECT_ID=
TRIGGER_SECRET_KEY=
TRIGGER_API_URL=

View File

@ -1,4 +1,4 @@
import { useNavigate } from "@remix-run/react";
import { useNavigate, useNavigation } from "@remix-run/react";
import { Button } from "~/components/ui/button";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { SidebarTrigger } from "~/components/ui/sidebar";
@ -67,8 +67,28 @@ export function PageHeader({
tabs,
showBackForward = true,
}: PageHeaderProps) {
const navigation = useNavigation();
const isLoading =
navigation.state === "loading" || navigation.state === "submitting";
return (
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b border-gray-300 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
<header className="relative flex h-(--header-height) shrink-0 items-center gap-2 border-b border-gray-300 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
{/* Keyframes for the loading bar animation */}
<style>
{`
@keyframes pageheader-loading-bar {
0% {
transform: translateX(-100%);
}
60% {
transform: translateX(0%);
}
100% {
transform: translateX(100%);
}
}
`}
</style>
<div className="flex w-full items-center justify-between gap-1 px-4 pr-2 lg:gap-2">
<div className="-ml-1 flex items-center gap-1">
{/* Back/Forward navigation before SidebarTrigger */}
@ -132,6 +152,25 @@ export function PageHeader({
</div>
)}
</div>
{isLoading && (
<div
aria-hidden="true"
className="pointer-events-none absolute top-[40px] left-0 z-20 h-0.5 w-full overflow-hidden rounded-md"
>
<div
className={`bg-primary h-full w-full transition-opacity duration-200 ${
isLoading ? "opacity-100" : "opacity-0"
}`}
style={{
transform: isLoading ? "translateX(-100%)" : "translateX(-100%)",
animation: isLoading
? "pageheader-loading-bar 1.2s cubic-bezier(0.4,0,0.2,1) infinite"
: "none",
}}
/>
</div>
)}
</header>
);
}

View File

@ -84,3 +84,4 @@ const EnvironmentSchema = z.object({
export type Environment = z.infer<typeof EnvironmentSchema>;
export const env = EnvironmentSchema.parse(process.env);
// export const env = process.env;

View File

@ -1,17 +1,8 @@
// lib/ingest.queue.ts
import { IngestionStatus } from "@core/database";
import { z } from "zod";
import { type z } from "zod";
import { prisma } from "~/db.server";
import { ingestTask } from "~/trigger/ingest/ingest";
export const IngestBodyRequest = z.object({
episodeBody: z.string(),
referenceTime: z.string(),
metadata: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
source: z.string(),
spaceId: z.string().optional(),
sessionId: z.string().optional(),
});
import { type IngestBodyRequest, ingestTask } from "~/trigger/ingest/ingest";
export const addToQueue = async (
body: z.infer<typeof IngestBodyRequest>,

View File

@ -1,4 +1,3 @@
import { LLMMappings, LLMModelEnum } from "@core/types";
import {
type CoreMessage,
type LanguageModelV1,
@ -8,7 +7,7 @@ import {
} from "ai";
import { openai } from "@ai-sdk/openai";
import { logger } from "~/services/logger.service";
import { env } from "~/env.server";
import { createOllama, type OllamaProvider } from "ollama-ai-provider";
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
@ -20,7 +19,7 @@ export async function makeModelCall(
options?: any,
) {
let modelInstance;
const model = env.MODEL;
const model = process.env.MODEL as any;
const ollamaUrl = process.env.OLLAMA_URL;
let ollama: OllamaProvider | undefined;
@ -79,7 +78,7 @@ export async function makeModelCall(
}
export async function getEmbedding(text: string) {
const ollamaUrl = env.OLLAMA_URL;
const ollamaUrl = process.env.OLLAMA_URL;
if (!ollamaUrl) {
// Use OpenAI embedding model when explicitly requested
@ -91,13 +90,13 @@ export async function getEmbedding(text: string) {
}
// Default to using Ollama
const model = env.EMBEDDING_MODEL;
const model = process.env.EMBEDDING_MODEL;
const ollama = createOllama({
baseURL: ollamaUrl,
});
const { embedding } = await embed({
model: ollama.embedding(model),
model: ollama.embedding(model as string),
value: text,
});

View File

@ -1,12 +1,14 @@
import neo4j from "neo4j-driver";
import { type RawTriplet } from "~/components/graph/type";
import { env } from "~/env.server";
import { logger } from "~/services/logger.service";
// Create a driver instance
const driver = neo4j.driver(
env.NEO4J_URI,
neo4j.auth.basic(env.NEO4J_USERNAME, env.NEO4J_PASSWORD),
process.env.NEO4J_URI ?? "bolt://localhost:7687",
neo4j.auth.basic(
process.env.NEO4J_USERNAME as string,
process.env.NEO4J_PASSWORD as string,
),
{
maxConnectionPoolSize: 50,
logging: {

View File

@ -7,7 +7,7 @@ import { getIntegrationDefinitionWithId } from "~/services/integrationDefinition
import { logger } from "~/services/logger.service";
import { getWorkspaceByUser } from "~/models/workspace.server";
import { tasks } from "@trigger.dev/sdk";
import { scheduler } from "~/trigger/integrations/scheduler";
import { type scheduler } from "~/trigger/integrations/scheduler";
// Schema for creating an integration account with API key
const IntegrationAccountBodySchema = z.object({

View File

@ -2,16 +2,13 @@ import { json } from "@remix-run/node";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
isInitializeRequest,
JSONRPCMessage,
} from "@modelcontextprotocol/sdk/types.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { createHybridActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { addToQueue, IngestBodyRequest } from "~/lib/ingest.server";
import { addToQueue } from "~/lib/ingest.server";
import { SearchService } from "~/services/search.server";
import { PassThrough } from "stream";
import { handleTransport } from "~/utils/mcp";
import { IngestBodyRequest } from "~/trigger/ingest/ingest";
// Map to store transports by session ID with cleanup tracking
const transports: {

View File

@ -1,49 +1,12 @@
import { parse } from "@conform-to/zod";
import { json } from "@remix-run/node";
import { useState, useEffect } from "react";
import {
type LoaderFunctionArgs,
type ActionFunctionArgs,
} from "@remix-run/server-runtime";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { requireUserId } from "~/services/session.server";
import { addToQueue, IngestBodyRequest } from "~/lib/ingest.server";
import { useTypedLoaderData } from "remix-typedjson";
import { SearchBodyRequest } from "./search";
import { SearchService } from "~/services/search.server";
import { GraphVisualizationClient } from "~/components/graph/graph-client";
import { LoaderCircle } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export async function action({ request }: ActionFunctionArgs) {
const userId = await requireUserId(request);
const formData = await request.formData();
// Check if this is a search request by looking for query parameter
if (formData.has("query")) {
// Handle ingest request
const submission = parse(formData, { schema: SearchBodyRequest });
const searchService = new SearchService();
if (!submission.value || submission.intent !== "submit") {
return json(submission);
}
const results = await searchService.search(submission.value.query, userId);
return json(results);
}
// Handle ingest request
const submission = parse(formData, { schema: IngestBodyRequest });
if (!submission.value || submission.intent !== "submit") {
return json(submission);
}
return await addToQueue(submission.value, userId);
}
export async function loader({ request }: LoaderFunctionArgs) {
// Only return userId, not the heavy nodeLinks
const userId = await requireUserId(request);

View File

@ -5,7 +5,7 @@ import { LogsFilters } from "~/components/logs/logs-filters";
import { VirtualLogsList } from "~/components/logs/virtual-logs-list";
import { AppContainer, PageContainer } from "~/components/layout/app-layout";
import { Card, CardContent } from "~/components/ui/card";
import { Activity } from "lucide-react";
import { Activity, LoaderCircle } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export default function LogsActivity() {
@ -31,7 +31,7 @@ export default function LogsActivity() {
<AppContainer>
<PageContainer>
<div className="flex h-64 items-center justify-center">
<div className="border-primary h-8 w-8 animate-spin rounded-full border-b-2"></div>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
</div>
</PageContainer>
</AppContainer>

View File

@ -5,7 +5,7 @@ import { LogsFilters } from "~/components/logs/logs-filters";
import { VirtualLogsList } from "~/components/logs/virtual-logs-list";
import { AppContainer, PageContainer } from "~/components/layout/app-layout";
import { Card, CardContent } from "~/components/ui/card";
import { Database } from "lucide-react";
import { Database, LoaderCircle } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
export default function LogsAll() {
@ -31,7 +31,7 @@ export default function LogsAll() {
<AppContainer>
<PageContainer>
<div className="flex h-64 items-center justify-center">
<div className="border-primary h-8 w-8 animate-spin rounded-full border-b-2"></div>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
</div>
</PageContainer>
</AppContainer>
@ -57,7 +57,7 @@ export default function LogsAll() {
},
]}
/>
<div className="space-y-6 p-4 px-5">
<div className="h-[calc(100vh_-_56px)] space-y-6 p-4 px-5">
{/* Filters */}
{logs.length > 0 && (
<LogsFilters

View File

@ -45,9 +45,9 @@ import {
getNodeTypes,
getNodeTypesString,
isPresetType,
getAllPresetTypes,
} from "~/utils/presets/nodes";
import { normalizePrompt } from "./prompts";
import { type PrismaClient } from "@prisma/client";
// Default number of previous episodes to retrieve for context
const DEFAULT_EPISODE_WINDOW = 5;
@ -63,7 +63,7 @@ export class KnowledgeGraphService {
* This method extracts information from the episode, creates nodes and statements,
* and updates the HelixDB database according to the reified + temporal approach.
*/
async addEpisode(params: AddEpisodeParams) {
async addEpisode(params: AddEpisodeParams, prisma: PrismaClient) {
const startTime = Date.now();
const now = new Date();
@ -81,6 +81,7 @@ export class KnowledgeGraphService {
params.episodeBody,
params.source,
params.userId,
prisma,
);
const relatedEpisodesEntities = await getRelatedEpisodesEntities({
@ -1008,6 +1009,7 @@ export class KnowledgeGraphService {
episodeBody: string,
source: string,
userId: string,
prisma: PrismaClient,
) {
let appEnumValues: Apps[] = [];
if (Apps[source.toUpperCase() as keyof typeof Apps]) {
@ -1020,6 +1022,7 @@ export class KnowledgeGraphService {
const ingestionRules = await this.getIngestionRulesForSource(
source,
userId,
prisma,
);
const context = {
@ -1117,10 +1120,10 @@ export class KnowledgeGraphService {
private async getIngestionRulesForSource(
source: string,
userId: string,
prisma: PrismaClient,
): Promise<string | null> {
try {
// Import prisma here to avoid circular dependencies
const { prisma } = await import("~/db.server");
// Get the user's workspace
const user = await prisma.user.findUnique({

View File

@ -22,7 +22,7 @@ export class Logger {
level: LogLevel = "info",
filteredKeys: string[] = [],
jsonReplacer?: (key: string, value: unknown) => unknown,
additionalFields?: () => Record<string, unknown>
additionalFields?: () => Record<string, unknown>,
) {
this.#name = name;
this.#level = logLevels.indexOf((env.APP_LOG_LEVEL ?? level) as LogLevel);
@ -37,14 +37,19 @@ export class Logger {
logLevels[this.#level],
this.#filteredKeys,
this.#jsonReplacer,
() => ({ ...this.#additionalFields(), ...fields })
() => ({ ...this.#additionalFields(), ...fields }),
);
}
// Return a new Logger instance with the same name and a new log level
// but filter out the keys from the log messages (at any level)
filter(...keys: string[]) {
return new Logger(this.#name, logLevels[this.#level], keys, this.#jsonReplacer);
return new Logger(
this.#name,
logLevels[this.#level],
keys,
this.#jsonReplacer,
);
}
static satisfiesLogLevel(logLevel: LogLevel, setLevel: LogLevel) {
@ -94,7 +99,10 @@ export class Logger {
const structuredMessage = extractStructuredMessageFromArgs(...args);
const structuredLog = {
...structureArgs(safeJsonClone(args) as Record<string, unknown>[], this.#filteredKeys),
...structureArgs(
safeJsonClone(args) as Record<string, unknown>[],
this.#filteredKeys,
),
...this.#additionalFields(),
...(structuredError ? { error: structuredError } : {}),
timestamp: new Date(),
@ -103,9 +111,13 @@ export class Logger {
...(structuredMessage ? { $message: structuredMessage } : {}),
level,
traceId:
currentSpan && currentSpan.isRecording() ? currentSpan?.spanContext().traceId : undefined,
currentSpan && currentSpan.isRecording()
? currentSpan?.spanContext().traceId
: undefined,
parentSpanId:
currentSpan && currentSpan.isRecording() ? currentSpan?.spanContext().spanId : undefined,
currentSpan && currentSpan.isRecording()
? currentSpan?.spanContext().spanId
: undefined,
};
// If the span is not recording, and it's a debug log, mark it so we can filter it out when we forward it
@ -120,7 +132,9 @@ export class Logger {
// Detect if args is an error object
// Or if args contains an error object at the "error" key
// In both cases, return the error object as a structured error
function extractStructuredErrorFromArgs(...args: Array<Record<string, unknown> | undefined>) {
function extractStructuredErrorFromArgs(
...args: Array<Record<string, unknown> | undefined>
) {
const error = args.find((arg) => arg instanceof Error) as Error | undefined;
if (error) {
@ -144,7 +158,9 @@ function extractStructuredErrorFromArgs(...args: Array<Record<string, unknown> |
return;
}
function extractStructuredMessageFromArgs(...args: Array<Record<string, unknown> | undefined>) {
function extractStructuredMessageFromArgs(
...args: Array<Record<string, unknown> | undefined>
) {
// Check to see if there is a `message` key in the args, and if so, return it
const structuredMessage = args.find((arg) => arg?.message);
@ -187,7 +203,10 @@ function safeJsonClone(obj: unknown) {
}
// If args is has a single item that is an object, return that object
function structureArgs(args: Array<Record<string, unknown>>, filteredKeys: string[] = []) {
function structureArgs(
args: Array<Record<string, unknown>>,
filteredKeys: string[] = [],
) {
if (!args) {
return;
}
@ -197,7 +216,10 @@ function structureArgs(args: Array<Record<string, unknown>>, filteredKeys: strin
}
if (args.length === 1 && typeof args[0] === "object") {
return filterKeys(JSON.parse(JSON.stringify(args[0], bigIntReplacer)), filteredKeys);
return filterKeys(
JSON.parse(JSON.stringify(args[0], bigIntReplacer)),
filteredKeys,
);
}
return args;
@ -270,7 +292,7 @@ export const logger = new Logger(
const fields = currentFieldsStore.getStore();
const httpContext = getHttpContext();
return { ...fields, http: httpContext };
}
},
);
export const workerLogger = new Logger(
@ -281,7 +303,7 @@ export const workerLogger = new Logger(
() => {
const fields = currentFieldsStore.getStore();
return fields ? { ...fields } : {};
}
},
);
export const socketLogger = new Logger(
@ -292,5 +314,5 @@ export const socketLogger = new Logger(
() => {
const fields = currentFieldsStore.getStore();
return fields ? { ...fields } : {};
}
},
);

View File

@ -1,4 +1,4 @@
import { LLMModelEnum, type StatementNode } from "@core/types";
import { type StatementNode } from "@core/types";
import { combineAndDeduplicateStatements } from "./utils";
import { type CoreMessage } from "ai";
import { makeModelCall } from "~/lib/model.server";

View File

@ -1,10 +1,21 @@
import { queue, task } from "@trigger.dev/sdk";
import { type z } from "zod";
import { z } from "zod";
import { KnowledgeGraphService } from "~/services/knowledgeGraph.server";
import { prisma } from "~/db.server";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
import { IngestionStatus } from "@core/database";
import { logger } from "~/services/logger.service";
import { type IngestBodyRequest } from "~/lib/ingest.server";
export const IngestBodyRequest = z.object({
episodeBody: z.string(),
referenceTime: z.string(),
metadata: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
source: z.string(),
spaceId: z.string().optional(),
sessionId: z.string().optional(),
});
const ingestionQueue = queue({
name: "ingestion-queue",
@ -34,10 +45,13 @@ export const ingestTask = task({
const episodeBody = payload.body as any;
const episodeDetails = await knowledgeGraphService.addEpisode({
...episodeBody,
userId: payload.userId,
});
const episodeDetails = await knowledgeGraphService.addEpisode(
{
...episodeBody,
userId: payload.userId,
},
prisma,
);
await prisma.ingestionQueue.update({
where: { id: payload.queueId },

View File

@ -132,7 +132,7 @@ const executeCLICommand = async (
// Use node to execute the integration file
const childProcess = spawn("node", args, {
env: process.env,
env: undefined,
cwd: tempDir,
stdio: ["pipe", "pipe", "pipe"],
});

View File

@ -1,6 +1,6 @@
import { Activity, PrismaClient } from "@prisma/client";
import { PrismaClient } from "@prisma/client";
import { type Message } from "@core/types";
import { addToQueue } from "~/lib/ingest.server";
import { addToQueue } from "./queue";
const prisma = new PrismaClient();

View File

@ -0,0 +1,48 @@
import { IngestionStatus, PrismaClient } from "@prisma/client";
import { type z } from "zod";
import { type IngestBodyRequest, ingestTask } from "../ingest/ingest";
const prisma = new PrismaClient();
export const addToQueue = async (
body: z.infer<typeof IngestBodyRequest>,
userId: string,
activityId?: string,
) => {
const user = await prisma.user.findFirst({
where: {
id: userId,
},
include: {
Workspace: true,
},
});
if (!user?.Workspace?.id) {
throw new Error(
"Workspace ID is required to create an ingestion queue entry.",
);
}
const queuePersist = await prisma.ingestionQueue.create({
data: {
spaceId: body.spaceId ? body.spaceId : null,
data: body,
status: IngestionStatus.PENDING,
priority: 1,
workspaceId: user.Workspace.id,
activityId,
},
});
const handler = await ingestTask.trigger(
{ body, userId, workspaceId: user.Workspace.id, queueId: queuePersist.id },
{
queue: "ingestion-queue",
concurrencyKey: userId,
tags: [user.id, queuePersist.id],
},
);
return { id: handler.id, token: handler.publicAccessToken };
};

View File

@ -1,9 +1,11 @@
import { queue, task } from "@trigger.dev/sdk";
import { PrismaClient } from "@prisma/client";
import { logger } from "~/services/logger.service";
import { WebhookDeliveryStatus } from "@core/database";
import crypto from "crypto";
import { prisma } from "~/db.server";
const prisma = new PrismaClient();
const webhookQueue = queue({
name: "webhook-delivery-queue",

View File

@ -10,7 +10,8 @@
"lint:fix": "eslint --fix --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
"start": "remix-serve ./build/server/index.js",
"typecheck": "tsc",
"trigger:dev": "pnpm dlx trigger.dev@v4-beta dev"
"trigger:dev": "pnpm dlx trigger.dev@v4-beta dev",
"trigger:deploy": "pnpm dlx trigger.dev@v4-beta deploy"
},
"dependencies": {
"@ai-sdk/anthropic": "^1.2.12",

View File

@ -6,7 +6,7 @@ import {
import { prismaExtension } from "@trigger.dev/build/extensions/prisma";
export default defineConfig({
project: process.env.TRIGGER_PROJECT_ID as string,
project: "proj_jqsgldpqilpdnvpzyzll",
runtime: "node",
logLevel: "log",
// The max compute seconds a task is allowed to run. If the task run exceeds this duration, it will be stopped.

View File

@ -26,6 +26,9 @@ services:
- OLLAMA_URL=${OLLAMA_URL}
- EMBEDDING_MODEL=${EMBEDDING_MODEL}
- MODEL=${MODEL}
- TRIGGER_PROJECT_ID=${TRIGGER_PROJECT_ID}
- TRIGGER_SECRET_KEY=${TRIGGER_SECRET_KEY}
- TRIGGER_API_URL=${TRIGGER_API_URL}
ports:
- "3033:3000"
depends_on:

View File

@ -18,7 +18,8 @@
"db:studio": "dotenv -- turbo run db:studio",
"db:populate": "dotenv -- turbo run db:populate",
"generate": "dotenv -- turbo run generate",
"trigger:dev": "dotenv -- turbo run trigger:dev"
"trigger:dev": "dotenv -- turbo run trigger:dev",
"trigger:deploy": "dotenv -- turbo run trigger:deploy"
},
"devDependencies": {
"dotenv-cli": "^7.4.4",

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
import { Command } from "commander";
import { z } from "zod";
export declare const CommonCommandOptions: z.ZodObject<{
logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "log", "warn", "error", "none"]>>;
}, "strip", z.ZodTypeAny, {
logLevel: "error" | "none" | "warn" | "info" | "log" | "debug";
}, {
logLevel?: "error" | "none" | "warn" | "info" | "log" | "debug" | undefined;
}>;
export type CommonCommandOptions = z.infer<typeof CommonCommandOptions>;
export declare function commonOptions(command: Command): Command;
export declare class SkipLoggingError extends Error {
}
export declare class SkipCommandError extends Error {
}
export declare class OutroCommandError extends SkipCommandError {
}
export declare function wrapCommandAction<T extends z.AnyZodObject, TResult>(name: string, schema: T, options: unknown, action: (opts: z.output<T>) => Promise<TResult>): Promise<TResult | undefined>;
export declare function installExitHandler(): void;

View File

@ -0,0 +1,54 @@
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { logger } from "../utils/logger.js";
import { outro } from "@clack/prompts";
import { chalkError } from "../utils/cliOutput.js";
export const CommonCommandOptions = z.object({
logLevel: z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log"),
});
export function commonOptions(command) {
return command.option("-l, --log-level <level>", "The CLI log level to use (debug, info, log, warn, error, none).", "log");
}
export class SkipLoggingError extends Error {
}
export class SkipCommandError extends Error {
}
export class OutroCommandError extends SkipCommandError {
}
export async function wrapCommandAction(name, schema, options, action) {
try {
const parsedOptions = schema.safeParse(options);
if (!parsedOptions.success) {
throw new Error(fromZodError(parsedOptions.error).toString());
}
logger.loggerLevel = parsedOptions.data.logLevel;
logger.debug(`Running "${name}" with the following options`, {
options: options,
});
const result = await action(parsedOptions.data);
return result;
}
catch (e) {
if (e instanceof SkipLoggingError) {
}
else if (e instanceof OutroCommandError) {
outro("Operation cancelled");
}
else if (e instanceof SkipCommandError) {
// do nothing
}
else {
logger.log(`${chalkError("X Error:")} ${e instanceof Error ? e.message : String(e)}`);
}
throw e;
}
}
export function installExitHandler() {
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});
}
//# sourceMappingURL=common.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/cli/common.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;CACnF,CAAC,CAAC;AAIH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,OAAO,CAAC,MAAM,CACnB,yBAAyB,EACzB,iEAAiE,EACjE,KAAK,CACN,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAC9C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAC9C,MAAM,OAAO,iBAAkB,SAAQ,gBAAgB;CAAG;AAE1D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,MAAS,EACT,OAAgB,EAChB,MAA+C;IAE/C,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAEhD,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;QAEjD,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,8BAA8B,EAAE;YAC3D,OAAO,EAAE,OAAO;SACjB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAEhD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;QACpC,CAAC;aAAM,IAAI,CAAC,YAAY,iBAAiB,EAAE,CAAC;YAC1C,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;YACzC,aAAa;QACf,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxF,CAAC;QAED,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":""}

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=init.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":""}

View File

@ -0,0 +1 @@
export {};

View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=index.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":""}

View File

@ -0,0 +1,25 @@
import { TerminalLinkOptions } from "./terminalLink.js";
export declare const isInteractive: boolean;
export declare const isLinksSupported: boolean;
export declare const green = "#4FFF54";
export declare const purple = "#735BF3";
export declare function chalkGreen(text: string): string;
export declare function chalkPurple(text: string): string;
export declare function chalkGrey(text: string): string;
export declare function chalkError(text: string): string;
export declare function chalkWarning(text: string): string;
export declare function chalkSuccess(text: string): string;
export declare function chalkLink(text: string): string;
export declare function chalkWorker(text: string): string;
export declare function chalkTask(text: string): string;
export declare function chalkRun(text: string): string;
export declare function logo(): string;
export declare function prettyPrintDate(date?: Date): string;
export declare function prettyError(header: string, body?: string, footer?: string): void;
export declare function prettyWarning(header: string, body?: string, footer?: string): void;
export declare function aiHelpLink({ dashboardUrl, project, query, }: {
dashboardUrl: string;
project: string;
query: string;
}): void;
export declare function cliLink(text: string, url: string, options?: TerminalLinkOptions): string;

View File

@ -0,0 +1,97 @@
import { log } from "@clack/prompts";
import chalk from "chalk";
import { terminalLink } from "./terminalLink.js";
import { hasTTY } from "std-env";
export const isInteractive = hasTTY;
export const isLinksSupported = terminalLink.isSupported;
export const green = "#4FFF54";
export const purple = "#735BF3";
export function chalkGreen(text) {
return chalk.hex(green)(text);
}
export function chalkPurple(text) {
return chalk.hex(purple)(text);
}
export function chalkGrey(text) {
return chalk.hex("#878C99")(text);
}
export function chalkError(text) {
return chalk.hex("#E11D48")(text);
}
export function chalkWarning(text) {
return chalk.yellow(text);
}
export function chalkSuccess(text) {
return chalk.hex("#28BF5C")(text);
}
export function chalkLink(text) {
return chalk.underline.hex("#D7D9DD")(text);
}
export function chalkWorker(text) {
return chalk.hex("#FFFF89")(text);
}
export function chalkTask(text) {
return chalk.hex("#60A5FA")(text);
}
export function chalkRun(text) {
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 = 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;
}
export function prettyError(header, body, footer) {
const prefix = "Error: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkError(prefix);
const withIndents = (text) => text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body?.trim());
const prettyFooter = withIndents(footer);
log.error(`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${prettyFooter ? `${spacing}${prettyFooter}` : ""}`);
}
export function prettyWarning(header, body, footer) {
const prefix = "Warning: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkWarning(prefix);
const withIndents = (text) => text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body);
const prettyFooter = withIndents(footer);
log.warn(`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${prettyFooter ? `${spacing}${prettyFooter}` : ""}`);
}
export function aiHelpLink({ dashboardUrl, project, query, }) {
const searchParams = new URLSearchParams();
//the max length for a URL is 1950 characters
const clippedQuery = query.slice(0, 1950);
searchParams.set("q", clippedQuery);
const url = new URL(`/projects/${project}/ai-help`, dashboardUrl);
url.search = searchParams.toString();
log.message(chalkLink(cliLink("💡 Get a fix for this error using AI", url.toString())));
}
export function cliLink(text, url, options) {
return terminalLink(text, url, {
fallback: (text, url) => `${text} ${url}`,
...options,
});
}
//# sourceMappingURL=cliOutput.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"cliOutput.js","sourceRoot":"","sources":["../../../src/utils/cliOutput.ts"],"names":[],"mappings":"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"}

View File

@ -0,0 +1,42 @@
import type { Message } from "esbuild";
export declare const LOGGER_LEVELS: {
readonly none: -1;
readonly error: 0;
readonly warn: 1;
readonly info: 2;
readonly log: 3;
readonly debug: 4;
};
export type LoggerLevel = keyof typeof LOGGER_LEVELS;
export type TableRow<Keys extends string> = Record<Keys, string>;
export declare class Logger {
constructor();
loggerLevel: "error" | "none" | "warn" | "info" | "log" | "debug";
columns: number;
debug: (...args: unknown[]) => void;
ignore: (...args: unknown[]) => void;
debugWithSanitization: (label: string, ...args: unknown[]) => void;
info: (...args: unknown[]) => void;
log: (...args: unknown[]) => void;
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
warn: (...args: unknown[]) => void;
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
error: (...args: unknown[]) => void;
table<Keys extends string>(data: TableRow<Keys>[], level?: Exclude<LoggerLevel, "none">): void;
private doLog;
private formatMessage;
}
/**
* A drop-in replacement for `console` for outputting logging messages.
*
* Errors and Warnings will get additional formatting to highlight them to the user.
* You can also set a `logger.loggerLevel` value to one of "debug", "log", "warn" or "error",
* to filter out logging messages.
*/
export declare const logger: Logger;
export declare function logBuildWarnings(warnings: Message[]): void;
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export declare function logBuildFailure(errors: Message[], warnings: Message[]): void;

View File

@ -0,0 +1,111 @@
// This is a copy of the logger utility from the wrangler repo: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/logger.ts
import { format } from "node:util";
import chalk from "chalk";
import CLITable from "cli-table3";
import { formatMessagesSync } from "esbuild";
import { env } from "std-env";
export const LOGGER_LEVELS = {
none: -1,
error: 0,
warn: 1,
info: 2,
log: 3,
debug: 4,
};
/** A map from LOGGER_LEVEL to the error `kind` needed by `formatMessagesSync()`. */
const LOGGER_LEVEL_FORMAT_TYPE_MAP = {
error: "error",
warn: "warning",
info: undefined,
log: undefined,
debug: undefined,
};
function getLoggerLevel() {
const fromEnv = env.TRIGGER_LOG_LEVEL?.toLowerCase();
if (fromEnv !== undefined) {
if (fromEnv in LOGGER_LEVELS)
return fromEnv;
const expected = Object.keys(LOGGER_LEVELS)
.map((level) => `"${level}"`)
.join(" | ");
console.warn(`Unrecognised TRIGGER_LOG_LEVEL value ${JSON.stringify(fromEnv)}, expected ${expected}, defaulting to "log"...`);
}
return "log";
}
export class Logger {
constructor() { }
loggerLevel = getLoggerLevel();
columns = process.stdout.columns;
debug = (...args) => this.doLog("debug", args);
ignore = (...args) => { };
debugWithSanitization = (label, ...args) => {
this.doLog("debug", [label, ...args]);
};
info = (...args) => this.doLog("info", args);
log = (...args) => this.doLog("log", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
warn = (...args) => this.doLog("warn", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
error = (...args) => this.doLog("error", args);
table(data, level) {
const keys = data.length === 0 ? [] : Object.keys(data[0]);
const t = new CLITable({
head: keys,
style: {
head: chalk.level ? ["blue"] : [],
border: chalk.level ? ["gray"] : [],
},
});
t.push(...data.map((row) => keys.map((k) => row[k])));
return this.doLog(level ?? "log", [t.toString()]);
}
doLog(messageLevel, args) {
const message = this.formatMessage(messageLevel, format(...args));
// only send logs to the terminal if their level is at least the configured log-level
if (LOGGER_LEVELS[this.loggerLevel] >= LOGGER_LEVELS[messageLevel]) {
console[messageLevel](message);
}
}
formatMessage(level, message) {
const kind = LOGGER_LEVEL_FORMAT_TYPE_MAP[level];
if (kind) {
// Format the message using the esbuild formatter.
// The first line of the message is the main `text`,
// subsequent lines are put into the `notes`.
const [firstLine, ...otherLines] = message.split("\n");
const notes = otherLines.length > 0 ? otherLines.map((text) => ({ text })) : undefined;
return formatMessagesSync([{ text: firstLine, notes }], {
color: true,
kind,
terminalWidth: this.columns,
})[0];
}
else {
return message;
}
}
}
/**
* A drop-in replacement for `console` for outputting logging messages.
*
* Errors and Warnings will get additional formatting to highlight them to the user.
* You can also set a `logger.loggerLevel` value to one of "debug", "log", "warn" or "error",
* to filter out logging messages.
*/
export const logger = new Logger();
export function logBuildWarnings(warnings) {
const logs = formatMessagesSync(warnings, { kind: "warning", color: true });
for (const log of logs)
console.warn(log);
}
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export function logBuildFailure(errors, warnings) {
const logs = formatMessagesSync(errors, { kind: "error", color: true });
for (const log of logs)
console.error(log);
logBuildWarnings(warnings);
}
//# sourceMappingURL=logger.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"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"}

View File

@ -0,0 +1,15 @@
/**
Creates a supports hyperlinks check for a given stream.
@param stream - Optional stream to check for hyperlink support.
@returns boolean indicating whether hyperlinks are supported.
*/
export declare function createSupportsHyperlinks(stream: NodeJS.WriteStream): boolean;
/** Object containing hyperlink support status for stdout and stderr. */
declare const supportsHyperlinks: {
/** Whether stdout supports hyperlinks. */
stdout: boolean;
/** Whether stderr supports hyperlinks. */
stderr: boolean;
};
export default supportsHyperlinks;

View File

@ -0,0 +1,122 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Copyright (c) James Talmage <james@talmage.io> (https://github.com/jamestalmage)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { createSupportsColor } from "supports-color";
import hasFlag from "has-flag";
function parseVersion(versionString = "") {
if (/^\d{3,4}$/.test(versionString)) {
// Env var doesn't always use dots. example: 4601 => 46.1.0
const match = /(\d{1,2})(\d{2})/.exec(versionString) ?? [];
return {
major: 0,
minor: Number.parseInt(match[1] ?? "0", 10),
patch: Number.parseInt(match[2] ?? "0", 10),
};
}
const versions = (versionString ?? "").split(".").map((n) => Number.parseInt(n, 10));
return {
major: versions[0] ?? 0,
minor: versions[1] ?? 0,
patch: versions[2] ?? 0,
};
}
/**
Creates a supports hyperlinks check for a given stream.
@param stream - Optional stream to check for hyperlink support.
@returns boolean indicating whether hyperlinks are supported.
*/
export function createSupportsHyperlinks(stream) {
const { CI, CURSOR_TRACE_ID, FORCE_HYPERLINK, NETLIFY, TEAMCITY_VERSION, TERM_PROGRAM, TERM_PROGRAM_VERSION, VTE_VERSION, TERM, } = process.env;
if (FORCE_HYPERLINK) {
return !(FORCE_HYPERLINK.length > 0 && Number.parseInt(FORCE_HYPERLINK, 10) === 0);
}
if (hasFlag("no-hyperlink") ||
hasFlag("no-hyperlinks") ||
hasFlag("hyperlink=false") ||
hasFlag("hyperlink=never")) {
return false;
}
if (hasFlag("hyperlink=true") || hasFlag("hyperlink=always")) {
return true;
}
// Netlify does not run a TTY, it does not need `supportsColor` check
if (NETLIFY) {
return true;
}
// If they specify no colors, they probably don't want hyperlinks.
if (!createSupportsColor(stream)) {
return false;
}
if (stream && !stream.isTTY) {
return false;
}
// Windows Terminal
if ("WT_SESSION" in process.env) {
return true;
}
if (process.platform === "win32") {
return false;
}
if (CI) {
return false;
}
if (TEAMCITY_VERSION) {
return false;
}
if (CURSOR_TRACE_ID) {
return true;
}
if (TERM_PROGRAM) {
const version = parseVersion(TERM_PROGRAM_VERSION);
switch (TERM_PROGRAM) {
case "iTerm.app": {
if (version.major === 3) {
return version.minor >= 1;
}
return version.major > 3;
}
case "WezTerm": {
return version.major >= 20_200_620;
}
case "vscode": {
// eslint-disable-next-line no-mixed-operators
return version.major > 1 || (version.major === 1 && version.minor >= 72);
}
case "ghostty": {
return true;
}
// No default
}
}
if (VTE_VERSION) {
// 0.50.0 was supposed to support hyperlinks, but throws a segfault
if (VTE_VERSION === "0.50.0") {
return false;
}
const version = parseVersion(VTE_VERSION);
return version.major > 0 || version.minor >= 50;
}
switch (TERM) {
case "alacritty": {
// Support added in v0.11 (2022-10-13)
return true;
}
// No default
}
return false;
}
/** Object containing hyperlink support status for stdout and stderr. */
const supportsHyperlinks = {
/** Whether stdout supports hyperlinks. */
stdout: createSupportsHyperlinks(process.stdout),
/** Whether stderr supports hyperlinks. */
stderr: createSupportsHyperlinks(process.stderr),
};
export default supportsHyperlinks;
//# sourceMappingURL=supportsHyperlinks.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"supportsHyperlinks.js","sourceRoot":"","sources":["../../../src/utils/supportsHyperlinks.ts"],"names":[],"mappings":"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"}

View File

@ -0,0 +1,56 @@
export type TerminalLinkOptions = {
/**
Override the default fallback. If false, the fallback will be disabled.
@default `${text} (${url})`
*/
readonly fallback?: ((text: string, url: string) => string) | boolean;
};
/**
Create a clickable link in the terminal's stdout.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`,
unless the fallback is disabled by setting the `fallback` option to `false`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);
```
@deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
declare function terminalLink(text: string, url: string, { target, ...options }?: {
target?: "stdout" | "stderr";
} & TerminalLinkOptions): string;
declare namespace terminalLink {
var isSupported: boolean;
var stderr: typeof terminalLinkStderr;
}
/**
Create a clickable link in the terminal's stderr.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink.stderr('My Website', 'https://sindresorhus.com');
console.error(link);
```
*/
declare function terminalLinkStderr(text: string, url: string, options?: TerminalLinkOptions): string;
declare namespace terminalLinkStderr {
var isSupported: boolean;
}
export { terminalLink };

View File

@ -0,0 +1,76 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import ansiEscapes from "ansi-escapes";
import supportsHyperlinks from "./supportsHyperlinks.js";
/**
Create a clickable link in the terminal's stdout.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`,
unless the fallback is disabled by setting the `fallback` option to `false`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);
```
@deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
function terminalLink(text, url, { target = "stdout", ...options } = {}) {
if (!supportsHyperlinks[target]) {
// If the fallback has been explicitly disabled, don't modify the text itself.
if (options.fallback === false) {
return text;
}
return typeof options.fallback === "function"
? options.fallback(text, url)
: `${text} (\u200B${url}\u200B)`;
}
return ansiEscapes.link(text, url);
}
/**
Check whether the terminal supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLink.isSupported = supportsHyperlinks.stdout;
terminalLink.stderr = terminalLinkStderr;
/**
Create a clickable link in the terminal's stderr.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink.stderr('My Website', 'https://sindresorhus.com');
console.error(link);
```
*/
function terminalLinkStderr(text, url, options = {}) {
return terminalLink(text, url, { target: "stderr", ...options });
}
/**
Check whether the terminal's stderr supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLinkStderr.isSupported = supportsHyperlinks.stderr;
export { terminalLink };
//# sourceMappingURL=terminalLink.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"terminalLink.js","sourceRoot":"","sources":["../../../src/utils/terminalLink.ts"],"names":[],"mappings":"AAAA;;;;;;EAME;AAEF,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,kBAAkB,MAAM,yBAAyB,CAAC;AAUzD;;;;;;;;;;;;;;;;;;;EAmBE;AACF,SAAS,YAAY,CACnB,IAAY,EACZ,GAAW,EACX,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,OAAO,KAA6D,EAAE;IAE9F,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,8EAA8E;QAC9E,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,OAAO,CAAC,QAAQ,KAAK,UAAU;YAC3C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC;YAC7B,CAAC,CAAC,GAAG,IAAI,WAAW,GAAG,SAAS,CAAC;IACrC,CAAC;IAED,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC;AACD;;;;EAIE;AACF,YAAY,CAAC,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC;AACrD,YAAY,CAAC,MAAM,GAAG,kBAAkB,CAAC;AAEzC;;;;;;;;;;;;;;;;EAgBE;AACF,SAAS,kBAAkB,CAAC,IAAY,EAAE,GAAW,EAAE,UAA+B,EAAE;IACtF,OAAO,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;;;EAIE;AACF,kBAAkB,CAAC,WAAW,GAAG,kBAAkB,CAAC,MAAM,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,CAAC"}

View File

@ -0,0 +1,8 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "../src",
"module": "nodenext",
"moduleResolution": "nodenext"
}
}

View File

@ -0,0 +1,16 @@
{
"extends": "./build.json",
"include": [
"../src/**/*.ts",
"../src/**/*.mts",
"../src/**/*.tsx",
"../src/**/*.json"
],
"exclude": [
"../**/*.test.ts",
"../src/package.json"
],
"compilerOptions": {
"outDir": "../.tshy-build/esm"
}
}

View File

@ -0,0 +1,141 @@
{
"name": "@redplanethq/core",
"version": "0.1.0",
"description": "A Command-Line Interface for Core",
"type": "module",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/redplanethq/core",
"directory": "packages/core-cli"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"typescript"
],
"files": [
"dist"
],
"bin": {
"core": "./dist/esm/index.js"
},
"tshy": {
"selfLink": false,
"main": false,
"module": false,
"dialects": [
"esm"
],
"project": "./tsconfig.json",
"exclude": [
"**/*.test.ts"
],
"exports": {
"./package.json": "./package.json",
".": "./src/index.ts"
}
},
"devDependencies": {
"@epic-web/test-server": "^0.1.0",
"@types/gradient-string": "^1.1.2",
"@types/ini": "^4.1.1",
"@types/object-hash": "3.0.6",
"@types/polka": "^0.5.7",
"@types/react": "^18.2.48",
"@types/resolve": "^1.20.6",
"@types/rimraf": "^4.0.5",
"@types/semver": "^7.5.0",
"@types/source-map-support": "0.5.10",
"@types/ws": "^8.5.3",
"cpy-cli": "^5.0.0",
"execa": "^8.0.1",
"find-up": "^7.0.0",
"rimraf": "^5.0.7",
"ts-essentials": "10.0.1",
"tshy": "^3.0.2",
"tsx": "4.17.0"
},
"scripts": {
"clean": "rimraf dist .tshy .tshy-build .turbo",
"typecheck": "tsc -p tsconfig.src.json --noEmit",
"build": "tshy && pnpm run update-version",
"dev": "tshy --watch",
"test": "vitest",
"test:e2e": "vitest --run -c ./e2e/vitest.config.ts",
"update-version": "tsx ../../scripts/updateVersion.ts"
},
"dependencies": {
"@clack/prompts": "^0.10.0",
"@depot/cli": "0.0.1-cli.2.80.0",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/api-logs": "0.52.1",
"@opentelemetry/exporter-logs-otlp-http": "0.52.1",
"@opentelemetry/exporter-trace-otlp-http": "0.52.1",
"@opentelemetry/instrumentation": "0.52.1",
"@opentelemetry/instrumentation-fetch": "0.52.1",
"@opentelemetry/resources": "1.25.1",
"@opentelemetry/sdk-logs": "0.52.1",
"@opentelemetry/sdk-node": "0.52.1",
"@opentelemetry/sdk-trace-base": "1.25.1",
"@opentelemetry/sdk-trace-node": "1.25.1",
"@opentelemetry/semantic-conventions": "1.25.1",
"ansi-escapes": "^7.0.0",
"braces": "^3.0.3",
"c12": "^1.11.1",
"chalk": "^5.2.0",
"chokidar": "^3.6.0",
"cli-table3": "^0.6.3",
"commander": "^9.4.1",
"defu": "^6.1.4",
"dotenv": "^16.4.5",
"esbuild": "^0.23.0",
"eventsource": "^3.0.2",
"evt": "^2.4.13",
"fast-npm-meta": "^0.2.2",
"git-last-commit": "^1.0.1",
"gradient-string": "^2.0.2",
"has-flag": "^5.0.1",
"import-in-the-middle": "1.11.0",
"import-meta-resolve": "^4.1.0",
"ini": "^5.0.0",
"jsonc-parser": "3.2.1",
"magicast": "^0.3.4",
"minimatch": "^10.0.1",
"mlly": "^1.7.1",
"nypm": "^0.5.4",
"object-hash": "^3.0.0",
"open": "^10.0.3",
"p-limit": "^6.2.0",
"p-retry": "^6.1.0",
"partysocket": "^1.0.2",
"pkg-types": "^1.1.3",
"polka": "^0.5.2",
"resolve": "^1.22.8",
"semver": "^7.5.0",
"signal-exit": "^4.1.0",
"source-map-support": "0.5.21",
"std-env": "^3.7.0",
"supports-color": "^10.0.0",
"tiny-invariant": "^1.2.0",
"tinyexec": "^0.3.1",
"tinyglobby": "^0.2.10",
"ws": "^8.18.0",
"xdg-app-paths": "^8.3.0",
"zod": "3.23.8",
"zod-validation-error": "^1.5.0"
},
"engines": {
"node": ">=18.20.0"
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
}
}
}
}

View File

@ -0,0 +1,70 @@
import { Command } from "commander";
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import { logger } from "../utils/logger.js";
import { outro } from "@clack/prompts";
import { chalkError } from "../utils/cliOutput.js";
export const CommonCommandOptions = z.object({
logLevel: z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log"),
});
export type CommonCommandOptions = z.infer<typeof CommonCommandOptions>;
export function commonOptions(command: Command) {
return command.option(
"-l, --log-level <level>",
"The CLI log level to use (debug, info, log, warn, error, none).",
"log"
);
}
export class SkipLoggingError extends Error {}
export class SkipCommandError extends Error {}
export class OutroCommandError extends SkipCommandError {}
export async function wrapCommandAction<T extends z.AnyZodObject, TResult>(
name: string,
schema: T,
options: unknown,
action: (opts: z.output<T>) => Promise<TResult>
): Promise<TResult | undefined> {
try {
const parsedOptions = schema.safeParse(options);
if (!parsedOptions.success) {
throw new Error(fromZodError(parsedOptions.error).toString());
}
logger.loggerLevel = parsedOptions.data.logLevel;
logger.debug(`Running "${name}" with the following options`, {
options: options,
});
const result = await action(parsedOptions.data);
return result;
} catch (e) {
if (e instanceof SkipLoggingError) {
} else if (e instanceof OutroCommandError) {
outro("Operation cancelled");
} else if (e instanceof SkipCommandError) {
// do nothing
} else {
logger.log(`${chalkError("X Error:")} ${e instanceof Error ? e.message : String(e)}`);
}
throw e;
}
}
export function installExitHandler() {
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});
}

View File

@ -0,0 +1 @@
import { Command } from "commander";

View File

View File

View File

@ -0,0 +1,145 @@
import { log } from "@clack/prompts";
import chalk from "chalk";
import { terminalLink, TerminalLinkOptions } from "./terminalLink.js";
import { hasTTY } from "std-env";
export const isInteractive = hasTTY;
export const isLinksSupported = terminalLink.isSupported;
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;
}
export function prettyError(header: string, body?: string, footer?: string) {
const prefix = "Error: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkError(prefix);
const withIndents = (text?: string) =>
text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body?.trim());
const prettyFooter = withIndents(footer);
log.error(
`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${
prettyFooter ? `${spacing}${prettyFooter}` : ""
}`
);
}
export function prettyWarning(header: string, body?: string, footer?: string) {
const prefix = "Warning: ";
const indent = Array(prefix.length).fill(" ").join("");
const spacing = "\n\n";
const prettyPrefix = chalkWarning(prefix);
const withIndents = (text?: string) =>
text
?.split("\n")
.map((line) => `${indent}${line}`)
.join("\n");
const prettyBody = withIndents(body);
const prettyFooter = withIndents(footer);
log.warn(
`${prettyPrefix}${header}${prettyBody ? `${spacing}${prettyBody}` : ""}${
prettyFooter ? `${spacing}${prettyFooter}` : ""
}`
);
}
export function aiHelpLink({
dashboardUrl,
project,
query,
}: {
dashboardUrl: string;
project: string;
query: string;
}) {
const searchParams = new URLSearchParams();
//the max length for a URL is 1950 characters
const clippedQuery = query.slice(0, 1950);
searchParams.set("q", clippedQuery);
const url = new URL(`/projects/${project}/ai-help`, dashboardUrl);
url.search = searchParams.toString();
log.message(chalkLink(cliLink("💡 Get a fix for this error using AI", url.toString())));
}
export function cliLink(text: string, url: string, options?: TerminalLinkOptions) {
return terminalLink(text, url, {
fallback: (text, url) => `${text} ${url}`,
...options,
});
}

View File

@ -0,0 +1,128 @@
// This is a copy of the logger utility from the wrangler repo: https://github.com/cloudflare/workers-sdk/blob/main/packages/wrangler/src/logger.ts
import { format } from "node:util";
import chalk from "chalk";
import CLITable from "cli-table3";
import { formatMessagesSync } from "esbuild";
import type { Message } from "esbuild";
import { env } from "std-env";
export const LOGGER_LEVELS = {
none: -1,
error: 0,
warn: 1,
info: 2,
log: 3,
debug: 4,
} as const;
export type LoggerLevel = keyof typeof LOGGER_LEVELS;
/** A map from LOGGER_LEVEL to the error `kind` needed by `formatMessagesSync()`. */
const LOGGER_LEVEL_FORMAT_TYPE_MAP = {
error: "error",
warn: "warning",
info: undefined,
log: undefined,
debug: undefined,
} as const;
function getLoggerLevel(): LoggerLevel {
const fromEnv = env.TRIGGER_LOG_LEVEL?.toLowerCase();
if (fromEnv !== undefined) {
if (fromEnv in LOGGER_LEVELS) return fromEnv as LoggerLevel;
const expected = Object.keys(LOGGER_LEVELS)
.map((level) => `"${level}"`)
.join(" | ");
console.warn(
`Unrecognised TRIGGER_LOG_LEVEL value ${JSON.stringify(
fromEnv
)}, expected ${expected}, defaulting to "log"...`
);
}
return "log";
}
export type TableRow<Keys extends string> = Record<Keys, string>;
export class Logger {
constructor() {}
loggerLevel = getLoggerLevel();
columns = process.stdout.columns;
debug = (...args: unknown[]) => this.doLog("debug", args);
ignore = (...args: unknown[]) => {};
debugWithSanitization = (label: string, ...args: unknown[]) => {
this.doLog("debug", [label, ...args]);
};
info = (...args: unknown[]) => this.doLog("info", args);
log = (...args: unknown[]) => this.doLog("log", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
warn = (...args: unknown[]) => this.doLog("warn", args);
/** @deprecated **ONLY USE THIS IN THE CLI** - It will hang the process when used in deployed code (!) */
error = (...args: unknown[]) => this.doLog("error", args);
table<Keys extends string>(data: TableRow<Keys>[], level?: Exclude<LoggerLevel, "none">) {
const keys: Keys[] = data.length === 0 ? [] : (Object.keys(data[0]!) as Keys[]);
const t = new CLITable({
head: keys,
style: {
head: chalk.level ? ["blue"] : [],
border: chalk.level ? ["gray"] : [],
},
});
t.push(...data.map((row) => keys.map((k) => row[k])));
return this.doLog(level ?? "log", [t.toString()]);
}
private doLog(messageLevel: Exclude<LoggerLevel, "none">, args: unknown[]) {
const message = this.formatMessage(messageLevel, format(...args));
// only send logs to the terminal if their level is at least the configured log-level
if (LOGGER_LEVELS[this.loggerLevel] >= LOGGER_LEVELS[messageLevel]) {
console[messageLevel](message);
}
}
private formatMessage(level: Exclude<LoggerLevel, "none">, message: string): string {
const kind = LOGGER_LEVEL_FORMAT_TYPE_MAP[level];
if (kind) {
// Format the message using the esbuild formatter.
// The first line of the message is the main `text`,
// subsequent lines are put into the `notes`.
const [firstLine, ...otherLines] = message.split("\n");
const notes = otherLines.length > 0 ? otherLines.map((text) => ({ text })) : undefined;
return formatMessagesSync([{ text: firstLine, notes }], {
color: true,
kind,
terminalWidth: this.columns,
})[0]!;
} else {
return message;
}
}
}
/**
* A drop-in replacement for `console` for outputting logging messages.
*
* Errors and Warnings will get additional formatting to highlight them to the user.
* You can also set a `logger.loggerLevel` value to one of "debug", "log", "warn" or "error",
* to filter out logging messages.
*/
export const logger = new Logger();
export function logBuildWarnings(warnings: Message[]) {
const logs = formatMessagesSync(warnings, { kind: "warning", color: true });
for (const log of logs) console.warn(log);
}
/**
* Logs all errors/warnings associated with an esbuild BuildFailure in the same
* style esbuild would.
*/
export function logBuildFailure(errors: Message[], warnings: Message[]) {
const logs = formatMessagesSync(errors, { kind: "error", color: true });
for (const log of logs) console.error(log);
logBuildWarnings(warnings);
}

View File

@ -0,0 +1,160 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Copyright (c) James Talmage <james@talmage.io> (https://github.com/jamestalmage)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { createSupportsColor } from "supports-color";
import hasFlag from "has-flag";
function parseVersion(versionString = ""): { major: number; minor: number; patch: number } {
if (/^\d{3,4}$/.test(versionString)) {
// Env var doesn't always use dots. example: 4601 => 46.1.0
const match = /(\d{1,2})(\d{2})/.exec(versionString) ?? [];
return {
major: 0,
minor: Number.parseInt(match[1] ?? "0", 10),
patch: Number.parseInt(match[2] ?? "0", 10),
};
}
const versions = (versionString ?? "").split(".").map((n) => Number.parseInt(n, 10));
return {
major: versions[0] ?? 0,
minor: versions[1] ?? 0,
patch: versions[2] ?? 0,
};
}
/**
Creates a supports hyperlinks check for a given stream.
@param stream - Optional stream to check for hyperlink support.
@returns boolean indicating whether hyperlinks are supported.
*/
export function createSupportsHyperlinks(stream: NodeJS.WriteStream): boolean {
const {
CI,
CURSOR_TRACE_ID,
FORCE_HYPERLINK,
NETLIFY,
TEAMCITY_VERSION,
TERM_PROGRAM,
TERM_PROGRAM_VERSION,
VTE_VERSION,
TERM,
} = process.env;
if (FORCE_HYPERLINK) {
return !(FORCE_HYPERLINK.length > 0 && Number.parseInt(FORCE_HYPERLINK, 10) === 0);
}
if (
hasFlag("no-hyperlink") ||
hasFlag("no-hyperlinks") ||
hasFlag("hyperlink=false") ||
hasFlag("hyperlink=never")
) {
return false;
}
if (hasFlag("hyperlink=true") || hasFlag("hyperlink=always")) {
return true;
}
// Netlify does not run a TTY, it does not need `supportsColor` check
if (NETLIFY) {
return true;
}
// If they specify no colors, they probably don't want hyperlinks.
if (!createSupportsColor(stream)) {
return false;
}
if (stream && !stream.isTTY) {
return false;
}
// Windows Terminal
if ("WT_SESSION" in process.env) {
return true;
}
if (process.platform === "win32") {
return false;
}
if (CI) {
return false;
}
if (TEAMCITY_VERSION) {
return false;
}
if (CURSOR_TRACE_ID) {
return true;
}
if (TERM_PROGRAM) {
const version = parseVersion(TERM_PROGRAM_VERSION);
switch (TERM_PROGRAM) {
case "iTerm.app": {
if (version.major === 3) {
return version.minor >= 1;
}
return version.major > 3;
}
case "WezTerm": {
return version.major >= 20_200_620;
}
case "vscode": {
// eslint-disable-next-line no-mixed-operators
return version.major > 1 || (version.major === 1 && version.minor >= 72);
}
case "ghostty": {
return true;
}
// No default
}
}
if (VTE_VERSION) {
// 0.50.0 was supposed to support hyperlinks, but throws a segfault
if (VTE_VERSION === "0.50.0") {
return false;
}
const version = parseVersion(VTE_VERSION);
return version.major > 0 || version.minor >= 50;
}
switch (TERM) {
case "alacritty": {
// Support added in v0.11 (2022-10-13)
return true;
}
// No default
}
return false;
}
/** Object containing hyperlink support status for stdout and stderr. */
const supportsHyperlinks = {
/** Whether stdout supports hyperlinks. */
stdout: createSupportsHyperlinks(process.stdout),
/** Whether stderr supports hyperlinks. */
stderr: createSupportsHyperlinks(process.stderr),
};
export default supportsHyperlinks;

View File

@ -0,0 +1,94 @@
/*
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import ansiEscapes from "ansi-escapes";
import supportsHyperlinks from "./supportsHyperlinks.js";
export type TerminalLinkOptions = {
/**
Override the default fallback. If false, the fallback will be disabled.
@default `${text} (${url})`
*/
readonly fallback?: ((text: string, url: string) => string) | boolean;
};
/**
Create a clickable link in the terminal's stdout.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`,
unless the fallback is disabled by setting the `fallback` option to `false`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink('My Website', 'https://sindresorhus.com');
console.log(link);
```
@deprecated The default fallback is broken in some terminals. Please use `cliLink` instead.
*/
function terminalLink(
text: string,
url: string,
{ target = "stdout", ...options }: { target?: "stdout" | "stderr" } & TerminalLinkOptions = {}
) {
if (!supportsHyperlinks[target]) {
// If the fallback has been explicitly disabled, don't modify the text itself.
if (options.fallback === false) {
return text;
}
return typeof options.fallback === "function"
? options.fallback(text, url)
: `${text} (\u200B${url}\u200B)`;
}
return ansiEscapes.link(text, url);
}
/**
Check whether the terminal supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLink.isSupported = supportsHyperlinks.stdout;
terminalLink.stderr = terminalLinkStderr;
/**
Create a clickable link in the terminal's stderr.
[Supported terminals.](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
For unsupported terminals, the link will be printed in parens after the text: `My website (https://sindresorhus.com)`.
@param text - Text to linkify.
@param url - URL to link to.
@example
```
import terminalLink from 'terminal-link';
const link = terminalLink.stderr('My Website', 'https://sindresorhus.com');
console.error(link);
```
*/
function terminalLinkStderr(text: string, url: string, options: TerminalLinkOptions = {}) {
return terminalLink(text, url, { target: "stderr", ...options });
}
/**
Check whether the terminal's stderr supports links.
Prefer just using the default fallback or the `fallback` option whenever possible.
*/
terminalLinkStderr.isSupported = supportsHyperlinks.stderr;
export { terminalLink };

View File

@ -0,0 +1,40 @@
{
"include": ["./src/**/*.ts"],
"exclude": ["./src/**/*.test.ts"],
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable", "DOM.AsyncIterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true,
"isolatedDeclarations": false,
"composite": true,
"sourceMap": true
}
}

View File

@ -0,0 +1,8 @@
import { configDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
exclude: [...configDefaults.exclude, "e2e/**/*"],
},
});

1734
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,10 @@
"trigger:dev": {
"interactive": true,
"cache": false
},
"trigger:deploy": {
"interactive": true,
"cache": false
}
},
"globalDependencies": [ ".env" ],