trigger.dev
Version:
A Command-Line Interface for Trigger.dev (v3) projects
287 lines • 13 kB
JavaScript
import { intro, log, outro, select } from "@clack/prompts";
import { recordSpanException } from "@trigger.dev/core/v3/workers";
import open from "open";
import pRetry, { AbortError } from "p-retry";
import { z } from "zod";
import { CliApiClient } from "../apiClient.js";
import { CommonCommandOptions, SkipLoggingError, commonOptions, handleTelemetry, tracer, wrapCommandAction, } from "../cli/common.js";
import { chalkLink, prettyError } from "../utilities/cliOutput.js";
import { readAuthConfigProfile, writeAuthConfigProfile } from "../utilities/configFiles.js";
import { printInitialBanner } from "../utilities/initialBanner.js";
import { whoAmI } from "./whoami.js";
import { logger } from "../utilities/logger.js";
import { spinner } from "../utilities/windows.js";
import { isLinuxServer } from "../utilities/linux.js";
import { VERSION } from "../version.js";
import { env } from "std-env";
import { isPersonalAccessToken, NotPersonalAccessTokenError, } from "../utilities/isPersonalAccessToken.js";
export const LoginCommandOptions = CommonCommandOptions.extend({
apiUrl: z.string(),
});
export function configureLoginCommand(program) {
return commonOptions(program
.command("login")
.description("Login with Trigger.dev so you can perform authenticated actions"))
.version(VERSION, "-v, --version", "Display the version number")
.action(async (options) => {
await handleTelemetry(async () => {
await printInitialBanner(false);
await loginCommand(options);
});
});
}
export async function loginCommand(options) {
return await wrapCommandAction("loginCommand", LoginCommandOptions, options, async (opts) => {
return await _loginCommand(opts);
});
}
async function _loginCommand(options) {
return login({ defaultApiUrl: options.apiUrl, embedded: false, profile: options.profile });
}
export async function login(options) {
return await tracer.startActiveSpan("login", async (span) => {
try {
const opts = {
defaultApiUrl: "https://api.trigger.dev",
embedded: false,
silent: false,
...options,
};
span.setAttributes({
"cli.config.apiUrl": opts.defaultApiUrl,
"cli.options.profile": opts.profile,
});
if (!opts.embedded) {
intro("Logging in to Trigger.dev");
}
const accessTokenFromEnv = env.TRIGGER_ACCESS_TOKEN;
if (accessTokenFromEnv) {
if (!isPersonalAccessToken(accessTokenFromEnv)) {
throw new NotPersonalAccessTokenError("Your TRIGGER_ACCESS_TOKEN is not a Personal Access Token, they start with 'tr_pat_'. You can generate one here: https://cloud.trigger.dev/account/tokens");
}
const auth = {
accessToken: accessTokenFromEnv,
apiUrl: env.TRIGGER_API_URL ?? opts.defaultApiUrl ?? "https://api.trigger.dev",
};
const apiClient = new CliApiClient(auth.apiUrl, auth.accessToken);
const userData = await apiClient.whoAmI();
if (!userData.success) {
throw new Error(userData.error);
}
return {
ok: true,
profile: options?.profile ?? "default",
userId: userData.data.userId,
email: userData.data.email,
dashboardUrl: userData.data.dashboardUrl,
auth: {
accessToken: auth.accessToken,
apiUrl: auth.apiUrl,
},
};
}
const authConfig = readAuthConfigProfile(options?.profile);
if (authConfig && authConfig.accessToken) {
const whoAmIResult = await whoAmI({
profile: options?.profile ?? "default",
skipTelemetry: !span.isRecording(),
logLevel: logger.loggerLevel,
}, true, opts.silent);
if (!whoAmIResult.success) {
prettyError("Unable to validate existing personal access token", whoAmIResult.error);
if (!opts.embedded) {
outro(`Login failed using stored token. To fix, first logout using \`trigger.dev logout${options?.profile ? ` --profile ${options.profile}` : ""}\` and then try again.`);
throw new SkipLoggingError(whoAmIResult.error);
}
else {
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,
});
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,
profile: options?.profile ?? "default",
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,
profile: options?.profile ?? "default",
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)}`);
if (await isLinuxServer()) {
log.message("Please install `xdg-utils` to automatically open the login URL.");
}
else {
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}`);
writeAuthConfigProfile({ accessToken: indexResult.token, apiUrl: opts.defaultApiUrl }, options?.profile);
const whoAmIResult = await whoAmI({
profile: options?.profile ?? "default",
skipTelemetry: !span.isRecording(),
logLevel: logger.loggerLevel,
}, opts.embedded);
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,
profile: options?.profile ?? "default",
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,
error: e instanceof Error ? e.message : String(e),
};
}
}
catch (e) {
recordSpanException(span, e);
span.end();
if (options?.embedded) {
if (e instanceof NotPersonalAccessTokenError) {
throw e;
}
return {
ok: false,
error: e instanceof Error ? e.message : String(e),
};
}
throw e;
}
});
}
async function getPersonalAccessToken(apiClient, 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.data.token) {
throw new Error("No token found yet");
}
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) {
return await tracer.startActiveSpan("createAuthorizationCode", async (span) => {
try {
//generate authorization code
const createAuthCodeSpinner = spinner();
createAuthCodeSpinner.start("Creating authorization 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;
}
});
}
//# sourceMappingURL=login.js.map