Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions 6 .changeset/healthy-apricots-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---

Add project details to the whoami command
56 changes: 56 additions & 0 deletions 56 apps/webapp/app/routes/api.v2.whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { logger } from "~/services/logger.server";
import { authenticateApiRequestWithPersonalAccessToken } from "~/services/personalAccessToken.server";
import { v3ProjectPath } from "~/utils/pathBuilder";

export async function loader({ request }: LoaderFunctionArgs) {
logger.info("whoami v2", { url: request.url });
Expand All @@ -27,10 +28,65 @@ export async function loader({ request }: LoaderFunctionArgs) {
return json({ error: "User not found" }, { status: 404 });
}

const url = new URL(request.url);
const projectRef = url.searchParams.get("projectRef");

let projectDetails: WhoAmIResponse["project"];

if (projectRef) {
const orgs = await prisma.organization.findMany({
select: {
id: true,
},
where: {
members: {
some: {
userId: authenticationResult.userId,
},
},
},
});

if (orgs.length > 0) {
const project = await prisma.project.findFirst({
select: {
externalRef: true,
name: true,
slug: true,
organization: {
select: {
slug: true,
title: true,
},
},
},
where: {
externalRef: projectRef,
organizationId: {
in: orgs.map((org) => org.id),
},
},
});

if (project) {
const projectPath = v3ProjectPath(
{ slug: project.organization.slug },
{ slug: project.slug }
);
projectDetails = {
url: new URL(projectPath, env.APP_ORIGIN).href,
name: project.name,
orgTitle: project.organization.title,
};
}
}
}

const result: WhoAmIResponse = {
userId: authenticationResult.userId,
email: user.email,
dashboardUrl: env.APP_ORIGIN,
project: projectDetails,
};
return json(result);
} catch (error) {
Expand Down
10 changes: 8 additions & 2 deletions 10 packages/cli-v3/src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,18 @@ export class CliApiClient {
});
}

async whoAmI() {
async whoAmI(projectRef?: string) {
if (!this.accessToken) {
throw new Error("whoAmI: No access token");
}

return wrapZodFetch(WhoAmIResponseSchema, `${this.apiURL}/api/v2/whoami`, {
const url = new URL("/api/v2/whoami", this.apiURL);

if (projectRef) {
url.searchParams.append("projectRef", projectRef);
}

return wrapZodFetch(WhoAmIResponseSchema, url.href, {
headers: {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
Expand Down
58 changes: 52 additions & 6 deletions 58 packages/cli-v3/src/commands/whoami.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
import { z } from "zod";
import { CliApiClient } from "../apiClient.js";
import { spinner } from "../utilities/windows.js";
import { loadConfig } from "../config.js";
import { resolveLocalEnvVars } from "../utilities/localEnvVars.js";
import { tryCatch } from "@trigger.dev/core";

type WhoAmIResult =
| {
Expand All @@ -21,20 +24,36 @@ type WhoAmIResult =
userId: string;
email: string;
dashboardUrl: string;
projectUrl?: string;
};
}
| {
success: false;
error: string;
};

const WhoamiCommandOptions = CommonCommandOptions;
const WhoamiCommandOptions = CommonCommandOptions.extend({
config: z.string().optional(),
projectRef: z.string().optional(),
envFile: z.string().optional(),
});

type WhoamiCommandOptions = z.infer<typeof WhoamiCommandOptions>;

export function configureWhoamiCommand(program: Command) {
return commonOptions(
program.command("whoami").description("display the current logged in user and project details")
program
.command("whoami")
.description("display the current logged in user and project details")
.option("-c, --config <config file>", "The name of the config file")
.option(
"-p, --project-ref <project ref>",
"The project ref. This will override the project specified in the config file."
)
.option(
"--env-file <env file>",
"Path to the .env file to load into the CLI process. Defaults to .env in the project directory."
)
).action(async (options) => {
await handleTelemetry(async () => {
await printInitialBanner(false);
Expand All @@ -58,6 +77,23 @@ export async function whoAmI(
intro(`Displaying your account details [${options?.profile ?? "default"}]`);
}

const envVars = resolveLocalEnvVars(options?.envFile);

if (envVars.TRIGGER_PROJECT_REF) {
logger.debug("Using project ref from env", { ref: envVars.TRIGGER_PROJECT_REF });
}

const [configError, resolvedConfig] = await tryCatch(
loadConfig({
overrides: { project: options?.projectRef ?? envVars.TRIGGER_PROJECT_REF },
configFile: options?.config,
})
);

if (configError) {
logger.debug("Error loading config", { error: configError });
}

const loadingSpinner = spinner();

if (!silent) {
Expand Down Expand Up @@ -94,7 +130,7 @@ export async function whoAmI(
}

const apiClient = new CliApiClient(authentication.auth.apiUrl, authentication.auth.accessToken);
const userData = await apiClient.whoAmI();
const userData = await apiClient.whoAmI(resolvedConfig?.project);

if (!userData.success) {
loadingSpinner.stop("Error getting your account details");
Expand All @@ -109,11 +145,21 @@ export async function whoAmI(
loadingSpinner.stop("Retrieved your account details");
note(
`User ID: ${userData.data.userId}
Email: ${userData.data.email}
URL: ${chalkLink(authentication.auth.apiUrl)}
`,
Email: ${userData.data.email}
URL: ${chalkLink(authentication.auth.apiUrl)}`,
`Account details [${authentication.profile}]`
);

const { project } = userData.data;

if (project) {
note(
`Name: ${project.name}
Org: ${project.orgTitle}
URL: ${chalkLink(project.url)}`,
`Project details [${resolvedConfig?.project}]`
);
}
} else {
!silent && loadingSpinner.stop(`Retrieved your account details for ${userData.data.email}`);
}
Expand Down
7 changes: 7 additions & 0 deletions 7 packages/core/src/v3/schemas/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export const WhoAmIResponseSchema = z.object({
userId: z.string(),
email: z.string().email(),
dashboardUrl: z.string(),
project: z
.object({
name: z.string(),
url: z.string(),
orgTitle: z.string(),
})
.optional(),
});

export type WhoAmIResponse = z.infer<typeof WhoAmIResponseSchema>;
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.