diff --git a/.changeset/target-cli-notifications.md b/.changeset/target-cli-notifications.md new file mode 100644 index 00000000000..bdb6f7240c0 --- /dev/null +++ b/.changeset/target-cli-notifications.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +Send the running CLI version when checking for platform notifications so notices can be limited to compatible CLI releases. diff --git a/apps/webapp/app/routes/admin.notifications.tsx b/apps/webapp/app/routes/admin.notifications.tsx index 42e02afa6cc..8e9e4beb374 100644 --- a/apps/webapp/app/routes/admin.notifications.tsx +++ b/apps/webapp/app/routes/admin.notifications.tsx @@ -140,6 +140,8 @@ function parseNotificationFormData(formData: FormData) { const cliShowEvery = formData.get("cliShowEvery") ? Number(formData.get("cliShowEvery")) : undefined; + const minimumCliVersion = + (formData.get("minimumCliVersion") as string | null)?.trim() || undefined; const discoveryFilePatterns = (formData.get("discoveryFilePatterns") as string) || ""; const discoveryContentPattern = (formData.get("discoveryContentPattern") as string) || undefined; @@ -177,6 +179,7 @@ function parseNotificationFormData(formData: FormData) { cliMaxShowCount, cliMaxDaysAfterFirstSeen, cliShowEvery, + minimumCliVersion, discovery, }; } @@ -192,6 +195,9 @@ function buildPayloadInput(fields: ReturnType) ...(fields.image ? { image: fields.image } : {}), ...(fields.dismissOnAction ? { dismissOnAction: true } : {}), ...(fields.discovery ? { discovery: fields.discovery } : {}), + ...(fields.surface === "CLI" && fields.minimumCliVersion + ? { minimumCliVersion: fields.minimumCliVersion } + : {}), }, }; } @@ -669,6 +675,7 @@ type NotificationFormDefaults = { contentPattern?: string; matchBehavior: "show-if-found" | "show-if-not-found"; } | null; + payloadMinimumCliVersion?: string | null; cliMaxShowCount?: number | null; cliMaxDaysAfterFirstSeen?: number | null; cliShowEvery?: number | null; @@ -1024,7 +1031,19 @@ function NotificationForm({ <> -
+
+
+ + + Exact SemVer, inclusive +

CLI Settings

+ {n.payloadMinimumCliVersion && ( + + )} {n.cliMaxShowCount != null && ( )} diff --git a/apps/webapp/app/routes/api.v1.platform-notifications.ts b/apps/webapp/app/routes/api.v1.platform-notifications.ts index 7299c407383..62e3b000f9f 100644 --- a/apps/webapp/app/routes/api.v1.platform-notifications.ts +++ b/apps/webapp/app/routes/api.v1.platform-notifications.ts @@ -12,10 +12,12 @@ export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const projectRef = url.searchParams.get("projectRef") ?? undefined; + const cliVersion = request.headers.get("x-trigger-cli-version")?.trim() || undefined; const notification = await getNextCliNotification({ userId: authenticationResult.userId, projectRef, + cliVersion, }); return json({ notification }); diff --git a/apps/webapp/app/services/platformNotificationSchemas.ts b/apps/webapp/app/services/platformNotificationSchemas.ts new file mode 100644 index 00000000000..a91521e07a5 --- /dev/null +++ b/apps/webapp/app/services/platformNotificationSchemas.ts @@ -0,0 +1,228 @@ +import { z } from "zod"; +import { normalizeExactSemVer } from "./platformNotificationVersionTargeting"; + +const DiscoverySchema = z.object({ + filePatterns: z.array(z.string().min(1)).min(1), + contentPattern: z + .string() + .max(200) + .optional() + .refine( + (val) => { + if (!val) return true; + try { + new RegExp(val); + return true; + } catch { + return false; + } + }, + { message: "contentPattern must be a valid regular expression" } + ), + matchBehavior: z.enum(["show-if-found", "show-if-not-found"]), +}); + +// Constrain URL fields to http/https; `.url()` alone accepts other schemes +// that would be unsafe to render into an ``. +const httpUrl = z + .string() + .url() + .refine( + (v) => { + try { + const proto = new URL(v).protocol; + return proto === "http:" || proto === "https:"; + } catch { + return false; + } + }, + { message: "URL must use http or https" } + ); + +const ExactSemVerSchema = z + .string() + .transform((value) => value.trim()) + .refine((value) => normalizeExactSemVer(value) !== null, { + message: "minimumCliVersion must be a complete, exact SemVer", + }) + .transform((value) => normalizeExactSemVer(value)!); + +const CardDataV1Schema = z.object({ + type: z.enum(["card", "info", "warn", "error", "success", "changelog"]), + title: z.string(), + description: z.string(), + image: httpUrl.optional(), + actionLabel: z.string().optional(), + actionUrl: httpUrl.optional(), + dismissOnAction: z.boolean().optional(), + discovery: DiscoverySchema.optional(), + minimumCliVersion: ExactSemVerSchema.optional(), +}); + +export const PayloadV1Schema = z.object({ + version: z.literal("1"), + data: CardDataV1Schema, +}); + +export type PayloadV1 = z.infer; + +const SCOPE_REQUIRED_FK: Record = { + USER: "userId", + ORGANIZATION: "organizationId", + PROJECT: "projectId", +}; + +const ALL_FK_FIELDS = ["userId", "organizationId", "projectId"] as const; +const CLI_ONLY_FIELDS = ["cliMaxDaysAfterFirstSeen", "cliMaxShowCount", "cliShowEvery"] as const; + +const NotificationBaseFields = { + title: z.string().min(1), + payload: PayloadV1Schema, + surface: z.enum(["WEBAPP", "CLI"]), + scope: z.enum(["USER", "PROJECT", "ORGANIZATION", "GLOBAL"]), + userId: z.string().optional(), + organizationId: z.string().optional(), + projectId: z.string().optional(), + endsAt: z + .string() + .datetime() + .transform((s) => new Date(s)), + priority: z.number().int().default(0), + cliMaxDaysAfterFirstSeen: z.number().int().positive().optional(), + cliMaxShowCount: z.number().int().positive().optional(), + cliShowEvery: z.number().int().min(2).optional(), +}; + +export const CreatePlatformNotificationSchema = z + .object({ + ...NotificationBaseFields, + startsAt: z + .string() + .datetime() + .transform((s) => new Date(s)) + .optional(), + }) + .superRefine((data, ctx) => { + validateScopeForeignKeys(data, ctx); + validateSurfaceFields(data, ctx); + validatePayloadTypeForSurface(data, ctx); + validateStartsAt(data, ctx); + validateEndsAt(data, ctx); + }); + +function validateScopeForeignKeys( + data: { scope: string; userId?: string; organizationId?: string; projectId?: string }, + ctx: z.RefinementCtx +) { + const requiredFk = SCOPE_REQUIRED_FK[data.scope]; + + if (requiredFk && !data[requiredFk]) { + ctx.addIssue({ + code: "custom", + message: `${requiredFk} is required when scope is ${data.scope}`, + path: [requiredFk], + }); + } + + const forbiddenFks = ALL_FK_FIELDS.filter((fk) => fk !== requiredFk); + for (const fk of forbiddenFks) { + if (data[fk]) { + ctx.addIssue({ + code: "custom", + message: `${fk} must not be set when scope is ${data.scope}`, + path: [fk], + }); + } + } +} + +function validateSurfaceFields( + data: { + surface: string; + payload: PayloadV1; + cliMaxDaysAfterFirstSeen?: number; + cliMaxShowCount?: number; + cliShowEvery?: number; + }, + ctx: z.RefinementCtx +) { + if (data.surface !== "WEBAPP") return; + + for (const field of CLI_ONLY_FIELDS) { + if (data[field] !== undefined) { + ctx.addIssue({ + code: "custom", + message: `${field} is not allowed for WEBAPP surface`, + path: [field], + }); + } + } + + if (data.payload.data.minimumCliVersion !== undefined) { + ctx.addIssue({ + code: "custom", + message: "minimumCliVersion is not allowed for WEBAPP surface", + path: ["payload", "data", "minimumCliVersion"], + }); + } +} + +function validateStartsAt(data: { startsAt?: Date }, ctx: z.RefinementCtx) { + if (!data.startsAt) return; + + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); + if (data.startsAt < oneHourAgo) { + ctx.addIssue({ + code: "custom", + message: "startsAt must be within the last hour or in the future", + path: ["startsAt"], + }); + } +} + +const CLI_TYPES = new Set(["info", "warn", "error", "success"]); +const WEBAPP_TYPES = new Set(["card", "changelog"]); + +function validatePayloadTypeForSurface( + data: { surface: string; payload: PayloadV1 }, + ctx: z.RefinementCtx +) { + const allowedTypes = data.surface === "CLI" ? CLI_TYPES : WEBAPP_TYPES; + if (!allowedTypes.has(data.payload.data.type)) { + ctx.addIssue({ + code: "custom", + message: `payload.data.type "${data.payload.data.type}" is not allowed for ${data.surface} surface`, + path: ["payload", "data", "type"], + }); + } +} + +function validateEndsAt(data: { startsAt?: Date; endsAt: Date }, ctx: z.RefinementCtx) { + const effectiveStart = data.startsAt ?? new Date(); + if (data.endsAt <= effectiveStart) { + ctx.addIssue({ + code: "custom", + message: "endsAt must be after startsAt", + path: ["endsAt"], + }); + } +} + +export type CreatePlatformNotificationInput = z.input; + +export const UpdatePlatformNotificationSchema = z + .object({ + ...NotificationBaseFields, + id: z.string().min(1), + startsAt: z + .string() + .datetime() + .transform((s) => new Date(s)), + }) + .superRefine((data, ctx) => { + validateScopeForeignKeys(data, ctx); + validateSurfaceFields(data, ctx); + validatePayloadTypeForSurface(data, ctx); + // Existing notifications may have a startsAt in the past. + validateEndsAt(data, ctx); + }); diff --git a/apps/webapp/app/services/platformNotificationVersionTargeting.ts b/apps/webapp/app/services/platformNotificationVersionTargeting.ts new file mode 100644 index 00000000000..30fbe001c11 --- /dev/null +++ b/apps/webapp/app/services/platformNotificationVersionTargeting.ts @@ -0,0 +1,29 @@ +import * as semver from "semver"; + +export function normalizeExactSemVer(value: string): string | null { + const trimmed = value.trim(); + const parsed = semver.parse(trimmed); + + if (!parsed) return null; + + const normalized = `${parsed.version}${ + parsed.build.length > 0 ? `+${parsed.build.join(".")}` : "" + }`; + + return normalized === trimmed ? normalized : null; +} + +export function isCliVersionEligible( + minimumCliVersion: string | undefined, + cliVersion: string | undefined +): boolean { + if (minimumCliVersion === undefined) return true; + + const normalizedMinimum = normalizeExactSemVer(minimumCliVersion); + if (!normalizedMinimum || cliVersion === undefined) return false; + + const normalizedCliVersion = normalizeExactSemVer(cliVersion); + if (!normalizedCliVersion) return false; + + return semver.gte(normalizedCliVersion, normalizedMinimum); +} diff --git a/apps/webapp/app/services/platformNotifications.server.ts b/apps/webapp/app/services/platformNotifications.server.ts index fa2e0ea8429..24e95740a70 100644 --- a/apps/webapp/app/services/platformNotifications.server.ts +++ b/apps/webapp/app/services/platformNotifications.server.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import type { z } from "zod"; import { errAsync, fromPromise, type ResultAsync } from "neverthrow"; import { prisma } from "~/db.server"; import { @@ -6,64 +6,20 @@ import { type PlatformNotificationSurface, } from "@trigger.dev/database"; import { incrementCliRequestCounter } from "./platformNotificationCounter.server"; - -// --- Payload schema (spec v1) --- - -const DiscoverySchema = z.object({ - filePatterns: z.array(z.string().min(1)).min(1), - contentPattern: z - .string() - .max(200) - .optional() - .refine( - (val) => { - if (!val) return true; - try { - new RegExp(val); - return true; - } catch { - return false; - } - }, - { message: "contentPattern must be a valid regular expression" } - ), - matchBehavior: z.enum(["show-if-found", "show-if-not-found"]), -}); - -// Constrain URL fields to http/https; `.url()` alone accepts other schemes -// that would be unsafe to render into an ``. -const httpUrl = z - .string() - .url() - .refine( - (v) => { - try { - const proto = new URL(v).protocol; - return proto === "http:" || proto === "https:"; - } catch { - return false; - } - }, - { message: "URL must use http or https" } - ); - -const CardDataV1Schema = z.object({ - type: z.enum(["card", "info", "warn", "error", "success", "changelog"]), - title: z.string(), - description: z.string(), - image: httpUrl.optional(), - actionLabel: z.string().optional(), - actionUrl: httpUrl.optional(), - dismissOnAction: z.boolean().optional(), - discovery: DiscoverySchema.optional(), -}); - -const PayloadV1Schema = z.object({ - version: z.literal("1"), - data: CardDataV1Schema, -}); - -export type PayloadV1 = z.infer; +import { + CreatePlatformNotificationSchema, + type CreatePlatformNotificationInput, + type PayloadV1, + PayloadV1Schema, + UpdatePlatformNotificationSchema, +} from "./platformNotificationSchemas"; +import { isCliVersionEligible } from "./platformNotificationVersionTargeting"; + +export { + CreatePlatformNotificationSchema, + UpdatePlatformNotificationSchema, +} from "./platformNotificationSchemas"; +export type { CreatePlatformNotificationInput, PayloadV1 } from "./platformNotificationSchemas"; export type PlatformNotificationWithPayload = { id: string; @@ -136,6 +92,9 @@ export async function getAdminNotificationsList({ ? (parsed.data.data.dismissOnAction ?? false) : false, payloadDiscovery: parsed.success ? (parsed.data.data.discovery ?? null) : null, + payloadMinimumCliVersion: parsed.success + ? (parsed.data.data.minimumCliVersion ?? null) + : null, cliMaxShowCount: n.cliMaxShowCount, cliMaxDaysAfterFirstSeen: n.cliMaxDaysAfterFirstSeen, cliShowEvery: n.cliShowEvery, @@ -435,9 +394,11 @@ function isCliNotificationExpired( export async function getNextCliNotification({ userId, projectRef, + cliVersion, }: { userId: string; projectRef?: string; + cliVersion?: string; }): Promise<{ id: string; payload: PayloadV1; @@ -520,10 +481,11 @@ export async function getNextCliNotification({ const interaction = n.interactions[0] ?? null; if (interaction?.cliDismissedAt) continue; - if (isCliNotificationExpired(interaction, n)) continue; const parsed = PayloadV1Schema.safeParse(n.payload); if (!parsed.success) continue; + if (!isCliVersionEligible(parsed.data.data.minimumCliVersion, cliVersion)) continue; + if (isCliNotificationExpired(interaction, n)) continue; // Check cliShowEvery using the global request counter if (n.cliShowEvery !== null && requestCounter % n.cliShowEvery !== 0) { @@ -562,161 +524,7 @@ export async function getNextCliNotification({ return null; } -// --- Create: admin endpoint support --- - -const SCOPE_REQUIRED_FK: Record = { - USER: "userId", - ORGANIZATION: "organizationId", - PROJECT: "projectId", -}; - -const ALL_FK_FIELDS = ["userId", "organizationId", "projectId"] as const; -const CLI_ONLY_FIELDS = ["cliMaxDaysAfterFirstSeen", "cliMaxShowCount", "cliShowEvery"] as const; - -const NotificationBaseFields = { - title: z.string().min(1), - payload: PayloadV1Schema, - surface: z.enum(["WEBAPP", "CLI"]), - scope: z.enum(["USER", "PROJECT", "ORGANIZATION", "GLOBAL"]), - userId: z.string().optional(), - organizationId: z.string().optional(), - projectId: z.string().optional(), - endsAt: z - .string() - .datetime() - .transform((s) => new Date(s)), - priority: z.number().int().default(0), - cliMaxDaysAfterFirstSeen: z.number().int().positive().optional(), - cliMaxShowCount: z.number().int().positive().optional(), - cliShowEvery: z.number().int().min(2).optional(), -}; - -export const CreatePlatformNotificationSchema = z - .object({ - ...NotificationBaseFields, - startsAt: z - .string() - .datetime() - .transform((s) => new Date(s)) - .optional(), - }) - .superRefine((data, ctx) => { - validateScopeForeignKeys(data, ctx); - validateSurfaceFields(data, ctx); - validatePayloadTypeForSurface(data, ctx); - validateStartsAt(data, ctx); - validateEndsAt(data, ctx); - }); - -function validateScopeForeignKeys( - data: { scope: string; userId?: string; organizationId?: string; projectId?: string }, - ctx: z.RefinementCtx -) { - const requiredFk = SCOPE_REQUIRED_FK[data.scope]; - - if (requiredFk && !data[requiredFk]) { - ctx.addIssue({ - code: "custom", - message: `${requiredFk} is required when scope is ${data.scope}`, - path: [requiredFk], - }); - } - - const forbiddenFks = ALL_FK_FIELDS.filter((fk) => fk !== requiredFk); - for (const fk of forbiddenFks) { - if (data[fk]) { - ctx.addIssue({ - code: "custom", - message: `${fk} must not be set when scope is ${data.scope}`, - path: [fk], - }); - } - } -} - -function validateSurfaceFields( - data: { - surface: string; - cliMaxDaysAfterFirstSeen?: number; - cliMaxShowCount?: number; - cliShowEvery?: number; - }, - ctx: z.RefinementCtx -) { - if (data.surface !== "WEBAPP") return; - - for (const field of CLI_ONLY_FIELDS) { - if (data[field] !== undefined) { - ctx.addIssue({ - code: "custom", - message: `${field} is not allowed for WEBAPP surface`, - path: [field], - }); - } - } -} - -function validateStartsAt(data: { startsAt?: Date }, ctx: z.RefinementCtx) { - if (!data.startsAt) return; - - const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); - if (data.startsAt < oneHourAgo) { - ctx.addIssue({ - code: "custom", - message: "startsAt must be within the last hour or in the future", - path: ["startsAt"], - }); - } -} - -const CLI_TYPES = new Set(["info", "warn", "error", "success"]); -const WEBAPP_TYPES = new Set(["card", "changelog"]); - -function validatePayloadTypeForSurface( - data: { surface: string; payload: PayloadV1 }, - ctx: z.RefinementCtx -) { - const allowedTypes = data.surface === "CLI" ? CLI_TYPES : WEBAPP_TYPES; - if (!allowedTypes.has(data.payload.data.type)) { - ctx.addIssue({ - code: "custom", - message: `payload.data.type "${data.payload.data.type}" is not allowed for ${data.surface} surface`, - path: ["payload", "data", "type"], - }); - } -} - -function validateEndsAt(data: { startsAt?: Date; endsAt: Date }, ctx: z.RefinementCtx) { - const effectiveStart = data.startsAt ?? new Date(); - if (data.endsAt <= effectiveStart) { - ctx.addIssue({ - code: "custom", - message: "endsAt must be after startsAt", - path: ["endsAt"], - }); - } -} - -export type CreatePlatformNotificationInput = z.input; - -// --- Update: admin endpoint support --- - -export const UpdatePlatformNotificationSchema = z - .object({ - ...NotificationBaseFields, - id: z.string().min(1), - startsAt: z - .string() - .datetime() - .transform((s) => new Date(s)), - }) - .superRefine((data, ctx) => { - validateScopeForeignKeys(data, ctx); - validateSurfaceFields(data, ctx); - validatePayloadTypeForSurface(data, ctx); - // NOTE: No validateStartsAt — existing notifications may have past startsAt - validateEndsAt(data, ctx); - }); +// --- Create and update: admin endpoint support --- type CreateError = { type: "validation"; issues: z.ZodIssue[] } | { type: "db"; message: string }; diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 0eb149f77ed..8819f322f93 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -196,6 +196,7 @@ "remix-auth-google": "^2.0.0", "remix-typedjson": "0.3.1", "remix-utils": "^7.7.0", + "semver": "^7.5.0", "simplur": "^3.0.1", "slug": "^6.0.0", "socket.io": "4.7.4", @@ -242,6 +243,7 @@ "@types/react": "18.2.69", "@types/react-dom": "18.2.7", "@types/regression": "^2.0.6", + "@types/semver": "^7.5.0", "@types/slug": "^5.0.3", "@types/supertest": "^6.0.2", "@types/tar": "^6.1.4", diff --git a/apps/webapp/test/platformNotifications.test.ts b/apps/webapp/test/platformNotifications.test.ts new file mode 100644 index 00000000000..b7b4634339c --- /dev/null +++ b/apps/webapp/test/platformNotifications.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from "vitest"; +import { CreatePlatformNotificationSchema } from "~/services/platformNotificationSchemas"; +import { isCliVersionEligible } from "~/services/platformNotificationVersionTargeting"; + +function createNotificationInput({ + surface = "CLI", + minimumCliVersion, +}: { + surface?: "CLI" | "WEBAPP"; + minimumCliVersion?: string; +} = {}) { + return { + title: "Notification", + surface, + scope: "GLOBAL" as const, + endsAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), + payload: { + version: "1" as const, + data: { + type: surface === "CLI" ? ("info" as const) : ("card" as const), + title: "Update available", + description: "Update the CLI", + ...(minimumCliVersion === undefined ? {} : { minimumCliVersion }), + }, + }, + }; +} + +describe("platform notification minimum CLI version schema", () => { + it("keeps existing unrestricted CLI payloads valid", () => { + const result = CreatePlatformNotificationSchema.safeParse(createNotificationInput()); + + expect(result.success).toBe(true); + }); + + it("trims and accepts a complete exact SemVer", () => { + const result = CreatePlatformNotificationSchema.parse( + createNotificationInput({ minimumCliVersion: " 4.5.7-beta.1+build.2 " }) + ); + + expect(result.payload.data.minimumCliVersion).toBe("4.5.7-beta.1+build.2"); + }); + + it.each(["4.5", ">=4.5.7", "4.5.x", "v4.5.7", "4.5.7 or newer", "01.2.3"])( + "rejects non-exact SemVer %s", + (minimumCliVersion) => { + const result = CreatePlatformNotificationSchema.safeParse( + createNotificationInput({ minimumCliVersion }) + ); + + expect(result.success).toBe(false); + } + ); + + it("rejects minimumCliVersion for WEBAPP notifications", () => { + const result = CreatePlatformNotificationSchema.safeParse( + createNotificationInput({ surface: "WEBAPP", minimumCliVersion: "4.5.7" }) + ); + + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues).toContainEqual( + expect.objectContaining({ path: ["payload", "data", "minimumCliVersion"] }) + ); + } + }); +}); + +describe("CLI notification version eligibility", () => { + it("allows unrestricted notifications without a request version", () => { + expect(isCliVersionEligible(undefined, undefined)).toBe(true); + }); + + it.each([undefined, "", "4.5", "not-a-version"])( + "rejects a targeted notification for missing or malformed request version %s", + (cliVersion) => { + expect(isCliVersionEligible("4.5.7", cliVersion)).toBe(false); + } + ); + + it("fails closed for an invalid stored minimum version", () => { + expect(isCliVersionEligible(">=4.5.7", "4.6.0")).toBe(false); + }); + + it.each([ + ["4.5.6", false], + ["4.5.7-beta.1", false], + ["4.5.7", true], + ["4.5.7+build.9", true], + ["4.6.0", true], + ])("compares %s against inclusive minimum 4.5.7", (cliVersion, expected) => { + expect(isCliVersionEligible("4.5.7", cliVersion)).toBe(expected); + }); + + it("uses SemVer precedence for prerelease minimums", () => { + expect(isCliVersionEligible("4.5.7-beta.2", "4.5.7-beta.1")).toBe(false); + expect(isCliVersionEligible("4.5.7-beta.2", "4.5.7-beta.2+build.1")).toBe(true); + expect(isCliVersionEligible("4.5.7-beta.2", "4.5.7")).toBe(true); + }); +}); diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index d01062440e9..2b76ae021e1 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -606,6 +606,7 @@ export class CliApiClient { headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", + "x-trigger-cli-version": VERSION, }, signal, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2849a27dd58..486a49560f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -716,6 +716,9 @@ importers: remix-utils: specifier: ^7.7.0 version: 7.7.0(@remix-run/node@2.17.5(typescript@6.0.3))(@remix-run/react@2.17.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(@remix-run/router@1.23.3(patch_hash=33e2966e9ef36aa09955cec9922c5063227b83429157a71ba226369a08627ac9))(crypto-js@4.2.0)(intl-parse-accept-language@1.0.0)(react@18.3.1)(zod@3.25.76) + semver: + specifier: ^7.5.0 + version: 7.8.1 simplur: specifier: ^3.0.1 version: 3.0.1 @@ -849,6 +852,9 @@ importers: '@types/regression': specifier: ^2.0.6 version: 2.0.6 + '@types/semver': + specifier: ^7.5.0 + version: 7.5.1 '@types/slug': specifier: ^5.0.3 version: 5.0.3 @@ -13958,11 +13964,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - semver@7.8.1: resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} engines: {node: '>=10'} @@ -17525,7 +17526,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.3 + semver: 7.8.1 '@changesets/assemble-release-plan@5.2.4(patch_hash=a7a12643e8d89a00d5322f750c292e8567b5ee0fc9c613a238a1184c25533e4b)': dependencies: @@ -17534,7 +17535,7 @@ snapshots: '@changesets/get-dependents-graph': 1.3.6 '@changesets/types': 5.2.1 '@manypkg/get-packages': 1.1.3 - semver: 7.7.3 + semver: 7.8.1 '@changesets/changelog-git@0.1.14': dependencies: @@ -17596,7 +17597,7 @@ snapshots: '@manypkg/get-packages': 1.1.3 chalk: 2.4.2 fs-extra: 7.0.1 - semver: 7.7.3 + semver: 7.8.1 '@changesets/get-github-info@0.5.2(encoding@0.1.13)': dependencies: @@ -24149,7 +24150,7 @@ snapshots: postcss-modules-scope: 3.1.1(postcss@8.5.10) postcss-modules-values: 4.0.0(postcss@8.5.10) postcss-value-parser: 4.2.0 - semver: 7.6.3 + semver: 7.8.1 optionalDependencies: webpack: 5.102.1(@swc/core@1.3.26)(esbuild@0.15.18) @@ -25570,7 +25571,7 @@ snapshots: process-warning: 5.0.0 rfdc: 1.4.1 secure-json-parse: 4.0.0 - semver: 7.7.3 + semver: 7.8.1 toad-cache: 3.7.0 fastq@1.15.0: @@ -28690,7 +28691,7 @@ snapshots: cosmiconfig: 9.0.0(typescript@6.0.3) jiti: 1.21.0 postcss: 8.5.15 - semver: 7.6.3 + semver: 7.8.1 optionalDependencies: webpack: 5.102.1(@swc/core@1.3.26)(esbuild@0.15.18) transitivePeerDependencies: @@ -29763,8 +29764,6 @@ snapshots: semver@7.6.3: {} - semver@7.7.3: {} - semver@7.8.1: {} send@0.18.0: