Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92ee40afd5 | |||
| 61c5cfc4d6 | |||
| d38d420ac6 | |||
| 473b603763 | |||
| b32a1ae501 | |||
| 6ffdbc2489 | |||
| e24e0da52a | |||
| 826cbaebc1 | |||
| 86ddd0b053 | |||
| 9e4d8b2103 | |||
| 51f7466173 | |||
| 3d6a713e1f | |||
| 6acdc1297f | |||
| e01b5bac93 | |||
| c02954cd3c | |||
| 2d24b758d1 | |||
| 5fc5006543 | |||
| feba1c9381 | |||
| 6150efe52e | |||
| 8d1cc30c75 | |||
| fef7bbe031 | |||
| 224c7186d5 | |||
| 673d7cb6eb | |||
| cb6c54936e | |||
| 4fa3137872 | |||
| e7ecb43f02 | |||
| 47ac5b8df5 |
@@ -9,11 +9,10 @@ const Alert = AlertDialogPrimitive.Root;
|
||||
const AlertTrigger = AlertDialogPrimitive.Trigger;
|
||||
|
||||
const AlertPortal = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
|
||||
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
<AlertDialogPrimitive.Portal {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@ const Dialog = DialogPrimitive.Root;
|
||||
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
|
||||
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal className={cn(className)} {...props}>
|
||||
const DialogPortal = ({ children, ...props }: DialogPrimitive.DialogPortalProps) => (
|
||||
<DialogPrimitive.Portal {...props}>
|
||||
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,8 @@ interface SheetPortalProps
|
||||
extends SheetPrimitive.DialogPortalProps,
|
||||
VariantProps<typeof portalVariants> {}
|
||||
|
||||
const SheetPortal = ({ position, className, children, ...props }: SheetPortalProps) => (
|
||||
<SheetPrimitive.Portal className={cn(className)} {...props}>
|
||||
const SheetPortal = ({ position, children, ...props }: SheetPortalProps) => (
|
||||
<SheetPrimitive.Portal {...props}>
|
||||
<div className={portalVariants({ position })}>{children}</div>
|
||||
</SheetPrimitive.Portal>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { VirtualElement as IVirtualElement } from "@popperjs/core";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { usePopper } from "react-popper";
|
||||
import { useEvent } from "react-use";
|
||||
import useEvent from "react-use/esm/useEvent";
|
||||
import useLazyRef from "~/hooks/useLazyRef";
|
||||
|
||||
// Recharts 3.x will have portal support, but until then we're using this:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DisplayProperty, StyleName } from "@trigger.dev/core";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { motion } from "framer-motion";
|
||||
import { HourglassIcon } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
RunPanelIconProperty,
|
||||
RunPanelIconSection,
|
||||
} from "./RunCard";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
export function RunCompletedDetail({ run }: { run: MatchedRun }) {
|
||||
return (
|
||||
|
||||
@@ -51,7 +51,7 @@ import {
|
||||
} from "./RunCard";
|
||||
import { TaskCard } from "./TaskCard";
|
||||
import { TaskCardSkeleton } from "./TaskCardSkeleton";
|
||||
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
type RunOverviewProps = {
|
||||
run: ViewRun;
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
UpdatingDuration,
|
||||
} from "./RunCard";
|
||||
import { TaskStatusIcon } from "./TaskStatus";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
type TaskCardProps = ViewTask & {
|
||||
selectedId?: string;
|
||||
|
||||
@@ -30,7 +30,7 @@ import { TaskStatusIcon } from "./TaskStatus";
|
||||
import { ClientOnly } from "remix-utils/client-only";
|
||||
import { Spinner } from "../primitives/Spinner";
|
||||
import type { DetailedTask } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam.tasks.$taskParam/route";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
export function TaskDetail({ task }: { task: DetailedTask }) {
|
||||
const {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
RunPanelIconSection,
|
||||
RunPanelProperties,
|
||||
} from "./RunCard";
|
||||
import { DisplayProperty } from "@trigger.dev/core";
|
||||
import type { DisplayProperty } from "@trigger.dev/core";
|
||||
|
||||
export function TriggerDetail({
|
||||
trigger,
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TableRow,
|
||||
} from "../primitives/Table";
|
||||
import { RunStatus } from "./RunStatuses";
|
||||
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
type RunTableItem = {
|
||||
id: string;
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
TableRow,
|
||||
} from "../primitives/Table";
|
||||
import { RunStatus } from "./RunStatuses";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
|
||||
type RunTableItem = {
|
||||
id: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function LiveTimer({
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
isExceptionSpanEvent,
|
||||
type ExceptionEventProperties,
|
||||
type SpanEvent as OtelSpanEvent,
|
||||
} from "@trigger.dev/core/v3";
|
||||
} from "@trigger.dev/core/v3/schemas";
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { Callout } from "~/components/primitives/Callout";
|
||||
import { DateTimeAccurate } from "~/components/primitives/DateTime";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChevronRightIcon } from "@heroicons/react/20/solid";
|
||||
import { TaskEventStyle } from "@trigger.dev/core/v3";
|
||||
import type { TaskEventStyle } from "@trigger.dev/core/v3";
|
||||
import type { TaskEventLevel } from "@trigger.dev/database";
|
||||
import { Fragment } from "react";
|
||||
import { RunEvent } from "~/presenters/v3/RunPresenter.server";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ArrowPathIcon, StopCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { StopIcon } from "@heroicons/react/24/outline";
|
||||
import { BeakerIcon, BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { useLocation } from "@remix-run/react";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { formatDuration } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { Button, LinkButton } from "~/components/primitives/Buttons";
|
||||
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
|
||||
import { useEnvironments } from "~/hooks/useEnvironments";
|
||||
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
OperatingSystemContextProvider,
|
||||
OperatingSystemPlatform,
|
||||
} from "./components/primitives/OperatingSystemProvider";
|
||||
import { getSharedSqsEventConsumer } from "./services/events/sqsEventConsumer";
|
||||
import { singleton } from "./utils/singleton";
|
||||
import { logger } from "./services/logger.server";
|
||||
|
||||
import { initializeWebSocketServer } from "./v3/handleWebsockets.server";
|
||||
initializeWebSocketServer();
|
||||
|
||||
const ABORT_DELAY = 30000;
|
||||
|
||||
@@ -180,9 +180,4 @@ function logError(error: unknown, request?: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
const sqsEventConsumer = singleton("sqsEventConsumer", getSharedSqsEventConsumer);
|
||||
|
||||
export { apiRateLimiter } from "./services/apiRateLimit.server";
|
||||
export { socketIo } from "./v3/handleSocketIo.server";
|
||||
export { wss } from "./v3/handleWebsockets.server";
|
||||
export { registryProxy } from "./v3/registryProxy.server";
|
||||
export { express } from "./express.server";
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { createExpressApp } from "remix-create-express-app";
|
||||
import http from "http";
|
||||
import morgan from "morgan";
|
||||
import compression from "compression";
|
||||
import { registryProxy } from "./v3/registryProxy.server";
|
||||
import { apiRateLimiter } from "./services/apiRateLimit.server";
|
||||
import { registerSocketIo } from "./socket.server";
|
||||
|
||||
export const express = createExpressApp({
|
||||
configure(app) {
|
||||
if (process.env.DISABLE_COMPRESSION !== "1") {
|
||||
app.use(compression());
|
||||
}
|
||||
|
||||
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
|
||||
app.disable("x-powered-by");
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
process.title = "node webapp-server";
|
||||
|
||||
if (!process.env.PORT) {
|
||||
// TODO: Test REMIX_APP_PORT
|
||||
process.env.PORT = process.env.REMIX_APP_PORT || "3000";
|
||||
}
|
||||
|
||||
if (process.env.HTTP_SERVER_DISABLED !== "true") {
|
||||
if (registryProxy && process.env.ENABLE_REGISTRY_PROXY === "true") {
|
||||
console.log(`🐳 Enabling container registry proxy to ${registryProxy.origin}`);
|
||||
|
||||
// Adjusted to match /v2 and any subpath under /v2
|
||||
app.all("/v2/*", async (req, res) => {
|
||||
await registryProxy?.call(req, res);
|
||||
});
|
||||
|
||||
// This might also be necessary if you need to explicitly match /v2 as well
|
||||
app.all("/v2", async (req, res) => {
|
||||
await registryProxy?.call(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// helpful headers:
|
||||
res.set("Strict-Transport-Security", `max-age=${60 * 60 * 24 * 365 * 100}`);
|
||||
|
||||
// /clean-urls/ -> /clean-urls
|
||||
if (req.path.endsWith("/") && req.path.length > 1) {
|
||||
const query = req.url.slice(req.path.length);
|
||||
const safepath = req.path.slice(0, -1).replace(/\/+/g, "/");
|
||||
res.redirect(301, safepath + query);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
app.use(apiRateLimiter);
|
||||
},
|
||||
createServer(app) {
|
||||
const server = http.createServer(app);
|
||||
|
||||
registerSocketIo(server);
|
||||
|
||||
return server;
|
||||
},
|
||||
// TODO: MISSING if (process.env.DASHBOARD_AND_API_DISABLED !== "true") {
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ExternalAccount, Integration, TriggerSource } from "@trigger.dev/database";
|
||||
import { ConnectionAuth } from "@trigger.dev/core";
|
||||
import type { ConnectionAuth } from "@trigger.dev/core";
|
||||
import { PrismaClientOrTransaction } from "~/db.server";
|
||||
import { integrationAuthRepository } from "~/services/externalApis/integrationAuthRepository.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { JobRun, Task, TaskAttempt } from "@trigger.dev/database";
|
||||
import { CachedTask, ServerTask } from "@trigger.dev/core";
|
||||
import type { CachedTask, ServerTask } from "@trigger.dev/core";
|
||||
import type { Task, TaskAttempt } from "@trigger.dev/database";
|
||||
|
||||
export type TaskWithAttempts = Task & {
|
||||
attempts: TaskAttempt[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { millisecondsToNanoseconds } from "@trigger.dev/core/v3";
|
||||
import { millisecondsToNanoseconds } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { getUsername } from "~/utils/username";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { LinksFunction, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
|
||||
import type { ShouldRevalidateFunction } from "@remix-run/react";
|
||||
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
|
||||
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react";
|
||||
import { UseDataFunctionReturn, typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { ExternalScripts } from "remix-utils/external-scripts";
|
||||
import type { ToastMessage } from "~/models/message.server";
|
||||
import { commitSession, getSession } from "~/models/message.server";
|
||||
import tailwindStylesheetUrl from "~/tailwind.css";
|
||||
import tailwindStylesheetUrl from "~/tailwind.css?url";
|
||||
import { RouteErrorDisplay } from "./components/ErrorDisplay";
|
||||
import { HighlightInit } from "./components/HighlightInit";
|
||||
import { AppContainer, MainCenteredContainer } from "./components/layout/AppLayout";
|
||||
@@ -115,7 +115,6 @@ function App() {
|
||||
<ScrollRestoration />
|
||||
<ExternalScripts />
|
||||
<Scripts />
|
||||
<LiveReload />
|
||||
</body>
|
||||
</html>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CalendarDaysIcon, ReceiptRefundIcon } from "@heroicons/react/20/solid";
|
||||
import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { Outlet } from "@remix-run/react";
|
||||
import { ActiveSubscription } from "@trigger.dev/billing";
|
||||
import { formatDurationInDays } from "@trigger.dev/core/v3";
|
||||
import { formatDurationInDays } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
import { DateTime } from "~/components/primitives/DateTime";
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { ChatBubbleLeftRightIcon, ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/20/solid";
|
||||
import { useRevalidator } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { TaskRunStatus } from "@trigger.dev/database";
|
||||
import { Fragment, Suspense, useEffect, useState } from "react";
|
||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, TooltipProps } from "recharts";
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import {
|
||||
formatDurationMilliseconds,
|
||||
millisecondsToNanoseconds,
|
||||
nanosecondsToMilliseconds,
|
||||
} from "@trigger.dev/core/v3";
|
||||
} from "@trigger.dev/core/v3/utils/durations";
|
||||
import { RuntimeEnvironmentType } from "@trigger.dev/database";
|
||||
import { motion } from "framer-motion";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
+4
-4
@@ -3,10 +3,9 @@ import { parse } from "@conform-to/zod";
|
||||
import { CheckIcon, XMarkIcon } from "@heroicons/react/20/solid";
|
||||
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
|
||||
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import { parseExpression } from "cron-parser";
|
||||
import cronParser from "cron-parser";
|
||||
import cronstrue from "cronstrue";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
environmentTextClassName,
|
||||
environmentTitle,
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
TableRow,
|
||||
} from "~/components/primitives/Table";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { TimezoneList } from "~/components/scheduled/timezones";
|
||||
import { prisma } from "~/db.server";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
@@ -43,8 +43,8 @@ import { ProjectParamSchema, docsPath, v3SchedulesPath } from "~/utils/pathBuild
|
||||
import { CronPattern, UpsertSchedule } from "~/v3/schedules";
|
||||
import { UpsertTaskScheduleService } from "~/v3/services/upsertTaskSchedule.server";
|
||||
import { AIGeneratedCronField } from "../resources.orgs.$organizationSlug.projects.$projectParam.schedules.new.natural-language";
|
||||
import { TimezoneList } from "~/components/scheduled/timezones";
|
||||
|
||||
const { parseExpression } = cronParser;
|
||||
const cronFormat = `* * * * *
|
||||
┬ ┬ ┬ ┬ ┬
|
||||
│ │ │ │ |
|
||||
|
||||
+4
-1
@@ -6,7 +6,10 @@ import {
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { useParams } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { formatDurationNanoseconds, nanosecondsToMilliseconds } from "@trigger.dev/core/v3";
|
||||
import {
|
||||
formatDurationNanoseconds,
|
||||
nanosecondsToMilliseconds,
|
||||
} from "@trigger.dev/core/v3/utils/durations";
|
||||
import { useEffect } from "react";
|
||||
import { typedjson, useTypedFetcher } from "remix-typedjson";
|
||||
import { ExitIcon } from "~/assets/icons/ExitIcon";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Slider from "@radix-ui/react-slider";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3";
|
||||
import { formatDurationMilliseconds } from "@trigger.dev/core/v3/utils/durations";
|
||||
import { useState } from "react";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { Switch } from "~/components/primitives/Switch";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Prettify } from "@trigger.dev/core";
|
||||
import type { Prettify } from "@trigger.dev/core";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
RuntimeEnvironment,
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
ScheduleMetadataSchema,
|
||||
} from "@trigger.dev/core";
|
||||
import { $transaction, PrismaClientOrTransaction, prisma } from "~/db.server";
|
||||
import { parseExpression } from "cron-parser";
|
||||
import cronParser from "cron-parser";
|
||||
import { logger } from "../logger.server";
|
||||
import { DeliverScheduledEventService } from "./deliverScheduledEvent.server";
|
||||
|
||||
const { parseExpression } = cronParser;
|
||||
export class NextScheduledEventService {
|
||||
#prismaClient: PrismaClientOrTransaction;
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import type http from "http";
|
||||
import type { Server as EngineServer } from "engine.io";
|
||||
import { singleton } from "./utils/singleton";
|
||||
import { env } from "./env.server";
|
||||
import { Redis } from "ioredis";
|
||||
import { createAdapter } from "@socket.io/redis-adapter";
|
||||
import { Server } from "socket.io";
|
||||
import { WebSocketServer } from "ws";
|
||||
|
||||
// wss and io are used directly in express.server.ts without bundling
|
||||
// so don't import from /app here
|
||||
// entry.server.ts gets bundled, so see initializeWebSocketServer() there
|
||||
// to add extra handlers that register on first request
|
||||
|
||||
export const wss = singleton("wss", () => {
|
||||
return new WebSocketServer({ noServer: true });
|
||||
});
|
||||
|
||||
export const io = singleton("socketIo", () => {
|
||||
if (!env.REDIS_HOST || !env.REDIS_PORT) {
|
||||
console.warn("No redis config found, skipping socket.io");
|
||||
return new Server();
|
||||
}
|
||||
|
||||
const pubClient = new Redis({
|
||||
port: env.REDIS_PORT,
|
||||
host: env.REDIS_HOST,
|
||||
username: env.REDIS_USERNAME,
|
||||
password: env.REDIS_PASSWORD,
|
||||
enableAutoPipelining: true,
|
||||
...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }),
|
||||
});
|
||||
const subClient = pubClient.duplicate();
|
||||
|
||||
const io = new Server({
|
||||
adapter: createAdapter(pubClient, subClient, {
|
||||
key: "tr:socket.io:",
|
||||
publishOnSpecificResponseChannel: true,
|
||||
}),
|
||||
});
|
||||
|
||||
return io;
|
||||
});
|
||||
|
||||
export function registerSocketIo(server: http.Server) {
|
||||
server.keepAliveTimeout = 65 * 1000;
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
console.error("Error closing express server:", err);
|
||||
} else {
|
||||
console.log("Express server closed gracefully.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log(`[socket.io][${socket.id}] connection at url: ${socket.request.url}`);
|
||||
});
|
||||
|
||||
console.log("Attaching socket.io");
|
||||
|
||||
io.attach(server);
|
||||
// prevent duplicate upgrades from listeners created by io.attach()
|
||||
server.removeAllListeners("upgrade");
|
||||
|
||||
server.on("upgrade", async (req, socket, head) => {
|
||||
console.log(
|
||||
`Attemping to upgrade connection at url ${req.url} with headers: ${JSON.stringify(
|
||||
req.headers
|
||||
)}`
|
||||
);
|
||||
|
||||
socket.on("error", (err) => {
|
||||
console.error("Connection upgrade error:", err);
|
||||
});
|
||||
|
||||
const url = new URL(req.url ?? "", "http://localhost");
|
||||
|
||||
// Upgrade socket.io connection
|
||||
if (url.pathname.startsWith("/socket.io/")) {
|
||||
console.log(`Socket.io client connected, upgrading their connection...`);
|
||||
|
||||
// https://github.com/socketio/socket.io/issues/4693
|
||||
(io.engine as EngineServer).handleUpgrade(req, socket, head);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only upgrade the connecting if the path is `/ws`
|
||||
if (url.pathname !== "/ws") {
|
||||
// Setting the socket.destroy() error param causes an error event to be emitted which needs to be handled with socket.on("error") to prevent uncaught exceptions.
|
||||
socket.destroy(
|
||||
new Error(
|
||||
"Cannot connect because of invalid path: Please include `/ws` in the path of your upgrade request."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Client connected, upgrading their connection...`);
|
||||
|
||||
// Handle the WebSocket connection
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss.emit("connection", ws, req);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { Prisma, TaskEvent, TaskEventStatus, type TaskEventKind } from "@trigger.dev/database";
|
||||
import Redis, { RedisOptions } from "ioredis";
|
||||
import { createHash } from "node:crypto";
|
||||
import { EventEmitter } from "node:stream";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { $replica, PrismaClient, PrismaReplicaClient, prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
|
||||
@@ -781,6 +781,7 @@ export class EventRepository {
|
||||
const unsubscribe = async () => {
|
||||
await redis.punsubscribe(channel);
|
||||
redis.quit();
|
||||
eventEmitter.removeAllListeners();
|
||||
this._subscriberCount--;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,20 +19,13 @@ import { findEnvironmentById } from "~/models/runtimeEnvironment.server";
|
||||
import { CreateDeployedBackgroundWorkerService } from "./services/createDeployedBackgroundWorker.server";
|
||||
import { ResumeAttemptService } from "./services/resumeAttempt.server";
|
||||
import { DeploymentIndexFailed } from "./services/deploymentIndexFailed.server";
|
||||
import { Redis } from "ioredis";
|
||||
import { createAdapter } from "@socket.io/redis-adapter";
|
||||
import { CrashTaskRunService } from "./services/crashTaskRun.server";
|
||||
import { CreateTaskRunAttemptService } from "./services/createTaskRunAttempt.server";
|
||||
import { io } from "~/socket.server";
|
||||
|
||||
export const socketIo = singleton("socketIo", initalizeIoServer);
|
||||
|
||||
function initalizeIoServer() {
|
||||
const io = initializeSocketIOServerInstance();
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
logger.log(`[socket.io][${socket.id}] connection at url: ${socket.request.url}`);
|
||||
});
|
||||
|
||||
const coordinatorNamespace = createCoordinatorNamespace(io);
|
||||
const providerNamespace = createProviderNamespace(io);
|
||||
const sharedQueueConsumerNamespace = createSharedQueueConsumerNamespace(io);
|
||||
@@ -45,31 +38,6 @@ function initalizeIoServer() {
|
||||
};
|
||||
}
|
||||
|
||||
function initializeSocketIOServerInstance() {
|
||||
if (env.REDIS_HOST && env.REDIS_PORT) {
|
||||
const pubClient = new Redis({
|
||||
port: env.REDIS_PORT,
|
||||
host: env.REDIS_HOST,
|
||||
username: env.REDIS_USERNAME,
|
||||
password: env.REDIS_PASSWORD,
|
||||
enableAutoPipelining: true,
|
||||
...(env.REDIS_TLS_DISABLED === "true" ? {} : { tls: {} }),
|
||||
});
|
||||
const subClient = pubClient.duplicate();
|
||||
|
||||
const io = new Server({
|
||||
adapter: createAdapter(pubClient, subClient, {
|
||||
key: "tr:socket.io:",
|
||||
publishOnSpecificResponseChannel: true,
|
||||
}),
|
||||
});
|
||||
|
||||
return io;
|
||||
}
|
||||
|
||||
return new Server();
|
||||
}
|
||||
|
||||
function createCoordinatorNamespace(io: Server) {
|
||||
const coordinator = new ZodNamespace({
|
||||
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
|
||||
|
||||
@@ -7,27 +7,26 @@ import { AuthenticatedSocketConnection } from "./authenticatedSocketConnection.s
|
||||
import { Gauge } from "prom-client";
|
||||
import { metricsRegister } from "~/metrics.server";
|
||||
|
||||
export const wss = singleton("wss", initalizeWebSocketServer);
|
||||
|
||||
import { wss } from "~/socket.server";
|
||||
let authenticatedConnections: Map<string, AuthenticatedSocketConnection>;
|
||||
|
||||
function initalizeWebSocketServer() {
|
||||
const server = new WebSocketServer({ noServer: true });
|
||||
export function initializeWebSocketServer() {
|
||||
return singleton("wss:register", () => {
|
||||
wss.on("connection", handleWebSocketConnection);
|
||||
|
||||
server.on("connection", handleWebSocketConnection);
|
||||
authenticatedConnections = new Map();
|
||||
|
||||
authenticatedConnections = new Map();
|
||||
new Gauge({
|
||||
name: "dev_authenticated_connections",
|
||||
help: "Number of authenticated dev connections",
|
||||
collect() {
|
||||
this.set(authenticatedConnections.size);
|
||||
},
|
||||
registers: [metricsRegister],
|
||||
});
|
||||
|
||||
new Gauge({
|
||||
name: "dev_authenticated_connections",
|
||||
help: "Number of authenticated dev connections",
|
||||
collect() {
|
||||
this.set(authenticatedConnections.size);
|
||||
},
|
||||
registers: [metricsRegister],
|
||||
return wss;
|
||||
});
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function handleWebSocketConnection(ws: WebSocket, req: IncomingMessage) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { parseExpression } from "cron-parser";
|
||||
import cronParser from "cron-parser";
|
||||
import { z } from "zod";
|
||||
|
||||
const { parseExpression } = cronParser;
|
||||
export const CronPattern = z.string().refine(
|
||||
(val) => {
|
||||
//only allow CRON expressions that don't include seconds (they have 5 parts)
|
||||
|
||||
@@ -24,12 +24,13 @@ import {
|
||||
} from "@opentelemetry/sdk-trace-base";
|
||||
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
||||
import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
||||
import { PrismaInstrumentation } from "@prisma/instrumentation";
|
||||
import prismaInstrumentation from "@prisma/instrumentation";
|
||||
import { env } from "~/env.server";
|
||||
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
|
||||
import { singleton } from "~/utils/singleton";
|
||||
import { LoggerSpanExporter } from "./telemetry/loggerExporter.server";
|
||||
|
||||
const { PrismaInstrumentation } = prismaInstrumentation;
|
||||
export const SEMINTATTRS_FORCE_RECORDING = "forceRecording";
|
||||
|
||||
class CustomWebappSampler implements Sampler {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { parseExpression } from "cron-parser";
|
||||
import cronParser from "cron-parser";
|
||||
|
||||
const { parseExpression } = cronParser;
|
||||
export function calculateNextScheduledTimestamp(
|
||||
schedule: string,
|
||||
timezone: string | null,
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="@remix-run/node" />
|
||||
+35
-24
@@ -3,12 +3,12 @@
|
||||
"name": "webapp",
|
||||
"version": "1.0.0",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "run-s build:**",
|
||||
"build:db:seed": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/seed.ts --outdir=prisma",
|
||||
"build:remix": "remix build",
|
||||
"build:server": "esbuild --platform=node --format=cjs ./server.ts --outdir=build",
|
||||
"dev": "cross-env PORT=3030 remix dev -c \"node ./build/server.js\"",
|
||||
"build:db:seed": "esbuild --platform=node --bundle --minify --format=esm ./prisma/seed.ts --outdir=prisma",
|
||||
"build:remix": "remix vite:build",
|
||||
"dev": "cross-env PORT=3030 vite",
|
||||
"dev:worker": "cross-env NODE_PATH=../../node_modules/.pnpm/node_modules node ./build/server.js",
|
||||
"format": "prettier --write .",
|
||||
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
||||
@@ -17,7 +17,7 @@
|
||||
"typecheck": "tsc -p ./tsconfig.check.json",
|
||||
"db:seed": "node prisma/seed.js",
|
||||
"db:seed:local": "ts-node prisma/seed.ts",
|
||||
"build:db:populate": "esbuild --platform=node --bundle --minify --format=cjs ./prisma/populate.ts --outdir=prisma",
|
||||
"build:db:populate": "esbuild --platform=node --bundle --minify --format=esm ./prisma/populate.ts --outdir=prisma",
|
||||
"db:populate": "node prisma/populate.js --",
|
||||
"generate:sourcemaps": "remix build --sourcemap",
|
||||
"clean:sourcemaps": "run-s clean:sourcemaps:*",
|
||||
@@ -43,6 +43,7 @@
|
||||
"@codemirror/search": "^6.2.3",
|
||||
"@codemirror/state": "^6.1.3",
|
||||
"@codemirror/view": "^6.5.0",
|
||||
"@conform-to/dom": "0.6.1",
|
||||
"@conform-to/react": "^0.6.1",
|
||||
"@conform-to/zod": "^0.6.1",
|
||||
"@depot/sdk-node": "^0.5.0",
|
||||
@@ -65,25 +66,25 @@
|
||||
"@opentelemetry/semantic-conventions": "^1.22.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@prisma/instrumentation": "^5.11.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.3",
|
||||
"@radix-ui/react-label": "^2.0.1",
|
||||
"@radix-ui/react-popover": "^1.0.5",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-select": "^1.2.1",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slider": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.5",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@react-aria/datepicker": "^3.9.1",
|
||||
"@react-stately/datepicker": "^3.9.1",
|
||||
"@react-types/datepicker": "^3.7.1",
|
||||
"@remix-run/express": "2.1.0",
|
||||
"@remix-run/node": "2.1.0",
|
||||
"@remix-run/react": "2.1.0",
|
||||
"@remix-run/router": "^1.15.3",
|
||||
"@remix-run/serve": "2.1.0",
|
||||
"@remix-run/server-runtime": "2.1.0",
|
||||
"@remix-run/express": "2.9.2",
|
||||
"@remix-run/node": "2.9.2",
|
||||
"@remix-run/react": "2.9.2",
|
||||
"@remix-run/router": "^1.16.1",
|
||||
"@remix-run/serve": "2.9.2",
|
||||
"@remix-run/server-runtime": "2.9.2",
|
||||
"@remix-run/v1-meta": "^0.1.3",
|
||||
"@slack/web-api": "^6.8.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -108,11 +109,13 @@
|
||||
"class-variance-authority": "^0.5.2",
|
||||
"clsx": "^1.2.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie": "^0.6.0",
|
||||
"cron-parser": "^4.9.0",
|
||||
"cronstrue": "^2.21.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cuid": "^2.1.8",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-expand": "^11.0.6",
|
||||
"emails": "workspace:*",
|
||||
"evt": "^2.4.13",
|
||||
"express": "^4.18.1",
|
||||
@@ -124,6 +127,7 @@
|
||||
"ioredis": "^5.3.2",
|
||||
"isbot": "^3.6.5",
|
||||
"jose": "^5.4.0",
|
||||
"js-beautify": "^1.15.1",
|
||||
"jsonpointer": "^5.0.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash.omit": "^4.5.0",
|
||||
@@ -155,10 +159,12 @@
|
||||
"remix-auth": "^3.6.0",
|
||||
"remix-auth-email-link": "2.0.2",
|
||||
"remix-auth-github": "^1.6.0",
|
||||
"remix-create-express-app": "^0.3.7",
|
||||
"remix-typedjson": "0.3.1",
|
||||
"remix-utils": "^7.1.0",
|
||||
"seedrandom": "^3.0.5",
|
||||
"semver": "^7.5.0",
|
||||
"set-cookie-parser": "^2.6.0",
|
||||
"simple-oauth2": "^5.0.0",
|
||||
"simplur": "^3.0.1",
|
||||
"slug": "^6.0.0",
|
||||
@@ -174,15 +180,16 @@
|
||||
"tiny-invariant": "^1.2.0",
|
||||
"ulid": "^2.3.0",
|
||||
"ulidx": "^2.2.1",
|
||||
"vite-env-only": "^3.0.1",
|
||||
"ws": "^8.11.0",
|
||||
"zod": "3.22.3",
|
||||
"zod-error": "1.5.0",
|
||||
"zod-validation-error": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "2.1.0",
|
||||
"@remix-run/eslint-config": "2.1.0",
|
||||
"@remix-run/testing": "^2.1.0",
|
||||
"@remix-run/dev": "2.9.2",
|
||||
"@remix-run/eslint-config": "2.9.2",
|
||||
"@remix-run/testing": "^2.9.2",
|
||||
"@swc/core": "^1.3.4",
|
||||
"@swc/helpers": "^0.4.11",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
@@ -217,8 +224,9 @@
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"css-loader": "^6.10.0",
|
||||
"datepicker": "link:@types/@react-aria/datepicker",
|
||||
"deepmerge": "^4.3.1",
|
||||
"engine.io": "^6.5.4",
|
||||
"esbuild": "^0.15.10",
|
||||
"esbuild": "^0.15.18",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -227,16 +235,19 @@
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"remix-express-dev-server": "^0.2.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"style-loader": "^3.3.4",
|
||||
"tailwind-scrollbar": "^3.0.1",
|
||||
"tailwindcss": "3.4.1",
|
||||
"ts-node": "^10.7.0",
|
||||
"tsconfig-paths": "^3.14.1",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.3.1",
|
||||
"vite-tsconfig-paths": "^4.0.5",
|
||||
"vitest": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
/// <reference types="@remix-run/dev" />
|
||||
/// <reference types="@remix-run/node/globals" />
|
||||
@@ -1,159 +0,0 @@
|
||||
import path from "path";
|
||||
import express from "express";
|
||||
import compression from "compression";
|
||||
import morgan from "morgan";
|
||||
import { createRequestHandler } from "@remix-run/express";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { broadcastDevReady, logDevReady } from "@remix-run/server-runtime";
|
||||
import type { Server as IoServer } from "socket.io";
|
||||
import type { Server as EngineServer } from "engine.io";
|
||||
import { RegistryProxy } from "~/v3/registryProxy.server";
|
||||
import { RateLimitMiddleware, apiRateLimiter } from "~/services/apiRateLimit.server";
|
||||
|
||||
const app = express();
|
||||
|
||||
if (process.env.DISABLE_COMPRESSION !== "1") {
|
||||
app.use(compression());
|
||||
}
|
||||
|
||||
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
|
||||
app.disable("x-powered-by");
|
||||
|
||||
// Remix fingerprints its assets so we can cache forever.
|
||||
app.use("/build", express.static("public/build", { immutable: true, maxAge: "1y" }));
|
||||
|
||||
// Everything else (like favicon.ico) is cached for an hour. You may want to be
|
||||
// more aggressive with this caching.
|
||||
app.use(express.static("public", { maxAge: "1h" }));
|
||||
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
process.title = "node webapp-server";
|
||||
|
||||
const MODE = process.env.NODE_ENV;
|
||||
const BUILD_DIR = path.join(process.cwd(), "build");
|
||||
const build = require(BUILD_DIR);
|
||||
|
||||
const port = process.env.REMIX_APP_PORT || process.env.PORT || 3000;
|
||||
|
||||
if (process.env.HTTP_SERVER_DISABLED !== "true") {
|
||||
const socketIo: { io: IoServer } | undefined = build.entry.module.socketIo;
|
||||
const wss: WebSocketServer | undefined = build.entry.module.wss;
|
||||
const registryProxy: RegistryProxy | undefined = build.entry.module.registryProxy;
|
||||
const apiRateLimiter: RateLimitMiddleware = build.entry.module.apiRateLimiter;
|
||||
|
||||
if (registryProxy && process.env.ENABLE_REGISTRY_PROXY === "true") {
|
||||
console.log(`🐳 Enabling container registry proxy to ${registryProxy.origin}`);
|
||||
|
||||
// Adjusted to match /v2 and any subpath under /v2
|
||||
app.all("/v2/*", async (req, res) => {
|
||||
await registryProxy.call(req, res);
|
||||
});
|
||||
|
||||
// This might also be necessary if you need to explicitly match /v2 as well
|
||||
app.all("/v2", async (req, res) => {
|
||||
await registryProxy.call(req, res);
|
||||
});
|
||||
}
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// helpful headers:
|
||||
res.set("Strict-Transport-Security", `max-age=${60 * 60 * 24 * 365 * 100}`);
|
||||
|
||||
// /clean-urls/ -> /clean-urls
|
||||
if (req.path.endsWith("/") && req.path.length > 1) {
|
||||
const query = req.url.slice(req.path.length);
|
||||
const safepath = req.path.slice(0, -1).replace(/\/+/g, "/");
|
||||
res.redirect(301, safepath + query);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
if (process.env.DASHBOARD_AND_API_DISABLED !== "true") {
|
||||
app.use(apiRateLimiter);
|
||||
|
||||
app.all(
|
||||
"*",
|
||||
// @ts-ignore
|
||||
createRequestHandler({
|
||||
build,
|
||||
mode: MODE,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// we need to do the health check here at /healthcheck
|
||||
app.get("/healthcheck", (req, res) => {
|
||||
res.status(200).send("OK");
|
||||
});
|
||||
}
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`✅ server ready: http://localhost:${port} [NODE_ENV: ${MODE}]`);
|
||||
|
||||
if (MODE === "development") {
|
||||
broadcastDevReady(build)
|
||||
.then(() => logDevReady(build))
|
||||
.catch(console.error);
|
||||
}
|
||||
});
|
||||
|
||||
server.keepAliveTimeout = 65 * 1000;
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
server.close((err) => {
|
||||
if (err) {
|
||||
console.error("Error closing express server:", err);
|
||||
} else {
|
||||
console.log("Express server closed gracefully.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socketIo?.io.attach(server);
|
||||
server.removeAllListeners("upgrade"); // prevent duplicate upgrades from listeners created by io.attach()
|
||||
|
||||
server.on("upgrade", async (req, socket, head) => {
|
||||
console.log(
|
||||
`Attemping to upgrade connection at url ${req.url} with headers: ${JSON.stringify(
|
||||
req.headers
|
||||
)}`
|
||||
);
|
||||
|
||||
socket.on("error", (err) => {
|
||||
console.error("Connection upgrade error:", err);
|
||||
});
|
||||
|
||||
const url = new URL(req.url ?? "", "http://localhost");
|
||||
|
||||
// Upgrade socket.io connection
|
||||
if (url.pathname.startsWith("/socket.io/")) {
|
||||
console.log(`Socket.io client connected, upgrading their connection...`);
|
||||
|
||||
// https://github.com/socketio/socket.io/issues/4693
|
||||
(socketIo?.io.engine as EngineServer).handleUpgrade(req, socket, head);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only upgrade the connecting if the path is `/ws`
|
||||
if (url.pathname !== "/ws") {
|
||||
// Setting the socket.destroy() error param causes an error event to be emitted which needs to be handled with socket.on("error") to prevent uncaught exceptions.
|
||||
socket.destroy(
|
||||
new Error(
|
||||
"Cannot connect because of invalid path: Please include `/ws` in the path of your upgrade request."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Client connected, upgrading their connection...`);
|
||||
|
||||
// Handle the WebSocket connection
|
||||
wss?.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss?.emit("connection", ws, req);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
require(BUILD_DIR);
|
||||
console.log(`✅ app ready (skipping http server)`);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"exclude": ["./cypress", "./cypress.config.ts"],
|
||||
"include": ["remix.env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"include": ["env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"types": ["vitest/globals"],
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||
@@ -8,6 +8,7 @@
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "Bundler",
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2019",
|
||||
"strict": true,
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { vitePlugin as remix } from "@remix-run/dev";
|
||||
import { defineConfig, Plugin } from "vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import { expressDevServer } from "remix-express-dev-server";
|
||||
import { installGlobals } from "@remix-run/node";
|
||||
import { config } from "dotenv";
|
||||
import { expand } from "dotenv-expand";
|
||||
import { denyImports } from "vite-env-only";
|
||||
|
||||
expand(
|
||||
config({
|
||||
path: "../../.env",
|
||||
})
|
||||
);
|
||||
|
||||
installGlobals({ nativeFetch: true });
|
||||
|
||||
const MODE = process.env.NODE_ENV;
|
||||
|
||||
export default defineConfig({
|
||||
ssr: {
|
||||
noExternal: [
|
||||
// Fixes Error when evaluating SSR module
|
||||
"react-use",
|
||||
],
|
||||
},
|
||||
optimizeDeps: {
|
||||
entries: ["./app/entry.client.tsx", "./app/root.tsx", "./app/routes/**/*"],
|
||||
include: [
|
||||
// Has cookie and set-cookie-parser deps that need to be optimized
|
||||
"@remix-run/server-runtime",
|
||||
],
|
||||
// Exclude if dep is ESM or runs server-side (just speeds up Vite on navigations)
|
||||
exclude: [
|
||||
// Include @prisma/client until @trigger-dev/database is ESM
|
||||
// https://github.com/prisma/prisma/issues/21474
|
||||
"@prisma/client",
|
||||
"@opentelemetry/api-logs",
|
||||
"@opentelemetry/api",
|
||||
"@opentelemetry/core",
|
||||
"@opentelemetry/exporter-trace-otlp-http",
|
||||
"@opentelemetry/instrumentation-express",
|
||||
"@opentelemetry/instrumentation-http",
|
||||
"@opentelemetry/instrumentation",
|
||||
"@opentelemetry/resources",
|
||||
"@opentelemetry/sdk-trace-base",
|
||||
"@opentelemetry/sdk-trace-node",
|
||||
"@opentelemetry/semantic-conventions",
|
||||
|
||||
"@prisma/instrumentation",
|
||||
"@radix-ui/react-alert-dialog",
|
||||
"@radix-ui/react-dialog",
|
||||
"@radix-ui/react-label",
|
||||
"@radix-ui/react-popover",
|
||||
"@radix-ui/react-radio-group",
|
||||
"@radix-ui/react-select",
|
||||
"@radix-ui/react-slider",
|
||||
"@radix-ui/react-switch",
|
||||
"@radix-ui/react-tabs",
|
||||
"@radix-ui/react-tooltip",
|
||||
"@react-email/components",
|
||||
"@react-email/render",
|
||||
"@remix-run/node",
|
||||
"@remix-run/server-runtime",
|
||||
"@slack/web-api",
|
||||
"@socket.io/redis-adapter",
|
||||
"@tanstack/react-virtual",
|
||||
"@team-plain/typescript-sdk",
|
||||
"@whatwg-node/fetch",
|
||||
"aws4fetch",
|
||||
"cron-parser",
|
||||
"cronstrue",
|
||||
"evt",
|
||||
"fsevents",
|
||||
"graphile-worker",
|
||||
"https",
|
||||
"ioredis",
|
||||
"jsonpointer",
|
||||
"lodash.omit",
|
||||
"nanoid",
|
||||
"ohash",
|
||||
"parse-duration",
|
||||
"posthog-node",
|
||||
"prism-react-renderer",
|
||||
"prom-client",
|
||||
"random-words",
|
||||
"react-use/esm/useEvent",
|
||||
"remix-auth-email-link",
|
||||
"remix-auth-github",
|
||||
"remix-auth",
|
||||
"resend",
|
||||
"seedrandom",
|
||||
"simple-oauth2",
|
||||
"slug",
|
||||
"socket.io-client",
|
||||
"socket.io",
|
||||
"superjson",
|
||||
"ulid",
|
||||
"ulidx",
|
||||
"util",
|
||||
"ws",
|
||||
"zod-error",
|
||||
],
|
||||
},
|
||||
build: {
|
||||
minify: true,
|
||||
cssCodeSplit: false,
|
||||
target: "esnext",
|
||||
cssMinify: MODE === "production",
|
||||
rollupOptions: {
|
||||
external: [/node:.*/, /.*\.node$/, "https", "stream", "crypto", "fsevents", "deepmerge"],
|
||||
},
|
||||
},
|
||||
|
||||
server: {
|
||||
hmr: {
|
||||
port: 8002,
|
||||
},
|
||||
strictPort: true,
|
||||
port: Number(process.env.PORT),
|
||||
warmup: {
|
||||
clientFiles: ["./app/entry.client.tsx", "./app/root.tsx", "./app/routes/**/*"],
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
expressDevServer({
|
||||
exportName: "express",
|
||||
async configureServer(server) {
|
||||
if (!server) return;
|
||||
|
||||
const { registerSocketIo } = await import("./app/socket.server");
|
||||
registerSocketIo(server);
|
||||
},
|
||||
}),
|
||||
remix({
|
||||
ignoredRouteFiles: ["**/.*"],
|
||||
serverModuleFormat: "esm",
|
||||
}).filter((plugin) => plugin.name !== "remix-dot-server"),
|
||||
tsconfigPaths() as Plugin,
|
||||
denyImports({
|
||||
client: {
|
||||
specifiers: ["node:stream", "@trigger.dev/core"],
|
||||
},
|
||||
server: {
|
||||
specifiers: ["@trigger.dev/core"],
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
+5
-1
@@ -56,7 +56,7 @@
|
||||
"prettier": "^3.0.0",
|
||||
"tsx": "^3.7.1",
|
||||
"turbo": "^1.10.3",
|
||||
"vite": "^4.1.1",
|
||||
"vite": "^5.3.1",
|
||||
"vite-tsconfig-paths": "^4.0.5",
|
||||
"vitest": "^0.28.4"
|
||||
},
|
||||
@@ -72,6 +72,10 @@
|
||||
"tsup@8.0.1": "patches/tsup@8.0.1.patch",
|
||||
"engine.io-parser@5.2.2": "patches/engine.io-parser@5.2.2.patch",
|
||||
"graphile-worker@0.16.6": "patches/graphile-worker@0.16.6.patch"
|
||||
},
|
||||
"overrides": {
|
||||
"@vanilla-extract/integration>vite": "5.2.12",
|
||||
"@vanilla-extract/integration>vite-node": "1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,22 @@
|
||||
"require": "./dist/v3/otel/index.js",
|
||||
"types": "./dist/v3/otel/index.d.ts"
|
||||
},
|
||||
"./v3/schemas": {
|
||||
"import": {
|
||||
"types": "./dist/v3/schemas/index.d.mts",
|
||||
"default": "./dist/v3/schemas/index.mjs"
|
||||
},
|
||||
"require": "./dist/v3/schemas/index.js",
|
||||
"types": "./dist/v3/schemas/index.d.ts"
|
||||
},
|
||||
"./v3/utils/durations": {
|
||||
"import": {
|
||||
"types": "./dist/v3/utils/durations.d.mts",
|
||||
"default": "./dist/v3/utils/durations.mjs"
|
||||
},
|
||||
"require": "./dist/v3/utils/durations.js",
|
||||
"types": "./dist/v3/utils/durations.d.ts"
|
||||
},
|
||||
"./v3/zodfetch": {
|
||||
"import": {
|
||||
"types": "./dist/v3/zodfetch.d.mts",
|
||||
@@ -167,4 +183,4 @@
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export default defineConfig({
|
||||
entry: [
|
||||
"./src/index.ts",
|
||||
"./src/v3/index.ts",
|
||||
"./src/v3/utils/durations.ts",
|
||||
"./src/v3/schemas/index.ts",
|
||||
"./src/v3/otel/index.ts",
|
||||
"./src/v3/zodMessageHandler.ts",
|
||||
"./src/v3/zodNamespace.ts",
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# Prevent importing from `@trigger.dev/core` directly (`trigger-dev/no-trigger-core-import`)
|
||||
|
||||
<!-- end auto-generated rule header -->
|
||||
|
||||
Due to [this Remix bug](https://github.com/remix-run/remix/issues/9597), the web app is very sensitive to importing server-side code when it's bundling for the client side. If a route imports from a barrel file that ALSO exports server-side code, it will break the webapp's client side navigation and the page simply refreshes. This only happens during development.
|
||||
|
||||
## Rule Details
|
||||
|
||||
This rule prevents importing from `@trigger.dev/core` and `@trigger.dev/core/v3` directly, which are barrel files that export server-side code.
|
||||
|
||||
It forces direct imports from the most specific file that exports a particular module and will autocorrect it.
|
||||
|
||||
Examples of **incorrect** code for this rule:
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
|
||||
```
|
||||
|
||||
Examples of **correct** code for this rule:
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
|
||||
import { parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
```
|
||||
|
||||
## When Not To Use It
|
||||
|
||||
This rule prevents issues when client-side navigating to routes that import from `@trigger.dev/core` or `@trigger.dev/core/v3`. If there will be no client-side navigation during development, this rule is not needed.
|
||||
|
||||
If [this bug](https://github.com/remix-run/remix/issues/9597) is fixed, this rule can be removed.
|
||||
|
||||
## Workflow
|
||||
|
||||
This rule runs in several steps
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
|
||||
```
|
||||
|
||||
First it splits Core imports into one line per import
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload } from "@trigger.dev/core/v3";
|
||||
import { parsePacket } from "@trigger.dev/core/v3";
|
||||
import { prettyPrintPacket } from "@trigger.dev/core/v3";
|
||||
```
|
||||
|
||||
Then refines each down to their most specific exported file
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas/api";
|
||||
import { parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
import { prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
```
|
||||
|
||||
if that exported file is downstream of an allowed barrel file (set to the schemas folders right now), it returns the export from the barrel instead
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
|
||||
import { parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
import { prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
```
|
||||
|
||||
then the normal lint plugin for merging multiple imports from the same file will run and merge any that are the same
|
||||
|
||||
```ts
|
||||
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
|
||||
import { parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
|
||||
```
|
||||
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* @fileoverview Prevent importing from `@trigger.dev/core` directly
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const parser = require("@babel/parser");
|
||||
const traverse = require("@babel/traverse").default;
|
||||
const tsconfigPaths = require("tsconfig-paths");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const blockedImportSources = ["@trigger.dev/core", "@trigger.dev/core/v3"];
|
||||
const allowedBarrelFiles = ["@trigger.dev/core/schemas", "@trigger.dev/core/v3/schemas"];
|
||||
|
||||
function resolveSpecifier(importSource, specifier, context) {
|
||||
const corePath = resolveModulePath(importSource);
|
||||
const coreDir = path.dirname(corePath);
|
||||
|
||||
const resolvedPath = resolveExport(coreDir, specifier);
|
||||
if (resolvedPath) {
|
||||
const baseName = "@trigger.dev/core";
|
||||
const relativePath = resolvedPath.split("packages/core/src/")[1].replace(/\\/g, "/");
|
||||
const finalPath = `${baseName}/${relativePath}`;
|
||||
|
||||
// Check if the resolved path should map to an allowed barrel file
|
||||
for (const barrelFile of allowedBarrelFiles) {
|
||||
if (finalPath.startsWith(barrelFile)) {
|
||||
return barrelFile;
|
||||
}
|
||||
}
|
||||
|
||||
return removeExtensionAndIndex(finalPath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveExport(fileOrDir, specifier) {
|
||||
const filePath = fs.lstatSync(fileOrDir).isDirectory()
|
||||
? path.join(fileOrDir, "index.ts")
|
||||
: fileOrDir;
|
||||
|
||||
if (!fs.existsSync(filePath)) return null;
|
||||
|
||||
const code = fs.readFileSync(filePath, "utf8");
|
||||
const ast = parser.parse(code, { sourceType: "module", plugins: ["typescript"] });
|
||||
|
||||
let foundPath = null;
|
||||
|
||||
traverse(ast, {
|
||||
ExportNamedDeclaration({ node }) {
|
||||
if (node.declaration) {
|
||||
if (
|
||||
node.declaration.type === "VariableDeclaration" &&
|
||||
node.declaration.declarations.some((decl) => decl.id.name === specifier)
|
||||
) {
|
||||
foundPath = filePath;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
node.declaration.type === "FunctionDeclaration" &&
|
||||
node.declaration.id.name === specifier
|
||||
) {
|
||||
foundPath = filePath;
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
node.declaration.type === "ClassDeclaration" &&
|
||||
node.declaration.id.name === specifier
|
||||
) {
|
||||
foundPath = filePath;
|
||||
return;
|
||||
}
|
||||
} else if (node.specifiers) {
|
||||
for (const exportSpecifier of node.specifiers) {
|
||||
if (exportSpecifier.exported.name === specifier) {
|
||||
const sourcePath = node.source.value;
|
||||
const dir = fs.lstatSync(fileOrDir).isDirectory() ? fileOrDir : path.dirname(fileOrDir);
|
||||
const resolvedSourcePath = tryResolveSourcePath(dir, sourcePath);
|
||||
|
||||
if (resolvedSourcePath) {
|
||||
const resolvedExport = resolveExport(resolvedSourcePath, specifier);
|
||||
if (resolvedExport) {
|
||||
foundPath = resolvedExport;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExportAllDeclaration({ node }) {
|
||||
const sourcePath = node.source.value;
|
||||
const dir = fs.lstatSync(fileOrDir).isDirectory() ? fileOrDir : path.dirname(fileOrDir);
|
||||
const resolvedSourcePath = tryResolveSourcePath(dir, sourcePath);
|
||||
|
||||
if (resolvedSourcePath) {
|
||||
const resolvedExport = resolveExport(resolvedSourcePath, specifier);
|
||||
if (resolvedExport) {
|
||||
foundPath = resolvedExport;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return foundPath ? foundPath : null;
|
||||
}
|
||||
|
||||
function tryResolveSourcePath(baseDir, sourcePath) {
|
||||
const possibleExtensions = ["", ".ts", ".tsx", ".js", ".jsx"];
|
||||
const possibleIndexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
||||
|
||||
for (const ext of possibleExtensions) {
|
||||
const fullPath = path.resolve(baseDir, `${sourcePath}${ext}`);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
const dirPath = path.resolve(baseDir, sourcePath);
|
||||
if (fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()) {
|
||||
for (const indexFile of possibleIndexFiles) {
|
||||
const indexPath = path.join(dirPath, indexFile);
|
||||
if (fs.existsSync(indexPath)) {
|
||||
return indexPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function removeExtensionAndIndex(filePath) {
|
||||
return filePath
|
||||
.replace(/\/index(\.ts|\.tsx|\.js|\.jsx)$/, "")
|
||||
.replace(/(\.ts|\.tsx|\.js|\.jsx)$/, "");
|
||||
}
|
||||
|
||||
// Configure tsconfig-paths
|
||||
function resolveModulePath(sourcePath) {
|
||||
const cwd = path.resolve(process.cwd());
|
||||
const tsconfigPath = path.resolve(cwd, "tsconfig.json");
|
||||
const tsconfig = require(tsconfigPath);
|
||||
const { absoluteBaseUrl, paths } = tsconfigPaths.loadConfig(tsconfigPath);
|
||||
|
||||
if (!absoluteBaseUrl || !paths) {
|
||||
throw new Error("Could not load tsconfig paths");
|
||||
}
|
||||
|
||||
const matchPath = tsconfigPaths.createMatchPath(absoluteBaseUrl, paths);
|
||||
|
||||
let resolvedPath = matchPath(sourcePath, undefined, undefined, [".ts", ".tsx", ".js", ".jsx"]);
|
||||
|
||||
if (resolvedPath) {
|
||||
if (fs.existsSync(resolvedPath) && fs.lstatSync(resolvedPath).isDirectory()) {
|
||||
const indexResolvedPath = path.join(resolvedPath, "index.ts");
|
||||
if (fs.existsSync(indexResolvedPath)) {
|
||||
resolvedPath = indexResolvedPath;
|
||||
}
|
||||
}
|
||||
|
||||
return path.resolve(resolvedPath);
|
||||
}
|
||||
|
||||
return path.resolve(sourcePath);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "problem",
|
||||
docs: {
|
||||
description: "Prevent importing from `@trigger.dev/core` or `@trigger.dev/core/v3` directly",
|
||||
recommended: true,
|
||||
url: null,
|
||||
},
|
||||
fixable: "code",
|
||||
schema: [],
|
||||
messages: {
|
||||
noTriggerCoreImport: "{{name}} should be imported from '{{resolvedPath}}'",
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
ImportDeclaration(node) {
|
||||
const importSource = node.source.value;
|
||||
|
||||
if (blockedImportSources.includes(importSource)) {
|
||||
const specifierFixes = node.specifiers
|
||||
.map((specifier) => {
|
||||
const resolvedPath = resolveSpecifier(importSource, specifier.local.name, context);
|
||||
if (resolvedPath) {
|
||||
return {
|
||||
name: specifier.local.name,
|
||||
path: resolvedPath,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
if (specifierFixes.length > 0) {
|
||||
const fixes = specifierFixes
|
||||
.map((fix) => {
|
||||
return `import { ${fix.name} } from '${fix.path}';`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId: "noTriggerCoreImport",
|
||||
data: {
|
||||
name: node.specifiers.map((spec) => spec.local.name).join(", "),
|
||||
resolvedPath: fixes,
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, fixes);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -18,9 +18,13 @@
|
||||
"update:eslint-docs": "eslint-doc-generator"
|
||||
},
|
||||
"dependencies": {
|
||||
"requireindex": "^1.2.0"
|
||||
"requireindex": "^1.2.0",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/traverse": "^7.24.7",
|
||||
"@types/babel__traverse": "^7.20.6",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-doc-generator": "^1.0.0",
|
||||
"eslint-plugin-eslint-plugin": "^5.0.0",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @fileoverview Prevent importing from `@trigger.dev/core` directly
|
||||
* @author
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const rule = require("../../../lib/rules/no-trigger-core-import"),
|
||||
RuleTester = require("eslint").RuleTester;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Tests
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020,
|
||||
},
|
||||
});
|
||||
ruleTester.run("no-trigger-core-import", rule, {
|
||||
valid: [
|
||||
{
|
||||
code: `import { conditionallyImportPacket, parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";`,
|
||||
},
|
||||
],
|
||||
|
||||
invalid: [
|
||||
{
|
||||
code: `import { parsePacket } from '@trigger.dev/core/v3';`,
|
||||
output: `import { parsePacket } from '@trigger.dev/core/v3/utils/ioSerialization';`,
|
||||
errors: [
|
||||
{
|
||||
messageId: "noTriggerCoreImport",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `import { CreateBackgroundWorkerRequestBody, TaskResource } from '@trigger.dev/core/v3';`,
|
||||
output: `import { CreateBackgroundWorkerRequestBody } from '@trigger.dev/core/v3/schemas';
|
||||
import { TaskResource } from '@trigger.dev/core/v3/schemas';`,
|
||||
errors: [
|
||||
{
|
||||
messageId: "noTriggerCoreImport",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `import { CreateBackgroundWorkerRequestBody, stringifyIO } from '@trigger.dev/core/v3';`,
|
||||
output: `import { CreateBackgroundWorkerRequestBody } from '@trigger.dev/core/v3/schemas';
|
||||
import { stringifyIO } from '@trigger.dev/core/v3/utils/ioSerialization';`,
|
||||
errors: [
|
||||
{
|
||||
messageId: "noTriggerCoreImport",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"types": ["vitest/globals"],
|
||||
"lib": ["ES2019"],
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"resolveJsonModule": true,
|
||||
"target": "ES2019",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"baseUrl": ".",
|
||||
// Need paths for the no-trigger-core-import plugin
|
||||
"paths": {
|
||||
"~/*": ["./app/*"],
|
||||
"@/*": ["./*"],
|
||||
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
|
||||
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"],
|
||||
"@trigger.dev/core-backend": ["../../packages/core-backend/src/index"],
|
||||
"@trigger.dev/core-backend/*": ["../../packages/core-backend/src/*"],
|
||||
"@trigger.dev/database": ["../../packages/database/src/index"],
|
||||
"@trigger.dev/database/*": ["../../packages/database/src/*"],
|
||||
"@trigger.dev/yalt": ["../../packages/yalt/src/index"],
|
||||
"@trigger.dev/yalt/*": ["../../packages/yalt/src/*"],
|
||||
"@trigger.dev/otlp-importer": ["../../packages/otlp-importer/src/index"],
|
||||
"@trigger.dev/otlp-importer/*": ["../../packages/otlp-importer/src/*"],
|
||||
"emails": ["../../packages/emails/src/index"],
|
||||
"emails/*": ["../../packages/emails/src/*"]
|
||||
},
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
Generated
+1267
-1215
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user