Merge branch 'main' into v3/infra-updates
This commit is contained in:
+3
-1
@@ -55,6 +55,8 @@ COORDINATOR_SECRET=coordinator-secret # generate the actual secret with `openssl
|
||||
# ENABLE_REGISTRY_PROXY=true
|
||||
# DEPOT_TOKEN=<Depot org token>
|
||||
# DEPOT_PROJECT_ID=<Depot project id>
|
||||
# DEPLOY_REGISTRY_HOST=${APP_ORIGIN} # This is the host that the deploy CLI will use to push images to the registry
|
||||
# CONTAINER_REGISTRY_ORIGIN=<Container registry origin e.g. https://registry.digitalocean.com>
|
||||
# CONTAINER_REGISTRY_USERNAME=<Container registry username e.g. Digital ocean email address>
|
||||
# CONTAINER_REGISTRY_PASSWORD=<Container registry password e.g. Digital ocean PAT>
|
||||
# CONTAINER_REGISTRY_PASSWORD=<Container registry password e.g. Digital ocean PAT>
|
||||
# DEV_OTEL_EXPORTER_OTLP_ENDPOINT="http://0.0.0.0:4318"
|
||||
@@ -3,21 +3,11 @@ on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
publish:
|
||||
strategy:
|
||||
fail-fast: true # when a job fails, all remaining ones will be cancelled
|
||||
matrix:
|
||||
runs-on: [buildjet-4vcpu-ubuntu-2204, buildjet-4vcpu-ubuntu-2204-arm]
|
||||
name: ${{matrix.runs-on}}
|
||||
runs-on: ${{matrix.runs-on}}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
short_sha: ${{ steps.get_commit.outputs.sha_short }}
|
||||
steps:
|
||||
- name: 🐳 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -43,19 +33,12 @@ jobs:
|
||||
echo "Invalid reference: ${GITHUB_REF}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ${{matrix.runs-on}} == *-arm ]]; then
|
||||
IMAGE_TAG="${IMAGE_TAG}-arm"
|
||||
fi
|
||||
echo "::set-output name=version::${IMAGE_TAG}"
|
||||
- name: 🔢 Get the commit hash
|
||||
id: get_commit
|
||||
run: |
|
||||
echo ::set-output name=sha_short::$(echo ${{ github.sha }} | cut -c1-7)
|
||||
|
||||
- name: 🐳 Build Docker Image
|
||||
run: |
|
||||
docker build -t release_build_image -f ./docker/Dockerfile .
|
||||
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -63,24 +46,11 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🐙 Push to GitHub Container Registry
|
||||
run: |
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
IMAGE_TAG: ${{ steps.get_version.outputs.version }}
|
||||
|
||||
- name: 🐙 Push 'latest' to GitHub Container Registry
|
||||
if: startsWith(github.ref, 'refs/tags/v.docker')
|
||||
run: |
|
||||
LATEST=latest
|
||||
if [[ ${{matrix.runs-on}} == *-arm ]]; then
|
||||
LATEST="${LATEST}-arm"
|
||||
fi
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:$LATEST
|
||||
docker push $REGISTRY/$REPOSITORY:$LATEST
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
- name: 🐳 Build image and push to GitHub Container Registry
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
file: ./docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/triggerdotdev/trigger.dev:${{ steps.get_version.outputs.version }}
|
||||
push: true
|
||||
|
||||
@@ -253,7 +253,7 @@ export const TableCellMenu = forwardRef<
|
||||
type TableBlankRowProps = {
|
||||
className?: string;
|
||||
colSpan: number;
|
||||
children: ReactNode;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const TableBlankRow = forwardRef<HTMLTableRowElement, TableBlankRowProps>(
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { StopCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import {
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
} from "~/components/primitives/Dialog";
|
||||
|
||||
type CancelRunDialogProps = {
|
||||
runFriendlyId: string;
|
||||
redirectPath: string;
|
||||
};
|
||||
|
||||
export function CancelRunDialog({ runFriendlyId, redirectPath }: CancelRunDialogProps) {
|
||||
const cancelFetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogHeader>Cancel this run?</DialogHeader>
|
||||
<DialogDescription>
|
||||
Canceling a run will stop execution. If you want to run this later you will have to replay
|
||||
the entire run with the original payload.
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<cancelFetcher.Form action={`/resources/taskruns/${runFriendlyId}/cancel`} method="post">
|
||||
<Button
|
||||
type="submit"
|
||||
name="redirectUrl"
|
||||
value={redirectPath}
|
||||
variant="danger/small"
|
||||
LeadingIcon={cancelFetcher.state === "idle" ? StopCircleIcon : "spinner-white"}
|
||||
disabled={cancelFetcher.state !== "idle"}
|
||||
shortcut={{ modifiers: ["meta"], key: "enter" }}
|
||||
>
|
||||
{cancelFetcher.state === "idle" ? "Cancel run" : "Canceling..."}
|
||||
</Button>
|
||||
</cancelFetcher.Form>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { StopIcon } from "@heroicons/react/24/outline";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { BeakerIcon, BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { User } from "@trigger.dev/database";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { RunListItem } from "~/presenters/v3/RunListPresenter.server";
|
||||
import { v3RunPath } from "~/utils/pathBuilder";
|
||||
import { RunListAppliedFilters, RunListItem } from "~/presenters/v3/RunListPresenter.server";
|
||||
import { docsPath, v3RunPath, v3TestPath } from "~/utils/pathBuilder";
|
||||
import { EnvironmentLabel } from "../../environments/EnvironmentLabel";
|
||||
import { DateTime } from "../../primitives/DateTime";
|
||||
import { Paragraph } from "../../primitives/Paragraph";
|
||||
@@ -15,16 +15,24 @@ import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellChevron,
|
||||
TableCellMenu,
|
||||
TableHeader,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
} from "../../primitives/Table";
|
||||
import { formatDuration } from "@trigger.dev/core/v3";
|
||||
import { TaskRunStatusCombo } from "./TaskRunStatus";
|
||||
import { useEnvironments } from "~/hooks/useEnvironments";
|
||||
import { Button, LinkButton } from "~/components/primitives/Buttons";
|
||||
import { StopCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
|
||||
import { CancelRunDialog } from "./CancelRunDialog";
|
||||
import { useLocation } from "@remix-run/react";
|
||||
|
||||
type RunsTableProps = {
|
||||
total: number;
|
||||
hasFilters: boolean;
|
||||
filters: RunListAppliedFilters;
|
||||
showJob?: boolean;
|
||||
runs: RunListItem[];
|
||||
isLoading?: boolean;
|
||||
@@ -34,12 +42,14 @@ type RunsTableProps = {
|
||||
export function TaskRunsTable({
|
||||
total,
|
||||
hasFilters,
|
||||
filters,
|
||||
runs,
|
||||
isLoading = false,
|
||||
currentUser,
|
||||
}: RunsTableProps) {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const location = useLocation();
|
||||
|
||||
return (
|
||||
<Table>
|
||||
@@ -65,9 +75,7 @@ export function TaskRunsTable({
|
||||
{!isLoading && <NoRuns title="No runs found" />}
|
||||
</TableBlankRow>
|
||||
) : runs.length === 0 ? (
|
||||
<TableBlankRow colSpan={9}>
|
||||
{!isLoading && <NoRuns title="No runs match your filters" />}
|
||||
</TableBlankRow>
|
||||
<BlankState isLoading={isLoading} filters={filters} />
|
||||
) : (
|
||||
runs.map((run) => {
|
||||
const path = v3RunPath(organization, project, run);
|
||||
@@ -102,7 +110,23 @@ export function TaskRunsTable({
|
||||
<TableCell to={path}>
|
||||
{run.createdAt ? <DateTime date={run.createdAt} /> : "–"}
|
||||
</TableCell>
|
||||
<TableCellChevron to={path} isSticky />
|
||||
{run.isCancellable ? (
|
||||
<TableCellMenu isSticky>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="small-menu-item" LeadingIcon={StopCircleIcon}>
|
||||
Cancel run
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<CancelRunDialog
|
||||
runFriendlyId={run.friendlyId}
|
||||
redirectPath={`${location.pathname}${location.search}`}
|
||||
/>
|
||||
</Dialog>
|
||||
</TableCellMenu>
|
||||
) : (
|
||||
<TableCell to={path}>{""}</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
@@ -127,3 +151,62 @@ function NoRuns({ title }: { title: string }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BlankState({ isLoading, filters }: Pick<RunsTableProps, "isLoading" | "filters">) {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const envs = useEnvironments();
|
||||
if (isLoading) return <TableBlankRow colSpan={9}></TableBlankRow>;
|
||||
|
||||
const { environments, tasks, from, to, ...otherFilters } = filters;
|
||||
|
||||
if (
|
||||
filters.environments.length === 1 &&
|
||||
filters.tasks.length === 1 &&
|
||||
filters.from === undefined &&
|
||||
filters.to === undefined &&
|
||||
Object.values(otherFilters).every((filterArray) => filterArray.length === 0)
|
||||
) {
|
||||
const environment = envs?.find((env) => env.id === filters.environments[0]);
|
||||
return (
|
||||
<TableBlankRow colSpan={9}>
|
||||
<div className="py-14">
|
||||
<Paragraph className="w-auto" variant="base/bright" spacing>
|
||||
There are no runs for {filters.tasks[0]}
|
||||
{environment ? (
|
||||
<>
|
||||
{" "}
|
||||
in <EnvironmentLabel environment={environment} size="large" />
|
||||
</>
|
||||
) : null}
|
||||
</Paragraph>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<LinkButton
|
||||
to={v3TestPath(organization, project)}
|
||||
variant="primary/small"
|
||||
LeadingIcon={BeakerIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
Create a test run
|
||||
</LinkButton>
|
||||
<Paragraph variant="small">or</Paragraph>
|
||||
<LinkButton
|
||||
to={docsPath("v3/triggering")}
|
||||
variant="primary/small"
|
||||
LeadingIcon={BookOpenIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
Triggering a task docs
|
||||
</LinkButton>
|
||||
</div>
|
||||
</div>
|
||||
</TableBlankRow>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableBlankRow colSpan={9}>
|
||||
<NoRuns title="No runs match your filters" />
|
||||
</TableBlankRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ const EnvironmentSchema = z.object({
|
||||
CONTAINER_REGISTRY_ORIGIN: z.string().optional(),
|
||||
CONTAINER_REGISTRY_USERNAME: z.string().optional(),
|
||||
CONTAINER_REGISTRY_PASSWORD: z.string().optional(),
|
||||
DEPLOY_REGISTRY_HOST: z.string().optional(),
|
||||
DEV_OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Environment = z.infer<typeof EnvironmentSchema>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Prisma, TaskRunAttemptStatus, TaskRunStatus } from "@trigger.dev/databa
|
||||
import { Direction } from "~/components/runs/RunStatuses";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { getUsername } from "~/utils/username";
|
||||
import { CANCELLABLE_STATUSES } from "~/v3/services/cancelTaskRun.server";
|
||||
|
||||
type RunListOptions = {
|
||||
userId: string;
|
||||
@@ -23,6 +24,7 @@ const DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
export type RunList = Awaited<ReturnType<RunListPresenter["call"]>>;
|
||||
export type RunListItem = RunList["runs"][0];
|
||||
export type RunListAppliedFilters = RunList["filters"];
|
||||
|
||||
export class RunListPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
@@ -220,6 +222,7 @@ export class RunListPresenter {
|
||||
version: run.version,
|
||||
taskIdentifier: run.taskIdentifier,
|
||||
attempts: Number(run.attempts),
|
||||
isCancellable: CANCELLABLE_STATUSES.includes(run.status),
|
||||
environment: {
|
||||
type: environment.type,
|
||||
slug: environment.slug,
|
||||
@@ -233,6 +236,14 @@ export class RunListPresenter {
|
||||
previous,
|
||||
},
|
||||
possibleTasks: possibleTasks.map((task) => task.slug),
|
||||
filters: {
|
||||
tasks: tasks || [],
|
||||
versions: versions || [],
|
||||
statuses: statuses || [],
|
||||
environments: environments || [],
|
||||
from,
|
||||
to,
|
||||
},
|
||||
hasFilters,
|
||||
};
|
||||
}
|
||||
|
||||
+10
-33
@@ -19,6 +19,7 @@ import {
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { Property, PropertyTable } from "~/components/primitives/PropertyTable";
|
||||
import { CancelRunDialog } from "~/components/runs/v3/CancelRunDialog";
|
||||
import { LiveTimer } from "~/components/runs/v3/LiveTimer";
|
||||
import { RunIcon } from "~/components/runs/v3/RunIcon";
|
||||
import { SpanEvents } from "~/components/runs/v3/SpanEvents";
|
||||
@@ -54,7 +55,6 @@ export default function Page() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const { runParam } = useParams();
|
||||
const cancelFetcher = useFetcher();
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -176,38 +176,15 @@ export default function Page() {
|
||||
Cancel run
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>Cancel this run?</DialogHeader>
|
||||
<DialogDescription>
|
||||
Canceling a run will stop execution. If you want to run this later you will have
|
||||
to replay the entire run with the original payload.
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<cancelFetcher.Form
|
||||
action={`/resources/taskruns/${event.runId}/cancel`}
|
||||
method="post"
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
name="redirectUrl"
|
||||
value={v3RunSpanPath(
|
||||
organization,
|
||||
project,
|
||||
{ friendlyId: runParam },
|
||||
{ spanId: event.spanId }
|
||||
)}
|
||||
variant="danger/small"
|
||||
LeadingIcon={
|
||||
cancelFetcher.state === "idle" ? StopCircleIcon : "spinner-white"
|
||||
}
|
||||
disabled={cancelFetcher.state !== "idle"}
|
||||
shortcut={{ modifiers: ["meta"], key: "enter" }}
|
||||
>
|
||||
{cancelFetcher.state === "idle" ? "Cancel run" : "Canceling..."}
|
||||
</Button>
|
||||
</cancelFetcher.Form>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
<CancelRunDialog
|
||||
runFriendlyId={event.runId}
|
||||
redirectPath={v3RunSpanPath(
|
||||
organization,
|
||||
project,
|
||||
{ friendlyId: runParam },
|
||||
{ spanId: event.spanId }
|
||||
)}
|
||||
/>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+1
-5
@@ -581,11 +581,7 @@ function NodeStatusIcon({ node }: { node: RunEvent }) {
|
||||
}
|
||||
|
||||
function TaskLine({ isError, isSelected }: { isError: boolean; isSelected: boolean }) {
|
||||
return (
|
||||
<div
|
||||
className={cn("h-8 w-2 border-r", isError ? "border-rose-500/10" : "border-charcoal-800")}
|
||||
/>
|
||||
);
|
||||
return <div className={cn("h-8 w-2 border-r border-grid-bright")} />;
|
||||
}
|
||||
|
||||
function ShowParentLink({ runFriendlyId }: { runFriendlyId: string }) {
|
||||
|
||||
+1
@@ -84,6 +84,7 @@ export default function Page() {
|
||||
<TaskRunsTable
|
||||
total={list.runs.length}
|
||||
hasFilters={list.hasFilters}
|
||||
filters={list.filters}
|
||||
runs={list.runs}
|
||||
isLoading={isLoading}
|
||||
currentUser={user}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
|
||||
import { InitializeDeploymentRequestBody } from "@trigger.dev/core/v3";
|
||||
import { InitializeDeploymentRequestBody, InitializeDeploymentResponseBody } from "@trigger.dev/core/v3";
|
||||
import { env } from "~/env.server";
|
||||
import { authenticateApiRequest } from "~/services/apiAuth.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { InitializeDeploymentService } from "~/v3/services/initializeDeployment.server";
|
||||
@@ -31,15 +32,18 @@ export async function action({ request, params }: ActionFunctionArgs) {
|
||||
|
||||
const { deployment, imageTag } = await service.call(authenticatedEnv, body.data);
|
||||
|
||||
const responseBody: InitializeDeploymentResponseBody = {
|
||||
id: deployment.friendlyId,
|
||||
contentHash: deployment.contentHash,
|
||||
shortCode: deployment.shortCode,
|
||||
version: deployment.version,
|
||||
externalBuildData: deployment.externalBuildData as InitializeDeploymentResponseBody["externalBuildData"],
|
||||
imageTag,
|
||||
registryHost: env.DEPLOY_REGISTRY_HOST
|
||||
}
|
||||
|
||||
return json(
|
||||
{
|
||||
id: deployment.friendlyId,
|
||||
contentHash: deployment.contentHash,
|
||||
shortCode: deployment.shortCode,
|
||||
version: deployment.version,
|
||||
externalBuildData: deployment.externalBuildData,
|
||||
imageTag,
|
||||
},
|
||||
responseBody,
|
||||
{ status: 200 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function parseSecretKey(key: string) {
|
||||
const SecretValue = z.object({ secret: z.string() });
|
||||
|
||||
export class EnvironmentVariablesRepository implements Repository {
|
||||
constructor(private prismaClient: PrismaClient = prisma) {}
|
||||
constructor(private prismaClient: PrismaClient = prisma) { }
|
||||
|
||||
async create(
|
||||
projectId: string,
|
||||
@@ -415,7 +415,12 @@ export class EnvironmentVariablesRepository implements Repository {
|
||||
}
|
||||
|
||||
if (environment.type === "DEVELOPMENT") {
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
||||
value: env.DEV_OTEL_EXPORTER_OTLP_ENDPOINT ?? env.APP_ORIGIN,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
SpanEvents,
|
||||
TaskEventStyle,
|
||||
correctErrorStackTrace,
|
||||
flattenAndNormalizeAttributes,
|
||||
flattenAttributes,
|
||||
isExceptionSpanEvent,
|
||||
omit,
|
||||
primitiveValueOrflattenedAttributes,
|
||||
unflattenAttributes,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import { Prisma, TaskEvent, TaskEventStatus, type TaskEventKind } from "@trigger.dev/database";
|
||||
@@ -178,10 +178,7 @@ export class EventRepository {
|
||||
metadata: event.metadata as Attributes,
|
||||
style: event.style as Attributes,
|
||||
output: options?.attributes.output
|
||||
? flattenAndNormalizeAttributes(
|
||||
options.attributes.output,
|
||||
SemanticInternalAttributes.OUTPUT
|
||||
)
|
||||
? primitiveValueOrflattenedAttributes(options.attributes.output, undefined)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
@@ -564,7 +561,6 @@ export class EventRepository {
|
||||
queueName: options.attributes.queueName,
|
||||
batchId: options.attributes.batchId ?? undefined,
|
||||
properties: {
|
||||
...style,
|
||||
...(flattenAttributes(metadata, SemanticInternalAttributes.METADATA) as Record<
|
||||
string,
|
||||
string
|
||||
|
||||
@@ -8,7 +8,7 @@ import { assertUnreachable } from "../utils/asserts.server";
|
||||
import { CancelAttemptService } from "./cancelAttempt.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
|
||||
const CANCELLABLE_STATUSES: Array<TaskRunStatus> = [
|
||||
export const CANCELLABLE_STATUSES: Array<TaskRunStatus> = [
|
||||
"PENDING",
|
||||
"EXECUTING",
|
||||
"PAUSED",
|
||||
|
||||
+17
-8
@@ -68,16 +68,25 @@ if (process.env.HTTP_SERVER_DISABLED !== "true") {
|
||||
next();
|
||||
});
|
||||
|
||||
app.all(
|
||||
"*",
|
||||
createRequestHandler({
|
||||
build,
|
||||
mode: MODE,
|
||||
})
|
||||
);
|
||||
if (process.env.DASHBOARD_AND_API_DISABLED !== "true") {
|
||||
app.all(
|
||||
"*",
|
||||
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(`✅ app ready: http://localhost:${port} [NODE_ENV: ${MODE}]`);
|
||||
console.log(`✅ server ready: http://localhost:${port} [NODE_ENV: ${MODE}]`);
|
||||
|
||||
if (MODE === "development") {
|
||||
broadcastDevReady(build)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": "g2k5ln95n6"
|
||||
}
|
||||
@@ -10,12 +10,14 @@ export const CommonCommandOptions = z.object({
|
||||
apiUrl: z.string().optional(),
|
||||
logLevel: z.enum(["debug", "info", "log", "warn", "error", "none"]).default("log"),
|
||||
skipTelemetry: z.boolean().default(false),
|
||||
profile: z.string().default("default"),
|
||||
});
|
||||
|
||||
export type CommonCommandOptions = z.infer<typeof CommonCommandOptions>;
|
||||
|
||||
export function commonOptions(command: Command) {
|
||||
return command
|
||||
.option("--profile <profile>", "The login profile to use", "default")
|
||||
.option("-a, --api-url <value>", "Override the API URL", "https://api.trigger.dev")
|
||||
.option(
|
||||
"-l, --log-level <level>",
|
||||
@@ -25,9 +27,9 @@ export function commonOptions(command: Command) {
|
||||
.option("--skip-telemetry", "Opt-out of sending telemetry");
|
||||
}
|
||||
|
||||
export class SkipLoggingError extends Error {}
|
||||
export class SkipCommandError extends Error {}
|
||||
export class OutroCommandError extends SkipCommandError {}
|
||||
export class SkipLoggingError extends Error { }
|
||||
export class SkipCommandError extends Error { }
|
||||
export class OutroCommandError extends SkipCommandError { }
|
||||
|
||||
export async function handleTelemetry(action: () => Promise<void>) {
|
||||
try {
|
||||
|
||||
@@ -3,12 +3,10 @@ import { configureDeployCommand } from "../commands/deploy.js";
|
||||
import { configureDevCommand } from "../commands/dev.js";
|
||||
import { configureInitCommand } from "../commands/init.js";
|
||||
import { configureLoginCommand } from "../commands/login.js";
|
||||
import { logoutCommand } from "../commands/logout.js";
|
||||
import { updateCommand } from "../commands/update.js";
|
||||
import { configureLogoutCommand } from "../commands/logout.js";
|
||||
import { configureWhoamiCommand } from "../commands/whoami.js";
|
||||
import { COMMAND_NAME } from "../consts.js";
|
||||
import { getVersion } from "../utilities/getVersion.js";
|
||||
import { printInitialBanner } from "../utilities/initialBanner.js";
|
||||
|
||||
export const program = new Command();
|
||||
|
||||
@@ -19,35 +17,7 @@ program
|
||||
|
||||
configureLoginCommand(program);
|
||||
configureInitCommand(program);
|
||||
|
||||
program
|
||||
.command("logout")
|
||||
.description("Logout of Trigger.dev")
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.action(async (options) => {
|
||||
try {
|
||||
await printInitialBanner(false);
|
||||
await logoutCommand(options);
|
||||
//todo login command
|
||||
} catch (e) {
|
||||
//todo error reporting
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
configureDevCommand(program);
|
||||
configureDeployCommand(program);
|
||||
|
||||
program
|
||||
.command("update")
|
||||
.description(
|
||||
"Updates all @trigger.dev/* packages to their latest compatible versions or the specified version"
|
||||
)
|
||||
.argument("[path]", "The path to the directory that contains the package.json file", ".")
|
||||
.option("-t, --to <version tag>", "The version to update to (ex: 2.1.4)", "latest")
|
||||
.action(async (path, options) => {
|
||||
await printInitialBanner(false);
|
||||
await updateCommand(path, options);
|
||||
});
|
||||
|
||||
configureWhoamiCommand(program);
|
||||
configureLogoutCommand(program);
|
||||
|
||||
@@ -134,7 +134,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
|
||||
intro("Deploying project");
|
||||
|
||||
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl });
|
||||
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl, profile: options.profile });
|
||||
|
||||
if (!authorization.ok) {
|
||||
if (authorization.error === "fetch failed") {
|
||||
@@ -228,7 +228,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
const deploymentSpinner = spinner();
|
||||
|
||||
deploymentSpinner.start(`Deploying version ${version}`);
|
||||
const registryHost = new URL(deploymentEnv.data.apiUrl).host;
|
||||
const registryHost = deploymentResponse.data.registryHost ?? options.registry ?? "registry.trigger.dev";
|
||||
|
||||
const buildImage = async () => {
|
||||
if (options.selfHosted) {
|
||||
@@ -345,8 +345,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
|
||||
);
|
||||
} else {
|
||||
outro(
|
||||
`Version ${version} deployed with ${taskCount} detected task${
|
||||
taskCount === 1 ? "" : "s"
|
||||
`Version ${version} deployed with ${taskCount} detected task${taskCount === 1 ? "" : "s"
|
||||
} ${deploymentLink}`
|
||||
);
|
||||
}
|
||||
@@ -510,14 +509,14 @@ type BuildAndPushImageOptions = {
|
||||
|
||||
type BuildAndPushImageResults =
|
||||
| {
|
||||
ok: true;
|
||||
image: string;
|
||||
digest?: string;
|
||||
}
|
||||
ok: true;
|
||||
image: string;
|
||||
digest?: string;
|
||||
}
|
||||
| {
|
||||
ok: false;
|
||||
error: string;
|
||||
};
|
||||
ok: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
async function buildAndPushImage(
|
||||
options: BuildAndPushImageOptions
|
||||
@@ -579,6 +578,7 @@ async function buildAndPushImage(
|
||||
DEPOT_TOKEN: options.buildToken,
|
||||
DEPOT_PROJECT_ID: options.buildProjectId,
|
||||
DEPOT_NO_SUMMARY_LINK: "1",
|
||||
DEPOT_NO_UPDATE_NOTIFIER: "1",
|
||||
DOCKER_CONFIG: dockerConfigDir,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ClientOptions, WebSocket as wsWebSocket } from "ws";
|
||||
import { z } from "zod";
|
||||
import * as packageJson from "../../package.json";
|
||||
import { CliApiClient } from "../apiClient";
|
||||
import { CommonCommandOptions } from "../cli/common.js";
|
||||
import { CommonCommandOptions, commonOptions, wrapCommandAction } from "../cli/common.js";
|
||||
import { readConfig } from "../utilities/configFiles";
|
||||
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { detectPackageNameFromImportPath } from "../utilities/installPackages";
|
||||
@@ -47,46 +47,31 @@ const DevCommandOptions = CommonCommandOptions.extend({
|
||||
type DevCommandOptions = z.infer<typeof DevCommandOptions>;
|
||||
|
||||
export function configureDevCommand(program: Command) {
|
||||
program
|
||||
.command("dev")
|
||||
.description("Run your Trigger.dev tasks locally")
|
||||
.argument("[path]", "The path to the project", ".")
|
||||
.option(
|
||||
"-l, --log-level <level>",
|
||||
"The log level to use (debug, info, log, warn, error, none)",
|
||||
"log"
|
||||
)
|
||||
.option(
|
||||
"-c, --config <config file>",
|
||||
"The name of the config file, found at [path]",
|
||||
"trigger.config.mjs"
|
||||
)
|
||||
.option(
|
||||
"-p, --project-ref <project ref>",
|
||||
"The project ref. Required if there is no config file."
|
||||
)
|
||||
.option("--debugger", "Enable the debugger")
|
||||
.option("--debug-otel", "Enable OpenTelemetry debugging")
|
||||
.action(async (path, options) => {
|
||||
try {
|
||||
await devCommand(path, options);
|
||||
} catch (e) {
|
||||
//todo error reporting
|
||||
throw e;
|
||||
}
|
||||
return commonOptions(
|
||||
program
|
||||
.command("dev")
|
||||
.description("Run your Trigger.dev tasks locally")
|
||||
.argument("[path]", "The path to the project", ".")
|
||||
.option(
|
||||
"-c, --config <config file>",
|
||||
"The name of the config file, found at [path]",
|
||||
"trigger.config.mjs"
|
||||
)
|
||||
.option(
|
||||
"-p, --project-ref <project ref>",
|
||||
"The project ref. Required if there is no config file."
|
||||
)
|
||||
.option("--debugger", "Enable the debugger")
|
||||
.option("--debug-otel", "Enable OpenTelemetry debugging")
|
||||
).action(async (path, options) => {
|
||||
wrapCommandAction("dev", DevCommandOptions, options, async (opts) => {
|
||||
await devCommand(path, opts);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function devCommand(dir: string, anyOptions: unknown) {
|
||||
const options = DevCommandOptions.safeParse(anyOptions);
|
||||
|
||||
if (!options.success) {
|
||||
console.log(fromZodError(options.error).toString());
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const authorization = await isLoggedIn();
|
||||
export async function devCommand(dir: string, options: DevCommandOptions) {
|
||||
const authorization = await isLoggedIn(options.profile);
|
||||
|
||||
if (!authorization.ok) {
|
||||
if (authorization.error === "fetch failed") {
|
||||
@@ -101,7 +86,7 @@ export async function devCommand(dir: string, anyOptions: unknown) {
|
||||
let watcher;
|
||||
|
||||
try {
|
||||
const devInstance = await startDev(dir, options.data, authorization.auth);
|
||||
const devInstance = await startDev(dir, options, authorization.auth);
|
||||
watcher = devInstance.watcher;
|
||||
const { waitUntilExit } = devInstance.devReactElement;
|
||||
await waitUntilExit();
|
||||
|
||||
@@ -80,7 +80,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
|
||||
|
||||
intro("Initializing project");
|
||||
|
||||
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl });
|
||||
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl, profile: options.profile });
|
||||
|
||||
if (!authorization.ok) {
|
||||
if (authorization.error === "fetch failed") {
|
||||
@@ -96,6 +96,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
|
||||
"cli.userId": authorization.userId,
|
||||
"cli.email": authorization.email,
|
||||
"cli.config.apiUrl": authorization.auth.apiUrl,
|
||||
"cli.config.profile": authorization.profile,
|
||||
});
|
||||
|
||||
if (!options.overrideConfig) {
|
||||
|
||||
@@ -14,11 +14,12 @@ import {
|
||||
wrapCommandAction,
|
||||
} from "../cli/common.js";
|
||||
import { chalkLink } from "../utilities/colors.js";
|
||||
import { readAuthConfigFile, writeAuthConfigFile } from "../utilities/configFiles.js";
|
||||
import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js";
|
||||
import { getVersion } from "../utilities/getVersion.js";
|
||||
import { printInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { LoginResult } from "../utilities/session.js";
|
||||
import { whoAmI } from "./whoami.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
|
||||
export const LoginCommandOptions = CommonCommandOptions.extend({
|
||||
apiUrl: z.string(),
|
||||
@@ -48,12 +49,13 @@ export async function loginCommand(options: unknown) {
|
||||
}
|
||||
|
||||
async function _loginCommand(options: LoginCommandOptions) {
|
||||
return login({ defaultApiUrl: options.apiUrl, embedded: false });
|
||||
return login({ defaultApiUrl: options.apiUrl, embedded: false, profile: options.profile });
|
||||
}
|
||||
|
||||
export type LoginOptions = {
|
||||
defaultApiUrl?: string;
|
||||
embedded?: boolean;
|
||||
profile?: string;
|
||||
};
|
||||
|
||||
export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
@@ -63,16 +65,17 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
|
||||
span.setAttributes({
|
||||
"cli.config.apiUrl": opts.defaultApiUrl,
|
||||
"cli.options.profile": opts.profile,
|
||||
});
|
||||
|
||||
if (!opts.embedded) {
|
||||
intro("Logging in to Trigger.dev");
|
||||
}
|
||||
|
||||
const authConfig = readAuthConfigFile();
|
||||
const authConfig = readAuthConfigProfile(options?.profile);
|
||||
|
||||
if (authConfig && authConfig.accessToken) {
|
||||
const whoAmIResult = await whoAmI(undefined, opts.embedded);
|
||||
const whoAmIResult = await whoAmI({ profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel }, opts.embedded);
|
||||
|
||||
if (!whoAmIResult.success) {
|
||||
throw new Error(whoAmIResult.error);
|
||||
@@ -106,6 +109,7 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
profile: options?.profile ?? "default",
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
@@ -126,6 +130,7 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
profile: options?.profile ?? "default",
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
@@ -170,9 +175,9 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
|
||||
getPersonalAccessTokenSpinner.stop(`Logged in with token ${indexResult.obfuscatedToken}`);
|
||||
|
||||
writeAuthConfigFile({ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl });
|
||||
writeAuthConfigProfile({ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl }, options?.profile);
|
||||
|
||||
const whoAmIResult = await whoAmI(undefined, opts.embedded);
|
||||
const whoAmIResult = await whoAmI({ profile: options?.profile ?? "default", skipTelemetry: !span.isRecording(), logLevel: logger.loggerLevel }, opts.embedded);
|
||||
|
||||
if (!whoAmIResult.success) {
|
||||
throw new Error(whoAmIResult.error);
|
||||
@@ -188,6 +193,7 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
profile: options?.profile ?? "default",
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
import { readAuthConfigFile, writeAuthConfigFile } from "../utilities/configFiles.js";
|
||||
import { Command } from "commander";
|
||||
import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { CommonCommandOptions, commonOptions, handleTelemetry, wrapCommandAction } from "../cli/common.js";
|
||||
import { printInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export async function logoutCommand(options: any) {
|
||||
const config = readAuthConfigFile();
|
||||
const LogoutCommandOptions = CommonCommandOptions;
|
||||
|
||||
type LogoutCommandOptions = z.infer<typeof LogoutCommandOptions>;
|
||||
|
||||
export function configureLogoutCommand(program: Command) {
|
||||
return commonOptions(program
|
||||
.command("logout")
|
||||
.description("Logout of Trigger.dev"))
|
||||
.action(async (options) => {
|
||||
await handleTelemetry(async () => {
|
||||
await printInitialBanner(false);
|
||||
await logoutCommand(options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function logoutCommand(options: unknown) {
|
||||
return await wrapCommandAction("logoutCommand", LogoutCommandOptions, options, async (opts) => {
|
||||
return await logout(opts);
|
||||
});
|
||||
}
|
||||
|
||||
export async function logout(options: LogoutCommandOptions) {
|
||||
const config = readAuthConfigProfile(options.profile);
|
||||
|
||||
if (!config?.accessToken) {
|
||||
logger.info("You are already logged out");
|
||||
logger.info(`You are already logged out [${options.profile ?? "default"}]`);
|
||||
return;
|
||||
}
|
||||
|
||||
writeAuthConfigFile({ ...config, accessToken: undefined, apiUrl: undefined });
|
||||
writeAuthConfigProfile({ ...config, accessToken: undefined, apiUrl: undefined }, options.profile);
|
||||
|
||||
logger.info("Logged out");
|
||||
logger.info(`Logged out of Trigger.dev [${options.profile ?? "default"}]`);
|
||||
}
|
||||
|
||||
@@ -1,68 +1,65 @@
|
||||
import { note, spinner } from "@clack/prompts";
|
||||
import { intro, note, spinner } from "@clack/prompts";
|
||||
import { chalkLink } from "../utilities/colors.js";
|
||||
import { logger } from "../utilities/logger.js";
|
||||
import { isLoggedIn } from "../utilities/session.js";
|
||||
import { Command } from "commander";
|
||||
import { printInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { CommonCommandOptions } from "../cli/common.js";
|
||||
import { CommonCommandOptions, commonOptions, handleTelemetry, wrapCommandAction } from "../cli/common.js";
|
||||
import { z } from "zod";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
|
||||
type WhoAmIResult =
|
||||
| {
|
||||
success: true;
|
||||
data: {
|
||||
userId: string;
|
||||
email: string;
|
||||
dashboardUrl: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
error: string;
|
||||
success: true;
|
||||
data: {
|
||||
userId: string;
|
||||
email: string;
|
||||
dashboardUrl: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
const WhoamiCommandOptions = CommonCommandOptions;
|
||||
|
||||
type WhoamiCommandOptions = z.infer<typeof WhoamiCommandOptions>;
|
||||
|
||||
export function configureWhoamiCommand(program: Command) {
|
||||
program
|
||||
return commonOptions(program
|
||||
.command("whoami")
|
||||
.description("display the current logged in user and project details")
|
||||
.option(
|
||||
"-l, --log-level <level>",
|
||||
"The log level to use (debug, info, log, warn, error, none)",
|
||||
"log"
|
||||
)
|
||||
.description("display the current logged in user and project details"))
|
||||
.action(async (options) => {
|
||||
try {
|
||||
await printInitialBanner();
|
||||
await whoAmI(WhoamiCommandOptions.parse(options));
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
await handleTelemetry(async () => {
|
||||
await printInitialBanner(false);
|
||||
await whoAmICommand(options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function whoAmICommand(options: unknown) {
|
||||
return await wrapCommandAction("whoamiCommand", WhoamiCommandOptions, options, async (opts) => {
|
||||
return await whoAmI(opts);
|
||||
});
|
||||
}
|
||||
|
||||
export async function whoAmI(
|
||||
options?: WhoamiCommandOptions,
|
||||
embedded: boolean = false
|
||||
): Promise<WhoAmIResult> {
|
||||
if (options?.logLevel) {
|
||||
logger.loggerLevel = options?.logLevel;
|
||||
}
|
||||
intro(`Displaying your account details [${options?.profile ?? "default"}]`);
|
||||
|
||||
const loadingSpinner = spinner();
|
||||
loadingSpinner.start("Checking your account details");
|
||||
|
||||
const authentication = await isLoggedIn();
|
||||
const authentication = await isLoggedIn(options?.profile);
|
||||
|
||||
if (!authentication.ok) {
|
||||
if (authentication.error === "fetch failed") {
|
||||
loadingSpinner.stop("Fetch failed. Platform down?");
|
||||
} else {
|
||||
loadingSpinner.stop("You must login first. Use `trigger.dev login` to login.");
|
||||
loadingSpinner.stop(`You must login first. Use \`trigger.dev login --profile ${options?.profile ?? "default"}\` to login.`);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -90,7 +87,7 @@ export async function whoAmI(
|
||||
Email: ${userData.data.email}
|
||||
URL: ${chalkLink(authentication.auth.apiUrl)}
|
||||
`,
|
||||
"Account details"
|
||||
`Account details [${authentication.profile}]`
|
||||
);
|
||||
} else {
|
||||
loadingSpinner.stop(`Retrieved your account details for ${userData.data.email}`);
|
||||
|
||||
@@ -24,11 +24,53 @@ export const UserAuthConfigSchema = z.object({
|
||||
|
||||
export type UserAuthConfig = z.infer<typeof UserAuthConfigSchema>;
|
||||
|
||||
const UserAuthConfigFileSchema = z.record(UserAuthConfigSchema);
|
||||
|
||||
type UserAuthConfigFile = z.infer<typeof UserAuthConfigFileSchema>;
|
||||
|
||||
function getAuthConfigFilePath() {
|
||||
return path.join(getGlobalConfigFolderPath(), "default.json");
|
||||
}
|
||||
|
||||
export function writeAuthConfigFile(config: UserAuthConfig) {
|
||||
export function writeAuthConfigProfile(config: UserAuthConfig, profile: string = "default") {
|
||||
const existingConfig = readAuthConfigFile() || {};
|
||||
|
||||
existingConfig[profile] = config;
|
||||
|
||||
writeAuthConfigFile(existingConfig);
|
||||
}
|
||||
|
||||
export function readAuthConfigProfile(profile: string = "default"): UserAuthConfig | undefined {
|
||||
try {
|
||||
const authConfigFilePath = getAuthConfigFilePath();
|
||||
|
||||
logger.debug(`Reading auth config file`, { authConfigFilePath });
|
||||
|
||||
const json = readJSONFileSync(authConfigFilePath);
|
||||
const parsed = UserAuthConfigFileSchema.parse(json);
|
||||
return parsed[profile];
|
||||
} catch (error) {
|
||||
logger.debug(`Error reading auth config file: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function readAuthConfigFile(): UserAuthConfigFile | undefined {
|
||||
try {
|
||||
const authConfigFilePath = getAuthConfigFilePath();
|
||||
|
||||
logger.debug(`Reading auth config file`, { authConfigFilePath });
|
||||
|
||||
const json = readJSONFileSync(authConfigFilePath);
|
||||
const parsed = UserAuthConfigFileSchema.parse(json);
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
logger.debug(`Error reading auth config file: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function writeAuthConfigFile(config: UserAuthConfigFile) {
|
||||
const authConfigFilePath = getAuthConfigFilePath();
|
||||
mkdirSync(path.dirname(authConfigFilePath), {
|
||||
recursive: true,
|
||||
@@ -38,19 +80,6 @@ export function writeAuthConfigFile(config: UserAuthConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
export function readAuthConfigFile(): UserAuthConfig | undefined {
|
||||
try {
|
||||
const authConfigFilePath = getAuthConfigFilePath();
|
||||
|
||||
const json = readJSONFileSync(authConfigFilePath);
|
||||
const parsed = UserAuthConfigSchema.parse(json);
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
logger.debug(`Error reading auth config file: ${error}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfigPath(dir: string, fileName?: string): Promise<string | undefined> {
|
||||
return await findUp(fileName ? [fileName] : CONFIG_FILES, { cwd: dir });
|
||||
}
|
||||
@@ -62,14 +91,14 @@ export type ReadConfigOptions = {
|
||||
|
||||
export type ReadConfigResult =
|
||||
| {
|
||||
status: "file";
|
||||
config: ResolvedConfig;
|
||||
path: string;
|
||||
}
|
||||
status: "file";
|
||||
config: ResolvedConfig;
|
||||
path: string;
|
||||
}
|
||||
| {
|
||||
status: "in-memory";
|
||||
config: ResolvedConfig;
|
||||
};
|
||||
status: "in-memory";
|
||||
config: ResolvedConfig;
|
||||
};
|
||||
|
||||
export async function readConfig(
|
||||
dir: string,
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import { recordSpanException } from "@trigger.dev/core/v3";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
import { readAuthConfigFile } from "./configFiles.js";
|
||||
import { readAuthConfigProfile } from "./configFiles.js";
|
||||
import { getTracer } from "../telemetry/tracing.js";
|
||||
|
||||
const tracer = getTracer();
|
||||
|
||||
export type LoginResult =
|
||||
| {
|
||||
ok: true;
|
||||
userId: string;
|
||||
email: string;
|
||||
dashboardUrl: string;
|
||||
auth: {
|
||||
apiUrl: string;
|
||||
accessToken: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
ok: false;
|
||||
error: string;
|
||||
auth?: {
|
||||
apiUrl: string;
|
||||
accessToken: string;
|
||||
};
|
||||
ok: true;
|
||||
profile: string,
|
||||
userId: string;
|
||||
email: string;
|
||||
dashboardUrl: string;
|
||||
auth: {
|
||||
apiUrl: string;
|
||||
accessToken: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
ok: false;
|
||||
error: string;
|
||||
auth?: {
|
||||
apiUrl: string;
|
||||
accessToken: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function isLoggedIn(): Promise<LoginResult> {
|
||||
export async function isLoggedIn(profile: string = "default"): Promise<LoginResult> {
|
||||
return await tracer.startActiveSpan("isLoggedIn", async (span) => {
|
||||
try {
|
||||
const config = readAuthConfigFile();
|
||||
const config = readAuthConfigProfile(profile);
|
||||
|
||||
if (!config?.accessToken || !config?.apiUrl) {
|
||||
span.recordException(new Error("You must login first"));
|
||||
@@ -57,12 +58,14 @@ export async function isLoggedIn(): Promise<LoginResult> {
|
||||
"login.userId": userData.data.userId,
|
||||
"login.email": userData.data.email,
|
||||
"login.dashboardUrl": userData.data.dashboardUrl,
|
||||
"login.profile": profile,
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
profile,
|
||||
userId: userData.data.userId,
|
||||
email: userData.data.email,
|
||||
dashboardUrl: userData.data.dashboardUrl,
|
||||
|
||||
@@ -547,8 +547,8 @@ class TaskRunProcess {
|
||||
...(this.worker.debugOtel ? { OTEL_LOG_LEVEL: "debug" } : {}),
|
||||
},
|
||||
execArgv: this.worker.debuggerOn
|
||||
? ["--inspect-brk", "--trace-uncaught"]
|
||||
: ["--trace-uncaught"],
|
||||
? ["--inspect-brk", "--trace-uncaught", "--no-warnings=ExperimentalWarning"]
|
||||
: ["--trace-uncaught", "--no-warnings=ExperimentalWarning"],
|
||||
});
|
||||
|
||||
this._child.on("message", this.#handleMessage.bind(this));
|
||||
|
||||
@@ -42,7 +42,7 @@ export { ConsoleInterceptor } from "./consoleInterceptor";
|
||||
export {
|
||||
flattenAttributes,
|
||||
unflattenAttributes,
|
||||
flattenAndNormalizeAttributes,
|
||||
primitiveValueOrflattenedAttributes,
|
||||
} from "./utils/flattenAttributes";
|
||||
export { defaultRetryOptions, calculateNextRetryDelay, calculateResetAt } from "./utils/retries";
|
||||
export { accessoryAttributes } from "./utils/styleAttributes";
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ResourceAttributes,
|
||||
ResourceDetectionConfig,
|
||||
detectResourcesSync,
|
||||
processDetectorSync,
|
||||
} from "@opentelemetry/resources";
|
||||
import { LoggerProvider, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
||||
import {
|
||||
@@ -89,7 +90,7 @@ export class TracingSDK {
|
||||
: {};
|
||||
|
||||
const commonResources = detectResourcesSync({
|
||||
detectors: [this.asyncResourceDetector],
|
||||
detectors: [this.asyncResourceDetector, processDetectorSync],
|
||||
})
|
||||
.merge(
|
||||
new Resource({
|
||||
|
||||
@@ -141,6 +141,7 @@ export const InitializeDeploymentResponseBody = z.object({
|
||||
version: z.string(),
|
||||
imageTag: z.string(),
|
||||
externalBuildData: ExternalBuildData.optional().nullable(),
|
||||
registryHost: z.string().optional(),
|
||||
});
|
||||
|
||||
export type InitializeDeploymentResponseBody = z.infer<typeof InitializeDeploymentResponseBody>;
|
||||
|
||||
@@ -95,13 +95,27 @@ export function unflattenAttributes(obj: Attributes): Record<string, unknown> {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function flattenAndNormalizeAttributes(
|
||||
export function primitiveValueOrflattenedAttributes(
|
||||
obj: Record<string, unknown> | Array<unknown> | string | boolean | number | undefined,
|
||||
prefix: string
|
||||
): Attributes {
|
||||
prefix: string | undefined
|
||||
): Attributes | string | number | boolean | undefined {
|
||||
if (
|
||||
typeof obj === "string" ||
|
||||
typeof obj === "number" ||
|
||||
typeof obj === "boolean" ||
|
||||
obj === null ||
|
||||
obj === undefined
|
||||
) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const attributes = flattenAttributes(obj, prefix);
|
||||
|
||||
if (typeof attributes[prefix] !== "undefined" && attributes[prefix] !== null) {
|
||||
if (
|
||||
prefix !== undefined &&
|
||||
typeof attributes[prefix] !== "undefined" &&
|
||||
attributes[prefix] !== null
|
||||
) {
|
||||
return attributes[prefix] as unknown as Attributes;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user