mirror of
				https://github.com/theoludwig/theoludwig.git
				synced 2025-10-14 20:23:25 +02:00 
			
		
		
		
	chore: cleaner setup
This commit is contained in:
		| @@ -1,2 +1,2 @@ | ||||
| TZ=UTC | ||||
| TZ=Europe/Paris | ||||
| WEBSITE_PORT=3000 | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/chromatic.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/chromatic.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,15 +10,19 @@ jobs: | ||||
|   chromatic: | ||||
|     timeout-minutes: 30 | ||||
|     runs-on: "ubuntu-latest" | ||||
|     env: | ||||
|       DO_NOT_TRACK: "1" | ||||
|       TURBO_TELEMETRY_DISABLED: "1" | ||||
|       NEXT_TELEMETRY_DISABLED: "1" | ||||
|     steps: | ||||
|       - uses: "actions/checkout@v4.2.2" | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - uses: "pnpm/action-setup@v4.0.0" | ||||
|       - uses: "pnpm/action-setup@v4.1.0" | ||||
|  | ||||
|       - name: "Setup Node.js" | ||||
|         uses: "actions/setup-node@v4.1.0" | ||||
|         uses: "actions/setup-node@v4.2.0" | ||||
|         with: | ||||
|           node-version: "22.x" | ||||
|           cache: "pnpm" | ||||
|   | ||||
							
								
								
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -10,13 +10,17 @@ jobs: | ||||
|   ci: | ||||
|     timeout-minutes: 30 | ||||
|     runs-on: "ubuntu-latest" | ||||
|     env: | ||||
|       DO_NOT_TRACK: "1" | ||||
|       TURBO_TELEMETRY_DISABLED: "1" | ||||
|       NEXT_TELEMETRY_DISABLED: "1" | ||||
|     steps: | ||||
|       - uses: "actions/checkout@v4.2.2" | ||||
|  | ||||
|       - uses: "pnpm/action-setup@v4.0.0" | ||||
|       - uses: "pnpm/action-setup@v4.1.0" | ||||
|  | ||||
|       - name: "Setup Node.js" | ||||
|         uses: "actions/setup-node@v4.1.0" | ||||
|         uses: "actions/setup-node@v4.2.0" | ||||
|         with: | ||||
|           node-version: "22.x" | ||||
|           cache: "pnpm" | ||||
|   | ||||
							
								
								
									
										45
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										45
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,45 +0,0 @@ | ||||
| name: "Release" | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [main, staging] | ||||
|  | ||||
| jobs: | ||||
|   release: | ||||
|     timeout-minutes: 30 | ||||
|     runs-on: "ubuntu-latest" | ||||
|     permissions: | ||||
|       contents: "write" | ||||
|       issues: "write" | ||||
|       pull-requests: "write" | ||||
|       id-token: "write" | ||||
|     steps: | ||||
|       - uses: "actions/checkout@v4.2.2" | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|           persist-credentials: false | ||||
|  | ||||
|       - name: "Import GPG key" | ||||
|         uses: "crazy-max/ghaction-import-gpg@v6.1.0" | ||||
|         with: | ||||
|           gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | ||||
|           git_user_signingkey: true | ||||
|           git_commit_gpgsign: true | ||||
|  | ||||
|       - uses: "pnpm/action-setup@v4.0.0" | ||||
|  | ||||
|       - name: "Setup Node.js" | ||||
|         uses: "actions/setup-node@v4.1.0" | ||||
|         with: | ||||
|           node-version: "22.x" | ||||
|           cache: "pnpm" | ||||
|  | ||||
|       - name: "Install dependencies" | ||||
|         run: "pnpm install --frozen-lockfile" | ||||
|  | ||||
|       - name: "Release" | ||||
|         run: "node --run release" | ||||
|         env: | ||||
|           GH_TOKEN: ${{ secrets.GH_TOKEN }} | ||||
|           GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }} | ||||
|           GIT_COMMITTER_EMAIL: ${{ secrets.GIT_EMAIL }} | ||||
| @@ -1,12 +0,0 @@ | ||||
| { | ||||
|   "config": { | ||||
|     "extends": "markdownlint/style/prettier", | ||||
|     "default": true, | ||||
|     "relative-links": true, | ||||
|     "no-duplicate-heading": false, | ||||
|     "no-inline-html": false, | ||||
|   }, | ||||
|   "globs": ["**/*.md"], | ||||
|   "ignores": ["**/node_modules"], | ||||
|   "customRules": ["markdownlint-rule-relative-links"], | ||||
| } | ||||
							
								
								
									
										16
									
								
								.markdownlint-cli2.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.markdownlint-cli2.mjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import relativeLinksRule from "markdownlint-rule-relative-links" | ||||
