Skip to content

Commit 4dabfca

Browse files
authored
feat(webapp,cli,core): list production project runtime updates (#4659)
1 parent 49aff3c commit 4dabfca

21 files changed

Lines changed: 770 additions & 24 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
List the current Production runtime for every accessible project with `trigger projects list`. Add `--needs-update` to identify projects currently running Node.js 21.

apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ArrowLeftIcon } from "@heroicons/react/24/solid";
22
import { BellIcon } from "~/assets/icons/BellIcon";
33
import { ChainLinkIcon } from "~/assets/icons/ChainLinkIcon";
44
import { CreditCardIcon } from "~/assets/icons/CreditCardIcon";
5+
import { FolderOpenIcon } from "~/assets/icons/FolderOpenIcon";
56
import { PadlockIcon } from "~/assets/icons/PadlockIcon";
67
import { UsageIcon } from "~/assets/icons/UsageIcon";
78
import { RolesIcon } from "~/assets/icons/RolesIcon";
@@ -16,6 +17,7 @@ import { cn } from "~/utils/cn";
1617
import {
1718
organizationPath,
1819
organizationRolesPath,
20+
organizationRuntimeUpdatesPath,
1921
organizationSettingsPath,
2022
organizationSlackIntegrationPath,
2123
organizationSsoPath,
@@ -49,11 +51,13 @@ export function OrganizationSettingsSideMenu({
4951
buildInfo,
5052
isUsingPlugin,
5153
isSsoUsingPlugin,
54+
hasProjectRuntimeUpdate,
5255
}: {
5356
organization: MatchedOrganization;
5457
buildInfo: BuildInfo;
5558
isUsingPlugin: boolean;
5659
isSsoUsingPlugin: boolean;
60+
hasProjectRuntimeUpdate: boolean;
5761
}) {
5862
const { isManagedCloud } = useFeatures();
5963
const featureFlags = useFeatureFlags();
@@ -127,6 +131,22 @@ export function OrganizationSettingsSideMenu({
127131
) : null}
128132
</>
129133
)}
134+
<SideMenuItem
135+
name="Projects"
136+
icon={FolderOpenIcon}
137+
activeIconColor="text-text-bright"
138+
inactiveIconColor="text-text-dimmed"
139+
to={organizationRuntimeUpdatesPath(organization)}
140+
data-action="runtime-updates"
141+
badge={
142+
hasProjectRuntimeUpdate ? (
143+
<>
144+
<span aria-hidden className="size-2 shrink-0 rounded-full bg-warning" />
145+
<span className="sr-only">Runtime update available.</span>
146+
</>
147+
) : undefined
148+
}
149+
/>
130150
<SideMenuItem
131151
name="Team"
132152
icon={UserGroupIcon}

