v3: cli init command + CLI telemetry (#948)
* Adding telemetry to the deploy command * Add ability to opt-out of telemetry in the CLI * Remove log * Unifying some telemetry stuff and adding it to the login command * init command * Remove extra trigger dirs * Couple tweaks to the init CLI * Don’t use npm init -y to create the package.json file
This commit is contained in:
@@ -54,6 +54,8 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
throw new Response(null, { status: 404, statusText: "Organization not found" });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
return typedjson({
|
||||
organization: {
|
||||
id: organization.id,
|
||||
@@ -61,6 +63,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
slug: organizationSlug,
|
||||
projectsCount: organization._count.projects,
|
||||
},
|
||||
defaultVersion: url.searchParams.get("version") ?? "v2",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +103,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
};
|
||||
|
||||
export default function NewOrganizationPage() {
|
||||
const { organization } = useTypedLoaderData<typeof loader>();
|
||||
const { organization, defaultVersion } = useTypedLoaderData<typeof loader>();
|
||||
const lastSubmission = useActionData();
|
||||
const { v3Enabled } = useFeatures();
|
||||
|
||||
@@ -144,7 +147,7 @@ export default function NewOrganizationPage() {
|
||||
<SelectGroup>
|
||||
<Select
|
||||
{...conform.input(projectVersion, { type: "select" })}
|
||||
defaultValue={"v2"}
|
||||
defaultValue={defaultVersion}
|
||||
>
|
||||
<SelectTrigger width="full" size="medium">
|
||||
<SelectValue placeholder="Project version" />
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { GetProjectResponseBody, GetProjectsResponseBody } from "@trigger.dev/core/v3";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
projectRef: z.string(),
|
||||
});
|
||||
|
||||
export async function loader({ request, params }: LoaderFunctionArgs) {
|
||||
logger.info("get project", { url: request.url });
|
||||
|
||||
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
|
||||
}
|
||||
|
||||
const parsedParams = ParamsSchema.safeParse(params);
|
||||
|
||||
if (!parsedParams.success) {
|
||||
return json({ error: "Invalid Params" }, { status: 400 });
|
||||
}
|
||||
|
||||
const { projectRef } = parsedParams.data;
|
||||
|
||||
const project = await prisma.project.findUnique({
|
||||
where: {
|
||||
externalRef: projectRef,
|
||||
organization: {
|
||||
deletedAt: null,
|
||||
members: {
|
||||
some: {
|
||||
userId: authenticationResult.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
deletedAt: null,
|
||||
},
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return json({ error: "Project not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
if (project.version !== "V3") {
|
||||
return json({ error: "Project found but was not a v3 project" }, { status: 404 });
|
||||
}
|
||||
|
||||
const result: GetProjectResponseBody = {
|
||||
id: project.id,
|
||||
externalRef: project.externalRef,
|
||||
name: project.name,
|
||||
slug: project.slug,
|
||||
createdAt: project.createdAt,
|
||||
organization: {
|
||||
id: project.organization.id,
|
||||
title: project.organization.title,
|
||||
slug: project.organization.slug,
|
||||
createdAt: project.organization.createdAt,
|
||||
},
|
||||
};
|
||||
|
||||
return json(result);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { GetProjectsResponseBody } from "@trigger.dev/core/v3";
|
||||
import { prisma } from "~/db.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
logger.info("get projects", { url: request.url });
|
||||
|
||||
const authenticationResult = await authenticateApiRequestWithPersonalAccessToken(request);
|
||||
|
||||
if (!authenticationResult) {
|
||||
return json({ error: "Invalid or Missing Access Token" }, { status: 401 });
|
||||
}
|
||||
|
||||
const projects = await prisma.project.findMany({
|
||||
where: {
|
||||
organization: {
|
||||
deletedAt: null,
|
||||
members: {
|
||||
some: {
|
||||
userId: authenticationResult.userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: "V3",
|
||||
deletedAt: null,
|
||||
},
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!projects) {
|
||||
return json({ error: "Projects not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const result: GetProjectsResponseBody = projects.map((project) => ({
|
||||
id: project.id,
|
||||
externalRef: project.externalRef,
|
||||
name: project.name,
|
||||
slug: project.slug,
|
||||
createdAt: project.createdAt,
|
||||
organization: {
|
||||
id: project.organization.id,
|
||||
title: project.organization.title,
|
||||
slug: project.organization.slug,
|
||||
createdAt: project.organization.createdAt,
|
||||
},
|
||||
}));
|
||||
|
||||
return json(result);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { json } from "@remix-run/server-runtime";
|
||||
import { WhoAmIResponse } from "@trigger.dev/core/v3";
|
||||
import { prisma } from "~/db.server";
|
||||
import { env } from "~/env.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
|
||||
|
||||
@@ -29,6 +30,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const result: WhoAmIResponse = {
|
||||
userId: authenticationResult.userId,
|
||||
email: user.email,
|
||||
dashboardUrl: env.APP_ORIGIN,
|
||||
};
|
||||
return json(result);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
|
||||
import { getUsersInvites } from "~/models/member.server";
|
||||
import { SelectBestProjectPresenter } from "~/presenters/SelectBestProjectPresenter.server";
|
||||
import { requireUser } from "~/services/session.server";
|
||||
import { invitesPath, newOrganizationPath, newProjectPath } from "~/utils/pathBuilder";
|
||||
|
||||
//this loader chooses the best project to redirect you to, ideally based on the cookie
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const user = await requireUser(request);
|
||||
|
||||
const url = new URL(request.url);
|
||||
|
||||
const presenter = new SelectBestProjectPresenter();
|
||||
|
||||
try {
|
||||
const { organization } = await presenter.call({ userId: user.id, request });
|
||||
//redirect them to the most appropriate project
|
||||
return redirect(`${newProjectPath(organization)}${url.search}`);
|
||||
} catch (e) {
|
||||
const invites = await getUsersInvites({ email: user.email });
|
||||
|
||||
if (invites.length > 0) {
|
||||
return redirect(invitesPath());
|
||||
}
|
||||
|
||||
//this should only happen if the user has no projects, and no invites
|
||||
return redirect(newOrganizationPath());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "~/db.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
|
||||
const ParamsSchema = z.object({
|
||||
projectRef: z.string(),
|
||||
});
|
||||
|
||||
export async function loader({ params, request }: LoaderFunctionArgs) {
|
||||
const userId = await requireUserId(request);
|
||||
|
||||
const validatedParams = ParamsSchema.parse(params);
|
||||
|
||||
const project = await prisma.project.findFirst({
|
||||
where: {
|
||||
externalRef: validatedParams.projectRef,
|
||||
organization: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
organization: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Redirect to the project's runs page
|
||||
return redirect(`/orgs/${project.organization.slug}/projects/v3/${project.slug}`);
|
||||
}
|
||||
@@ -75,6 +75,7 @@
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@baselime/node-opentelemetry": "^0.4.6",
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@depot/cli": "0.0.1-cli.2.55.0",
|
||||
"@opentelemetry/api": "^1.7.0",
|
||||
@@ -118,7 +119,6 @@
|
||||
"object-hash": "^3.0.0",
|
||||
"p-throttle": "^6.1.0",
|
||||
"partysocket": "^0.0.17",
|
||||
"posthog-node": "^3.1.1",
|
||||
"proxy-agent": "^6.3.0",
|
||||
"react": "^18.2.0",
|
||||
"react-error-boundary": "^4.0.12",
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
InitializeDeploymentRequestBody,
|
||||
StartDeploymentIndexingRequestBody,
|
||||
GetDeploymentResponseBody,
|
||||
GetProjectsResponseBody,
|
||||
GetProjectResponseBody,
|
||||
} from "@trigger.dev/core/v3";
|
||||
|
||||
export class CliApiClient {
|
||||
@@ -56,6 +58,32 @@ export class CliApiClient {
|
||||
});
|
||||
}
|
||||
|
||||
async getProject(projectRef: string) {
|
||||
if (!this.accessToken) {
|
||||
throw new Error("getProject: No access token");
|
||||
}
|
||||
|
||||
return zodfetch(GetProjectResponseBody, `${this.apiURL}/api/v1/projects/${projectRef}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getProjects() {
|
||||
if (!this.accessToken) {
|
||||
throw new Error("getProjects: No access token");
|
||||
}
|
||||
|
||||
return zodfetch(GetProjectsResponseBody, `${this.apiURL}/api/v1/projects`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createBackgroundWorker(projectRef: string, body: CreateBackgroundWorkerRequestBody) {
|
||||
if (!this.accessToken) {
|
||||
throw new Error("createBackgroundWorker: No access token");
|
||||
|
||||
@@ -1,7 +1,93 @@
|
||||
import { flattenAttributes, recordSpanException } from "@trigger.dev/core/v3";
|
||||
import { Command } from "commander";
|
||||
import { z } from "zod";
|
||||
import { getTracer, provider } from "../telemetry/tracing";
|
||||
import { fromZodError } from "zod-validation-error";
|
||||
import { logger } from "../utilities/logger";
|
||||
import { outro } from "@clack/prompts";
|
||||
|
||||
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),
|
||||
});
|
||||
|
||||
export type CommonCommandOptions = z.infer<typeof CommonCommandOptions>;
|
||||
|
||||
export function commonOptions(command: Command) {
|
||||
return command
|
||||
.option("-a, --api-url <value>", "Override the API URL", "https://api.trigger.dev")
|
||||
.option(
|
||||
"-l, --log-level <level>",
|
||||
"The log level to use (debug, info, log, warn, error, none)",
|
||||
"log"
|
||||
)
|
||||
.option("--skip-telemetry", "Opt-out of sending telemetry");
|
||||
}
|
||||
|
||||
export class SkipLoggingError extends Error {}
|
||||
export class SkipCommandError extends Error {}
|
||||
export class OutroCommandError extends SkipCommandError {}
|
||||
|
||||
export async function handleTelemetry(action: () => Promise<void>) {
|
||||
try {
|
||||
await action();
|
||||
|
||||
await provider?.forceFlush();
|
||||
} catch (e) {
|
||||
await provider?.forceFlush();
|
||||
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
export const tracer = getTracer();
|
||||
|
||||
export async function wrapCommandAction<T extends z.AnyZodObject, TResult>(
|
||||
name: string,
|
||||
schema: T,
|
||||
options: unknown,
|
||||
action: (opts: z.output<T>) => Promise<TResult>
|
||||
): Promise<TResult> {
|
||||
return await tracer.startActiveSpan(name, async (span) => {
|
||||
try {
|
||||
const parsedOptions = schema.safeParse(options);
|
||||
|
||||
if (!parsedOptions.success) {
|
||||
throw new Error(fromZodError(parsedOptions.error).toString());
|
||||
}
|
||||
|
||||
span.setAttributes({
|
||||
...flattenAttributes(parsedOptions.data, "cli.options"),
|
||||
});
|
||||
|
||||
logger.loggerLevel = parsedOptions.data.logLevel;
|
||||
|
||||
logger.debug(`Running "${name}" with the following options`, {
|
||||
options: options,
|
||||
spanContext: span?.spanContext(),
|
||||
});
|
||||
|
||||
const result = await action(parsedOptions.data);
|
||||
|
||||
span.end();
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e instanceof SkipLoggingError) {
|
||||
recordSpanException(span, e);
|
||||
} else if (e instanceof OutroCommandError) {
|
||||
outro("Operation cancelled");
|
||||
} else if (e instanceof SkipCommandError) {
|
||||
// do nothing
|
||||
} else {
|
||||
recordSpanException(span, e);
|
||||
logger.error(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,45 +1,24 @@
|
||||
import { Command } from "commander";
|
||||
import { z } from "zod";
|
||||
import { configureDeployCommand } from "../commands/deploy.js";
|
||||
import { configureDevCommand } from "../commands/dev.js";
|
||||
import { loginCommand } from "../commands/login.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 { configureWhoamiCommand } from "../commands/whoami.js";
|
||||
import { COMMAND_NAME } from "../consts.js";
|
||||
import { getVersion } from "../utilities/getVersion.js";
|
||||
import { printInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { configureDeployCommand } from "../commands/deploy.js";
|
||||
|
||||
export const program = new Command();
|
||||
|
||||
export const ApiUrlOptionsSchema = z.object({
|
||||
apiUrl: z.string(),
|
||||
});
|
||||
|
||||
program
|
||||
.name(COMMAND_NAME)
|
||||
.description("Create, run locally and deploy Trigger.dev background tasks.")
|
||||
.version(getVersion(), "-v, --version", "Display the version number");
|
||||
|
||||
program
|
||||
.command("login")
|
||||
.description("Login with Trigger.dev so you can perform authenticated actions")
|
||||
.option(
|
||||
"-a, --api-url <value>",
|
||||
"Override the API URL, defaults to https://api.trigger.dev",
|
||||
"https://api.trigger.dev"
|
||||
)
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.action(async (options) => {
|
||||
try {
|
||||
await printInitialBanner(false);
|
||||
await loginCommand(options);
|
||||
//todo login command
|
||||
} catch (e) {
|
||||
//todo error reporting
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
configureLoginCommand(program);
|
||||
configureInitCommand(program);
|
||||
|
||||
program
|
||||
.command("logout")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,7 +101,7 @@ export async function devCommand(dir: string, anyOptions: unknown) {
|
||||
let watcher;
|
||||
|
||||
try {
|
||||
const devInstance = await startDev(dir, options.data, authorization.config);
|
||||
const devInstance = await startDev(dir, options.data, authorization.auth);
|
||||
watcher = devInstance.watcher;
|
||||
const { waitUntilExit } = devInstance.devReactElement;
|
||||
await waitUntilExit();
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
import { intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
|
||||
import { context, trace } from "@opentelemetry/api";
|
||||
import {
|
||||
GetProjectResponseBody,
|
||||
flattenAttributes,
|
||||
recordSpanException,
|
||||
} from "@trigger.dev/core/v3";
|
||||
import chalk from "chalk";
|
||||
import { Command } from "commander";
|
||||
import { execa } from "execa";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import terminalLink from "terminal-link";
|
||||
import { z } from "zod";
|
||||
import { CliApiClient } from "../apiClient";
|
||||
import {
|
||||
CommonCommandOptions,
|
||||
OutroCommandError,
|
||||
SkipCommandError,
|
||||
SkipLoggingError,
|
||||
commonOptions,
|
||||
handleTelemetry,
|
||||
tracer,
|
||||
wrapCommandAction,
|
||||
} from "../cli/common.js";
|
||||
import { readConfig } from "../utilities/configFiles.js";
|
||||
import { createFileFromTemplate } from "../utilities/createFileFromTemplate";
|
||||
import { createFile, pathExists } from "../utilities/fileSystem";
|
||||
import { getUserPackageManager } from "../utilities/getUserPackageManager";
|
||||
import { printStandloneInitialBanner } from "../utilities/initialBanner.js";
|
||||
import { logger } from "../utilities/logger";
|
||||
import { resolveInternalFilePath } from "../utilities/resolveInternalFilePath";
|
||||
import { login } from "./login";
|
||||
|
||||
const InitCommandOptions = CommonCommandOptions.extend({
|
||||
projectRef: z.string().optional(),
|
||||
overrideConfig: z.boolean().default(false),
|
||||
tag: z.string().default("latest"),
|
||||
skipPackageInstall: z.boolean().default(false),
|
||||
});
|
||||
|
||||
type InitCommandOptions = z.infer<typeof InitCommandOptions>;
|
||||
|
||||
export function configureInitCommand(program: Command) {
|
||||
return commonOptions(
|
||||
program
|
||||
.command("init")
|
||||
.description("Initialize your existing project for development with Trigger.dev")
|
||||
.argument("[path]", "The path to the project", ".")
|
||||
.option(
|
||||
"-p, --project-ref <project ref>",
|
||||
"The project ref to use when initializing the project"
|
||||
)
|
||||
.option(
|
||||
"-p, --project-ref <project ref>",
|
||||
"The project ref to use when initializing the project"
|
||||
)
|
||||
.option(
|
||||
"-t, --tag <package tag>",
|
||||
"The version of the @trigger.dev/sdk package to install",
|
||||
"latest"
|
||||
)
|
||||
.option("--skip-package-install", "Skip installing the @trigger.dev/sdk package")
|
||||
.option("--override-config", "Override the existing config file if it exists")
|
||||
).action(async (path, options) => {
|
||||
await handleTelemetry(async () => {
|
||||
await printStandloneInitialBanner(true);
|
||||
await initCommand(path, options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function initCommand(dir: string, options: unknown) {
|
||||
return await wrapCommandAction("initCommand", InitCommandOptions, options, async (opts) => {
|
||||
return await _initCommand(dir, opts);
|
||||
});
|
||||
}
|
||||
|
||||
async function _initCommand(dir: string, options: InitCommandOptions) {
|
||||
const span = trace.getSpan(context.active());
|
||||
|
||||
intro("Initializing project");
|
||||
|
||||
const authorization = await login({ embedded: true, defaultApiUrl: options.apiUrl });
|
||||
|
||||
if (!authorization.ok) {
|
||||
if (authorization.error === "fetch failed") {
|
||||
throw new Error(
|
||||
`Failed to connect to ${authorization.auth?.apiUrl}. Are you sure it's the correct URL?`
|
||||
);
|
||||
} else {
|
||||
throw new Error("You must login first. Use `trigger.dev login` to login.");
|
||||
}
|
||||
}
|
||||
|
||||
span?.setAttributes({
|
||||
"cli.userId": authorization.userId,
|
||||
"cli.email": authorization.email,
|
||||
"cli.config.apiUrl": authorization.auth.apiUrl,
|
||||
});
|
||||
|
||||
if (!options.overrideConfig) {
|
||||
try {
|
||||
// check to see if there is an existing trigger.dev config file in the project directory
|
||||
const result = await readConfig(dir);
|
||||
|
||||
outro(
|
||||
result.status === "file"
|
||||
? `Project already initialized: Found config file at ${result.path}. Pass --override-config to override`
|
||||
: "Project already initialized"
|
||||
);
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
const apiClient = new CliApiClient(authorization.auth.apiUrl, authorization.auth.accessToken);
|
||||
|
||||
const selectedProject = await selectProject(
|
||||
apiClient,
|
||||
authorization.dashboardUrl,
|
||||
options.projectRef
|
||||
);
|
||||
|
||||
span?.setAttributes({
|
||||
...flattenAttributes(selectedProject, "cli.project"),
|
||||
});
|
||||
|
||||
logger.debug("Selected project", selectedProject);
|
||||
|
||||
log.step(`Configuring project "${selectedProject.name}" (${selectedProject.externalRef})`);
|
||||
|
||||
// Install @trigger.dev/sdk package
|
||||
if (!options.skipPackageInstall) {
|
||||
await installPackages(dir, options);
|
||||
} else {
|
||||
log.info("Skipping package installation");
|
||||
}
|
||||
|
||||
// Create the config file
|
||||
await writeConfigFile(dir, selectedProject, options);
|
||||
|
||||
// Create the trigger dir
|
||||
await createTriggerDir(dir, options);
|
||||
|
||||
const projectDashboard = terminalLink(
|
||||
"project dashboard",
|
||||
`${authorization.dashboardUrl}/projects/v3/${selectedProject.externalRef}`
|
||||
);
|
||||
|
||||
log.success("Successfully initialized project for Trigger.dev v3 🫡");
|
||||
log.info("Next steps:");
|
||||
log.info(
|
||||
` 1. To start developing, run ${chalk.green(
|
||||
"npx trigger.dev@latest dev"
|
||||
)} in your project directory`
|
||||
);
|
||||
log.info(` 2. Visit your ${projectDashboard} to view your newly created tasks.`);
|
||||
log.info(
|
||||
` 3. Head over to our ${terminalLink(
|
||||
"v3 docs",
|
||||
"https://trigger.dev/docs/v3"
|
||||
)} to learn more.`
|
||||
);
|
||||
log.info(
|
||||
` 4. Need help? Join our ${terminalLink(
|
||||
"Discord community",
|
||||
"https://trigger.dev/discord"
|
||||
)} or email us at ${chalk.cyan("help@trigger.dev")}`
|
||||
);
|
||||
|
||||
outro(`Project initialized successfully. Happy coding!`);
|
||||
}
|
||||
|
||||
async function createTriggerDir(dir: string, options: InitCommandOptions) {
|
||||
return await tracer.startActiveSpan("createTriggerDir", async (span) => {
|
||||
try {
|
||||
const location = await text({
|
||||
message: "Where would you like to create the Trigger.dev directory?",
|
||||
defaultValue: `${dir}/src/trigger`,
|
||||
placeholder: `${dir}/src/trigger`,
|
||||
});
|
||||
|
||||
if (isCancel(location)) {
|
||||
throw new OutroCommandError();
|
||||
}
|
||||
|
||||
const triggerDir = resolve(process.cwd(), location);
|
||||
|
||||
span.setAttributes({
|
||||
"cli.triggerDir": triggerDir,
|
||||
});
|
||||
|
||||
if (await pathExists(triggerDir)) {
|
||||
throw new Error(`Directory already exists at ${triggerDir}`);
|
||||
}
|
||||
|
||||
const exampleSelection = await select({
|
||||
message: `Choose an example to create in the ${location} directory`,
|
||||
options: [
|
||||
{ value: "simple", label: "Simple (Hello World)" },
|
||||
{
|
||||
value: "none",
|
||||
label: "None",
|
||||
hint: "skip creating an example",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (isCancel(exampleSelection)) {
|
||||
throw new OutroCommandError();
|
||||
}
|
||||
|
||||
const example = exampleSelection as string;
|
||||
|
||||
span.setAttributes({
|
||||
"cli.example": example,
|
||||
});
|
||||
|
||||
if (example === "none") {
|
||||
// Create a .gitkeep file in the trigger dir
|
||||
await createFile(join(triggerDir, ".gitkeep"), "");
|
||||
|
||||
log.step(`Created directory at ${location}`);
|
||||
|
||||
span.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const exampleFile = resolveInternalFilePath(`./templates/examples/${example}.js`);
|
||||
const outputPath = join(triggerDir, "example.ts");
|
||||
|
||||
await createFileFromTemplate({
|
||||
templatePath: exampleFile,
|
||||
outputPath,
|
||||
replacements: {},
|
||||
});
|
||||
|
||||
const relativeOutputPath = relative(process.cwd(), outputPath);
|
||||
|
||||
log.step(`Created example file at ${relativeOutputPath}`);
|
||||
|
||||
span.end();
|
||||
} catch (e) {
|
||||
if (!(e instanceof SkipCommandError)) {
|
||||
recordSpanException(span, e);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function installPackages(dir: string, options: InitCommandOptions) {
|
||||
return await tracer.startActiveSpan("installPackages", async (span) => {
|
||||
const installSpinner = spinner();
|
||||
|
||||
try {
|
||||
const projectDir = resolve(process.cwd(), dir);
|
||||
const pkgManager = await getUserPackageManager(projectDir);
|
||||
|
||||
span.setAttributes({
|
||||
"cli.projectDir": projectDir,
|
||||
"cli.packageManager": pkgManager,
|
||||
"cli.tag": options.tag,
|
||||
});
|
||||
|
||||
switch (pkgManager) {
|
||||
case "npm": {
|
||||
installSpinner.start(`Running npm install @trigger.dev/sdk@${options.tag}`);
|
||||
|
||||
await execa("npm", ["install", `@trigger.dev/sdk@${options.tag}`], {
|
||||
cwd: projectDir,
|
||||
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "pnpm": {
|
||||
installSpinner.start(`Running pnpm add @trigger.dev/sdk@${options.tag}`);
|
||||
|
||||
await execa("pnpm", ["add", `@trigger.dev/sdk@${options.tag}`], {
|
||||
cwd: projectDir,
|
||||
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case "yarn": {
|
||||
installSpinner.start(`Running yarn add @trigger.dev/sdk@${options.tag}`);
|
||||
|
||||
await execa("yarn", ["add", `@trigger.dev/sdk@${options.tag}`], {
|
||||
cwd: projectDir,
|
||||
stdio: options.logLevel === "debug" ? "inherit" : "ignore",
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
installSpinner.stop(`@trigger.dev/sdk@${options.tag} installed`);
|
||||
|
||||
span.end();
|
||||
} catch (e) {
|
||||
installSpinner.stop(
|
||||
`Failed to install @trigger.dev/sdk@${options.tag}. Rerun command with --log-level debug for more details.`
|
||||
);
|
||||
|
||||
if (!(e instanceof SkipCommandError)) {
|
||||
recordSpanException(span, e);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function writeConfigFile(
|
||||
dir: string,
|
||||
project: GetProjectResponseBody,
|
||||
options: InitCommandOptions
|
||||
) {
|
||||
return await tracer.startActiveSpan("writeConfigFile", async (span) => {
|
||||
try {
|
||||
const spnnr = spinner();
|
||||
spnnr.start("Creating config file");
|
||||
|
||||
const projectDir = resolve(process.cwd(), dir);
|
||||
const templatePath = resolveInternalFilePath("./templates/trigger.config.mjs");
|
||||
const outputPath = join(projectDir, "trigger.config.mjs");
|
||||
|
||||
span.setAttributes({
|
||||
"cli.projectDir": projectDir,
|
||||
"cli.templatePath": templatePath,
|
||||
"cli.outputPath": outputPath,
|
||||
});
|
||||
|
||||
const result = await createFileFromTemplate({
|
||||
templatePath,
|
||||
replacements: {
|
||||
projectRef: project.externalRef,
|
||||
},
|
||||
outputPath,
|
||||
override: options.overrideConfig,
|
||||
});
|
||||
|
||||
const relativePathToOutput = relative(process.cwd(), outputPath);
|
||||
|
||||
spnnr.stop(
|
||||
result.success
|
||||
? `Config file created at ${relativePathToOutput}`
|
||||
: `Failed to create config file: ${result.error}`
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new SkipLoggingError(result.error);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
return result.success;
|
||||
} catch (e) {
|
||||
if (!(e instanceof SkipCommandError)) {
|
||||
recordSpanException(span, e);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function selectProject(apiClient: CliApiClient, dashboardUrl: string, projectRef?: string) {
|
||||
return await tracer.startActiveSpan("selectProject", async (span) => {
|
||||
try {
|
||||
if (projectRef) {
|
||||
const projectResponse = await apiClient.getProject(projectRef);
|
||||
|
||||
if (!projectResponse.success) {
|
||||
log.error(
|
||||
`--project-ref ${projectRef} is not a valid project ref. Request to fetch data resulted in: ${projectResponse.error}`
|
||||
);
|
||||
|
||||
throw new SkipCommandError(projectResponse.error);
|
||||
}
|
||||
|
||||
span.setAttributes({
|
||||
...flattenAttributes(projectResponse.data, "cli.project"),
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return projectResponse.data;
|
||||
}
|
||||
|
||||
const projectsResponse = await apiClient.getProjects();
|
||||
|
||||
if (!projectsResponse.success) {
|
||||
throw new Error(`Failed to get projects: ${projectsResponse.error}`);
|
||||
}
|
||||
|
||||
if (projectsResponse.data.length === 0) {
|
||||
const newProjectLink = terminalLink(
|
||||
"Create new project",
|
||||
`${dashboardUrl}/projects/new?version=v3`
|
||||
);
|
||||
|
||||
outro(`You don't have any projects yet. ${newProjectLink}`);
|
||||
|
||||
throw new SkipCommandError();
|
||||
}
|
||||
|
||||
const selectedProject = await select({
|
||||
message: "Select an existing Trigger.dev project",
|
||||
options: projectsResponse.data.map((project) => ({
|
||||
value: project.externalRef,
|
||||
label: `${project.name} - ${project.externalRef}`,
|
||||
hint: project.organization.title,
|
||||
})),
|
||||
});
|
||||
|
||||
if (isCancel(selectedProject)) {
|
||||
throw new OutroCommandError();
|
||||
}
|
||||
|
||||
const projectData = projectsResponse.data.find(
|
||||
(project) => project.externalRef === selectedProject
|
||||
);
|
||||
|
||||
if (!projectData) {
|
||||
throw new Error("Invalid project ref");
|
||||
}
|
||||
|
||||
span.setAttributes({
|
||||
...flattenAttributes(projectData, "cli.project"),
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return projectData;
|
||||
} catch (e) {
|
||||
if (!(e instanceof SkipCommandError)) {
|
||||
recordSpanException(span, e);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,136 +1,292 @@
|
||||
import { intro, log, outro, select, spinner } from "@clack/prompts";
|
||||
import { recordSpanException } from "@trigger.dev/core/v3";
|
||||
import { Command } from "commander";
|
||||
import open from "open";
|
||||
import pRetry, { AbortError } from "p-retry";
|
||||
import { ApiUrlOptionsSchema } from "../cli/index.js";
|
||||
import { z } from "zod";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
import {
|
||||
CommonCommandOptions,
|
||||
SkipLoggingError,
|
||||
commonOptions,
|
||||
handleTelemetry,
|
||||
tracer,
|
||||
wrapCommandAction,
|
||||
} from "../cli/common.js";
|
||||
import { chalkLink } from "../utilities/colors.js";
|
||||
import { readAuthConfigFile, writeAuthConfigFile } from "../utilities/configFiles.js";
|
||||
import { logger } from "../utilities/logger.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 { CliApiClient } from "../apiClient.js";
|
||||
|
||||
export async function loginCommand(options: any) {
|
||||
const result = ApiUrlOptionsSchema.safeParse(options);
|
||||
if (!result.success) {
|
||||
logger.error(result.error.message);
|
||||
return;
|
||||
}
|
||||
export const LoginCommandOptions = CommonCommandOptions.extend({
|
||||
apiUrl: z.string(),
|
||||
});
|
||||
|
||||
return login(result.data.apiUrl);
|
||||
export type LoginCommandOptions = z.infer<typeof LoginCommandOptions>;
|
||||
|
||||
export function configureLoginCommand(program: Command) {
|
||||
return commonOptions(
|
||||
program
|
||||
.command("login")
|
||||
.description("Login with Trigger.dev so you can perform authenticated actions")
|
||||
)
|
||||
.version(getVersion(), "-v, --version", "Display the version number")
|
||||
.action(async (options) => {
|
||||
await handleTelemetry(async () => {
|
||||
await printInitialBanner(false);
|
||||
await loginCommand(options);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export type LoginResult =
|
||||
| {
|
||||
success: true;
|
||||
accessToken: string;
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
error: string;
|
||||
};
|
||||
export async function loginCommand(options: unknown) {
|
||||
return await wrapCommandAction("loginCommand", LoginCommandOptions, options, async (opts) => {
|
||||
return await _loginCommand(opts);
|
||||
});
|
||||
}
|
||||
|
||||
export async function login(apiUrl: string): Promise<LoginResult> {
|
||||
const apiClient = new CliApiClient(apiUrl);
|
||||
async function _loginCommand(options: LoginCommandOptions) {
|
||||
return login({ defaultApiUrl: options.apiUrl, embedded: false });
|
||||
}
|
||||
|
||||
intro("Logging in to Trigger.dev");
|
||||
export type LoginOptions = {
|
||||
defaultApiUrl?: string;
|
||||
embedded?: boolean;
|
||||
};
|
||||
|
||||
const existingAccessToken = readAuthConfigFile()?.accessToken;
|
||||
if (existingAccessToken) {
|
||||
const whoAmiI = await whoAmI();
|
||||
export async function login(options?: LoginOptions): Promise<LoginResult> {
|
||||
return await tracer.startActiveSpan("login", async (span) => {
|
||||
try {
|
||||
const opts = { defaultApiUrl: "https://api.trigger.dev", embedded: false, ...options };
|
||||
|
||||
const continueOption = await select({
|
||||
message: "You are already logged in.",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
label: "Exit",
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
label: "Login with a different account",
|
||||
},
|
||||
],
|
||||
initialValue: false,
|
||||
});
|
||||
span.setAttributes({
|
||||
"cli.config.apiUrl": opts.defaultApiUrl,
|
||||
});
|
||||
|
||||
if (continueOption !== true) {
|
||||
outro("Already logged in");
|
||||
return {
|
||||
success: true,
|
||||
accessToken: existingAccessToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//generate authorization code
|
||||
const createAuthCodeSpinner = spinner();
|
||||
createAuthCodeSpinner.start("Creating authorition code");
|
||||
const authorizationCodeResult = await apiClient.createAuthorizationCode();
|
||||
if (!authorizationCodeResult.success) {
|
||||
createAuthCodeSpinner.stop(
|
||||
`Failed to create authorization code\n${authorizationCodeResult.error}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: authorizationCodeResult.error,
|
||||
};
|
||||
}
|
||||
createAuthCodeSpinner.stop("Created authorization code");
|
||||
|
||||
//Link the user to the authorization code
|
||||
log.step(
|
||||
`Please visit the following URL to login:\n${chalkLink(authorizationCodeResult.data.url)}`
|
||||
);
|
||||
await open(authorizationCodeResult.data.url);
|
||||
|
||||
//poll for personal access token (we need to poll for it)
|
||||
const getPersonalAccessTokenSpinner = spinner();
|
||||
getPersonalAccessTokenSpinner.start("Waiting for you to login");
|
||||
try {
|
||||
const indexResult = await pRetry(
|
||||
() => getPersonalAccessToken(apiClient, authorizationCodeResult.data.authorizationCode),
|
||||
{
|
||||
//this means we're polling, same distance between each attempt
|
||||
factor: 1,
|
||||
retries: 60,
|
||||
minTimeout: 1000,
|
||||
if (!opts.embedded) {
|
||||
intro("Logging in to Trigger.dev");
|
||||
}
|
||||
);
|
||||
|
||||
getPersonalAccessTokenSpinner.stop(`Logged in with token ${indexResult.obfuscatedToken}`);
|
||||
const authConfig = readAuthConfigFile();
|
||||
|
||||
writeAuthConfigFile({ accessToken: indexResult.token, apiUrl });
|
||||
if (authConfig && authConfig.accessToken) {
|
||||
const whoAmIResult = await whoAmI(undefined, opts.embedded);
|
||||
|
||||
outro("Logged in successfully");
|
||||
if (!whoAmIResult.success) {
|
||||
throw new Error(whoAmIResult.error);
|
||||
} else {
|
||||
if (!opts.embedded) {
|
||||
const continueOption = await select({
|
||||
message: "You are already logged in.",
|
||||
options: [
|
||||
{
|
||||
value: false,
|
||||
label: "Exit",
|
||||
},
|
||||
{
|
||||
value: true,
|
||||
label: "Login with a different account",
|
||||
},
|
||||
],
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
accessToken: indexResult.token,
|
||||
};
|
||||
} catch (e) {
|
||||
getPersonalAccessTokenSpinner.stop(`Failed to get access token`);
|
||||
if (e instanceof AbortError) {
|
||||
log.error(e.message);
|
||||
if (continueOption !== true) {
|
||||
outro("Already logged in");
|
||||
|
||||
span.setAttributes({
|
||||
"cli.userId": whoAmIResult.data.userId,
|
||||
"cli.email": whoAmIResult.data.email,
|
||||
"cli.config.apiUrl": authConfig.apiUrl ?? opts.defaultApiUrl,
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
auth: {
|
||||
accessToken: authConfig.accessToken,
|
||||
apiUrl: authConfig.apiUrl ?? opts.defaultApiUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
span.setAttributes({
|
||||
"cli.userId": whoAmIResult.data.userId,
|
||||
"cli.email": whoAmIResult.data.email,
|
||||
"cli.config.apiUrl": authConfig.apiUrl ?? opts.defaultApiUrl,
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
auth: {
|
||||
accessToken: authConfig.accessToken,
|
||||
apiUrl: authConfig.apiUrl ?? opts.defaultApiUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.embedded) {
|
||||
log.step("You must login to continue.");
|
||||
}
|
||||
|
||||
const apiClient = new CliApiClient(authConfig?.apiUrl ?? opts.defaultApiUrl);
|
||||
|
||||
//generate authorization code
|
||||
const authorizationCodeResult = await createAuthorizationCode(apiClient);
|
||||
|
||||
//Link the user to the authorization code
|
||||
log.step(
|
||||
`Please visit the following URL to login:\n${chalkLink(authorizationCodeResult.url)}`
|
||||
);
|
||||
|
||||
await open(authorizationCodeResult.url);
|
||||
|
||||
//poll for personal access token (we need to poll for it)
|
||||
const getPersonalAccessTokenSpinner = spinner();
|
||||
getPersonalAccessTokenSpinner.start("Waiting for you to login");
|
||||
try {
|
||||
const indexResult = await pRetry(
|
||||
() => getPersonalAccessToken(apiClient, authorizationCodeResult.authorizationCode),
|
||||
{
|
||||
//this means we're polling, same distance between each attempt
|
||||
factor: 1,
|
||||
retries: 60,
|
||||
minTimeout: 1000,
|
||||
}
|
||||
);
|
||||
|
||||
getPersonalAccessTokenSpinner.stop(`Logged in with token ${indexResult.obfuscatedToken}`);
|
||||
|
||||
writeAuthConfigFile({ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl });
|
||||
|
||||
const whoAmIResult = await whoAmI(undefined, opts.embedded);
|
||||
|
||||
if (!whoAmIResult.success) {
|
||||
throw new Error(whoAmIResult.error);
|
||||
}
|
||||
|
||||
if (opts.embedded) {
|
||||
log.step("Logged in successfully");
|
||||
} else {
|
||||
outro("Logged in successfully");
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
userId: whoAmIResult.data.userId,
|
||||
email: whoAmIResult.data.email,
|
||||
dashboardUrl: whoAmIResult.data.dashboardUrl,
|
||||
auth: {
|
||||
accessToken: indexResult.token,
|
||||
apiUrl: authConfig?.apiUrl ?? opts.defaultApiUrl,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
getPersonalAccessTokenSpinner.stop(`Failed to get access token`);
|
||||
|
||||
if (e instanceof AbortError) {
|
||||
log.error(e.message);
|
||||
}
|
||||
|
||||
recordSpanException(span, e);
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: false as const,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
recordSpanException(span, e);
|
||||
span.end();
|
||||
|
||||
if (options?.embedded) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: e instanceof Error ? e.message : String(e),
|
||||
};
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: e instanceof Error ? e.message : JSON.stringify(e),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function getPersonalAccessToken(apiClient: CliApiClient, authorizationCode: string) {
|
||||
const token = await apiClient.getPersonalAccessToken(authorizationCode);
|
||||
return await tracer.startActiveSpan("getPersonalAccessToken", async (span) => {
|
||||
try {
|
||||
const token = await apiClient.getPersonalAccessToken(authorizationCode);
|
||||
|
||||
if (!token.success) {
|
||||
throw new AbortError(token.error);
|
||||
}
|
||||
if (!token.success) {
|
||||
throw new AbortError(token.error);
|
||||
}
|
||||
|
||||
if (!token.data.token) {
|
||||
throw new Error("No token found yet");
|
||||
}
|
||||
if (!token.data.token) {
|
||||
throw new Error("No token found yet");
|
||||
}
|
||||
|
||||
return {
|
||||
token: token.data.token.token,
|
||||
obfuscatedToken: token.data.token.obfuscatedToken,
|
||||
};
|
||||
span.end();
|
||||
|
||||
return {
|
||||
token: token.data.token.token,
|
||||
obfuscatedToken: token.data.token.obfuscatedToken,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof AbortError) {
|
||||
recordSpanException(span, e);
|
||||
}
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function createAuthorizationCode(apiClient: CliApiClient) {
|
||||
return await tracer.startActiveSpan("createAuthorizationCode", async (span) => {
|
||||
try {
|
||||
//generate authorization code
|
||||
const createAuthCodeSpinner = spinner();
|
||||
createAuthCodeSpinner.start("Creating authorition code");
|
||||
const authorizationCodeResult = await apiClient.createAuthorizationCode();
|
||||
|
||||
if (!authorizationCodeResult.success) {
|
||||
createAuthCodeSpinner.stop(
|
||||
`Failed to create authorization code\n${authorizationCodeResult.error}`
|
||||
);
|
||||
|
||||
throw new SkipLoggingError(
|
||||
`Failed to create authorization code\n${authorizationCodeResult.error}`
|
||||
);
|
||||
}
|
||||
|
||||
createAuthCodeSpinner.stop("Created authorization code");
|
||||
|
||||
span.end();
|
||||
|
||||
return authorizationCodeResult.data;
|
||||
} catch (e) {
|
||||
recordSpanException(span, e);
|
||||
|
||||
span.end();
|
||||
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type WhoAmIResult =
|
||||
data: {
|
||||
userId: string;
|
||||
email: string;
|
||||
dashboardUrl: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
@@ -44,7 +45,10 @@ export function configureWhoamiCommand(program: Command) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function whoAmI(options?: WhoamiCommandOptions): Promise<WhoAmIResult> {
|
||||
export async function whoAmI(
|
||||
options?: WhoamiCommandOptions,
|
||||
embedded: boolean = false
|
||||
): Promise<WhoAmIResult> {
|
||||
if (options?.logLevel) {
|
||||
logger.loggerLevel = options?.logLevel;
|
||||
}
|
||||
@@ -67,10 +71,7 @@ export async function whoAmI(options?: WhoamiCommandOptions): Promise<WhoAmIResu
|
||||
};
|
||||
}
|
||||
|
||||
const apiClient = new CliApiClient(
|
||||
authentication.config.apiUrl,
|
||||
authentication.config.accessToken
|
||||
);
|
||||
const apiClient = new CliApiClient(authentication.auth.apiUrl, authentication.auth.accessToken);
|
||||
const userData = await apiClient.whoAmI();
|
||||
|
||||
if (!userData.success) {
|
||||
@@ -82,15 +83,18 @@ export async function whoAmI(options?: WhoamiCommandOptions): Promise<WhoAmIResu
|
||||
};
|
||||
}
|
||||
|
||||
loadingSpinner.stop("Retrieved your account details");
|
||||
|
||||
note(
|
||||
`User ID: ${userData.data.userId}
|
||||
if (!embedded) {
|
||||
loadingSpinner.stop("Retrieved your account details");
|
||||
note(
|
||||
`User ID: ${userData.data.userId}
|
||||
Email: ${userData.data.email}
|
||||
URL: ${chalkLink(authentication.config.apiUrl)}
|
||||
URL: ${chalkLink(authentication.auth.apiUrl)}
|
||||
`,
|
||||
"Account details"
|
||||
);
|
||||
"Account details"
|
||||
);
|
||||
} else {
|
||||
loadingSpinner.stop(`Retrieved your account details for ${userData.data.email}`);
|
||||
}
|
||||
|
||||
return userData;
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { PostHog } from "posthog-node";
|
||||
import { nanoid } from "nanoid";
|
||||
import { getVersion } from "../utilities/getVersion.js";
|
||||
|
||||
const postHogApiKey = "phc_9aSDbJCaDUMdZdHxxMPTvcj7A9fsl3mCgM1RBPmPsl7";
|
||||
|
||||
export class TelemetryClient {
|
||||
#client: PostHog;
|
||||
#sessionId: string;
|
||||
#version: string;
|
||||
|
||||
constructor() {
|
||||
this.#client = new PostHog(postHogApiKey, {
|
||||
host: "https://eu.posthog.com",
|
||||
flushAt: 1,
|
||||
});
|
||||
this.#sessionId = `cli-${nanoid()}`;
|
||||
this.#version = getVersion();
|
||||
}
|
||||
|
||||
identify(organizationId: string, projectId: string, userId?: string) {
|
||||
if (userId) {
|
||||
this.#client.alias({
|
||||
distinctId: userId,
|
||||
alias: this.#sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
this.#client.groupIdentify({
|
||||
groupType: "organization",
|
||||
groupKey: organizationId,
|
||||
});
|
||||
|
||||
this.#client.groupIdentify({
|
||||
groupType: "project",
|
||||
groupKey: projectId,
|
||||
});
|
||||
}
|
||||
|
||||
dev = {
|
||||
started: (path: string, options: Record<string, string | number | boolean>) => {
|
||||
this.#client.capture({
|
||||
distinctId: this.#sessionId,
|
||||
event: "cli_dev_started",
|
||||
properties: { ...options, path },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const telemetryClient = new TelemetryClient();
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BaselimeSDK } from "@baselime/node-opentelemetry";
|
||||
import { trace } from "@opentelemetry/api";
|
||||
import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
|
||||
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
|
||||
import * as packageJson from "../../package.json";
|
||||
|
||||
const sdk = new BaselimeSDK({
|
||||
baselimeKey: "e9f963244f8b092850d42e34a5339b2d5e68070b".split("").reverse().join(""), // this is a joke
|
||||
instrumentations: [new FetchInstrumentation()],
|
||||
service: "cli-v3",
|
||||
serverless: true,
|
||||
});
|
||||
|
||||
function initializeTracing(): NodeTracerProvider | undefined {
|
||||
if (!process.argv.includes("--skip-telemetry")) {
|
||||
return sdk.start();
|
||||
}
|
||||
}
|
||||
|
||||
export const provider = initializeTracing();
|
||||
|
||||
export function getTracer() {
|
||||
return trace.getTracer("trigger.dev cli", packageJson.version);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { logger, task, wait } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const helloWorldTask = task({
|
||||
id: "hello-world",
|
||||
run: async (payload: any, { ctx }) => {
|
||||
logger.log("Hello, world!", { payload, ctx });
|
||||
|
||||
await wait.for({ seconds: 5 });
|
||||
|
||||
return {
|
||||
message: "Hello, world!",
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
/** @type {import('@trigger.dev/sdk/v3').Config} */
|
||||
|
||||
export default {
|
||||
project: "${projectRef}",
|
||||
retries: {
|
||||
enabledInDev: false,
|
||||
default: {
|
||||
maxAttempts: 3,
|
||||
minTimeoutInMs: 1000,
|
||||
maxTimeoutInMs: 10000,
|
||||
factor: 2,
|
||||
randomize: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import fs from "fs/promises";
|
||||
import { pathExists, readFile } from "./fileSystem";
|
||||
import path from "path";
|
||||
|
||||
type Result =
|
||||
| {
|
||||
success: true;
|
||||
alreadyExisted: boolean;
|
||||
}
|
||||
| {
|
||||
success: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export async function createFileFromTemplate(params: {
|
||||
templatePath: string;
|
||||
replacements: Record<string, string>;
|
||||
outputPath: string;
|
||||
override?: boolean;
|
||||
}): Promise<Result> {
|
||||
let template = await readFile(params.templatePath);
|
||||
|
||||
if ((await pathExists(params.outputPath)) && !params.override) {
|
||||
return {
|
||||
success: true,
|
||||
alreadyExisted: true,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const output = replaceAll(template, params.replacements);
|
||||
|
||||
const directoryName = path.dirname(params.outputPath);
|
||||
await fs.mkdir(directoryName, { recursive: true });
|
||||
await fs.writeFile(params.outputPath, output);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
alreadyExisted: false,
|
||||
};
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
return {
|
||||
success: false,
|
||||
error: e.message,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: JSON.stringify(e),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// find strings that match ${varName} and replace with the value from a Record<string, string> where { varName: "value" }
|
||||
export function replaceAll(input: string, replacements: Record<string, string>) {
|
||||
let output = input;
|
||||
for (const [key, value] of Object.entries(replacements)) {
|
||||
output = output.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@@ -41,7 +41,7 @@ After installation, run Trigger.dev with \`npx trigger.dev\`.`
|
||||
export async function printStandloneInitialBanner(performUpdateCheck = true) {
|
||||
const packageVersion = getVersion();
|
||||
|
||||
let text = `\n${logo()} ${chalkGrey(`${packageVersion}`)}`;
|
||||
let text = `\n${logo()} ${chalkGrey("(v3 Developer Preview)")}`;
|
||||
|
||||
if (performUpdateCheck) {
|
||||
const maybeNewVersion = await updateCheck();
|
||||
|
||||
@@ -2,7 +2,7 @@ import semver from "semver";
|
||||
import { execa } from "execa";
|
||||
import { logger } from "./logger";
|
||||
import { join } from "node:path";
|
||||
import { readJSONFile } from "./fileSystem";
|
||||
import { readJSONFile, writeJSONFile } from "./fileSystem";
|
||||
|
||||
export type InstallPackagesOptions = { cwd?: string };
|
||||
|
||||
@@ -16,7 +16,11 @@ export async function installPackages(
|
||||
try {
|
||||
await readJSONFile(join(cwd, "package.json"));
|
||||
} catch (error) {
|
||||
await execa("npm", ["init", "-y"], { cwd });
|
||||
await writeJSONFile(join(cwd, "package.json"), {
|
||||
name: "temp",
|
||||
version: "1.0.0",
|
||||
description: "",
|
||||
});
|
||||
}
|
||||
|
||||
// Detect with packages have already been installed at the specified version (use semver to compare)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { resolve as importResolve } from "import-meta-resolve";
|
||||
|
||||
export function resolveInternalFilePath(filePath: string): string {
|
||||
return new URL(importResolve(filePath, import.meta.url)).href.replace("file://", "");
|
||||
}
|
||||
@@ -1,34 +1,84 @@
|
||||
import { recordSpanException } from "@trigger.dev/core/v3";
|
||||
import { CliApiClient } from "../apiClient.js";
|
||||
import { readAuthConfigFile } from "./configFiles.js";
|
||||
import { getTracer } from "../telemetry/tracing.js";
|
||||
|
||||
export async function isLoggedIn() {
|
||||
const config = readAuthConfigFile();
|
||||
const tracer = getTracer();
|
||||
|
||||
if (!config?.accessToken || !config?.apiUrl) {
|
||||
return { ok: false as const, error: "You must login first" };
|
||||
}
|
||||
|
||||
const apiClient = new CliApiClient(config.apiUrl, config.accessToken);
|
||||
const userData = await apiClient.whoAmI();
|
||||
|
||||
if (!userData.success) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: userData.error,
|
||||
config: {
|
||||
apiUrl: config.apiUrl,
|
||||
accessToken: config.accessToken,
|
||||
},
|
||||
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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
userId: userData.data.userId,
|
||||
email: userData.data.email,
|
||||
config: {
|
||||
apiUrl: config.apiUrl,
|
||||
accessToken: config.accessToken,
|
||||
},
|
||||
};
|
||||
export async function isLoggedIn(): Promise<LoginResult> {
|
||||
return await tracer.startActiveSpan("isLoggedIn", async (span) => {
|
||||
try {
|
||||
const config = readAuthConfigFile();
|
||||
|
||||
if (!config?.accessToken || !config?.apiUrl) {
|
||||
span.recordException(new Error("You must login first"));
|
||||
span.end();
|
||||
return { ok: false as const, error: "You must login first" };
|
||||
}
|
||||
|
||||
const apiClient = new CliApiClient(config.apiUrl, config.accessToken);
|
||||
const userData = await apiClient.whoAmI();
|
||||
|
||||
if (!userData.success) {
|
||||
recordSpanException(span, userData.error);
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: false as const,
|
||||
error: userData.error,
|
||||
auth: {
|
||||
apiUrl: config.apiUrl,
|
||||
accessToken: config.accessToken,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
span.setAttributes({
|
||||
"login.userId": userData.data.userId,
|
||||
"login.email": userData.data.email,
|
||||
"login.dashboardUrl": userData.data.dashboardUrl,
|
||||
});
|
||||
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
userId: userData.data.userId,
|
||||
email: userData.data.email,
|
||||
dashboardUrl: userData.data.dashboardUrl,
|
||||
auth: {
|
||||
apiUrl: config.apiUrl,
|
||||
accessToken: config.accessToken,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
recordSpanException(span, e);
|
||||
span.end();
|
||||
|
||||
return {
|
||||
ok: false as const,
|
||||
error: e instanceof Error ? e.message : "Unknown error",
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
const isDev = process.env.npm_lifecycle_event === "dev";
|
||||
const copyTemplates = "cp -r src/templates dist";
|
||||
|
||||
export default defineConfig({
|
||||
clean: false,
|
||||
@@ -14,7 +15,7 @@ export default defineConfig({
|
||||
sourcemap: true,
|
||||
target: "esnext",
|
||||
outDir: "dist",
|
||||
onSuccess: isDev ? `node dist/index.js` : "",
|
||||
onSuccess: isDev ? `${copyTemplates} && node dist/index.js` : copyTemplates,
|
||||
banner: {
|
||||
js: "import { createRequire } from 'module';const require = createRequire(import.meta.url);",
|
||||
},
|
||||
|
||||
@@ -5,10 +5,31 @@ import { QueueOptions } from "./messages";
|
||||
export const WhoAmIResponseSchema = z.object({
|
||||
userId: z.string(),
|
||||
email: z.string().email(),
|
||||
dashboardUrl: z.string(),
|
||||
});
|
||||
|
||||
export type WhoAmIResponse = z.infer<typeof WhoAmIResponseSchema>;
|
||||
|
||||
export const GetProjectResponseBody = z.object({
|
||||
id: z.string(),
|
||||
externalRef: z.string(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
createdAt: z.coerce.date(),
|
||||
organization: z.object({
|
||||
id: z.string(),
|
||||
title: z.string(),
|
||||
slug: z.string(),
|
||||
createdAt: z.coerce.date(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type GetProjectResponseBody = z.infer<typeof GetProjectResponseBody>;
|
||||
|
||||
export const GetProjectsResponseBody = z.array(GetProjectResponseBody);
|
||||
|
||||
export type GetProjectsResponseBody = z.infer<typeof GetProjectsResponseBody>;
|
||||
|
||||
export const GetProjectEnvResponse = z.object({
|
||||
apiKey: z.string(),
|
||||
name: z.string(),
|
||||
|
||||
@@ -7,7 +7,7 @@ export function flattenAttributes(
|
||||
const result: Attributes = {};
|
||||
|
||||
// Check if obj is null or undefined
|
||||
if (obj == null) {
|
||||
if (!obj) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Generated
+82
-4
@@ -1033,6 +1033,7 @@ importers:
|
||||
|
||||
packages/cli-v3:
|
||||
specifiers:
|
||||
'@baselime/node-opentelemetry': ^0.4.6
|
||||
'@clack/prompts': ^0.7.0
|
||||
'@depot/cli': 0.0.1-cli.2.55.0
|
||||
'@opentelemetry/api': ^1.7.0
|
||||
@@ -1090,7 +1091,6 @@ importers:
|
||||
p-retry: ^6.1.0
|
||||
p-throttle: ^6.1.0
|
||||
partysocket: ^0.0.17
|
||||
posthog-node: ^3.1.1
|
||||
proxy-agent: ^6.3.0
|
||||
react: ^18.2.0
|
||||
react-error-boundary: ^4.0.12
|
||||
@@ -1113,6 +1113,7 @@ importers:
|
||||
zod: 3.22.3
|
||||
zod-validation-error: ^1.5.0
|
||||
dependencies:
|
||||
'@baselime/node-opentelemetry': 0.4.6_supports-color@9.4.0
|
||||
'@clack/prompts': 0.7.0
|
||||
'@depot/cli': 0.0.1-cli.2.55.0
|
||||
'@opentelemetry/api': 1.7.0
|
||||
@@ -1156,7 +1157,6 @@ importers:
|
||||
object-hash: 3.0.0
|
||||
p-throttle: 6.1.0
|
||||
partysocket: 0.0.17
|
||||
posthog-node: 3.1.3
|
||||
proxy-agent: 6.3.0_supports-color@9.4.0
|
||||
react: 18.2.0
|
||||
react-error-boundary: 4.0.12_react@18.2.0
|
||||
@@ -2179,7 +2179,7 @@ importers:
|
||||
|
||||
references/v3-catalog:
|
||||
specifiers:
|
||||
'@trigger.dev/sdk': workspace:*
|
||||
'@trigger.dev/sdk': 2.3.18
|
||||
'@trigger.dev/tsconfig': workspace:*
|
||||
'@types/node': 20.4.2
|
||||
concurrently: ^8.2.0
|
||||
@@ -2193,7 +2193,7 @@ importers:
|
||||
tsconfig-paths: ^3.14.1
|
||||
typescript: 5.1.6
|
||||
dependencies:
|
||||
'@trigger.dev/sdk': link:../../packages/trigger-sdk
|
||||
'@trigger.dev/sdk': 2.3.18
|
||||
msw: 2.2.1_typescript@5.1.6
|
||||
openai: 4.28.0
|
||||
stripe: 12.14.0
|
||||
@@ -5590,6 +5590,28 @@ packages:
|
||||
resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==}
|
||||
dev: true
|
||||
|
||||
/@baselime/node-opentelemetry/0.4.6_supports-color@9.4.0:
|
||||
resolution: {integrity: sha512-ZsPa4y0WW9/rGELsaAT3oheM19c1EaTOY3W5Rx5CeNzcB7EZAWL8xMF6fNKf6u8Bk/gQ5x6PHKzd2rs3GeRLmQ==}
|
||||
peerDependencies:
|
||||
'@trpc/server': ^10.0.0 || ^11.0.0
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.7.0
|
||||
'@opentelemetry/exporter-trace-otlp-http': 0.48.0_@opentelemetry+api@1.7.0
|
||||
'@opentelemetry/instrumentation': 0.48.0_rr4fxqkzz7nhz3auplkqn4p6em
|
||||
'@opentelemetry/instrumentation-http': 0.48.0_rr4fxqkzz7nhz3auplkqn4p6em
|
||||
'@opentelemetry/resource-detector-aws': 1.3.6_@opentelemetry+api@1.7.0
|
||||
'@opentelemetry/resources': 1.21.0_@opentelemetry+api@1.7.0
|
||||
'@opentelemetry/sdk-node': 0.48.0_rr4fxqkzz7nhz3auplkqn4p6em
|
||||
'@opentelemetry/sdk-trace-node': 1.21.0_@opentelemetry+api@1.7.0
|
||||
'@types/aws-lambda': 8.10.136
|
||||
axios: 1.6.2
|
||||
flat: 6.0.1
|
||||
undici: 5.28.3
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@bcoe/v8-coverage/0.2.3:
|
||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||
dev: true
|
||||
@@ -17687,6 +17709,11 @@ packages:
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/core-backend/2.3.18:
|
||||
resolution: {integrity: sha512-LVeeerraGeqKNd2gtajQY+mnGWqkYW7Q2r5oWpL5xIZ8aQg3HRhSIfZs1dryexwKlfqnRjGWueGTy2+j1tbzcg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/core/2.0.7:
|
||||
resolution: {integrity: sha512-z86G0sbqu0ePP0eg3jA/v65TOq9ZLCHUpgis/cqaLopsybnMScWt9S9MV1Dq9FVqHHqKvyuLBugZTJwsj+FXGg==}
|
||||
engines: {node: '>=16.8.0'}
|
||||
@@ -17696,6 +17723,15 @@ packages:
|
||||
zod-error: 1.5.0
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/core/2.3.18:
|
||||
resolution: {integrity: sha512-j2EdCeyMkZ+zlVnnHl5zmBb+YURSw4x75NqQU1G5X08pQAza7G0qEn8DDGIMR5ieUMiHP0WS9oYy/voYdNfibQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
ulidx: 2.2.1
|
||||
zod: 3.22.3
|
||||
zod-error: 1.5.0
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/nextjs/1.0.0_fy3ffmrhk4zoekjz4cwhclpq64:
|
||||
resolution: {integrity: sha512-Dt32BaAKYNJqYbPcpdzUOfkgpDRIftyd4oj2lBFilIHZUCJpfG1MdYbr4YWFhNE5Z04Pj0I+uN8kaxTkTZp+Ig==}
|
||||
engines: {node: '>=16.8.0'}
|
||||
@@ -17737,6 +17773,31 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@trigger.dev/sdk/2.3.18:
|
||||
resolution: {integrity: sha512-Bjxgl4BbWOAL8rhxeBkl7SzvLLRBMJjiftq/7W7u96MDyPRFUoZZvVMSZzTJufnLBf/xS2JTi8LWU8gzhDJDvw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
'@trigger.dev/core': 2.3.18
|
||||
'@trigger.dev/core-backend': 2.3.18
|
||||
chalk: 5.3.0
|
||||
cronstrue: 2.21.0
|
||||
debug: 4.3.4
|
||||
evt: 2.5.7
|
||||
get-caller-file: 2.0.5
|
||||
git-remote-origin-url: 4.0.0
|
||||
git-repo-info: 2.1.1
|
||||
slug: 6.1.0
|
||||
terminal-link: 3.0.0
|
||||
ulid: 2.3.0
|
||||
uuid: 9.0.0
|
||||
ws: 8.16.0
|
||||
zod: 3.22.3
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@tsconfig/node10/1.0.9:
|
||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||
|
||||
@@ -17795,6 +17856,10 @@ packages:
|
||||
resolution: {integrity: sha512-vBkIh9AY22kVOCEKo5CJlyCgmSWvasC+SWUxL/x/vOwRobMpI/HG1xp/Ae3AqmSiZeLUbOhW0FCD3ZjqqUxmXw==}
|
||||
dev: false
|
||||
|
||||
/@types/aws-lambda/8.10.136:
|
||||
resolution: {integrity: sha512-cmmgqxdVGhxYK9lZMYYXYRJk6twBo53ivtXjIUEFZxfxe4TkZTZBK3RRWrY2HjJcUIix0mdifn15yjOAat5lTA==}
|
||||
dev: false
|
||||
|
||||
/@types/aws-lambda/8.10.81:
|
||||
resolution: {integrity: sha512-C1rFKGVZ8KwqhwBOYlpoybTSRtxu2433ea6JaO3amc6ubEe08yQoFsPa9aU9YqvX7ppeZ25CnCtC4AH9mhtxsQ==}
|
||||
dev: false
|
||||
@@ -25672,6 +25737,12 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/flat/6.0.1:
|
||||
resolution: {integrity: sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/flatted/3.2.7:
|
||||
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
|
||||
|
||||
@@ -37959,6 +38030,13 @@ packages:
|
||||
dependencies:
|
||||
'@fastify/busboy': 2.0.0
|
||||
|
||||
/undici/5.28.3:
|
||||
resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==}
|
||||
engines: {node: '>=14.0'}
|
||||
dependencies:
|
||||
'@fastify/busboy': 2.0.0
|
||||
dev: false
|
||||
|
||||
/unfetch/4.2.0:
|
||||
resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"start:stripe": "ts-node -r tsconfig-paths/register -r dotenv/config src/stripeUsage.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trigger.dev/sdk": "workspace:*",
|
||||
"@trigger.dev/sdk": "2.3.18",
|
||||
"msw": "^2.2.1",
|
||||
"openai": "^4.28.0",
|
||||
"stripe": "^12.14.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
export default {
|
||||
project: "yubjwjsfkxnylobaqvqz",
|
||||
retries: {
|
||||
enabledInDev: true,
|
||||
enabledInDev: false,
|
||||
default: {
|
||||
maxAttempts: 3,
|
||||
minTimeoutInMs: 1000,
|
||||
@@ -13,7 +13,4 @@ export default {
|
||||
randomize: true,
|
||||
},
|
||||
},
|
||||
handleError: async (payload, error, { ctx, retryAt, retryDelayInMs, retry }) => {
|
||||
return { skipRetrying: true };
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user