diff --git a/.env.dev b/.env.dev index 6640842..a74195b 100644 --- a/.env.dev +++ b/.env.dev @@ -12,10 +12,11 @@ DOTENV_PUBLIC_KEY_DEV="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c8 DOTENV_PUBLIC_KEY="02a9d0f6522086b04ac7763e008a6ad0273c58258715e613c977fd6c82a2030d92" # .env -COM_ERMNVLDMR_STATIC_S3_BUCKET="encrypted:BL1vJLB9uPSvsUvrJah4UegRU8uPdFjE7sya+Fae3rwOmVzWsK5+TP4z4YzigTAuwit4FANvANvhRV46se+ZgprYplU8Cw1tZ5mHKO4rWAjif2sJCnsGXUKp8GZ2J7PrMH35MY0YrXX4NVqe18b2NEv2B9K7" -COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BAj3iuo7tWWNR2V4yQ+LHnFCB4JcZp7TTfUtvv+2FPntNhwc/fV95IeFWz6S0z6aPRkDG+UQzPXNwdMz7y8ZbTHbduDDrgydHu1fST1NfQd2s1Bbpf+yJTG7dPBSc5RSNKZkkpB4v55qg/UC" -COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BNkQrgsrxtz544vuJbgwWUh5cPjHG5wbmHNcxTUWrzbFJJsdncaAitviCvIrS7Q41w+FiWkssv3D05Tu8Inn2P62E6G3PU4gNxO2oLdmGEdrzUqB/xvxOvLvos42j54QOZf7lOqMPKH6SL2/rheCAo10gSCicCqkfh4xsCAbYpyL" -COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BCsp8p4bp3cB+9grxHo634isRUaFGU3MOc1lr8ClSFRYLjqjqukyrbJCTZWV2Xdo8iQ33EylmbzUltbwzTWMQcjJSvIskesVLyV93q60o/Mu9L9jg/apwHuBgHv4zVuHstJ0zkHWgapbNTIlWD3RCm0ujeX/H1gBH3U=" -COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BM7aPliOyiPrqFPZMfqpn534Nu+Y0Nbh/YRoxv2KY6XAi2dNO0LSEf97DqSUQYdsnu9iibZmVNktF2K/D6mtHr9+gumF6KsmaLH65KE7c9ly7NdFQHL9I26wxRq1Sr3Zgrfs954J6oURSlBRKqJFdcTsBltuuupGuqJHyLS9+DE0jD+bl0Qg0lA=" +COM_ERMNVLDMR_STATIC_S3_BUCKET="encrypted:BE65d5I9m1jrHGVNKDmfGL3Cjb7LjhB4tVWyNzSPU1Zu5Y00+R8NXgqSBrzBnXVj3vVinibjihVt3fStegyiqI52GtSycX1NlFSVJlRFWadUJ6SVK+SYfB7sFKSJ2jKGfwRaXKMzaKCdPWY+ni685oPXU9Rp" +COM_ERMNVLDMR_STATIC_S3_REGION="encrypted:BPQ2+ms76A+PLFUeKL1Cnz8bdOcsJou03e8AyGwzdhozpcAwhP5yWXSqhTK8ZccAvmfjJcM4NK+x2a2r4g0ALd/UaQDa9xy2eZ6QnXwkoHXu2ce+F86/HuFnzX0qWqYTWi4q+N4=" +COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL="encrypted:BCcK2ih1QrABMImpUj31KIQYlbp9zhnafzrXINAwvyCaCjbOst6UGnhhi5M3LtQ+Fhgpx52ZKGjmke1oVxnvU6BBGz/lAbSD5euhHNbJ6EIoSqc25XpcmgucIQGx1cfiJxsL4sDR95FyR3fNaS9KGr005DwQaIRKGg86gALUCPhaLekq/Sd6Nn+TDoqmigLJPozdxp33AFpmWF+EwayYae2u" +COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID="encrypted:BPaBvzNo1eH3Z2ro+NFq4zLi4krZQT48h20r0etBfVFLDNS01gh5Uosy70Ip29rC8sa1JoP4Q8isJ2Xz0pr5Vxxa62vVFOJ9wJ6LkXbYF2bUm+UNyjfsIztJsMyiVZfPEEPEEKcniXZ3faqePwgLwCZSTa/Qry6znNmMV5FshSsP" +COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY="encrypted:BFiOxOo/TNvSIMOjyeOswLcl+9AVdqp4axW+I46c1CL0ckSucQS+7CLpO8fNfakmW2bSibqru+xfWUbQT7FDH1YB3CPXUc0qPm1PsG0FiLixDefQuDrC/JHuA0Z3/dOAkn/e94BBuPtyJXxcLRP1v517MijPa0MuNqhI9BS3jFHKjWivqc9AVPs2ybglo14b/nEO1GqlM0TYflPoDhHoWjM=" SOPS_AGE_KEY="encrypted:BKiJVxKYhiadu0kNCw8wVLYdaEWZreAVu2PaujxhvcXeCowCGjivEuQD/uhduAy/CwwDWyQkgQJ2FJMEKA47e7QDjS9TMdPCXO19/teR7LThgqTf6AbSM3sebln5zalgyplay4A/7cVytPXuOvVkaYyoh9CgzTVsPJKXRq5yMaDEx/9/91Yo8zUMF3z1vq3/preI5baK4/1xXewR3OwfZqnNGyd7LXkdqyT5" COM_ERMNVLDMR_DEV_ROOT_CA_KEY="encrypted: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" +COM_ERMNVLDMR_STATIC_PUBLIC_URL="encrypted:BEZbNy1WbNKhgUWmKO648nvuVNyMNXrMnq64WkjsScTOcE/HFtMX3nQktbIvEYiTSNHvoceUeW10E9i4n+C5ZEHKIBi9VUk3nuqVIcwAr6xTfqKjM1cJlRNKFQzwhHbBTYcImLoxFvM56KU4L/9Ei/XuQ/QRz0KTabhvGMM=" diff --git a/.github/workflows/ci-next-build-golden-image.yml b/.github/workflows/ci-next-build-golden-image.yml index 1a06454..484d037 100644 --- a/.github/workflows/ci-next-build-golden-image.yml +++ b/.github/workflows/ci-next-build-golden-image.yml @@ -116,7 +116,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.STATIC_AWS_ACCESS_KEY_ID || secrets.COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.STATIC_AWS_SECRET_ACCESS_KEY || secrets.COM_ERMNVLDMR_STATIC_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION || secrets.COM_ERMNVLDMR_STATIC_S3_REGION || 'ru-central1' }} + AWS_DEFAULT_REGION: ${{ secrets.STATIC_AWS_REGION || secrets.COM_ERMNVLDMR_STATIC_S3_REGION || 'auto' }} S3_ENDPOINT: ${{ secrets.STATIC_S3_ENDPOINT_URL || secrets.COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL }} S3_BUCKET: ${{ secrets.STATIC_S3_BUCKET || secrets.COM_ERMNVLDMR_STATIC_S3_BUCKET }} IMAGE_NAME: ${{ needs.build-image.outputs.image_name }} diff --git a/ci/scripts/ensure-dev-certs.mjs b/ci/scripts/ensure-dev-certs.mjs index eefa942..2092501 100644 --- a/ci/scripts/ensure-dev-certs.mjs +++ b/ci/scripts/ensure-dev-certs.mjs @@ -162,7 +162,7 @@ export function loadDevRootCaKey(workspaceRoot = findWorkspaceRoot()) { * Remote public URL for the Ermnvldmr development root CA certificate. */ export const REMOTE_ROOT_CA_URL = - 'https://static.ermnvldmr.com/ssl/certs/ErmnvldmrDevelopmentRootCA.pem'; + 'https://static.ermnvldmr.com/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem'; /** * Ensures valid development certificates exist for localhost.ermnvldmr.com. diff --git a/packages/cli/src/lib/runners/s3SyncRunner.test.ts b/packages/cli/src/lib/runners/s3SyncRunner.test.ts index 8ff196f..0d9a37b 100644 --- a/packages/cli/src/lib/runners/s3SyncRunner.test.ts +++ b/packages/cli/src/lib/runners/s3SyncRunner.test.ts @@ -376,4 +376,33 @@ describe('runS3Sync', () => { const content = fs.readFileSync(srcPath, 'utf-8'); expect(content).toContain('https://cdn.example.com/www/img.webp'); }); + + it('should fallback to COM_ERMNVLDMR_STATIC_PUBLIC_URL when publicUrl is not explicitly configured', async () => { + process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL = 'https://static.ermnvldmr.com'; + + const staticDir = path.join(tempDir, 'static'); + fs.mkdirSync(staticDir, { recursive: true }); + fs.writeFileSync(path.join(staticDir, 'asset.png'), 'png-content'); + + const srcDir = path.join(tempDir, 'src'); + fs.mkdirSync(srcDir, { recursive: true }); + const srcPath = path.join(srcDir, 'index.ts'); + fs.writeFileSync(srcPath, 'const img = "/static/asset.png";'); + + const cmd: S3SyncCommandConfig = { + name: 'sync', + description: 'desc', + bucket: 'com-ermnvldmr-static', + targetPrefix: 'www', + sourceDir: './static', + type: 's3-sync', + }; + + await runS3Sync(cmd, {}); + + const content = fs.readFileSync(srcPath, 'utf-8'); + expect(content).toContain('https://static.ermnvldmr.com/www/asset.png'); + + delete process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL; + }); }); diff --git a/packages/cli/src/lib/runners/s3SyncRunner.ts b/packages/cli/src/lib/runners/s3SyncRunner.ts index 99339e0..8c36ebe 100644 --- a/packages/cli/src/lib/runners/s3SyncRunner.ts +++ b/packages/cli/src/lib/runners/s3SyncRunner.ts @@ -53,6 +53,7 @@ export async function runS3Sync( const bucket = cmd.bucket ?? process.env.S3_BUCKET; const region = cmd.region ?? process.env.AWS_REGION ?? process.env.S3_REGION; const endpoint = cmd.endpoint ?? process.env.S3_ENDPOINT_URL ?? process.env.S3_ENDPOINT; + const publicUrl = cmd.publicUrl ?? process.env.COM_ERMNVLDMR_STATIC_PUBLIC_URL ?? process.env.S3_PUBLIC_URL; const accessKey = cmd.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID ?? process.env.S3_ACCESS_KEY_ID; const secretKey = cmd.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY ?? process.env.S3_SECRET_ACCESS_KEY; @@ -149,8 +150,8 @@ export async function runS3Sync( */ const getS3Url = (finalName: string): string => { const normalizedKey = normalizeKey(cmd.targetPrefix, finalName); - if (cmd.publicUrl) { - const cleanPublicUrl = cmd.publicUrl.replace(/\/+$/, ''); + if (publicUrl) { + const cleanPublicUrl = publicUrl.replace(/\/+$/, ''); return `${cleanPublicUrl}/${normalizedKey}`; } if (bucket.includes('.')) { diff --git a/packages/rsbuild-config/src/lib/config/server.ts b/packages/rsbuild-config/src/lib/config/server.ts index 143f575..1aa34a8 100644 --- a/packages/rsbuild-config/src/lib/config/server.ts +++ b/packages/rsbuild-config/src/lib/config/server.ts @@ -214,7 +214,7 @@ export function loadDevRootCaKey( * Remote public URL for the Ermnvldmr development root CA certificate. */ export const REMOTE_ROOT_CA_URL = - 'https://static.ermnvldmr.com/ssl/certs/ErmnvldmrDevelopmentRootCA.pem'; + 'https://static.ermnvldmr.com/dev/ssl/certs/ErmnvldmrDevelopmentRootCA.pem'; /** * Ensures that valid TLS development certificates exist for localhost.ermnvldmr.com. diff --git a/packages/ssg/package.json b/packages/ssg/package.json index b239817..ca4fc18 100644 --- a/packages/ssg/package.json +++ b/packages/ssg/package.json @@ -24,6 +24,7 @@ "build": "tsc", "lint": "eslint src --max-warnings 0", "typecheck": "tsc --noEmit", + "test": "vitest run", "clean": "rm -rf dist" }, "dependencies": { @@ -39,6 +40,8 @@ "@rsbuild/core": "^1.7.3", "@rsbuild/plugin-react": "^1.1.1", "@rsbuild/plugin-type-check": "^1.1.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.1", "@types/node": "*", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", @@ -46,7 +49,8 @@ "fast-glob": "^3.3.3", "jsdom": "^28.0.0", "rsbuild-plugin-dts": "^0.19.5", - "typescript": "^5.8.2" + "typescript": "^5.8.2", + "vitest": "^4.1.11" }, "peerDependencies": { "react": "^19.1.0", diff --git a/packages/ssg/src/lib/config/ssg.ts b/packages/ssg/src/lib/config/ssg.ts index 600154f..daaa941 100644 --- a/packages/ssg/src/lib/config/ssg.ts +++ b/packages/ssg/src/lib/config/ssg.ts @@ -5,14 +5,36 @@ import path from 'node:path'; import React from 'react'; +import { getPageOptions } from '../core/createPage.js'; + import type { RsbuildPlugin } from '@rsbuild/core'; +/** + * Escapes HTML special characters for safe injection into markup. + * + * @param str - The raw string to escape. + * @returns The escaped HTML string. + * @example + * ```ts + * escapeHtml(''); + * ``` + */ +function escapeHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + const SSR_RUNNER_SCRIPT = ` import { createRsbuild } from '@rsbuild/core'; async function main() { const entries = JSON.parse(process.argv[1]); const ssrDistPath = process.argv[2]; + const locale = process.argv[3] || process.env.LOCALE || 'en'; const plugins = []; try { @@ -31,10 +53,35 @@ async function main() { // MDX plugins not installed or needed in this service } + if (locale !== 'en') { + plugins.push({ + name: 'ermnvldmr:ssr-locale-mdx-replacement', + setup(api) { + api.modifyRspackConfig((config, { rspack }) => { + config.plugins = [ + ...(config.plugins ?? []), + new rspack.NormalModuleReplacementPlugin( + /\\.en\\.mdx$/, + (resource) => { + resource.request = resource.request.replace(/\\.en\\.mdx$/, '.' + locale + '.mdx'); + } + ), + ]; + return config; + }); + }, + }); + } + const ssrBuild = await createRsbuild({ rsbuildConfig: { plugins, - source: { entry: entries }, + source: { + entry: entries, + define: { + 'process.env.LOCALE': JSON.stringify(locale), + }, + }, output: { target: 'node', distPath: { root: ssrDistPath, js: '.' }, @@ -119,11 +166,16 @@ export function ssgPlugin(): RsbuildPlugin { ); const ssrDistPath = path.join(path.dirname(webDistPath), 'dist-ssr'); + const locale = process.env.LOCALE ?? 'en'; + console.log('[ssg-plugin] Building SSR bundles...'); execFileSync( process.execPath, - ['--input-type=module', '-e', SSR_RUNNER_SCRIPT, JSON.stringify(entries), ssrDistPath], - { stdio: 'inherit' } + ['--input-type=module', '-e', SSR_RUNNER_SCRIPT, JSON.stringify(entries), ssrDistPath, locale], + { + stdio: 'inherit', + env: { ...process.env, LOCALE: locale }, + } ); console.log('[ssg-plugin] SSR bundles ready.'); @@ -175,8 +227,10 @@ export function ssgPlugin(): RsbuildPlugin { React.createElement(React.StrictMode, null, React.createElement(Component)) ); + const pageOptions = getPageOptions(Component); + const source = await fs.readFile(webHtmlPath, 'utf8'); - const injected = source.replace( + let injected = source.replace( /
]*>\s*<\/div>/, `
${renderedHtml}
` ); @@ -187,9 +241,29 @@ export function ssgPlugin(): RsbuildPlugin { ); } + if (pageOptions?.title) { + injected = injected.replace( + /[^<]*<\/title>/i, + `<title>${escapeHtml(pageOptions.title)}` + ); + } + + if (pageOptions?.description) { + if (/]*name=["']description["'][^>]*>/i.test(injected)) { + injected = injected.replace( + /]*name=["']description["'][^>]*>/i, + `` + ); + } else { + injected = injected.replace( + /<\/head>/i, + ` \n` + ); + } + } + const processed = await critters.process(injected); - const locale = process.env.LOCALE ?? 'en'; const localized = processed.replace( /]*)\slang="[^"]*"/, ` { + beforeEach(() => { + document.title = 'Default Title'; + const existingMeta = document.querySelector('meta[name="description"]'); + if (existingMeta) { + existingMeta.remove(); + } + }); + + it('attaches metadata options to component and retrieves them via getPageOptions', () => { + function SamplePage(): React.JSX.Element { + return
Sample
; + } + + createPage(SamplePage, { + title: 'Sample Title', + description: 'Sample Description', + }); + + const options = getPageOptions(SamplePage); + expect(options).toEqual({ + title: 'Sample Title', + description: 'Sample Description', + }); + }); + + it('returns undefined for entities without metadata options', () => { + function PlainComponent(): React.JSX.Element { + return
Plain
; + } + + expect(getPageOptions(PlainComponent)).toBeUndefined(); + expect(getPageOptions(null)).toBeUndefined(); + expect(getPageOptions(undefined)).toBeUndefined(); + expect(getPageOptions('string')).toBeUndefined(); + expect(getPageOptions(123)).toBeUndefined(); + expect(getPageOptions({})).toBeUndefined(); + }); + + it('updates document.title and creates meta description in DOM', () => { + function MetaPage(): React.JSX.Element { + return
Meta
; + } + + createPage(MetaPage, { + title: 'Custom Title', + description: 'Custom Description', + }); + + expect(document.title).toBe('Custom Title'); + const meta = document.querySelector('meta[name="description"]'); + expect(meta).not.toBeNull(); + expect(meta?.getAttribute('content')).toBe('Custom Description'); + }); + + it('updates existing meta description if already present in DOM', () => { + const existingMeta = document.createElement('meta'); + existingMeta.setAttribute('name', 'description'); + existingMeta.setAttribute('content', 'Old Description'); + document.head.appendChild(existingMeta); + + function AnotherMetaPage(): React.JSX.Element { + return
Another Meta
; + } + + createPage(AnotherMetaPage, { + description: 'Updated Description', + }); + + const meta = document.querySelector('meta[name="description"]'); + expect(meta?.getAttribute('content')).toBe('Updated Description'); + }); +}); diff --git a/packages/ssg/src/lib/core/createPage.ts b/packages/ssg/src/lib/core/createPage.ts new file mode 100644 index 0000000..39708a2 --- /dev/null +++ b/packages/ssg/src/lib/core/createPage.ts @@ -0,0 +1,121 @@ +import React from 'react'; + +/** + * Options for page creation. + */ +export interface PageOptions { + /** Document title to set on mount. */ + title?: string; + /** Content of the meta description tag. */ + description?: string; +} + +/** + * Internal interface representing a component with attached page options. + */ +interface HasPageOptions { + /** The attached page options. */ + __pageOptions?: PageOptions; +} + +/** + * Type predicate checking if a value is an object or function with attached page options. + * + * @param value - The value to inspect. + * @returns True if value has __pageOptions. + * @example + * ```ts + * if (hasPageOptions(Component)) { + * console.log(Component.__pageOptions); + * } + * ``` + */ +function hasPageOptions(value: unknown): value is HasPageOptions { + return ( + (typeof value === 'function' || (typeof value === 'object' && value !== null)) && + '__pageOptions' in value + ); +} + +/** + * Retrieves the page metadata options attached to a component via createPage. + * + * @param component - The component to inspect. + * @returns The associated PageOptions, or undefined if none are set. + * @example + * ```ts + * const options = getPageOptions(MyPageComponent); + * ``` + */ +export function getPageOptions(component: unknown): PageOptions | undefined { + if (hasPageOptions(component)) { + return component.__pageOptions; + } + return undefined; +} + +/** + * Mounts or hydrates a React component into the `#root` element. + * + * Attaches metadata options for SSG extraction. Skips DOM mounting during the SSG + * server-side render pass (`typeof window === 'undefined'`). On the client, hydrates + * asynchronously during idle time to unblock First Contentful Paint. + * + * @param Component - The React component to render. + * @param options - Optional page metadata (title, description). + * + * @example + * ```tsx + * createPage(App, { title: 'My Page' }); + * ``` + */ +export function createPage(Component: React.ComponentType, options: PageOptions = {}): void { + Object.assign(Component, { __pageOptions: options }); + + if (typeof window === 'undefined') return; // SSG build context: skip DOM mounting + const rootElement = document.getElementById('root'); + + if (options.title && document.title !== options.title) { + document.title = options.title; + } + + if (options.description) { + let metaDescription = document.querySelector('meta[name="description"]'); + if (!metaDescription) { + metaDescription = document.createElement('meta'); + metaDescription.setAttribute('name', 'description'); + document.head.appendChild(metaDescription); + } + if (metaDescription.getAttribute('content') !== options.description) { + metaDescription.setAttribute('content', options.description); + } + } + + if (!rootElement) return; + + const mountOrHydrate = () => { + void import('react-dom/client') + .then(({ createRoot, hydrateRoot }) => { + const node = React.createElement( + React.StrictMode, + null, + React.createElement(Component) + ); + + if (rootElement.hasChildNodes()) { + hydrateRoot(rootElement, node); + } else { + createRoot(rootElement).render(node); + } + }) + .catch((err: unknown) => { + console.error('[createPage] Client hydration failed:', err); + }); + }; + + if ('requestIdleCallback' in window) { + window.requestIdleCallback(mountOrHydrate); + } else { + setTimeout(mountOrHydrate, 1); + } +} diff --git a/packages/ssg/src/lib/core/createPage.tsx b/packages/ssg/src/lib/core/createPage.tsx deleted file mode 100644 index b114568..0000000 --- a/packages/ssg/src/lib/core/createPage.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; - -/** - * Options for page creation. - */ -export interface PageOptions { - /** Document title to set on mount. */ - title?: string; - /** Content of the meta description tag. */ - description?: string; -} - -/** - * Mounts or hydrates a React component into the `#root` element. - * - * Skips DOM mounting during the SSG server-side render pass - * (`typeof window === 'undefined'`). On the client, hydrates asynchronously - * during idle time to unblock First Contentful Paint. - * - * @param Component - The React component to render. - * @param options - Optional page metadata (title, description). - * - * @example - * ```tsx - * createPage(App, { title: 'My Page' }); - * ``` - */ -export function createPage(Component: React.ComponentType, options: PageOptions = {}): void { - if (typeof window === 'undefined') return; // SSG build context: skip DOM mounting - const rootElement = document.getElementById('root'); - - if (options.title) { - document.title = options.title; - } - - if (options.description) { - let metaDescription = document.querySelector('meta[name="description"]'); - if (!metaDescription) { - metaDescription = document.createElement('meta'); - metaDescription.setAttribute('name', 'description'); - document.head.appendChild(metaDescription); - } - metaDescription.setAttribute('content', options.description); - } - - if (!rootElement) return; - - const mountOrHydrate = () => { - void import('react-dom/client') - .then(({ createRoot, hydrateRoot }) => { - const node = ( - - - - ); - - if (rootElement.hasChildNodes()) { - hydrateRoot(rootElement, node); - } else { - createRoot(rootElement).render(node); - } - }) - .catch((err: unknown) => { - console.error('[createPage] Client hydration failed:', err); - }); - }; - - if ('requestIdleCallback' in window) { - window.requestIdleCallback(mountOrHydrate); - } else { - setTimeout(mountOrHydrate, 1); - } -} diff --git a/packages/ssg/vitest.config.ts b/packages/ssg/vitest.config.ts new file mode 100644 index 0000000..962c6ca --- /dev/null +++ b/packages/ssg/vitest.config.ts @@ -0,0 +1,9 @@ +import { baseVitestConfig } from '@ermnvldmr/rsbuild-config/dev'; +import { mergeConfig } from 'vitest/config'; + +export default mergeConfig(baseVitestConfig, { + test: { + environment: 'jsdom', + exclude: ['dist/**', 'node_modules/**'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baba032..f495565 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,7 +69,7 @@ importers: version: 5.8.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@22.20.0)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) packages/eslint-config: dependencies: @@ -176,7 +176,7 @@ importers: version: 8.6.15(storybook@8.6.17(prettier@3.9.10)) '@storybook/addon-coverage': specifier: ^3.0.0 - version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) '@storybook/addon-essentials': specifier: ^8.6.15 version: 8.6.15(@types/react@19.2.9)(storybook@8.6.17(prettier@3.9.10)) @@ -236,13 +236,13 @@ importers: version: 8.6.17(prettier@3.9.10) storybook-react-rsbuild: specifier: ^1.0.3 - version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)) + version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)) typescript: specifier: ^5.8.3 version: 5.8.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) wait-on: specifier: ^8.0.3 version: 8.0.5 @@ -300,7 +300,7 @@ importers: dependencies: '@rsbuild/plugin-mdx': specifier: ^1.1.1 - version: 1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1) + version: 1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1(esbuild@0.27.7)) react: specifier: ^19.1.0 version: 19.2.3 @@ -329,6 +329,12 @@ importers: '@rsbuild/plugin-type-check': specifier: ^1.1.1 version: 1.3.3(@rsbuild/core@1.7.3)(@rspack/core@1.7.6(@swc/helpers@0.5.18))(tslib@2.8.1)(typescript@5.8.3) + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.1 + version: 16.3.2(@testing-library/dom@10.4.0)(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@types/node': specifier: '*' version: 22.20.0 @@ -353,6 +359,9 @@ importers: typescript: specifier: ^5.8.2 version: 5.8.3 + vitest: + specifier: ^4.1.11 + version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) packages/stl: dependencies: @@ -444,7 +453,7 @@ importers: version: 7.0.1 storybook-react-rsbuild: specifier: ^1.0.3 - version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)) + version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)) typescript: specifier: ^5.8.3 version: 5.8.3 @@ -523,7 +532,7 @@ importers: version: 8.6.15(storybook@8.6.17(prettier@3.9.10)) '@storybook/addon-coverage': specifier: ^3.0.0 - version: 3.0.0(vite@8.1.6(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + version: 3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) '@storybook/addon-essentials': specifier: ^8.6.15 version: 8.6.15(@types/react@19.2.9)(storybook@8.6.17(prettier@3.9.10)) @@ -541,7 +550,7 @@ importers: version: 8.6.15(storybook@8.6.17(prettier@3.9.10)) '@storybook/test-runner': specifier: ^0.19.1 - version: 0.19.1(@swc/helpers@0.5.18)(@types/node@25.6.2)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) + version: 0.19.1(@swc/helpers@0.5.18)(@types/node@22.20.0)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@22.20.0)(typescript@5.8.3)) '@tailwindcss/postcss': specifier: ^4.1.18 version: 4.1.18 @@ -589,13 +598,13 @@ importers: version: 8.6.17(prettier@3.9.10) storybook-react-rsbuild: specifier: ^1.0.3 - version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)) + version: 1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)) typescript: specifier: ^5.8.3 version: 5.8.3 vitest: specifier: ^4.1.11 - version: 4.1.11(@types/node@25.6.2)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@25.6.2)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + version: 4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) wait-on: specifier: ^8.0.3 version: 8.0.5 @@ -10259,41 +10268,6 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))': - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 22.20.0 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - '@jest/create-cache-key-function@30.2.0': dependencies: '@jest/types': 30.2.0 @@ -10628,12 +10602,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/loader@3.1.1(webpack@5.105.1)': + '@mdx-js/loader@3.1.1(webpack@5.105.1(esbuild@0.27.7))': dependencies: '@mdx-js/mdx': 3.1.1 source-map: 0.7.6 optionalDependencies: - webpack: 5.105.1 + webpack: 5.105.1(esbuild@0.27.7) transitivePeerDependencies: - supports-color @@ -12563,9 +12537,9 @@ snapshots: - supports-color - webpack - '@rsbuild/plugin-mdx@1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1)': + '@rsbuild/plugin-mdx@1.1.1(@rsbuild/core@1.7.3)(webpack@5.105.1(esbuild@0.27.7))': dependencies: - '@mdx-js/loader': 3.1.1(webpack@5.105.1) + '@mdx-js/loader': 3.1.1(webpack@5.105.1(esbuild@0.27.7)) optionalDependencies: '@rsbuild/core': 1.7.3 transitivePeerDependencies: @@ -13083,6 +13057,20 @@ snapshots: - supports-color - vite + '@storybook/addon-coverage@3.0.0(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))': + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + '@jsdevtools/coverage-istanbul-loader': 3.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + espree: 9.6.1 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + vite-plugin-istanbul: 6.0.2(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) + transitivePeerDependencies: + - supports-color + - vite + '@storybook/addon-coverage@3.0.0(vite@8.1.6(@types/node@25.0.10)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2))': dependencies: '@istanbuljs/load-nyc-config': 1.1.0 @@ -13408,7 +13396,7 @@ snapshots: dependencies: storybook: 8.6.17(prettier@3.9.10) - '@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12))': + '@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31))': dependencies: debug: 4.4.3 endent: 2.1.0 @@ -13418,7 +13406,7 @@ snapshots: react-docgen-typescript: 2.4.0(typescript@5.8.3) tslib: 2.8.1 typescript: 5.8.3 - webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12) + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31) transitivePeerDependencies: - supports-color @@ -13436,6 +13424,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7))': + dependencies: + debug: 4.4.3 + endent: 2.1.0 + find-cache-dir: 3.3.2 + flat-cache: 3.2.0 + micromatch: 4.0.8 + react-docgen-typescript: 2.4.0(typescript@5.8.3) + tslib: 2.8.1 + typescript: 5.8.3 + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7) + transitivePeerDependencies: + - supports-color + '@storybook/react-docgen-typescript-plugin@1.0.1(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7))': dependencies: debug: 4.4.3 @@ -13604,40 +13606,6 @@ snapshots: - supports-color - ts-node - '@storybook/test-runner@0.19.1(@swc/helpers@0.5.18)(@types/node@25.6.2)(storybook@8.6.17(prettier@3.9.10))(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))': - dependencies: - '@babel/core': 7.28.6 - '@babel/generator': 7.28.6 - '@babel/template': 7.28.6 - '@babel/types': 7.28.6 - '@jest/types': 29.6.3 - '@storybook/core-common': 8.6.14(storybook@8.6.17(prettier@3.9.10)) - '@storybook/csf': 0.1.13 - '@storybook/csf-tools': 8.6.14(storybook@8.6.17(prettier@3.9.10)) - '@storybook/preview-api': 8.6.15(storybook@8.6.17(prettier@3.9.10)) - '@swc/core': 1.15.10(@swc/helpers@0.5.18) - '@swc/jest': 0.2.39(@swc/core@1.15.10(@swc/helpers@0.5.18)) - expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-junit: 16.0.0 - jest-playwright-preset: 4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))) - jest-runner: 29.7.0 - jest-serializer-html: 7.1.0 - jest-watch-typeahead: 2.2.2(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))) - nyc: 15.1.0 - playwright: 1.58.0 - transitivePeerDependencies: - - '@swc/helpers' - - '@types/node' - - babel-plugin-macros - - debug - - node-notifier - - storybook - - supports-color - - ts-node - '@storybook/test@8.6.15(storybook@8.6.17(prettier@3.5.3))': dependencies: '@storybook/global': 5.0.0 @@ -14972,21 +14940,6 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - create-require@1.1.1: {} critters@0.0.25: @@ -16629,25 +16582,6 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - exit: 0.1.2 - import-local: 3.2.0 - jest-config: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jest-config@29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@22.20.0)(typescript@5.8.3)): dependencies: '@babel/core': 7.28.6 @@ -16710,37 +16644,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@22.20.0)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)): - dependencies: - '@babel/core': 7.28.6 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.6) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 22.20.0 - ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-config@29.7.0(@types/node@25.0.10)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.0.10)(typescript@5.8.3)): dependencies: '@babel/core': 7.28.6 @@ -16772,37 +16675,6 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)): - dependencies: - '@babel/core': 7.28.6 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.6) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 25.6.2 - ts-node: 10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3) - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -16918,22 +16790,6 @@ snapshots: - debug - supports-color - jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))): - dependencies: - expect-playwright: 0.8.0 - jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-process-manager: 0.4.0 - jest-runner: 29.7.0 - nyc: 15.1.0 - playwright-core: 1.58.0 - rimraf: 3.0.2 - uuid: 8.3.2 - transitivePeerDependencies: - - debug - - supports-color - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): optionalDependencies: jest-resolve: 29.7.0 @@ -17099,17 +16955,6 @@ snapshots: string-length: 5.0.1 strip-ansi: 7.1.2 - jest-watch-typeahead@2.2.2(jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3))): - dependencies: - ansi-escapes: 6.2.1 - chalk: 5.6.2 - jest: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - jest-regex-util: 29.6.3 - jest-watcher: 29.7.0 - slash: 5.1.0 - string-length: 5.0.1 - strip-ansi: 7.1.2 - jest-watcher@29.7.0: dependencies: '@jest/test-result': 29.7.0 @@ -17158,18 +17003,6 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@25.6.2)(ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jiti@2.6.1: {} jju@1.4.0: @@ -18765,14 +18598,14 @@ snapshots: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)): + schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)): dependencies: '@types/json-schema': 7.0.15 ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) ajv-keywords: 5.1.0(ajv@8.20.0) optionalDependencies: - webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12) + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31) schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)): dependencies: @@ -18783,6 +18616,15 @@ snapshots: optionalDependencies: webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31) + schema-utils@4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)): + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) + optionalDependencies: + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7) + schema-utils@4.8.1(webpack@5.105.1(esbuild@0.27.7)): dependencies: '@types/json-schema': 7.0.15 @@ -18792,16 +18634,6 @@ snapshots: optionalDependencies: webpack: 5.105.1(esbuild@0.27.7) - schema-utils@4.8.1(webpack@5.105.1): - dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - ajv-keywords: 5.1.0(ajv@8.20.0) - optionalDependencies: - webpack: 5.105.1 - optional: true - secure-compare@3.0.1: {} semver@6.3.1: {} @@ -19120,12 +18952,12 @@ snapshots: - tslib - webpack - storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)): + storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) '@rsbuild/core': 1.7.3 '@storybook/react': 8.6.15(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3) - '@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)) + '@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)) '@types/node': 18.19.130 find-up: 5.0.0 magic-string: 0.30.21 @@ -19148,12 +18980,12 @@ snapshots: - tslib - webpack - storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)): + storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) '@rsbuild/core': 1.7.3 '@storybook/react': 8.6.15(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3) - '@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)) + '@storybook/react-docgen-typescript-plugin': 1.0.1(typescript@5.8.3)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)) '@types/node': 18.19.130 find-up: 5.0.0 magic-string: 0.30.21 @@ -19176,7 +19008,7 @@ snapshots: - tslib - webpack - storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)): + storybook-react-rsbuild@1.0.3(@rsbuild/core@1.7.3)(@storybook/test@8.6.15(storybook@8.6.17(prettier@3.9.10)))(@types/react@19.2.9)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.60.1)(storybook@8.6.17(prettier@3.9.10))(typescript@5.8.3)(webpack@5.105.1(esbuild@0.27.7)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1) '@rsbuild/core': 1.7.3 @@ -19373,16 +19205,17 @@ snapshots: tapable@2.3.3: {} - terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)): + terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)) + schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)) terser: 5.51.2 - webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12) + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31) optionalDependencies: '@swc/core': 1.15.10(@swc/helpers@0.5.18) esbuild: 0.25.12 + postcss: 8.5.31 terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(postcss@8.5.31)): dependencies: @@ -19396,6 +19229,17 @@ snapshots: esbuild: 0.27.7 postcss: 8.5.31 + terser-webpack-plugin@5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)) + terser: 5.51.2 + webpack: 5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7) + optionalDependencies: + '@swc/core': 1.15.10(@swc/helpers@0.5.18) + esbuild: 0.27.7 + terser-webpack-plugin@5.6.1(esbuild@0.27.7)(webpack@5.105.1(esbuild@0.27.7)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -19406,15 +19250,6 @@ snapshots: optionalDependencies: esbuild: 0.27.7 - terser-webpack-plugin@5.6.1(webpack@5.105.1): - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - jest-worker: 27.5.1 - schema-utils: 4.8.1(webpack@5.105.1) - terser: 5.51.2 - webpack: 5.105.1 - optional: true - terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 @@ -19574,27 +19409,6 @@ snapshots: optionalDependencies: '@swc/core': 1.15.10(@swc/helpers@0.5.18) - ts-node@10.9.2(@swc/core@1.15.10(@swc/helpers@0.5.18))(@types/node@25.6.2)(typescript@5.8.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 25.6.2 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.8.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optionalDependencies: - '@swc/core': 1.15.10(@swc/helpers@0.5.18) - optional: true - tsconfck@3.1.6(typescript@5.8.3): optionalDependencies: typescript: 5.8.3 @@ -19849,6 +19663,18 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-istanbul@6.0.2(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)): + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + espree: 10.4.0 + istanbul-lib-instrument: 6.0.3 + picocolors: 1.1.1 + source-map: 0.7.6 + test-exclude: 6.0.0 + vite: 8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + vite-plugin-istanbul@6.0.2(vite@8.1.6(@types/node@25.0.10)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)): dependencies: '@istanbuljs/load-nyc-config': 1.1.0 @@ -19972,7 +19798,7 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.11(@types/node@22.20.0)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)): + vitest@4.1.11(@types/node@22.20.0)(@vitest/coverage-v8@4.0.18)(jsdom@28.0.0)(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.11 '@vitest/mocker': 4.1.11(vite@8.1.6(@types/node@22.20.0)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.51.2)(yaml@2.8.2)) @@ -19996,6 +19822,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.20.0 + '@vitest/coverage-v8': 4.0.18(vitest@4.1.11) jsdom: 28.0.0 transitivePeerDependencies: - msw @@ -20104,7 +19931,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.105.1: + webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -20126,9 +19953,9 @@ snapshots: loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.8.1(webpack@5.105.1) + schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)) tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(webpack@5.105.1) + terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(postcss@8.5.31)) watchpack: 2.6.3 webpack-sources: 3.6.0 transitivePeerDependencies: @@ -20144,9 +19971,8 @@ snapshots: - lightningcss - postcss - uglify-js - optional: true - webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12): + webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.9 @@ -20168,9 +19994,9 @@ snapshots: loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)) + schema-utils: 4.8.1(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)) tapable: 2.3.3 - terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.25.12)) + terser-webpack-plugin: 5.6.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)(webpack@5.105.1(@swc/core@1.15.10(@swc/helpers@0.5.18))(esbuild@0.27.7)) watchpack: 2.6.3 webpack-sources: 3.6.0 transitivePeerDependencies: diff --git a/services/docs/.config/cli.ts b/services/docs/.config/cli.ts index 7fe9889..542de72 100644 --- a/services/docs/.config/cli.ts +++ b/services/docs/.config/cli.ts @@ -10,6 +10,7 @@ export default defineCli({ sourceFilesPattern: 'content/**/*.{md,html}', urlPrefixes: ['/static/', '/images/'], bucket: env('COM_ERMNVLDMR_STATIC_S3_BUCKET'), + publicUrl: env('COM_ERMNVLDMR_STATIC_PUBLIC_URL', 'https://static.ermnvldmr.com'), region: env('COM_ERMNVLDMR_STATIC_S3_REGION'), endpoint: env('COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL'), accessKeyId: env('COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID'), diff --git a/services/www/.config/cli.ts b/services/www/.config/cli.ts index e839d4d..ba150de 100644 --- a/services/www/.config/cli.ts +++ b/services/www/.config/cli.ts @@ -7,6 +7,7 @@ export default defineCli({ description: 'Sync static assets to S3', targetPrefix: 'www', bucket: env('COM_ERMNVLDMR_STATIC_S3_BUCKET'), + publicUrl: env('COM_ERMNVLDMR_STATIC_PUBLIC_URL', 'https://static.ermnvldmr.com'), region: env('COM_ERMNVLDMR_STATIC_S3_REGION'), endpoint: env('COM_ERMNVLDMR_STATIC_S3_ENDPOINT_URL'), accessKeyId: env('COM_ERMNVLDMR_STATIC_AWS_ACCESS_KEY_ID'), diff --git a/services/www/package.json b/services/www/package.json index 1d722bb..fb3d6ed 100644 --- a/services/www/package.json +++ b/services/www/package.json @@ -7,7 +7,9 @@ "cli": "ermnvldmr-cli", "sync:static": "pnpm cli sync:static", "dev": "rsbuild dev", - "build": "rsbuild build", + "dev:en": "LOCALE=en rsbuild dev", + "dev:ru": "LOCALE=ru rsbuild dev", + "build": "pnpm run build:all", "build:en": "LOCALE=en rsbuild build", "build:ru": "LOCALE=ru rsbuild build", "build:all": "pnpm run build:en && pnpm run build:ru",