diff --git a/apps/partner-sas/env/client.ts b/apps/partner-sas/env/client.ts index 21e43e577..836832584 100644 --- a/apps/partner-sas/env/client.ts +++ b/apps/partner-sas/env/client.ts @@ -1,11 +1,16 @@ import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" +import { getSemver } from "@scandic-hotels/common/utils/getSemver" + export const env = createEnv({ client: { NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().default("development"), NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001), - NEXT_PUBLIC_RELEASE_TAG: z.string().optional(), + NEXT_PUBLIC_RELEASE_TAG: z + .string() + .optional() + .transform((s) => getSemver(s, process.env.BRANCH || "development")), }, emptyStringAsUndefined: true, runtimeEnv: { diff --git a/apps/partner-sas/env/server.ts b/apps/partner-sas/env/server.ts index ffb776bdf..4949ead5a 100644 --- a/apps/partner-sas/env/server.ts +++ b/apps/partner-sas/env/server.ts @@ -1,6 +1,8 @@ import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" +import { getSemver } from "@scandic-hotels/common/utils/getSemver" + export const env = createEnv({ /** * Due to t3-env only checking typeof window === "undefined" @@ -36,7 +38,10 @@ export const env = createEnv({ .refine((s) => s === "true" || s === "false") .transform((s) => s === "true") .default("false"), - RELEASE_TAG: z.string().optional(), + RELEASE_TAG: z + .string() + .optional() + .transform((s) => getSemver(s, process.env.BRANCH || "development")), }, emptyStringAsUndefined: true, runtimeEnv: { diff --git a/apps/scandic-web/components/EnvironmentWatermark/index.tsx b/apps/scandic-web/components/EnvironmentWatermark/index.tsx index 933ab0455..b773a007b 100644 --- a/apps/scandic-web/components/EnvironmentWatermark/index.tsx +++ b/apps/scandic-web/components/EnvironmentWatermark/index.tsx @@ -27,12 +27,12 @@ export function EnvironmentWatermark() { } const { environment, name } = getEnvironmentName() - const displayValues = [name] - if (name !== environment) { - displayValues.push(`(${environment})`) - } + const displayValues: string[] = [environment] + if (env.NEXT_PUBLIC_RELEASE_TAG) { displayValues.push(`[${env.NEXT_PUBLIC_RELEASE_TAG}]`) + } else if (name !== environment) { + displayValues.push(`(${name})`) } return ( diff --git a/apps/scandic-web/env/client.ts b/apps/scandic-web/env/client.ts index 58f1e2603..174492700 100644 --- a/apps/scandic-web/env/client.ts +++ b/apps/scandic-web/env/client.ts @@ -1,6 +1,8 @@ import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" +import { getSemver } from "@scandic-hotels/common/utils/getSemver" + export const env = createEnv({ client: { NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]), @@ -8,7 +10,10 @@ export const env = createEnv({ NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().default("development"), NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001), NEXT_PUBLIC_PUBLIC_URL: z.string().optional(), - NEXT_PUBLIC_RELEASE_TAG: z.string().optional(), + NEXT_PUBLIC_RELEASE_TAG: z + .string() + .optional() + .transform((s) => getSemver(s, process.env.BRANCH || "development")), }, emptyStringAsUndefined: true, runtimeEnv: { diff --git a/apps/scandic-web/env/server.ts b/apps/scandic-web/env/server.ts index 3edabcb44..3e3a5d19e 100644 --- a/apps/scandic-web/env/server.ts +++ b/apps/scandic-web/env/server.ts @@ -1,6 +1,8 @@ import { createEnv } from "@t3-oss/env-nextjs" import { z } from "zod" +import { getSemver } from "@scandic-hotels/common/utils/getSemver" + export const env = createEnv({ /** * Due to t3-env only checking typeof window === "undefined" @@ -112,7 +114,10 @@ export const env = createEnv({ .refine((s) => s === "true" || s === "false") .transform((s) => s === "true") .default("false"), - RELEASE_TAG: z.string().optional(), + RELEASE_TAG: z + .string() + .optional() + .transform((s) => getSemver(s, process.env.BRANCH || "development")), }, emptyStringAsUndefined: true, runtimeEnv: { diff --git a/packages/common/package.json b/packages/common/package.json index c51eaa4e5..4b30128c1 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -40,6 +40,7 @@ "./stores/*": "./stores/*.ts", "./telemetry": "./telemetry/index.ts", "./tokenManager": "./tokenManager/index.ts", + "./utils/getSemver": "./utils/getSemver.ts", "./utils/stringEquals": "./utils/stringEquals.ts", "./utils/chunk": "./utils/chunk.ts", "./utils/dateFormatting": "./utils/dateFormatting.ts", diff --git a/packages/common/utils/getSemver.test.ts b/packages/common/utils/getSemver.test.ts new file mode 100644 index 000000000..bcd40a5a5 --- /dev/null +++ b/packages/common/utils/getSemver.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest" + +import { getSemver } from "./getSemver" + +describe("getSemver", () => { + it("should return undefined if version is undefined", () => { + expect(getSemver(undefined, "master")).toBeUndefined() + }) + + it("should return undefined if version is not a string", () => { + // @ts-expect-error Testing invalid input + expect(getSemver(123, "master")).toBeUndefined() + }) + + it("should strip 'v' prefix from version", () => { + expect(getSemver("v1.0.0", "master")).toBe("1.0.0") + }) + + it("should return undefined if version format is invalid", () => { + expect(getSemver("invalid-version", "master")).toBeUndefined() + }) + + it("should return the version as-is for named branches", () => { + expect(getSemver("1.0.0", "release")).toBe("1.0.0") + expect(getSemver("1.0.0", "master")).toBe("1.0.0") + expect(getSemver("1.0.0", "test")).toBe("1.0.0") + expect(getSemver("1.0.0", "stage")).toBe("1.0.0") + expect(getSemver("1.0.0", "prod")).toBe("1.0.0") + }) + + it("should append cleaned branch name for non-named branches", () => { + expect(getSemver("1.0.0", "feature/my-feature")).toBe( + "1.0.0-feature-my-feature" + ) + }) + + it("should handle special characters in non-named branches", () => { + expect(getSemver("1.0.0", "fix/bug#123")).toBe("1.0.0-fix-bug-123") + }) + + it("should lowercase and trim non-named branches", () => { + expect(getSemver("1.0.0", " MY-BRANCH ")).toBe("1.0.0-my-branch") + }) + + it("should truncate long branch names", () => { + expect( + getSemver("1.0.0", "feature/very-long-branch-name-that-exceeds-limit") + ).toBe("1.0.0-feature-very-long-br") + }) +}) diff --git a/packages/common/utils/getSemver.ts b/packages/common/utils/getSemver.ts new file mode 100644 index 000000000..bc2af0c7d --- /dev/null +++ b/packages/common/utils/getSemver.ts @@ -0,0 +1,32 @@ +const namedBranches = ["release", "master", "test", "stage", "prod"] as const + +type NamedBranch = (typeof namedBranches)[number] | (string & {}) + +export function getSemver( + version: string | undefined, + branch: NamedBranch +): string | undefined { + if (!version || typeof version !== "string") { + return undefined + } + + if (version.startsWith("v")) { + version = version.substring(1) + } + + if (!/^\d+\.\d+\.\d+/.test(version)) { + return undefined + } + + if (!namedBranches.includes(branch as any)) { + let cleanedBranch = branch + .substring(0, 20) + .trim() + .replace(/[^a-zA-Z0-9]/g, "-") + .toLowerCase() + + return `${version}-${cleanedBranch}` + } + + return version +}