diff --git a/.github/labeler.yml b/.github/labeler.yml index 7d1533cd8f9f..5a7038ee8b43 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -18,4 +18,4 @@ packages: - any: ["packages/**/*"] local dev: - - any: ["**/turbo.json", "**/tsconfig.json", "**/knip.json", "**/.prettierrc", "**/.oxlintrc.json", "**/.eslintrc.cjs", "**/vite.config.dev.js"] + - any: ["**/turbo.json", "**/tsconfig.json", "**/knip.json", "**/.prettierrc", "**/.oxlintrc.json", "**/.eslintrc.cjs"] diff --git a/frontend/package.json b/frontend/package.json index aed5df9013bd..2d4482084da5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,7 +58,6 @@ "@vitest/coverage-v8": "4.0.8", "autoprefixer": "10.4.20", "concurrently": "8.2.2", - "dotenv": "16.4.5", "eslint": "8.57.1", "eslint-plugin-compat": "6.0.2", "firebase-tools": "13.15.1", diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index ec867261db1c..d1c1641ec26c 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -217,7 +217,7 @@ export function updateActiveElement( | { direction: "forward" | "back"; initial?: undefined } | { direction?: undefined; initial: true }, ): void { - requestDebouncedAnimationFrame("test-ui.updateActiveElement", () => { + requestDebouncedAnimationFrame("test-ui.updateActiveElement", async () => { const { direction, initial } = options; let previousActiveWordTop: number | null = null; @@ -251,10 +251,6 @@ export function updateActiveElement( updateWordsInputPosition(); - if (!initial && Config.tapeMode !== "off") { - void scrollTape(); - } - if (previousActiveWordTop === null) return; const isTimedTest = @@ -265,9 +261,13 @@ export function updateActiveElement( if (isTimedTest || !Config.showAllLines) { const newActiveWordTop = newActiveWord.offsetTop; if (newActiveWordTop > previousActiveWordTop) { - void lineJump(previousActiveWordTop); + await lineJump(previousActiveWordTop); } } + + if (!initial && Config.tapeMode !== "off") { + await scrollTape(); + } }); } @@ -942,7 +942,7 @@ export async function updateWordLetters({ if (!Config.showAllLines) { const wordTopAfterUpdate = wordAtIndex.offsetTop; if (wordTopAfterUpdate > activeWordTop) { - void lineJump(activeWordTop, true); + await lineJump(activeWordTop, true); } } } @@ -1210,14 +1210,13 @@ export async function lineJump( currentTop: number, force = false, ): Promise { - const { resolve, promise } = Misc.promiseWithResolvers(); //last word of the line if (currentTestLine > 0 || force) { const hideBound = currentTop; const activeWordEl = getActiveWordElement(); if (!activeWordEl) { - resolve(); + // resolve(); return; } @@ -1244,10 +1243,9 @@ export async function lineJump( } if (lastElementIndexToRemove === undefined) { - resolve(); currentTestLine++; updateWordsWrapperHeight(); - return promise; + return; } currentLinesJumping++; @@ -1265,29 +1263,24 @@ export async function lineJump( if (Config.smoothLineScroll) { lineTransition = true; - animate(wordsEl, { + await Misc.promiseAnimate(wordsEl, { marginTop: newMarginTop, duration, - onComplete: () => { - currentLinesJumping = 0; - activeWordTop = activeWordEl.offsetTop; - activeWordHeight = activeWordEl.offsetHeight; - removeTestElements(lastElementIndexToRemove); - wordsEl.style.marginTop = "0"; - lineTransition = false; - resolve(); - }, }); + currentLinesJumping = 0; + activeWordTop = activeWordEl.offsetTop; + activeWordHeight = activeWordEl.offsetHeight; + removeTestElements(lastElementIndexToRemove); + wordsEl.style.marginTop = "0"; + lineTransition = false; } else { currentLinesJumping = 0; removeTestElements(lastElementIndexToRemove); - resolve(); } } currentTestLine++; updateWordsWrapperHeight(); - - return promise; + return; } export function setRightToLeft(isEnabled: boolean): void { diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index f134d4208d50..8f63926cb756 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -448,7 +448,8 @@ export async function promiseAnimate( return new Promise((resolve) => { animate(el, { ...options, - onComplete: () => { + onComplete: (self, e) => { + options.onComplete?.(self, e); resolve(); }, }); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 86b316e33875..df28ff992246 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -15,6 +15,11 @@ "virtual:env-config": ["./src/ts/types/virtual-env-config.d.ts"] } }, - "include": ["./src/**/*.ts", "./scripts/**/*.ts", "vite-plugins/**/*.ts"], + "include": [ + "./src/**/*.ts", + "./scripts/**/*.ts", + "vite-plugins/**/*.ts", + "vite.config.ts" + ], "exclude": ["node_modules", "build", "setup-tests.ts", "**/*.spec.ts"] } diff --git a/frontend/vite-plugins/env-config.ts b/frontend/vite-plugins/env-config.ts index 702193cd93fb..c0088a7d0bd7 100644 --- a/frontend/vite-plugins/env-config.ts +++ b/frontend/vite-plugins/env-config.ts @@ -1,40 +1,14 @@ import { Plugin } from "vite"; import { EnvConfig } from "virtual:env-config"; -import { config as dotenvConfig } from "dotenv"; - -const envFile = - process.env["NODE_ENV"] === "production" ? ".env.production" : ".env"; -dotenvConfig({ path: envFile }); const virtualModuleId = "virtual:env-config"; const resolvedVirtualModuleId = "\0" + virtualModuleId; -const developmentConfig: EnvConfig = { - isDevelopment: true, - backendUrl: fallbackEnv("BACKEND_URL", "http://localhost:5005"), - clientVersion: "DEVELOPMENT_CLIENT", - recaptchaSiteKey: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", - quickLoginEmail: process.env["QUICK_LOGIN_EMAIL"], - quickLoginPassword: process.env["QUICK_LOGIN_PASSWORD"], -}; -const productionConfig: Omit = { - isDevelopment: false, - backendUrl: fallbackEnv("BACKEND_URL", "https://api.monkeytype.com"), - recaptchaSiteKey: process.env["RECAPTCHA_SITE_KEY"] ?? "", - quickLoginEmail: undefined, - quickLoginPassword: undefined, -}; - -export function envConfig( - options: - | { - isDevelopment: true; - } - | { - isDevelopment: false; - clientVersion: string; - }, -): Plugin { +export function envConfig(options: { + isDevelopment: boolean; + clientVersion: string; + env: Record; +}): Plugin { return { name: "virtual-env-config", resolveId(id) { @@ -43,13 +17,31 @@ export function envConfig( }, load(id) { if (id === resolvedVirtualModuleId) { - const envConfig = options.isDevelopment - ? developmentConfig - : { - ...productionConfig, - clientVersion: options.clientVersion, - }; + const devConfig: EnvConfig = { + isDevelopment: true, + backendUrl: fallback( + options.env["BACKEND_URL"], + "http://localhost:5005", + ), + clientVersion: options.clientVersion, + recaptchaSiteKey: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", + quickLoginEmail: options.env["QUICK_LOGIN_EMAIL"], + quickLoginPassword: options.env["QUICK_LOGIN_PASSWORD"], + }; + + const prodConfig: EnvConfig = { + isDevelopment: false, + backendUrl: fallback( + options.env["BACKEND_URL"], + "https://api.monkeytype.com", + ), + recaptchaSiteKey: options.env["RECAPTCHA_SITE_KEY"] ?? "", + quickLoginEmail: undefined, + quickLoginPassword: undefined, + clientVersion: options.clientVersion, + }; + const envConfig = options.isDevelopment ? devConfig : prodConfig; return ` export const envConfig = ${JSON.stringify(envConfig)}; `; @@ -59,8 +51,7 @@ export function envConfig( }; } -function fallbackEnv(envVariable: string, fallback: string): string { - const value = process.env[envVariable]; +function fallback(value: string | undefined | null, fallback: string): string { if (value === null || value === undefined || value === "") return fallback; return value; } diff --git a/frontend/vite-plugins/jquery-inject.ts b/frontend/vite-plugins/jquery-inject.ts new file mode 100644 index 000000000000..6577cbcdcc60 --- /dev/null +++ b/frontend/vite-plugins/jquery-inject.ts @@ -0,0 +1,29 @@ +import { Plugin } from "vite"; +import MagicString from "magic-string"; + +export function jqueryInject(): Plugin { + return { + name: "simple-jquery-inject", + async transform(src: string, id: string) { + if (id.endsWith(".ts")) { + //check if file has a jQuery or $() call + if (/(?:jQuery|\$)\([^)]*\)/.test(src)) { + const s = new MagicString(src); + + //if file has "use strict"; at the top, add it below that line, if not, add it at the very top + if (src.startsWith(`"use strict";`)) { + s.appendRight(12, `\nimport $ from "jquery";`); + } else { + s.prepend(`import $ from "jquery";`); + } + + return { + code: s.toString(), + map: s.generateMap({ hires: true, source: id }), + }; + } + } + return; + }, + }; +} diff --git a/frontend/vite-plugins/language-hashes.ts b/frontend/vite-plugins/language-hashes.ts index 48faf83ce105..936b40b5f8f0 100644 --- a/frontend/vite-plugins/language-hashes.ts +++ b/frontend/vite-plugins/language-hashes.ts @@ -16,7 +16,7 @@ export function languageHashes(options?: { skip: boolean }): Plugin { load(id) { if (id === resolvedVirtualModuleId) { if (options?.skip) { - console.log("Skipping language hashing in dev environment."); + console.log("Skipping language hashing."); } const hashes: Record = options?.skip ? {} : getHashes(); diff --git a/frontend/vite.config.dev.js b/frontend/vite.config.dev.js deleted file mode 100644 index 2d6fab7f021b..000000000000 --- a/frontend/vite.config.dev.js +++ /dev/null @@ -1,42 +0,0 @@ -import { checker } from "vite-plugin-checker"; -import Inspect from "vite-plugin-inspect"; -import path from "node:path"; -import { getFontsConig } from "./vite.config"; -import { envConfig } from "./vite-plugins/env-config"; -import { languageHashes } from "./vite-plugins/language-hashes"; - -/** @type {import("vite").UserConfig} */ -export default { - plugins: [ - envConfig({ isDevelopment: true }), - languageHashes({ skip: true }), - checker({ - typescript: { - tsconfigPath: path.resolve(__dirname, "./tsconfig.json"), - }, - oxlint: true, - eslint: { - lintCommand: `eslint "${path.resolve(__dirname, "./src/ts/**/*.ts")}"`, - watchPath: path.resolve(__dirname, "./src/"), - }, - overlay: { - initialIsOpen: false, - }, - }), - Inspect(), - ], - css: { - preprocessorOptions: { - scss: { - additionalData: ` - $fontAwesomeOverride:"@fortawesome/fontawesome-free/webfonts"; - $previewFontsPath:"webfonts"; - $fonts: (${getFontsConig()}); - `, - }, - }, - }, - build: { - outDir: "../dist", - }, -}; diff --git a/frontend/vite.config.js b/frontend/vite.config.js deleted file mode 100644 index dc08c12fdfbe..000000000000 --- a/frontend/vite.config.js +++ /dev/null @@ -1,108 +0,0 @@ -import { defineConfig, mergeConfig } from "vite"; -import injectHTML from "vite-plugin-html-inject"; -import autoprefixer from "autoprefixer"; -import { config as dotenvConfig } from "dotenv"; -import PROD_CONFIG from "./vite.config.prod"; -import DEV_CONFIG from "./vite.config.dev"; -import MagicString from "magic-string"; -import { Fonts } from "./src/ts/constants/fonts"; - -// Load environment variables based on NODE_ENV -const envFile = - process.env.NODE_ENV === "production" ? ".env.production" : ".env"; -dotenvConfig({ path: envFile }); - -/** @type {import("vite").UserConfig} */ -const BASE_CONFIG = { - plugins: [ - { - name: "simple-jquery-inject", - async transform(src, id) { - if (id.endsWith(".ts")) { - //check if file has a jQuery or $() call - if (/(?:jQuery|\$)\([^)]*\)/.test(src)) { - const s = new MagicString(src); - - //if file has "use strict"; at the top, add it below that line, if not, add it at the very top - if (src.startsWith(`"use strict";`)) { - s.appendRight(12, `\nimport $ from "jquery";`); - } else { - s.prepend(`import $ from "jquery";`); - } - - return { - code: s.toString(), - map: s.generateMap({ hires: true, source: id }), - }; - } - } - }, - }, - injectHTML(), - ], - server: { - open: process.env.SERVER_OPEN !== "false", - port: 3000, - host: process.env.BACKEND_URL !== undefined, - watch: { - //we rebuild the whole contracts package when a file changes - //so we only want to watch one file - ignored: [/.*\/packages\/contracts\/dist\/(?!configs).*/], - }, - }, - clearScreen: false, - root: "src", - publicDir: "../static", - css: { - devSourcemap: true, - postcss: { - plugins: [autoprefixer({})], - }, - }, - envDir: "../", - optimizeDeps: { - include: ["jquery"], - exclude: ["@fortawesome/fontawesome-free"], - }, -}; - -export default defineConfig(({ command }) => { - if (command === "build") { - const envFileName = - process.env.NODE_ENV === "production" ? ".env.production" : ".env"; - if (process.env.RECAPTCHA_SITE_KEY === undefined) { - throw new Error(`${envFileName}: RECAPTCHA_SITE_KEY is not defined`); - } - if (process.env.SENTRY && process.env.SENTRY_AUTH_TOKEN === undefined) { - throw new Error(`${envFileName}: SENTRY_AUTH_TOKEN is not defined`); - } - return mergeConfig(BASE_CONFIG, PROD_CONFIG); - } else { - return mergeConfig(BASE_CONFIG, DEV_CONFIG); - } -}); - -/** Enable for font awesome v6 */ -/* -function sassList(values) { - return values.map((it) => `"${it}"`).join(","); -} -*/ - -export function getFontsConig() { - return ( - "\n" + - Object.keys(Fonts) - .sort() - .map((name) => { - const config = Fonts[name]; - if (config.systemFont === true) return ""; - return `"${name.replaceAll("_", " ")}": ( - "src": "${config.fileName}", - "weight": ${config.weight ?? 400}, - ),`; - }) - .join("\n") + - "\n" - ); -} diff --git a/frontend/vite.config.prod.js b/frontend/vite.config.ts similarity index 57% rename from frontend/vite.config.prod.js rename to frontend/vite.config.ts index 218de3e4f46d..ac378fe3727c 100644 --- a/frontend/vite.config.prod.js +++ b/frontend/vite.config.ts @@ -1,70 +1,112 @@ -import { VitePWA } from "vite-plugin-pwa"; -import replace from "vite-plugin-filter-replace"; +import { + defineConfig, + loadEnv, + UserConfig, + BuildEnvironmentOptions, + PluginOption, + Plugin, + CSSOptions, +} from "vite"; import path from "node:path"; +import injectHTML from "vite-plugin-html-inject"; import childProcess from "child_process"; -import { checker } from "vite-plugin-checker"; -// eslint-disable-next-line import/no-unresolved -import UnpluginInjectPreload from "unplugin-inject-preload/vite"; -import { ViteMinifyPlugin } from "vite-plugin-minify"; -import { sentryVitePlugin } from "@sentry/vite-plugin"; -import { getFontsConig } from "./vite.config"; +import autoprefixer from "autoprefixer"; +import { Fonts } from "./src/ts/constants/fonts"; import { fontawesomeSubset } from "./vite-plugins/fontawesome-subset"; import { fontPreview } from "./vite-plugins/font-preview"; import { envConfig } from "./vite-plugins/env-config"; import { languageHashes } from "./vite-plugins/language-hashes"; import { minifyJson } from "./vite-plugins/minify-json"; import { versionFile } from "./vite-plugins/version-file"; +import { jqueryInject } from "./vite-plugins/jquery-inject"; +import { checker } from "vite-plugin-checker"; +import Inspect from "vite-plugin-inspect"; +import { ViteMinifyPlugin } from "vite-plugin-minify"; +import { VitePWA } from "vite-plugin-pwa"; +import { sentryVitePlugin } from "@sentry/vite-plugin"; +import replace from "vite-plugin-filter-replace"; +// eslint-disable-next-line import/no-unresolved +import UnpluginInjectPreload from "unplugin-inject-preload/vite"; +import { KnownFontName } from "@monkeytype/schemas/fonts"; -function pad(numbers, maxLength, fillString) { - return numbers.map((number) => - number.toString().padStart(maxLength, fillString), - ); -} - -const CLIENT_VERSION = (() => { - const date = new Date(); - const versionPrefix = pad( - [date.getFullYear(), date.getMonth() + 1, date.getDate()], - 2, - "0", - ).join("."); - const versionSuffix = pad([date.getHours(), date.getMinutes()], 2, "0").join( - ".", - ); - const version = [versionPrefix, versionSuffix].join("_"); - - try { - const commitHash = childProcess - .execSync("git rev-parse --short HEAD") - .toString(); +export default defineConfig(({ mode }): UserConfig => { + const env = loadEnv(mode, process.cwd(), ""); + const useSentry = env["SENTRY"] !== undefined; + const isDevelopment = mode !== "production"; - return `${version}_${commitHash}`.replace(/\n/g, ""); - } catch (e) { - return `${version}_unknown-hash`; + if (!isDevelopment) { + if (env["RECAPTCHA_SITE_KEY"] === undefined) { + throw new Error(`${mode}: RECAPTCHA_SITE_KEY is not defined`); + } + if (useSentry && env["SENTRY_AUTH_TOKEN"] === undefined) { + throw new Error(`${mode}: SENTRY_AUTH_TOKEN is not defined`); + } } -})(); -/** Enable for font awesome v6 */ -/* -function sassList(values) { - return values.map((it) => `"${it}"`).join(","); -} -*/ + return { + plugins: getPlugins({ isDevelopment, useSentry: useSentry, env }), + build: getBuildOptions({ enableSourceMaps: useSentry }), + css: getCssOptions({ isDevelopment }), + server: { + open: env["SERVER_OPEN"] !== "false", + port: 3000, + host: env["BACKEND_URL"] !== undefined, + watch: { + //we rebuild the whole contracts package when a file changes + //so we only want to watch one file + ignored: [/.*\/packages\/contracts\/dist\/(?!configs).*/], + }, + }, + clearScreen: false, + root: "src", + publicDir: "../static", + optimizeDeps: { + include: ["jquery"], + exclude: ["@fortawesome/fontawesome-free"], + }, + }; +}); -/** @type {import("vite").UserConfig} */ -export default { - plugins: [ - envConfig({ isDevelopment: false, clientVersion: CLIENT_VERSION }), - languageHashes(), - fontawesomeSubset(), - versionFile({ clientVersion: CLIENT_VERSION }), - fontPreview(), +function getPlugins({ + isDevelopment, + env, + useSentry, +}: { + isDevelopment: boolean; + env: Record; + useSentry: boolean; +}): PluginOption[] { + const clientVersion = getClientVersion(isDevelopment); + + const plugins: PluginOption[] = [ + envConfig({ isDevelopment, clientVersion, env }), + languageHashes({ skip: isDevelopment }), checker({ typescript: { tsconfigPath: path.resolve(__dirname, "./tsconfig.json"), }, + oxlint: isDevelopment, + eslint: isDevelopment + ? { + lintCommand: `eslint "${path.resolve(__dirname, "./src/ts/**/*.ts")}"`, + watchPath: path.resolve(__dirname, "./src/"), + } + : false, + overlay: { + initialIsOpen: false, + }, }), - ViteMinifyPlugin({}), + jqueryInject(), + injectHTML(), + ]; + + const devPlugins: PluginOption[] = [Inspect()]; + + const prodPlugins: PluginOption[] = [ + fontPreview(), + fontawesomeSubset(), + versionFile({ clientVersion }), + ViteMinifyPlugin(), VitePWA({ // injectRegister: "networkfirst", injectRegister: null, @@ -118,16 +160,16 @@ export default { ], }, }), - process.env.SENTRY - ? sentryVitePlugin({ - authToken: process.env.SENTRY_AUTH_TOKEN, + useSentry + ? (sentryVitePlugin({ + authToken: env["SENTRY_AUTH_TOKEN"], org: "monkeytype", project: "frontend", release: { - name: CLIENT_VERSION, + name: clientVersion, }, applicationKey: "monkeytype-frontend", - }) + }) as Plugin) : null, replace([ { @@ -169,9 +211,20 @@ export default { injectTo: "head-prepend", }), minifyJson(), - ], - build: { - sourcemap: process.env.SENTRY, + ]; + + return [...plugins, ...(isDevelopment ? devPlugins : prodPlugins)].filter( + (it) => it !== null, + ); +} + +function getBuildOptions({ + enableSourceMaps, +}: { + enableSourceMaps: boolean; +}): BuildEnvironmentOptions { + return { + sourcemap: enableSourceMaps, emptyOutDir: true, outDir: "../dist", assetsInlineLimit: 0, //dont inline small files as data @@ -185,8 +238,8 @@ export default { 404: path.resolve(__dirname, "src/404.html"), }, output: { - assetFileNames: (assetInfo) => { - let extType = assetInfo.name.split(".").at(1); + assetFileNames: (assetInfo: { name: string }) => { + let extType = assetInfo.name.split(".").at(1) as string; if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) { extType = "images"; } @@ -213,26 +266,50 @@ export default { if (id.includes("node_modules")) { return "vendor"; } + return; }, }, }, - }, - css: { + } as BuildEnvironmentOptions; +} + +function getCssOptions({ + isDevelopment, +}: { + isDevelopment: boolean; +}): CSSOptions { + return { + devSourcemap: true, + postcss: { + plugins: [ + // @ts-expect-error this is fine + autoprefixer({}), + ], + }, preprocessorOptions: { scss: { additionalData(source, fp) { if (fp.endsWith("index.scss")) { /** Enable for font awesome v6 */ /* - const fontawesomeClasses = getFontawesomeConfig(); - - //inject variables into sass context - $fontawesomeBrands: ${sassList( - fontawesomeClasses.brands - )}; - $fontawesomeSolid: ${sassList(fontawesomeClasses.solid)}; - */ - const fonts = `$fonts: (${getFontsConig()});`; + const fontawesomeClasses = getFontawesomeConfig(); + + //inject variables into sass context + $fontawesomeBrands: ${sassList( + fontawesomeClasses.brands + )}; + $fontawesomeSolid: ${sassList(fontawesomeClasses.solid)}; + */ + + const bypassFonts = isDevelopment + ? ` + $fontAwesomeOverride:"@fortawesome/fontawesome-free/webfonts"; + $previewFontsPath:"webfonts";` + : ""; + const fonts = ` + ${bypassFonts} + $fonts: (${getFontsConfig()}); + `; return ` //inject variables into sass context ${fonts} @@ -244,5 +321,66 @@ export default { }, }, }, - }, -}; + }; +} + +function getFontsConfig(): string { + return ( + "\n" + + Object.keys(Fonts) + .sort() + .map((name: string) => { + const config = Fonts[name as KnownFontName]; + if (config.systemFont === true) return ""; + return `"${name.replaceAll("_", " ")}": ( + "src": "${config.fileName}", + "weight": ${config.weight ?? 400}, + ),`; + }) + .join("\n") + + "\n" + ); +} + +function pad( + numbers: number[], + maxLength: number, + fillString: string, +): string[] { + return numbers.map((number) => + number.toString().padStart(maxLength, fillString), + ); +} + +/** Enable for font awesome v6 */ +/* +function sassList(values) { + return values.map((it) => `"${it}"`).join(","); +} +*/ + +function getClientVersion(isDevelopment: boolean): string { + if (isDevelopment) { + return "DEVELOPMENT_CLIENT"; + } + const date = new Date(); + const versionPrefix = pad( + [date.getFullYear(), date.getMonth() + 1, date.getDate()], + 2, + "0", + ).join("."); + const versionSuffix = pad([date.getHours(), date.getMinutes()], 2, "0").join( + ".", + ); + const version = [versionPrefix, versionSuffix].join("_"); + + try { + const commitHash = childProcess + .execSync("git rev-parse --short HEAD") + .toString(); + + return `${version}_${commitHash}`.replace(/\n/g, ""); + } catch (e) { + return `${version}_unknown-hash`; + } +} diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 7081b6fae04f..28ec5c5802c2 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -20,5 +20,8 @@ export default defineConfig({ }, }, - plugins: [languageHashes({ skip: true }), envConfig({ isDevelopment: true })], + plugins: [ + languageHashes({ skip: true }), + envConfig({ isDevelopment: true, clientVersion: "TESTING", env: {} }), + ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e895a63bf7cd..c550b6b33bbe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -409,9 +409,6 @@ importers: concurrently: specifier: 8.2.2 version: 8.2.2 - dotenv: - specifier: 16.4.5 - version: 16.4.5 eslint: specifier: 8.57.1 version: 8.57.1