apps/webapp/app/components/primitives/ClipboardField.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ export function ClipboardField({
142142
readOnly={true}
143143
className={cn(
144144
"shrink grow select-all overflow-x-auto",
145-
fullWidth ? "w-full" : "max-w-fit",
145+
// min-w-0 stops the input's intrinsic min-width pushing the copy button out of the row.
146+
fullWidth ? "w-full min-w-0" : "max-w-fit",
146147
input
147148
)}
148149
onFocus={(e) => {

apps/webapp/app/components/primitives/CopyButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function CopyButton({
5050
onClick={copy}
5151
className={cn(
5252
buttonSize,
53-
"flex items-center justify-center rounded border border-border-bright bg-background-hover",
53+
"flex shrink-0 items-center justify-center rounded border border-border-bright bg-background-hover",
5454
copied
5555
? "text-green-500"
5656
: "text-text-dimmed hover:border-border-bright hover:bg-background-raised hover:text-text-bright",

apps/webapp/app/components/primitives/Label.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as React from "react";
22
import { cn } from "~/utils/cn";
33
import { InfoIconTooltip } from "./Tooltip";
44

5-
const variants = {
5+
// Non-form labels (e.g. settings row titles) share this typography, so it is exported.
6+
export const labelVariants = {
67
small: {
78
text: "font-sans text-[0.8125rem] font-normal text-text-bright leading-tight flex items-center gap-1",
89
},
@@ -17,7 +18,7 @@ const variants = {
1718
type LabelProps = React.AllHTMLAttributes<HTMLLabelElement> & {
1819
className?: string;
1920
children: React.ReactNode;
20-
variant?: keyof typeof variants;
21+
variant?: keyof typeof labelVariants;
2122
required?: boolean;
2223
tooltip?: React.ReactNode;
2324
};
@@ -30,7 +31,7 @@ export function Label({
3031
tooltip,
3132
...props
3233
}: LabelProps) {
33-
const variation = variants[variant];
34+
const variation = labelVariants[variant];
3435
return (
3536
<label className={cn(variation.text, className)} {...props}>
3637
{children}

apps/webapp/app/components/primitives/SettingsLayout.tsx

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type ReactNode } from "react";
33
import { MainHorizontallyCenteredContainer } from "~/components/layout/AppLayout";
44
import { cn } from "~/utils/cn";
55
import { Header2, Header3 } from "./Headers";
6+
import { labelVariants } from "./Label";
67
import { Paragraph } from "./Paragraph";
78

89
// A composable layout system for settings pages: a centered container holds
@@ -82,7 +83,7 @@ export function SettingsHeader({
8283
className
8384
)}
8485
>
85-
<div className="space-y-1">
86+
<div className="space-y-0.5">
8687
<Heading>{title}</Heading>
8788
{description ? <Paragraph variant="small">{description}</Paragraph> : null}
8889
</div>
@@ -101,10 +102,7 @@ export function SettingsRowTitle({
101102
htmlFor?: string;
102103
className?: string;
103104
}) {
104-
const classes = cn(
105-
"block font-sans text-sm font-semibold leading-tight text-text-bright",
106-
className
107-
);
105+
const classes = cn(labelVariants.medium.text, className);
108106
return htmlFor ? (
109107
<label htmlFor={htmlFor} className={classes}>
110108
{children}
@@ -123,7 +121,7 @@ export function SettingsRowDescription({
123121
className?: string;
124122
}) {
125123
return (
126-
<Paragraph variant="small" className={className}>
124+
<Paragraph variant="extra-small" className={className}>
127125
{children}
128126
</Paragraph>
129127
);
@@ -170,7 +168,7 @@ export function SettingsRow({
170168
)}
171169
>
172170
{children ?? (
173-
<div className="flex-1 space-y-1">
171+
<div className="flex-1 space-y-0.5">
174172
{title ? (
175173
<SettingsRowTitle htmlFor={htmlFor} className={titleClassName}>
176174
{title}
@@ -229,7 +227,7 @@ export function SettingsAlertRow({
229227

230228
return (
231229
<SettingsRow action={action}>
232-
<div className="flex-1 space-y-1">
230+
<div className="flex-1 space-y-0.5">
233231
<div className="flex items-center gap-1.5">
234232
<Icon className={cn("size-4 shrink-0", color)} />
235233
<SettingsRowTitle className={color}>{title}</SettingsRowTitle>

apps/webapp/app/routes/[_].$.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { prisma } from "~/db.server";
33
import { getUsersInvites } from "~/models/member.server";
44
import { SelectBestEnvironmentPresenter } from "~/presenters/SelectBestEnvironmentPresenter.server";
55
import { requireUser } from "~/services/session.server";
6-
import { deeplinkSuffix, resolveDeeplinkPage } from "~/utils/deeplinkPages";
6+
import {
7+
deeplinkSuffix,
8+
resolveDeeplinkPage,
9+
resolveOrganizationDeeplinkPage,
10+
} from "~/utils/deeplinkPages";
711
import {
812
invitesPath,
913
newOrganizationPath,
1014
newProjectPath,
15+
organizationRuntimeUpdatesPath,
1116
v3EnvironmentPath,
1217
} from "~/utils/pathBuilder";
1318

@@ -16,7 +21,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
1621
const user = await requireUser(request);
1722

1823
const { pathname, search } = new URL(request.url);
19-
const page = resolveDeeplinkPage(deeplinkSuffix(pathname));
24+
const suffix = deeplinkSuffix(pathname);
25+
const page = resolveDeeplinkPage(suffix);
26+
const organizationPage = resolveOrganizationDeeplinkPage(suffix);
2027

2128
const invites = await getUsersInvites({ email: user.email });
2229
if (invites.length > 0) {
@@ -26,11 +33,14 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
2633
const presenter = new SelectBestEnvironmentPresenter();
2734
try {
2835
const { project, organization, environment } = await presenter.call({ user });
29-
const environmentPath = v3EnvironmentPath(organization, project, environment);
36+
if (organizationPage === "runtime-updates") {
37+
return redirect(`${organizationRuntimeUpdatesPath(organization)}${search}`);
38+
}
3039

31-
const suffix = page ? `/${page}` : "";
40+
const environmentPath = v3EnvironmentPath(organization, project, environment);
41+
const pageSuffix = page ? `/${page}` : "";
3242

33-
return redirect(`${environmentPath}${suffix}${search}`);
43+
return redirect(`${environmentPath}${pageSuffix}${search}`);
3444
} catch (_e) {
3545
const organization = await prisma.organization.findFirst({
3646
where: {
@@ -47,6 +57,10 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
4757
});
4858

4959
if (organization) {
60+
if (organizationPage === "runtime-updates") {
61+
return redirect(`${organizationRuntimeUpdatesPath(organization)}${search}`);
62+
}
63+
5064
return redirect(newProjectPath(organization));
5165
}
5266

0 commit comments

Comments
 (0)