|  | ||||
| const config = { | ||||
|   config: { | ||||
|     extends: "markdownlint/style/prettier", | ||||
|     default: true, | ||||
|     "relative-links": true, | ||||
|     "no-duplicate-heading": false, | ||||
|     "no-inline-html": false, | ||||
|   }, | ||||
|   globs: ["**/*.md"], | ||||
|   ignores: ["**/node_modules"], | ||||
|   customRules: [relativeLinksRule], | ||||
| } | ||||
|  | ||||
| export default config | ||||
| @@ -1,34 +0,0 @@ | ||||
| { | ||||
|   "branches": ["main", { "name": "staging", "prerelease": true }], | ||||
|   "plugins": [ | ||||
|     "@semantic-release/commit-analyzer", | ||||
|     "@semantic-release/release-notes-generator", | ||||
|     [ | ||||
|       "@semantic-release/exec", | ||||
|       { | ||||
|         "prepareCmd": "replace-in-files --regex='version\": *\"[^\"]*' --replacement='\"version\": \"${nextRelease.version}\"' '**/package.json' '!**/node_modules/**'" | ||||
|       } | ||||
|     ], | ||||
|     [ | ||||
|       "@semantic-release/git", | ||||
|       { | ||||
|         "assets": [ | ||||
|           "package.json", | ||||
|           "apps/*/package.json", | ||||
|           "packages/*/package.json" | ||||
|         ], | ||||
|         "message": "chore(release): ${nextRelease.version} [skip ci]" | ||||
|       } | ||||
|     ], | ||||
|     "@semantic-release/github", | ||||
|     [ | ||||
|       "@saithodev/semantic-release-backmerge", | ||||
|       { | ||||
|         "branches": [ | ||||
|           { "from": "main", "to": "develop" }, | ||||
|           { "from": "staging", "to": "develop" } | ||||
|         ] | ||||
|       } | ||||
|     ] | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -15,7 +15,7 @@ | ||||
|   ], | ||||
|   "i18n-ally.localesPaths": ["./packages/i18n/src/translations/"], | ||||
|   "i18n-ally.keystyle": "nested", | ||||
|   "i18n-ally.sortKeys": true, | ||||
|   "i18n-ally.sortKeys": false, | ||||
|   "i18n-ally.sourceLanguage": "en-US", | ||||
|   "i18n-ally.displayLanguage": "en-US", | ||||
|   "i18n-ally.enabledFrameworks": ["next-intl", "general"], | ||||
|   | ||||
| @@ -31,8 +31,8 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv | ||||
|  | ||||
| ### Prerequisites | ||||
|  | ||||
| - [Node.js](https://nodejs.org/) >= 22.0.0 | ||||
| - [pnpm](https://pnpm.io/) >= 9.15.0 [(`corepack enable`)](https://nodejs.org/docs/latest-v22.x/api/corepack.html) | ||||
| - [Node.js](https://nodejs.org/) >= 22.12.0 [(`nvm install 22`)](https://nvm.sh) | ||||
| - [pnpm](https://pnpm.io/) >= 10.2.1 [(`corepack enable`)](https://nodejs.org/docs/latest-v22.x/api/corepack.html) | ||||
| - [Docker](https://www.docker.com/) | ||||
|  | ||||
| ### Installation | ||||
| @@ -83,9 +83,9 @@ node --run test | ||||
|  | ||||
| ```sh | ||||
| # Setup and run all the services for you | ||||
| docker compose up --build | ||||
| VERSION=$(git describe --tags) docker compose up --build --detach | ||||
| ``` | ||||
|  | ||||
| #### Services started | ||||
|  | ||||
| `theoludwig`: <http://127.0.0.1:3000> | ||||
| `theoludwig`: <http://localhost:3000> | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import "@repo/config-tailwind/styles.css" | ||||
| import "./storybook-css-overrides.css" | ||||
| import i18nMessages from "@repo/i18n/translations/en-US.json" | ||||
| import { LOCALE_DEFAULT, TIMEZONE } from "@repo/utils/constants" | ||||
| import type { Preview } from "@storybook/react" | ||||
| @@ -7,6 +8,11 @@ import { ThemeProvider as NextThemeProvider } from "next-themes" | ||||
| import React from "react" | ||||
|  | ||||
| const preview: Preview = { | ||||
|   globals: { | ||||
|     a11y: { | ||||
|       manual: true, | ||||
|     }, | ||||
|   }, | ||||
|   parameters: { | ||||
|     nextjs: { | ||||
|       appDirectory: true, | ||||
|   | ||||
							
								
								
									
										3
									
								
								apps/storybook/.storybook/storybook-css-overrides.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								apps/storybook/.storybook/storybook-css-overrides.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| body { | ||||
|   overflow: auto !important; | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import config from "@repo/eslint-config" | ||||
| import config from "@repo/config-eslint" | ||||
|  | ||||
| export default typescriptESLint.config(...config, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
							
								
								
									
										84
									
								
								apps/storybook/http-server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								apps/storybook/http-server.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| import http from "node:http" | ||||
| import fs from "node:fs" | ||||
| import path from "node:path" | ||||
| import util from "node:util" | ||||
| import mime from "mime" | ||||
|  | ||||
| const MIMETYPE_DEFAULT = "application/octet-stream" | ||||
|  | ||||
| const args = util.parseArgs({ | ||||
|   options: { | ||||
|     path: { type: "string", default: "public", required: true }, | ||||
|     port: { type: "string", default: "3000", required: true }, | ||||
|     host: { type: "string", default: "0.0.0.0" }, | ||||
|   }, | ||||
| }) | ||||
|  | ||||
| const host = args.values.host | ||||
| const basePath = args.values.path | ||||
|  | ||||
| const port = Number.parseInt(args.values.port, 10) | ||||
| if (Number.isNaN(port)) { | ||||
|   console.error("Error: Invalid port number.") | ||||
|   process.exit(1) | ||||
| } | ||||
|  | ||||
| const serverURL = `http://${host}:${port}` | ||||
|  | ||||
| const server = http.createServer(async (request, response) => { | ||||
|   if (request.url == null) { | ||||
|     response.writeHead(400, { "Content-Type": "text/plain" }) | ||||
|     response.end("Bad Request") | ||||
|     return | ||||
|   } | ||||
|   const url = new URL(request.url, serverURL) | ||||
|   const urlPath = url.pathname | ||||
|   const filePath = path.join(process.cwd(), basePath, urlPath) | ||||
|   try { | ||||
|     const stat = await fs.promises.stat(filePath) | ||||
|     if (stat.isDirectory()) { | ||||
|       const indexFile = path.join(filePath, "index.html") | ||||
|       try { | ||||
|         const fileContent = await fs.promises.readFile(indexFile) | ||||
|         response.writeHead(200, { "Content-Type": "text/html" }) | ||||
|         response.end(fileContent) | ||||
|       } catch { | ||||
|         response.writeHead(403, { "Content-Type": "text/plain" }) | ||||
|         response.end("Error: Directory listing not allowed.") | ||||
|       } | ||||
|     } else { | ||||
|       const mimeType = mime.getType(filePath) ?? MIMETYPE_DEFAULT | ||||
|       const fileContent = await fs.promises.readFile(filePath) | ||||
|       response.writeHead(200, { "Content-Type": mimeType }) | ||||
|       response.end(fileContent) | ||||
|     } | ||||
|   } catch (error) { | ||||
|     if (error instanceof Error && "code" in error && error.code === "ENOENT") { | ||||
|       response.writeHead(404, { "Content-Type": "text/plain" }) | ||||
|       response.end("Error: File not found.") | ||||
|     } else { | ||||
|       response.writeHead(500, { "Content-Type": "text/plain" }) | ||||
|       response.end("Error: Internal Server Error.") | ||||
|     } | ||||
|   } | ||||
| }) | ||||
|  | ||||
| const gracefulShutdown = (): void => { | ||||
|   server.close() | ||||
|   process.exit(0) | ||||
| } | ||||
| process.on("SIGTERM", gracefulShutdown) | ||||
| process.on("SIGINT", gracefulShutdown) | ||||
|  | ||||
| server.listen( | ||||
|   { | ||||
|     host, | ||||
|     port, | ||||
|   }, | ||||
|   () => { | ||||
|     console.log( | ||||
|       `HTTP Server is listening at ${util.styleText("cyan", serverURL)}`, | ||||
|     ) | ||||
|     console.log(`Serving files from: \`${basePath}\``) | ||||
|   }, | ||||
| ) | ||||
| @@ -1,14 +1,14 @@ | ||||
| { | ||||
|   "name": "@repo/storybook", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "build": "storybook build", | ||||
|     "dev": "storybook dev --port 6006 --no-open", | ||||
|     "start": "http-server \"storybook-static\" --port 6006 --silent", | ||||
|     "test": "start-server-and-test \"start\" http://127.0.0.1:6006 \"test:storybook\"", | ||||
|     "test:dev": "start-server-and-test \"dev\" http://127.0.0.1:6006 \"test:storybook\"", | ||||
|     "start": "node --experimental-strip-types http-server.ts --path=storybook-static --port=6006", | ||||
|     "test": "start-server-and-test \"start\" http://localhost:6006 \"test:storybook\"", | ||||
|     "test:dev": "start-server-and-test \"dev\" http://localhost:6006 \"test:storybook\"", | ||||
|     "test:storybook": "test-storybook --testTimeout=60000 --maxWorkers=2", | ||||
|     "chromatic": "chromatic" | ||||
|   }, | ||||
| @@ -22,10 +22,11 @@ | ||||
|     "next-intl": "catalog:", | ||||
|     "next-themes": "catalog:", | ||||
|     "react": "catalog:", | ||||
|     "react-dom": "catalog:" | ||||
|     "react-dom": "catalog:", | ||||
|     "mime": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@chromatic-com/storybook": "catalog:", | ||||
|     "@playwright/test": "catalog:", | ||||
| @@ -45,7 +46,6 @@ | ||||
|     "axe-playwright": "catalog:", | ||||
|     "chromatic": "catalog:", | ||||
|     "eslint": "catalog:", | ||||
|     "http-server": "catalog:", | ||||
|     "start-server-and-test": "catalog:", | ||||
|     "storybook": "catalog:", | ||||
|     "storybook-dark-mode": "catalog:", | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| TZ=UTC | ||||
| TZ=Europe/Paris | ||||
| HOSTNAME=0.0.0.0 | ||||
| PORT=3000 | ||||
| NEXT_TELEMETRY_DISABLED=1 | ||||
|   | ||||
| @@ -1,14 +1,15 @@ | ||||
| FROM node:22.13.1-slim AS node-pnpm | ||||
| ENV PNPM_HOME="/pnpm" | ||||
| ENV PATH="$PNPM_HOME:$PATH" | ||||
| RUN corepack enable | ||||
| RUN npm install --global corepack@0.31.0 && corepack enable | ||||
| ENV TURBO_TELEMETRY_DISABLED=1 | ||||
| ENV NEXT_TELEMETRY_DISABLED=1 | ||||
| ENV DO_NOT_TRACK=1 | ||||
| WORKDIR /usr/src/app | ||||
|  | ||||
| FROM node-pnpm AS builder | ||||
| COPY ./ ./ | ||||
| RUN pnpm install --global turbo@2.3.3 | ||||
| RUN pnpm install --global turbo@2.4.0 | ||||
| RUN turbo prune @repo/website --docker | ||||
|  | ||||
| FROM node-pnpm AS installer | ||||
| @@ -20,12 +21,15 @@ COPY --from=builder /usr/src/app/out/pnpm-lock.yaml ./pnpm-lock.yaml | ||||
| RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile | ||||
| COPY --from=builder /usr/src/app/out/full/ ./ | ||||
| COPY turbo.json turbo.json | ||||
|  | ||||
| ARG VERSION="0.0.0-develop" | ||||
| RUN pnpm install --global replace-in-files-cli@3.0.0 | ||||
| RUN VERSION_STRIPPED=${VERSION#v} && replace-in-files --regex='version": *"[^"]*' --replacement='"version": "'"$VERSION_STRIPPED"'"' '**/package.json' '!**/node_modules/**' | ||||
| RUN pnpm --filter=@repo/website... exec turbo run build | ||||
|  | ||||
| FROM node-pnpm AS runner | ||||
| ENV NODE_ENV=production | ||||
| ENV HOSTNAME=0.0.0.0 | ||||
| ENV NEXT_TELEMETRY_DISABLED=1 | ||||
| ENV IS_STANDALONE=true | ||||
|  | ||||
| RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 applicationrunner | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import configNextjs from "@repo/eslint-config/nextjs" | ||||
| import configNextjs from "@repo/config-eslint/nextjs" | ||||
|  | ||||
| export default typescriptESLint.config(...configNextjs, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
							
								
								
									
										2
									
								
								apps/website/next-env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								apps/website/next-env.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -2,4 +2,4 @@ | ||||
| /// <reference types="next/image-types/global" /> | ||||
|  | ||||
| // NOTE: This file should not be edited | ||||
| // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. | ||||
| // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/website", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "imports": { | ||||
| @@ -28,7 +28,7 @@ | ||||
|     "react-dom": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@types/node": "catalog:", | ||||
|     "@types/react": "catalog:", | ||||
|   | ||||
| @@ -6,6 +6,8 @@ services: | ||||
|     build: | ||||
|       context: "./" | ||||
|       dockerfile: "./apps/website/Dockerfile" | ||||
|       args: | ||||
|         VERSION: ${VERSION-0.0.0-develop} | ||||
|     ports: | ||||
|       - "${WEBSITE_PORT-3000}:${WEBSITE_PORT-3000}" | ||||
|     environment: | ||||
|   | ||||
| @@ -10,7 +10,6 @@ export default typescriptESLint.config( | ||||
|       "**/eslint.config.js", | ||||
|       "**/tailwind.config.js", | ||||
|       "**/postcss.config.js", | ||||
|       "**/vitest.config.ts", | ||||
|       "**/kysely.config.ts", | ||||
|     ], | ||||
|   }, | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/eslint-config", | ||||
|   "version": "4.1.3", | ||||
|   "name": "@repo/config-eslint", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import config from "@repo/eslint-config" | ||||
| import config from "@repo/config-eslint" | ||||
| 
 | ||||
| export default typescriptESLint.config(...config, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/config-tailwind", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "main": "./tailwind.config.js", | ||||
| @@ -21,7 +21,7 @@ | ||||
|     "tailwind-merge": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@tailwindcss/typography": "catalog:", | ||||
|     "typescript-eslint": "catalog:", | ||||
| @@ -9,8 +9,9 @@ | ||||
| @tailwind utilities; | ||||
| 
 | ||||
| @layer base { | ||||
|   * { | ||||
|     min-width: 0; | ||||
|   [type="search"]::-webkit-search-decoration, | ||||
|   [type="search"]::-webkit-search-cancel-button { | ||||
|     appearance: none; | ||||
|   } | ||||
| 
 | ||||
|   b, | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/config-typescript", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "files": [ | ||||
|     "tsconfig.json" | ||||
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "repo", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c", | ||||
|   "packageManager": "pnpm@10.2.1+sha512.398035c7bd696d0ba0b10a688ed558285329d27ea994804a52bad9167d8e3a72bcb993f9699585d3ca25779ac64949ef422757a6c31102c12ab932e5cbe5cc92", | ||||
|   "engines": { | ||||
|     "node": ">=22.0.0", | ||||
|     "pnpm": ">=9.15.0" | ||||
|     "node": ">=22.12.0", | ||||
|     "pnpm": ">=10.2.1" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "build": "turbo run build", | ||||
| @@ -17,13 +17,9 @@ | ||||
|     "lint:markdown": "markdownlint-cli2", | ||||
|     "lint:typescript": "turbo run lint:typescript", | ||||
|     "lint:eslint": "turbo run lint:eslint", | ||||
|     "lint:prettier": "prettier . --check", | ||||
|     "release": "semantic-release" | ||||
|     "lint:prettier": "prettier . --check" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@saithodev/semantic-release-backmerge": "catalog:", | ||||
|     "@semantic-release/exec": "catalog:", | ||||
|     "@semantic-release/git": "catalog:", | ||||
|     "editorconfig-checker": "catalog:", | ||||
|     "playwright": "catalog:", | ||||
|     "prettier": "catalog:", | ||||
| @@ -31,8 +27,6 @@ | ||||
|     "markdownlint-cli2": "catalog:", | ||||
|     "markdownlint": "catalog:", | ||||
|     "markdownlint-rule-relative-links": "catalog:", | ||||
|     "replace-in-files-cli": "catalog:", | ||||
|     "semantic-release": "catalog:", | ||||
|     "turbo": "catalog:", | ||||
|     "typescript": "catalog:" | ||||
|   } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import configNextjs from "@repo/eslint-config/nextjs" | ||||
| import configNextjs from "@repo/config-eslint/nextjs" | ||||
|  | ||||
| export default typescriptESLint.config(...configNextjs, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/blog", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
| @@ -37,7 +37,7 @@ | ||||
|     "react-icons": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@types/node": "catalog:", | ||||
|     "@types/react": "catalog:", | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import config from "@repo/eslint-config" | ||||
| import config from "@repo/config-eslint" | ||||
|  | ||||
| export default typescriptESLint.config(...config, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/i18n", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
| @@ -11,26 +11,23 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "lint:eslint": "eslint src --max-warnings 0", | ||||
|     "lint:typescript": "tsc --noEmit", | ||||
|     "test": "vitest run" | ||||
|     "lint:typescript": "tsc --noEmit" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@repo/utils": "workspace:*", | ||||
|     "deepmerge": "catalog:", | ||||
|     "next": "catalog:", | ||||
|     "next-intl": "catalog:", | ||||
|     "react": "catalog:", | ||||
|     "react-dom": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@types/react": "catalog:", | ||||
|     "@types/react-dom": "catalog:", | ||||
|     "@total-typescript/ts-reset": "catalog:", | ||||
|     "eslint": "catalog:", | ||||
|     "typescript-eslint": "catalog:", | ||||
|     "typescript": "catalog:", | ||||
|     "vitest": "catalog:" | ||||
|     "typescript": "catalog:" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								packages/i18n/src/messages.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								packages/i18n/src/messages.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| import type en from "./translations/en-US.json" | ||||
| import type fr from "./translations/fr-FR.json" | ||||
|  | ||||
| type Messages = typeof en | ||||
| type Messages = typeof fr | ||||
|  | ||||
| declare global { | ||||
|   /** | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| import deepmerge from "deepmerge" | ||||
| import type { AbstractIntlMessages } from "next-intl" | ||||
| import { getRequestConfig } from "next-intl/server" | ||||
|  | ||||
| import type { Locale } from "@repo/utils/constants" | ||||
| import { LOCALE_DEFAULT, LOCALES } from "@repo/utils/constants" | ||||
| import { deepMerge } from "@repo/utils/objects" | ||||
|  | ||||
| export default getRequestConfig(async ({ requestLocale }) => { | ||||
|   let locale = await requestLocale | ||||
| @@ -15,7 +15,7 @@ export default getRequestConfig(async ({ requestLocale }) => { | ||||
|   const defaultMessages = ( | ||||
|     await import(`./translations/${LOCALE_DEFAULT}.json`) | ||||
|   ).default | ||||
|   const messages = deepmerge<AbstractIntlMessages>( | ||||
|   const messages = deepMerge<AbstractIntlMessages>( | ||||
|     defaultMessages, | ||||
|     userMessages, | ||||
|   ) | ||||
|   | ||||
| @@ -4,6 +4,13 @@ import { LOCALES, LOCALE_DEFAULT, LOCALE_PREFIX } from "@repo/utils/constants" | ||||
| import { defineRouting } from "next-intl/routing" | ||||
| import type { Locale } from "@repo/utils/constants" | ||||
|  | ||||
| // Countries: https://github.com/umpirsky/country-list/blob/master/data/en/country.json | ||||
| // Country flag picture: https://purecatamphetamine.github.io/country-flag-icons/3x2/US.svg | ||||
|  | ||||
| // Locale codes: https://simplelocalize.io/data/locales/ | ||||
| // Locale code is a combination of ISO 639-1 language code and ISO 3166-1 country code. | ||||
| // For example, `fr-FR` is a locale code for French language in France. | ||||
|  | ||||
| export interface LocaleProps { | ||||
|   params: Promise<{ | ||||
|     locale: Locale | ||||
|   | ||||
| @@ -1,7 +0,0 @@ | ||||
| import { expectTypeOf, test } from "vitest" | ||||
| import en from "../translations/en-US.json" | ||||
| import fr from "../translations/fr-FR.json" | ||||
|  | ||||
| test("translations types should match", () => { | ||||
|   expectTypeOf(en).toEqualTypeOf(fr) | ||||
| }) | ||||
| @@ -1,4 +1,24 @@ | ||||
| { | ||||
|   "meta": { | ||||
|     "description": "Developer Full Stack • Open-Source Enthusiast", | ||||
|     "title": "Théo LUDWIG" | ||||
|   }, | ||||
|   "locales": { | ||||
|     "en-US": "English", | ||||
|     "fr-FR": "French" | ||||
|   }, | ||||
|   "loading": "Loading...", | ||||
|   "errors": { | ||||
|     "error": "Error", | ||||
|     "not-found": "Not Found", | ||||
|     "page-doesnt-exist": "This page doesn't exist!", | ||||
|     "return-to-home-page": "Return to the home page?", | ||||
|     "server-error": "Internal Server Error!", | ||||
|     "try-again": "Try again?" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "All rights reserved" | ||||
|   }, | ||||
|   "curriculum-vitae": { | ||||
|     "about": { | ||||
|       "description": "I constantly wonder how to improve our present, to make our future better, particularly thanks to the advancements in computer science. <br></br> My priority is to craft intuitive user experiences (UX), that meet the needs of the users in the most efficient way possible.", | ||||
| @@ -84,17 +104,6 @@ | ||||
|       "title": "Work experiences" | ||||
|     } | ||||
|   }, | ||||
|   "errors": { | ||||
|     "error": "Error", | ||||
|     "not-found": "Not Found", | ||||
|     "page-doesnt-exist": "This page doesn't exist!", | ||||
|     "return-to-home-page": "Return to the home page?", | ||||
|     "server-error": "Internal Server Error!", | ||||
|     "try-again": "Try again?" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "All rights reserved" | ||||
|   }, | ||||
|   "home": { | ||||
|     "about": { | ||||
|       "birth-date": { | ||||
| @@ -150,13 +159,5 @@ | ||||
|       "software-tools": "Software and tools", | ||||
|       "title": "Skills" | ||||
|     } | ||||
|   }, | ||||
|   "locales": { | ||||
|     "en-US": "English", | ||||
|     "fr-FR": "French" | ||||
|   }, | ||||
|   "meta": { | ||||
|     "description": "Developer Full Stack • Open-Source Enthusiast", | ||||
|     "title": "Théo LUDWIG" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,24 @@ | ||||
| { | ||||
|   "meta": { | ||||
|     "description": "Développeur Full Stack • Enthousiaste de l'Open-Source", | ||||
|     "title": "Théo LUDWIG" | ||||
|   }, | ||||
|   "locales": { | ||||
|     "en-US": "Anglais", | ||||
|     "fr-FR": "Français" | ||||
|   }, | ||||
|   "loading": "Chargement...", | ||||
|   "errors": { | ||||
|     "error": "Erreur", | ||||
|     "not-found": "Introuvable", | ||||
|     "page-doesnt-exist": "Cette page n'existe pas !", | ||||
|     "return-to-home-page": "Retour à la page d'accueil ?", | ||||
|     "server-error": "Erreur interne du serveur !", | ||||
|     "try-again": "Réessayer ?" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "Tous droits réservés" | ||||
|   }, | ||||
|   "curriculum-vitae": { | ||||
|     "about": { | ||||
|       "description": "Je me demande constamment comment améliorer notre présent, afin de rendre notre futur meilleur, particulièrement grâce aux progrès de l'informatique. <br></br> Ma priorité réside dans la création d'expériences utilisateurs (UX) intuitives, répondant aux besoins des utilisateurs de la manière la plus efficace que possible.", | ||||
| @@ -84,17 +104,6 @@ | ||||
|       "title": "Expériences professionnelles" | ||||
|     } | ||||
|   }, | ||||
|   "errors": { | ||||
|     "error": "Erreur", | ||||
|     "not-found": "Introuvable", | ||||
|     "page-doesnt-exist": "Cette page n'existe pas !", | ||||
|     "return-to-home-page": "Retour à la page d'accueil ?", | ||||
|     "server-error": "Erreur interne du serveur !", | ||||
|     "try-again": "Réessayer ?" | ||||
|   }, | ||||
|   "footer": { | ||||
|     "all-rights-reserved": "Tous droits réservés" | ||||
|   }, | ||||
|   "home": { | ||||
|     "about": { | ||||
|       "birth-date": { | ||||
| @@ -150,13 +159,5 @@ | ||||
|       "software-tools": "Logiciels et outils", | ||||
|       "title": "Compétences" | ||||
|     } | ||||
|   }, | ||||
|   "locales": { | ||||
|     "en-US": "Anglais", | ||||
|     "fr-FR": "Français" | ||||
|   }, | ||||
|   "meta": { | ||||
|     "description": "Développeur Full Stack • Enthousiaste de l'Open-Source", | ||||
|     "title": "Théo LUDWIG" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +0,0 @@ | ||||
| import { defineConfig } from "vitest/config" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   test: { | ||||
|     typecheck: { | ||||
|       enabled: true, | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import configNextjs from "@repo/eslint-config/nextjs" | ||||
| import configNextjs from "@repo/config-eslint/nextjs" | ||||
|  | ||||
| export default typescriptESLint.config(...configNextjs, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/react-hooks", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
| @@ -9,28 +9,21 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "lint:eslint": "eslint src --max-warnings 0", | ||||
|     "lint:typescript": "tsc --noEmit", | ||||
|     "test": "vitest run --browser.headless", | ||||
|     "test:ui": "vitest --ui --no-open" | ||||
|     "lint:typescript": "tsc --noEmit" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "react": "catalog:", | ||||
|     "react-dom": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@testing-library/react": "catalog:", | ||||
|     "@types/react": "catalog:", | ||||
|     "@types/react-dom": "catalog:", | ||||
|     "@total-typescript/ts-reset": "catalog:", | ||||
|     "@vitest/browser": "catalog:", | ||||
|     "@vitest/coverage-v8": "catalog:", | ||||
|     "@vitest/ui": "catalog:", | ||||
|     "eslint": "catalog:", | ||||
|     "playwright": "catalog:", | ||||
|     "typescript-eslint": "catalog:", | ||||
|     "typescript": "catalog:", | ||||
|     "vitest": "catalog:" | ||||
|     "typescript-eslint": "catalog:" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,83 +0,0 @@ | ||||
| import { act, renderHook } from "@testing-library/react" | ||||
|  | ||||
| import { describe, expect, it } from "vitest" | ||||
| import { useBoolean } from "../useBoolean.ts" | ||||
|  | ||||
| describe("useBoolean", () => { | ||||
|   const initialValues = [true, false] | ||||
|  | ||||
|   for (const initialValue of initialValues) { | ||||
|     it(`should set the initial value to ${initialValue}`, () => { | ||||
|       // Arrange - Given | ||||
|       const { result } = renderHook(() => { | ||||
|         return useBoolean({ initialValue }) | ||||
|       }) | ||||
|  | ||||
|       // Assert - Then | ||||
|       expect(result.current.value).toBe(initialValue) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   it("should by default set the initial value to false", () => { | ||||
|     // Arrange - Given | ||||
|     const { result } = renderHook(() => { | ||||
|       return useBoolean() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.value).toBe(false) | ||||
|   }) | ||||
|  | ||||
|   it("should toggle the value", () => { | ||||
|     // Arrange - Given | ||||
|     const { result } = renderHook(() => { | ||||
|       return useBoolean({ initialValue: false }) | ||||
|     }) | ||||
|  | ||||
|     // Act - When | ||||
|     act(() => { | ||||
|       return result.current.toggle() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.value).toBe(true) | ||||
|  | ||||
|     // Act - When | ||||
|     act(() => { | ||||
|       return result.current.toggle() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.value).toBe(false) | ||||
|   }) | ||||
|  | ||||
|   it("should set the value to true", () => { | ||||
|     // Arrange - Given | ||||
|     const { result } = renderHook(() => { | ||||
|       return useBoolean({ initialValue: false }) | ||||
|     }) | ||||
|  | ||||
|     // Act - When | ||||
|     act(() => { | ||||
|       return result.current.setTrue() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.value).toBe(true) | ||||
|   }) | ||||
|  | ||||
|   it("should set the value to false", () => { | ||||
|     // Arrange - Given | ||||
|     const { result } = renderHook(() => { | ||||
|       return useBoolean({ initialValue: true }) | ||||
|     }) | ||||
|  | ||||
|     // Act - When | ||||
|     act(() => { | ||||
|       return result.current.setFalse() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.value).toBe(false) | ||||
|   }) | ||||
| }) | ||||
| @@ -1,16 +0,0 @@ | ||||
| import { renderHook } from "@testing-library/react" | ||||
|  | ||||
| import { describe, expect, it } from "vitest" | ||||
| import { useIsMounted } from "../useIsMounted.ts" | ||||
|  | ||||
| describe("useIsMounted", () => { | ||||
|   it("should return true", () => { | ||||
|     // Arrange - Given | ||||
|     const { result } = renderHook(() => { | ||||
|       return useIsMounted() | ||||
|     }) | ||||
|  | ||||
|     // Assert - Then | ||||
|     expect(result.current.isMounted).toBe(true) | ||||
|   }) | ||||
| }) | ||||
| @@ -18,11 +18,11 @@ export interface UseBooleanInput { | ||||
|  | ||||
| /** | ||||
|  * Hook to manage a boolean state. | ||||
|  * @param options | ||||
|  * @param input | ||||
|  * @returns | ||||
|  */ | ||||
| export const useBoolean = (options: UseBooleanInput = {}): UseBooleanOutput => { | ||||
|   const { initialValue = false } = options | ||||
| export const useBoolean = (input: UseBooleanInput = {}): UseBooleanOutput => { | ||||
|   const { initialValue = false } = input | ||||
|  | ||||
|   const [value, setValue] = useState(initialValue) | ||||
|  | ||||
|   | ||||
| @@ -1,19 +0,0 @@ | ||||
| import { defineConfig } from "vitest/config" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   optimizeDeps: { | ||||
|     include: ["@vitest/coverage-v8/browser"], | ||||
|   }, | ||||
|   test: { | ||||
|     browser: { | ||||
|       provider: "playwright", | ||||
|       enabled: true, | ||||
|       name: "chromium", | ||||
|       screenshotFailures: false, | ||||
|     }, | ||||
|     coverage: { | ||||
|       enabled: true, | ||||
|       provider: "v8", | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import configNextjs from "@repo/eslint-config/nextjs" | ||||
| import configNextjs from "@repo/config-eslint/nextjs" | ||||
|  | ||||
| export default typescriptESLint.config(...configNextjs, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@repo/ui", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
| @@ -40,7 +40,7 @@ | ||||
|     "react-icons": "catalog:" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@types/react": "catalog:", | ||||
|     "@types/react-dom": "catalog:", | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import typescriptESLint from "typescript-eslint" | ||||
| import config from "@repo/eslint-config" | ||||
| import config from "@repo/config-eslint" | ||||
|  | ||||
| export default typescriptESLint.config(...config, { | ||||
|   files: ["**/*.ts", "**/*.tsx"], | ||||
|   | ||||
| @@ -1,29 +1,28 @@ | ||||
| { | ||||
|   "name": "@repo/utils", | ||||
|   "version": "4.1.3", | ||||
|   "version": "0.0.0-develop", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "exports": { | ||||
|     "./constants": "./src/constants.ts", | ||||
|     "./dates": "./src/dates.ts", | ||||
|     "./strings": "./src/strings.ts" | ||||
|     "./objects": "./src/objects.ts", | ||||
|     "./strings": "./src/strings.ts", | ||||
|     "./types": "./src/types.ts", | ||||
|     "./urls": "./src/urls.ts" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "lint:eslint": "eslint src --max-warnings 0", | ||||
|     "lint:typescript": "tsc --noEmit", | ||||
|     "test": "vitest run", | ||||
|     "test:ui": "vitest --ui --no-open" | ||||
|     "test": "node --experimental-strip-types --test" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@repo/eslint-config": "workspace:*", | ||||
|     "@repo/config-eslint": "workspace:*", | ||||
|     "@repo/config-typescript": "workspace:*", | ||||
|     "@types/node": "catalog:", | ||||
|     "@total-typescript/ts-reset": "catalog:", | ||||
|     "@vitest/coverage-v8": "catalog:", | ||||
|     "@vitest/ui": "catalog:", | ||||
|     "eslint": "catalog:", | ||||
|     "typescript-eslint": "catalog:", | ||||
|     "typescript": "catalog:", | ||||
|     "vitest": "catalog:" | ||||
|     "typescript": "catalog:" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ export const THEMES = ["light", "dark"] as const | ||||
| export type Theme = (typeof THEMES)[number] | ||||
| export const THEME_DEFAULT = "light" satisfies Theme | ||||
|  | ||||
| export const TIMEZONE = process.env["TZ"] ?? "UTC" | ||||
| export const TIMEZONE = process.env["TZ"] ?? "Europe/Paris" | ||||
|  | ||||
| export const BIRTH_DATE_DAY = "31" | ||||
| export const BIRTH_DATE_MONTH = "03" | ||||
|   | ||||
							
								
								
									
										19
									
								
								packages/utils/src/objects.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								packages/utils/src/objects.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| export const deepMerge = < | ||||
|   Object1 extends object, | ||||
|   Object2 extends object = Object1, | ||||
| >( | ||||
|   object1: Object1, | ||||
|   object2: Object2, | ||||
| ): Object1 & Object2 => { | ||||
|   const result = { ...object1 } as Object1 & Object2 | ||||
|   for (const key in object2) { | ||||
|     if (Object.hasOwn(object2, key)) { | ||||
|       if (typeof object2[key] === "object" && object2[key] !== null) { | ||||
|         result[key] = deepMerge(result[key] as any, object2[key] as any) | ||||
|       } else { | ||||
|         result[key] = object2[key] as any | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   return result | ||||
| } | ||||
| @@ -1,36 +0,0 @@ | ||||
| import { afterEach, describe, expect, it, vi } from "vitest" | ||||
|  | ||||
| describe("VERSION", () => { | ||||
|   afterEach(() => { | ||||
|     vi.unstubAllEnvs() | ||||
|     vi.resetModules() | ||||
|     vi.restoreAllMocks() | ||||
|   }) | ||||
|  | ||||
|   it('should return "0.0.0-development" when NODE_ENV is development', async () => { | ||||
|     // Arrange - Given | ||||
|     vi.stubEnv("NODE_ENV", "development") | ||||
|  | ||||
|     // Act - When | ||||
|     const { VERSION } = await import("../constants.ts") | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "0.0.0-development" | ||||
|     expect(VERSION).toEqual(expected) | ||||
|   }) | ||||
|  | ||||
|   it("should return the version from package.json when NODE_ENV is not development", async () => { | ||||
|     // Arrange - Given | ||||
|     vi.stubEnv("NODE_ENV", "production") | ||||
|     vi.mock("../../package.json", () => { | ||||
|       return { default: { version: "1.0.0" } } | ||||
|     }) | ||||
|  | ||||
|     // Act - When | ||||
|     const { VERSION } = await import("../constants.ts") | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "1.0.0" | ||||
|     expect(VERSION).toEqual(expected) | ||||
|   }) | ||||
| }) | ||||
| @@ -1,79 +1,19 @@ | ||||
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" | ||||
| import assert from "node:assert/strict" | ||||
| import { describe, it } from "node:test" | ||||
| import { getISODate } from "../dates.ts" | ||||
|  | ||||
| import { getAge, getISODate } from "../dates.ts" | ||||
| describe("dates", () => { | ||||
|   describe("getISODate", () => { | ||||
|     it("should return the correct date in ISO format (e.g: 2012-05-23)", () => { | ||||
|       // Arrange - Given | ||||
|       const input = new Date("2012-05-23") | ||||
|  | ||||
| describe("getISODate", () => { | ||||
|   it("should return the correct date in ISO format (e.g: 2012-05-23)", () => { | ||||
|     // Arrange - Given | ||||
|     const input = new Date("2012-05-23") | ||||
|       // Act - When | ||||
|       const output = getISODate(input) | ||||
|  | ||||
|     // Act - When | ||||
|     const output = getISODate(input) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "2012-05-23" | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| describe("getAge", () => { | ||||
|   beforeEach(() => { | ||||
|     vi.useFakeTimers() | ||||
|   }) | ||||
|  | ||||
|   afterEach(() => { | ||||
|     vi.useRealTimers() | ||||
|   }) | ||||
|  | ||||
|   it("should return the correct age based on the birth date", () => { | ||||
|     // Arrange - Given | ||||
|     vi.setSystemTime(new Date("2018-03-20")) | ||||
|     const birthDate = new Date("1980-02-20") | ||||
|  | ||||
|     // Act - When | ||||
|     const output = getAge(birthDate) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = 38 | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
|  | ||||
|   it("should return the correct age based on the birth date when the birthday has not happened yet", () => { | ||||
|     // Arrange - Given | ||||
|     vi.setSystemTime(new Date("2018-03-20")) | ||||
|     const birthDate = new Date("1980-07-20") | ||||
|  | ||||
|     // Act - When | ||||
|     const output = getAge(birthDate) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = 37 | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
|  | ||||
|   it("should return the correct age based on the birth date when the birthday is today", () => { | ||||
|     // Arrange - Given | ||||
|     vi.setSystemTime(new Date("2018-03-20")) | ||||
|     const birthDate = new Date("1980-03-20") | ||||
|  | ||||
|     // Act - When | ||||
|     const output = getAge(birthDate) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = 38 | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
|  | ||||
|   it("should return the correct age based on the birth date when the birthday has not happened yet, but will happen this month", () => { | ||||
|     // Arrange - Given | ||||
|     vi.setSystemTime(new Date("2018-03-20")) | ||||
|     const birthDate = new Date("1980-03-25") | ||||
|  | ||||
|     // Act - When | ||||
|     const output = getAge(birthDate) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = 37 | ||||
|     expect(output).toEqual(expected) | ||||
|       // Assert - Then | ||||
|       const expected = "2012-05-23" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
							
								
								
									
										85
									
								
								packages/utils/src/tests/objects.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								packages/utils/src/tests/objects.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| import assert from "node:assert/strict" | ||||
| import { describe, it } from "node:test" | ||||
| import { deepMerge } from "../objects.ts" | ||||
|  | ||||
| describe("objects", () => { | ||||
|   describe("deepMerge", () => { | ||||
|     it("should merge two simple objects", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = { a: 1, b: 2 } | ||||
|       const object2 = { b: 3, c: 4 } | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 1, b: 3, c: 4 } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should deeply merge nested objects", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = { a: 1, b: { x: 2, y: 3 } } | ||||
|       const object2 = { b: { y: 4, z: 5 }, c: 6 } | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 1, b: { x: 2, y: 4, z: 5 }, c: 6 } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should overwrite primitive values", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = { a: 1, b: "hello" } | ||||
|       const object2 = { a: 2, b: "world" } | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 2, b: "world" } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the second object if the first is empty", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = {} | ||||
|       const object2 = { a: 1, b: 2 } | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 1, b: 2 } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the first object if the second is empty", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = { a: 1, b: 2 } | ||||
|       const object2 = {} | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 1, b: 2 } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should handle null and undefined values correctly", () => { | ||||
|       // Arrange - Given | ||||
|       const object1 = { a: 1, b: null } | ||||
|       const object2 = { b: { c: 2 }, d: undefined } | ||||
|  | ||||
|       // Act - When | ||||
|       const output = deepMerge(object1, object2) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = { a: 1, b: { c: 2 }, d: undefined } | ||||
|       assert.deepStrictEqual(output, expected) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
| @@ -1,41 +1,43 @@ | ||||
| import { describe, expect, it } from "vitest" | ||||
|  | ||||
| import assert from "node:assert/strict" | ||||
| import { describe, it } from "node:test" | ||||
| import { capitalize } from "../strings.ts" | ||||
|  | ||||
| describe("capitalize", () => { | ||||
|   it("should capitalize the first letter of a string", () => { | ||||
|     // Arrange - Given | ||||
|     const input = "hello, world!" | ||||
| describe("strings", () => { | ||||
|   describe("capitalize", () => { | ||||
|     it("should capitalize the first letter of a string", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "hello, world!" | ||||
|  | ||||
|     // Act - When | ||||
|     const output = capitalize(input) | ||||
|       // Act - When | ||||
|       const output = capitalize(input) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "Hello, world!" | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
|       // Assert - Then | ||||
|       const expected = "Hello, world!" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|   it("should return an empty string when the input is an empty string", () => { | ||||
|     // Arrange - Given | ||||
|     const input = "" | ||||
|     it("should return an empty string when the input is an empty string", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "" | ||||
|  | ||||
|     // Act - When | ||||
|     const output = capitalize(input) | ||||
|       // Act - When | ||||
|       const output = capitalize(input) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "" | ||||
|     expect(output).toEqual(expected) | ||||
|   }) | ||||
|       // Assert - Then | ||||
|       const expected = "" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|   it("should return the same string when the first letter is already capitalized", () => { | ||||
|     // Arrange - Given | ||||
|     const input = "Hello, world!" | ||||
|     it("should return the same string when the first letter is already capitalized", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "Hello, world!" | ||||
|  | ||||
|     // Act - When | ||||
|     const output = capitalize(input) | ||||
|       // Act - When | ||||
|       const output = capitalize(input) | ||||
|  | ||||
|     // Assert - Then | ||||
|     const expected = "Hello, world!" | ||||
|     expect(output).toEqual(expected) | ||||
|       // Assert - Then | ||||
|       const expected = "Hello, world!" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
							
								
								
									
										80
									
								
								packages/utils/src/tests/urls.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								packages/utils/src/tests/urls.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| import assert from "node:assert/strict" | ||||
| import { describe, it } from "node:test" | ||||
| import { LOCALE_DEFAULT } from "../constants.ts" | ||||
| import { getPathnameWithoutLocale } from "../urls.ts" | ||||
|  | ||||
| describe("urls", () => { | ||||
|   describe("getPathnameWithoutLocale", () => { | ||||
|     it("should return the pathname without the known locale prefix", () => { | ||||
|       // Arrange - Given | ||||
|       const input = `/${LOCALE_DEFAULT}/about` | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/about" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the same pathname when the input does not start with a known locale prefix", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "/about" | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/about" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the same pathname when the input starts with an unknown locale prefix", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "/abc-ABC/about" | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/abc-ABC/about" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the index route when the input is an empty string", () => { | ||||
|       // Arrange - Given | ||||
|       const input = "" | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the index route when the input starts with a known locale prefix and with a trailing slash", () => { | ||||
|       // Arrange - Given | ||||
|       const input = `/${LOCALE_DEFAULT}/` | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|  | ||||
|     it("should return the index route when the input starts with a known locale prefix and without a trailing slash", () => { | ||||
|       // Arrange - Given | ||||
|       const input = `/${LOCALE_DEFAULT}` | ||||
|  | ||||
|       // Act - When | ||||
|       const output = getPathnameWithoutLocale(input) | ||||
|  | ||||
|       // Assert - Then | ||||
|       const expected = "/" | ||||
|       assert.strictEqual(output, expected) | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										49
									
								
								packages/utils/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/utils/src/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| /** | ||||
|  * Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). | ||||
|  */ | ||||
| export type Primitive = | ||||
|   | null | ||||
|   | undefined | ||||
|   | string | ||||
|   | number | ||||
|   | boolean | ||||
|   | symbol | ||||
|   | bigint | ||||
|  | ||||
| export type Satisfies<U, T extends U> = T | ||||
|  | ||||
| export type OmitStrict<T, K extends keyof T> = Omit<T, K> | ||||
| export type PickStrict<T, K extends keyof T> = Pick<T, K> | ||||
|  | ||||
| export type OverrideStrict< | ||||
|   Type, | ||||
|   NewType extends { | ||||
|     [Key in keyof Type]?: unknown | ||||
|   }, | ||||
| > = Omit<Type, keyof NewType> & NewType | ||||
|  | ||||
| export type PartialDeep<T> = T extends object | ||||
|   ? { | ||||
|       [P in keyof T]?: PartialDeep<T[P]> | ||||
|     } | ||||
|   : T | ||||
|  | ||||
| export type Status = "error" | "idle" | "pending" | "success" | ||||
|  | ||||
| /** | ||||
|  * Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. | ||||
|  * | ||||
|  * @see https://github.com/Microsoft/TypeScript/issues/29729 | ||||
|  * | ||||
|  * @example | ||||
|   ``` | ||||
|   // Before | ||||
|   type Pet = 'dog' | 'cat' | string; | ||||
|  | ||||
|   // After | ||||
|   type Pet2 = LiteralUnion<'dog' | 'cat', string>; | ||||
|   ``` | ||||
|  */ | ||||
| export type LiteralUnion<LiteralType, BaseType extends Primitive> = | ||||
|   | LiteralType | ||||
|   | (BaseType & Record<never, never>) | ||||
							
								
								
									
										18
									
								
								packages/utils/src/urls.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/utils/src/urls.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import { LOCALES } from "./constants.ts" | ||||
|  | ||||
| /** | ||||
|  * Get the pathname without the known locale prefix. | ||||
|  * @param input | ||||
|  * @returns | ||||
|  * @example getRoutePathnameWithoutLocale("/fr-FR/about") // "/about" | ||||
|  */ | ||||
| export const getPathnameWithoutLocale = (input: string): string => { | ||||
|   const locale = LOCALES.find((locale) => { | ||||
|     return input.startsWith(`/${locale}`) | ||||
|   }) | ||||
|   const pathname = locale != null ? input.slice(locale.length + 1) : input | ||||
|   if (pathname.length <= 0) { | ||||
|     return `/${pathname}` | ||||
|   } | ||||
|   return pathname | ||||
| } | ||||
| @@ -1,10 +0,0 @@ | ||||
| import { defineConfig } from "vitest/config" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   test: { | ||||
|     coverage: { | ||||
|       enabled: true, | ||||
|       provider: "v8", | ||||
|     }, | ||||
|   }, | ||||
| }) | ||||
							
								
								
									
										8157
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8157
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,33 +1,30 @@ | ||||
| packages: | ||||
|   - "apps/*" | ||||
|   - "configs/*" | ||||
|   - "packages/*" | ||||
|  | ||||
| catalog: | ||||
|   # Turborepo and Releases | ||||
|   "turbo": "2.3.3" | ||||
|   "@saithodev/semantic-release-backmerge": "4.0.1" | ||||
|   "@semantic-release/exec": "6.0.3" | ||||
|   "@semantic-release/git": "10.0.1" | ||||
|   "semantic-release": "23.1.1" | ||||
|   "replace-in-files-cli": "3.0.0" | ||||
|   # Turborepo | ||||
|   "turbo": "2.4.0" | ||||
|  | ||||
|   # Utils | ||||
|   "deepmerge": "4.3.1" | ||||
|   "mime": "4.0.6" | ||||
|  | ||||
|   # React.js/Next.js | ||||
|   "next": "15.0.4" | ||||
|   "next-intl": "3.26.0" | ||||
|   "next": &next "15.1.6" | ||||
|   "next-intl": "3.26.3" | ||||
|   "next-themes": "0.4.4" | ||||
|   "nuqs": "2.3.2" | ||||
|   "react": "19.0.0" | ||||
|   "react-dom": "19.0.0" | ||||
|   "react-icons": "5.4.0" | ||||
|   "@types/react": "19.0.1" | ||||
|   "@types/react-dom": "19.0.1" | ||||
|   "@types/react": "19.0.8" | ||||
|   "@types/react-dom": "19.0.3" | ||||
|  | ||||
|   # Blog | ||||
|   "@giscus/react": "3.0.0" | ||||
|   "@giscus/react": "3.1.0" | ||||
|   "gray-matter": "4.0.3" | ||||
|   "katex": "0.16.11" | ||||
|   "katex": "0.16.21" | ||||
|   "next-mdx-remote": "5.0.0" | ||||
|   "@mdx-js/mdx": "3.1.0" | ||||
|   "rehype-katex": "7.0.1" | ||||
| @@ -39,66 +36,60 @@ catalog: | ||||
|   "@shikijs/rehype": "1.24.0" | ||||
|  | ||||
|   # Markdown Lint | ||||
|   "markdownlint-cli2": "0.15.0" | ||||
|   "markdownlint": "0.36.1" | ||||
|   "markdownlint-rule-relative-links": "3.0.0" | ||||
|   "markdownlint-cli2": "0.17.2" | ||||
|   "markdownlint": "0.37.4" | ||||
|   "markdownlint-rule-relative-links": "4.0.1" | ||||
|  | ||||
|   # TypeScript | ||||
|   "typescript": "5.7.2" | ||||
|   "typescript": "5.7.3" | ||||
|   "@total-typescript/ts-reset": "0.6.1" | ||||
|   "@types/node": "22.10.1" | ||||
|   "@types/node": "22.13.1" | ||||
|  | ||||
|   # ESLint | ||||
|   "globals": "15.13.0" | ||||
|   "typescript-eslint": "8.17.0" | ||||
|   "globals": "15.14.0" | ||||
|   "typescript-eslint": "8.23.0" | ||||
|   "@eslint/eslintrc": "3.2.0" | ||||
|   "eslint": "9.16.0" | ||||
|   "eslint-config-conventions": "17.0.1" | ||||
|   "eslint": "9.20.0" | ||||
|   "eslint-config-conventions": "18.0.2" | ||||
|   "eslint-plugin-promise": "7.2.1" | ||||
|   "eslint-plugin-unicorn": "56.0.1" | ||||
|   "eslint-config-next": "15.0.4" | ||||
|   "eslint-plugin-storybook": "0.11.1" | ||||
|   "eslint-plugin-tailwindcss": "3.17.5" | ||||
|   "eslint-plugin-import-x": "4.5.0" | ||||
|   "eslint-config-next": *next | ||||
|   "eslint-plugin-storybook": "0.11.2" | ||||
|   "eslint-plugin-tailwindcss": "3.18.0" | ||||
|   "eslint-plugin-import-x": "4.6.1" | ||||
|  | ||||
|   # Prettier | ||||
|   "prettier": "3.4.2" | ||||
|   "prettier-plugin-tailwindcss": "0.6.9" | ||||
|   "editorconfig-checker": "6.0.0" | ||||
|   "prettier-plugin-tailwindcss": "0.6.11" | ||||
|   "editorconfig-checker": "6.0.1" | ||||
|  | ||||
|   # Storybook | ||||
|   "@chromatic-com/storybook": "3.2.2" | ||||
|   "@storybook/addon-a11y": "8.4.7" | ||||
|   "@storybook/addon-essentials": "8.4.7" | ||||
|   "@storybook/addon-interactions": "8.4.7" | ||||
|   "@storybook/addon-storysource": "8.4.7" | ||||
|   "@storybook/addon-themes": "8.4.7" | ||||
|   "@storybook/blocks": "8.4.7" | ||||
|   "@storybook/nextjs": "8.4.7" | ||||
|   "@storybook/react": "8.4.7" | ||||
|   "@storybook/test": "8.4.7" | ||||
|   "@storybook/test-runner": "0.20.1" | ||||
|   "chromatic": "11.20.0" | ||||
|   "http-server": "14.1.1" | ||||
|   "storybook": "8.4.7" | ||||
|   "storybook": &storybook "8.5.3" | ||||
|   "@storybook/addon-essentials": *storybook | ||||
|   "@storybook/addon-storysource": *storybook | ||||
|   "@storybook/addon-a11y": *storybook | ||||
|   "@storybook/addon-interactions": *storybook | ||||
|   "@storybook/blocks": *storybook | ||||
|   "@storybook/nextjs": *storybook | ||||
|   "@storybook/react": *storybook | ||||
|   "@storybook/test": *storybook | ||||
|   "@storybook/addon-themes": *storybook | ||||
|   "@storybook/test-runner": "0.21.0" | ||||
|   "@chromatic-com/storybook": "3.2.4" | ||||
|   "chromatic": "11.25.2" | ||||
|   "storybook-dark-mode": "4.0.2" | ||||
|  | ||||
|   # Testing | ||||
|   "playwright": "1.49.0" | ||||
|   "@playwright/test": "1.49.0" | ||||
|   "axe-playwright": "2.0.3" | ||||
|   "start-server-and-test": "2.0.8" | ||||
|   "@vitest/browser": "2.1.8" | ||||
|   "@vitest/coverage-v8": "2.1.8" | ||||
|   "@vitest/ui": "2.1.8" | ||||
|   "vitest": "2.1.8" | ||||
|   "@testing-library/react": "16.1.0" | ||||
|   "playwright": &playwright "1.50.1" | ||||
|   "@playwright/test": *playwright | ||||
|   "axe-playwright": "2.1.0" | ||||
|   "start-server-and-test": "2.0.10" | ||||
|  | ||||
|   # CSS | ||||
|   "postcss": "8.4.49" | ||||
|   "tailwindcss": "3.4.16" | ||||
|   "postcss": "8.5.1" | ||||
|   "tailwindcss": "3.4.17" | ||||
|   "@tailwindcss/typography": "0.5.15" | ||||
|   "tailwind-merge": "2.5.5" | ||||
|   "tailwind-merge": "2.6.0" | ||||
|   "clsx": "2.1.1" | ||||
|   "cva": "1.0.0-beta.2" | ||||
|   "@fontsource/montserrat": "5.1.0" | ||||
|   "cva": "1.0.0-beta.3" | ||||
|   "@fontsource/montserrat": "5.1.1" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| { | ||||
|   "$schema": "https://turbo.build/schema.json", | ||||
|   "$schema": "./node_modules/turbo/schema.json", | ||||
|   "ui": "tui", | ||||
|   "tasks": { | ||||
|     "build": { | ||||
| @@ -15,7 +15,7 @@ | ||||
|     }, | ||||
|     "test": { | ||||
|       "dependsOn": ["^test"], | ||||
|       "outputs": ["coverage/**"] | ||||
|       "outputs": [] | ||||
|     }, | ||||
|     "lint:eslint-transit-node": { | ||||
|       "dependsOn": ["^lint:eslint-transit-node"] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user