diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..cef51aec --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,7 @@ +# Project guidelines + +## General writing style + +- Prefer British English spelling in prose (for example: "synchronise", + "synchronisation"). +- Keep language simple, direct, and concise. diff --git a/.github/instructions/architecture.instructions.md b/.github/instructions/architecture.instructions.md new file mode 100644 index 00000000..b613a731 --- /dev/null +++ b/.github/instructions/architecture.instructions.md @@ -0,0 +1,29 @@ +--- +description: + "Use when editing app or lib source files. Covers the main workspace structure + and package responsibilities." +applyTo: "apps/**, libs/**, scripts/**" +--- + +# Architecture guidelines + +## Workspace structure + +- `apps/*` contains deployable applications. +- `libs/*` contains shared workspace packages used by the apps. +- `scripts/` contains workspace-level tooling. + +## Package responsibilities + +- Keep app-specific entry points, routes, and composition inside `apps/*`. +- Keep reusable domain logic, UI primitives, and shared utilities inside + `libs/*`. +- `libs/prisma` owns Prisma-related code and generated client integration for + the workspace. + +## Build model + +- Shared libs are generally consumed from their built output. +- Changes to shared libs may require synchronisation into injected workspace + packages. +- See: docs/decisions/002-workspace-package-injection.md diff --git a/.github/instructions/code-comments.instructions.md b/.github/instructions/code-comments.instructions.md new file mode 100644 index 00000000..f81e8589 --- /dev/null +++ b/.github/instructions/code-comments.instructions.md @@ -0,0 +1,14 @@ +--- +description: + "Use when creating or editing source code files. Covers the required style for + See reference lines in code comments." +applyTo: "**/*.{js,ts,tsx}, **/Dockerfile, **/*.yml" +--- + +# Code comment guidelines + +## See reference lines + +- For "See" reference lines in code comments, use this style: + - `See: path/to/file` +- In "See" reference lines, do not wrap the path in backticks. diff --git a/.github/instructions/dependency-upgrades.instructions.md b/.github/instructions/dependency-upgrades.instructions.md new file mode 100644 index 00000000..955fef98 --- /dev/null +++ b/.github/instructions/dependency-upgrades.instructions.md @@ -0,0 +1,34 @@ +--- +description: + "Use when editing dependencies, pnpm-workspace.yaml, package.json dependency + fields. Covers workspace package injection and safe step-by-step dependency + changes." +applyTo: "pnpm-workspace.yaml, **/package.json" +--- + +# Dependency upgrade guidelines + +## Workspace dependency model + +- Workspace packages are injected, not symlinked. +- Keep `injectWorkspacePackages: true`. +- Keep `dedupeInjectedDeps: false`. +- Keep `syncInjectedDepsAfterScripts` pointing to `sync-workspace`. +- See: docs/decisions/002-workspace-package-injection.md + +## Package manifest rules + +- Use `workspace:*` for internal dependencies. +- Use `catalog:` for third-party dependencies managed at workspace level. +- For libs that are consumed from `build/`, keep the `files` field aligned with + the published build output. +- For injected libs, keep the `sync-workspace` hook and the scripts that trigger + it. + +## Upgrade strategy + +- Prefer step-by-step dependency changes over broad workspace-wide upgrades, + unless the upgrade is a patch release or a clearly simple minor release. +- Keep peer dependency changes scoped and explicit. +- When changing shared lib dependencies, consider the consuming apps before + aligning versions. diff --git a/.github/instructions/docs-writing.instructions.md b/.github/instructions/docs-writing.instructions.md new file mode 100644 index 00000000..f01be488 --- /dev/null +++ b/.github/instructions/docs-writing.instructions.md @@ -0,0 +1,30 @@ +--- +description: + "Use when creating or editing documentation and markdown files. Covers heading + case, prose style, shorthand policy, and markdown formatting conventions." +applyTo: "**/*.md" +--- + +# Documentation writing guidelines + +## Writing style + +- Use sentence case for document and section headings. + +## Naming and shorthand + +- Avoid shortened words in prose when a clear full word is available. +- Allowed shorthand in prose: "apps" and "libs". + +## Markdown formatting + +- Wrap file paths and script names in backticks in prose and markdown text. +- Prefer reference-style markdown links, with link references defined at the + bottom of the file. +- Sort link references alphabetically by reference name. + +## Documentation changes + +- When updating ADRs or docs, preserve existing meaning and improve clarity. +- Prefer fluid paragraphs over fragmented bullet points unless a list improves + readability. diff --git a/.github/instructions/package-scripts.instructions.md b/.github/instructions/package-scripts.instructions.md new file mode 100644 index 00000000..5bf4bfa3 --- /dev/null +++ b/.github/instructions/package-scripts.instructions.md @@ -0,0 +1,43 @@ +--- +description: + "Use when creating or editing package.json scripts. Covers the script naming + conventions shared by apps, libs, and workspace packages." +applyTo: "**/package.json" +--- + +# Package script guidelines + +## Script naming + +- Keep the existing script prefixes and meanings. +- Use `build` for the production build entry point. +- Use `build:*` for build subtasks. +- Use `prebuild` for cleanup or generation that must happen before `build`. +- Use `dev` for the long-running local workflow. +- Use `dev:*` for long-running subtasks started by `dev`. +- Use `generate` for one-off asset or code generation. +- Use `generate:*` for generation subtasks. +- Use `clean` to remove generated outputs. +- Use `lint` for read-only validation checks. +- Use `lint-fix` for safe automatic fixes. +- Use `start` for the production runtime entry point. +- Use `docker-build` and `docker-start` for local container workflows when + needed. + +## Script composition + +- Prefer `run-s` for sequential orchestration and `run-p` for parallel + orchestration. +- When a script runs multiple subtasks, keep `--print-label` so output stays + readable. +- For long-running parallel `dev` or `generate` workflows, keep + `--continue-on-error` unless a task must fail fast. + +## Workspace injection hooks + +- For libs that publish only `build/`, keep `postbuild` calling + `pnpm sync-workspace`. +- For watch mode, use `dev:sync-workspace` to watch `build/` and trigger + `sync-workspace`. +- Keep `sync-workspace` as the dedicated hook script used by + `pnpm-workspace.yaml`. diff --git a/.github/instructions/source-editing.instructions.md b/.github/instructions/source-editing.instructions.md new file mode 100644 index 00000000..c0b3b6d1 --- /dev/null +++ b/.github/instructions/source-editing.instructions.md @@ -0,0 +1,39 @@ +--- +description: + "Use when creating or editing source code files. Covers common-sense editing + constraints, safe defaults, and refactor expectations for this workspace." +applyTo: "**/*.{js,ts,tsx}, **/Dockerfile, **/*.yml" +--- + +# Source editing guidelines + +## General approach + +- Prefer small, focused changes that solve the root cause. +- Preserve existing behaviour unless the task explicitly asks for a behaviour + change. +- Preserve public APIs and script names unless the task requires changing them. + +## Opportunistic cleanups + +- Opportunistic cleanups are allowed. +- Keep them small, local, and low risk. +- Limit them to the code you are already touching or to directly related dead + code. +- Do not mix broad refactors with functional changes unless the task asks for + both. + +## Generated outputs + +- Do not edit generated outputs by hand. +- Treat `build/`, `public/build/`, and `src/generated/` as generated unless the + task is specifically about generation. +- Prefer changing the source, generator, or script that produces generated + files. + +## Shared code + +- When a change affects multiple apps, prefer fixing the shared lib instead of + duplicating changes in each app. +- Keep app-specific composition inside `apps/*` and reusable logic inside + `libs/*`. diff --git a/.github/workflows/admin.checks.yml b/.github/workflows/admin.checks.yml index 85d788f2..462d33da 100644 --- a/.github/workflows/admin.checks.yml +++ b/.github/workflows/admin.checks.yml @@ -22,9 +22,12 @@ concurrency: cancel-in-progress: true jobs: - build: - name: πŸ“¦ Build + prepare: + name: πŸ”§ Prepare runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -36,6 +39,25 @@ jobs: file: ./apps/admin/fly.toml field: app + # In workflows triggered by pull requests, `github.ref_name` is + # `/merge`. We use `github.head_ref` instead to get the + # source branch name. + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.head_ref }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + + build: + name: πŸ“¦ Build + runs-on: ubuntu-latest + needs: + - prepare + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -54,9 +76,7 @@ jobs: with: context: . file: ./apps/admin/Dockerfile - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.head_ref }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new diff --git a/.github/workflows/admin.deploy.yml b/.github/workflows/admin.deploy.yml index 4862c48f..3b8013f0 100644 --- a/.github/workflows/admin.deploy.yml +++ b/.github/workflows/admin.deploy.yml @@ -24,6 +24,30 @@ concurrency: cancel-in-progress: true jobs: + prepare: + name: πŸ”§ Prepare + runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: πŸ‘€ Read app name + uses: SebRollen/toml-action@v1.0.2 + id: app_name + with: + file: ./apps/admin/fly.toml + field: app + + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.ref_name }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + lint: name: πŸ”¬ Lint runs-on: ubuntu-latest @@ -57,17 +81,12 @@ jobs: build: name: πŸ“¦ Build runs-on: ubuntu-latest + needs: + - prepare steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/admin/fly.toml - field: app - - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -94,9 +113,7 @@ jobs: context: . file: ./apps/admin/Dockerfile push: true - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.ref_name }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new @@ -114,27 +131,20 @@ jobs: name: πŸš€ Deploy runs-on: ubuntu-latest needs: + - prepare - lint - build steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/admin/fly.toml - field: app - - name: πŸš€ Deploy Production if: ${{ github.ref == 'refs/heads/main' }} uses: superfly/flyctl-actions@1.4 with: args: - deploy --config ./apps/admin/fly.toml --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + deploy --config ./apps/admin/fly.toml --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} @@ -144,8 +154,7 @@ jobs: with: args: deploy --config ./apps/admin/fly.toml --app ${{ - steps.app_name.outputs.value }}-staging --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + needs.prepare.outputs.app_name }}-staging --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/show.checks.yml b/.github/workflows/show.checks.yml index b27bc9f6..a6a3cbef 100644 --- a/.github/workflows/show.checks.yml +++ b/.github/workflows/show.checks.yml @@ -22,9 +22,12 @@ concurrency: cancel-in-progress: true jobs: - build: - name: πŸ“¦ Build + prepare: + name: πŸ”§ Prepare runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -36,6 +39,25 @@ jobs: file: ./apps/show/fly.toml field: app + # In workflows triggered by pull requests, `github.ref_name` is + # `/merge`. We use `github.head_ref` instead to get the + # source branch name. + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.head_ref }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + + build: + name: πŸ“¦ Build + runs-on: ubuntu-latest + needs: + - prepare + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -54,9 +76,7 @@ jobs: with: context: . file: ./apps/show/Dockerfile - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.head_ref }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new diff --git a/.github/workflows/show.deploy.yml b/.github/workflows/show.deploy.yml index 56e6dd00..6ad3cecd 100644 --- a/.github/workflows/show.deploy.yml +++ b/.github/workflows/show.deploy.yml @@ -24,6 +24,30 @@ concurrency: cancel-in-progress: true jobs: + prepare: + name: πŸ”§ Prepare + runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: πŸ‘€ Read app name + uses: SebRollen/toml-action@v1.0.2 + id: app_name + with: + file: ./apps/show/fly.toml + field: app + + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.ref_name }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + lint: name: πŸ”¬ Lint runs-on: ubuntu-latest @@ -57,17 +81,12 @@ jobs: build: name: πŸ“¦ Build runs-on: ubuntu-latest + needs: + - prepare steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/show/fly.toml - field: app - - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -94,9 +113,7 @@ jobs: context: . file: ./apps/show/Dockerfile push: true - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.ref_name }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new @@ -114,27 +131,20 @@ jobs: name: πŸš€ Deploy runs-on: ubuntu-latest needs: + - prepare - lint - build steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/show/fly.toml - field: app - - name: πŸš€ Deploy Production if: ${{ github.ref == 'refs/heads/main' }} uses: superfly/flyctl-actions@1.4 with: args: - deploy --config ./apps/show/fly.toml --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + deploy --config ./apps/show/fly.toml --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} @@ -144,8 +154,7 @@ jobs: with: args: deploy --config ./apps/show/fly.toml --app ${{ - steps.app_name.outputs.value }}-staging --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + needs.prepare.outputs.app_name }}-staging --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/website.checks.yml b/.github/workflows/website.checks.yml index da9cbb91..b5bac072 100644 --- a/.github/workflows/website.checks.yml +++ b/.github/workflows/website.checks.yml @@ -22,9 +22,12 @@ concurrency: cancel-in-progress: true jobs: - build: - name: πŸ“¦ Build + prepare: + name: πŸ”§ Prepare runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 @@ -36,6 +39,25 @@ jobs: file: ./apps/website/fly.toml field: app + # In workflows triggered by pull requests, `github.ref_name` is + # `/merge`. We use `github.head_ref` instead to get the + # source branch name. + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.head_ref }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + + build: + name: πŸ“¦ Build + runs-on: ubuntu-latest + needs: + - prepare + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -54,9 +76,7 @@ jobs: with: context: . file: ./apps/website/Dockerfile - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.head_ref }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new diff --git a/.github/workflows/website.deploy.yml b/.github/workflows/website.deploy.yml index f757ac1b..559b6996 100644 --- a/.github/workflows/website.deploy.yml +++ b/.github/workflows/website.deploy.yml @@ -24,6 +24,30 @@ concurrency: cancel-in-progress: true jobs: + prepare: + name: πŸ”§ Prepare + runs-on: ubuntu-latest + outputs: + app_name: ${{ steps.app_name.outputs.value }} + image_tag: ${{ steps.image_tag.outputs.value }} + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + + - name: πŸ‘€ Read app name + uses: SebRollen/toml-action@v1.0.2 + id: app_name + with: + file: ./apps/website/fly.toml + field: app + + - name: 🏷️ Build image tag + id: image_tag + run: + echo "value=registry.fly.io/${{ steps.app_name.outputs.value }}:$(echo + '${{ github.ref_name }}' | tr '/' '-')-${{ github.sha }}" >> + $GITHUB_OUTPUT + lint: name: πŸ”¬ Lint runs-on: ubuntu-latest @@ -57,17 +81,12 @@ jobs: build: name: πŸ“¦ Build runs-on: ubuntu-latest + needs: + - prepare steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/website/fly.toml - field: app - - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v2 with: @@ -94,9 +113,7 @@ jobs: context: . file: ./apps/website/Dockerfile push: true - tags: - registry.fly.io/${{ steps.app_name.outputs.value }}:${{ - github.ref_name }}-${{ github.sha }} + tags: ${{ needs.prepare.outputs.image_tag }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new @@ -114,27 +131,20 @@ jobs: name: πŸš€ Deploy runs-on: ubuntu-latest needs: + - prepare - lint - build steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 - - name: πŸ‘€ Read app name - uses: SebRollen/toml-action@v1.0.2 - id: app_name - with: - file: ./apps/website/fly.toml - field: app - - name: πŸš€ Deploy Production if: ${{ github.ref == 'refs/heads/main' }} uses: superfly/flyctl-actions@1.4 with: args: - deploy --config ./apps/website/fly.toml --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + deploy --config ./apps/website/fly.toml --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} @@ -144,8 +154,7 @@ jobs: with: args: deploy --config ./apps/website/fly.toml --app ${{ - steps.app_name.outputs.value }}-staging --image registry.fly.io/${{ - steps.app_name.outputs.value }}:${{ github.ref_name }}-${{ - github.sha }} + needs.prepare.outputs.app_name }}-staging --image ${{ + needs.prepare.outputs.image_tag }} env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/apps/admin/Dockerfile b/apps/admin/Dockerfile index a9dcf748..ffff74bc 100644 --- a/apps/admin/Dockerfile +++ b/apps/admin/Dockerfile @@ -65,6 +65,10 @@ COPY libs/tailwind-animation libs/tailwind-animation COPY libs/zod-utils libs/zod-utils COPY apps/admin apps/admin +# Synchronise injected workspace packages after copying source code. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --frozen-lockfile --offline + # Build libs and app. RUN pnpm --filter admin... build @@ -99,4 +103,8 @@ COPY libs/prisma/migrations libs/prisma/migrations COPY libs/prisma/prisma.config.ts libs/prisma/prisma.config.ts COPY libs/prisma/schema.prisma libs/prisma/schema.prisma +# Synchronise injected workspace packages after copying build artifacts. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --prod --frozen-lockfile --offline + CMD ["pnpm", "--filter", "admin", "start"] diff --git a/apps/admin/package.json b/apps/admin/package.json index 0023b1fa..7847acdb 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -59,13 +59,7 @@ "downshift": "catalog:", "history": "catalog:", "isbot": "catalog:", - "lodash.chunk": "catalog:", - "lodash.difference": "catalog:", - "lodash.intersection": "catalog:", - "lodash.isequal": "catalog:", - "lodash.merge": "catalog:", - "lodash.orderby": "catalog:", - "lodash.pick": "catalog:", + "es-toolkit": "catalog:", "luxon": "catalog:", "metascraper": "catalog:", "metascraper-date": "catalog:", @@ -94,13 +88,6 @@ "@types/autosize": "catalog:", "@types/cookie-signature": "catalog:", "@types/d3-dsv": "catalog:", - "@types/lodash.chunk": "catalog:", - "@types/lodash.difference": "catalog:", - "@types/lodash.intersection": "catalog:", - "@types/lodash.isequal": "catalog:", - "@types/lodash.merge": "catalog:", - "@types/lodash.orderby": "catalog:", - "@types/lodash.pick": "catalog:", "@types/luxon": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", diff --git a/apps/admin/src/activity/db.server.ts b/apps/admin/src/activity/db.server.ts index fa62490a..a4a0aa15 100644 --- a/apps/admin/src/activity/db.server.ts +++ b/apps/admin/src/activity/db.server.ts @@ -1,7 +1,7 @@ import type { Prisma } from "@animeaux/prisma/server" import { captureException } from "@sentry/remix" -import isEqual from "lodash.isequal" -import pick from "lodash.pick" +import { pick } from "es-toolkit/object" +import { isEqual } from "es-toolkit/predicate" import { promiseHash } from "remix-utils/promise" import { ActivityAction } from "#i/activity/action.js" diff --git a/apps/admin/src/animals/adoption.tsx b/apps/admin/src/animals/adoption.tsx index 11101e52..3b0a5a19 100644 --- a/apps/admin/src/animals/adoption.tsx +++ b/apps/admin/src/animals/adoption.tsx @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { AdoptionOption } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -17,7 +17,7 @@ export const ADOPTION_OPTION_ICON: Record = { [AdoptionOption.UNKNOWN]: "icon-circle-question-solid", } -export const SORTED_ADOPTION_OPTION = orderBy( +export const SORTED_ADOPTION_OPTION = orderEnumBy( Object.values(AdoptionOption), (adoptionOption) => ADOPTION_OPTION_TRANSLATION[adoptionOption], ) diff --git a/apps/admin/src/animals/age.tsx b/apps/admin/src/animals/age.tsx index bae97834..9d8cbcd6 100644 --- a/apps/admin/src/animals/age.tsx +++ b/apps/admin/src/animals/age.tsx @@ -1,9 +1,8 @@ -import { AnimalAge } from "@animeaux/core" -import orderBy from "lodash.orderby" +import { AnimalAge, orderEnumBy } from "@animeaux/core" import type { IconName } from "#i/generated/icon.js" -export const SORTED_AGES = orderBy(Object.values(AnimalAge), (age) => +export const SORTED_AGES = orderEnumBy(Object.values(AnimalAge), (age) => age === AnimalAge.JUNIOR ? 0 : age === AnimalAge.ADULT ? 1 : 2, ) diff --git a/apps/admin/src/animals/gender.tsx b/apps/admin/src/animals/gender.tsx index deff4e59..04e81749 100644 --- a/apps/admin/src/animals/gender.tsx +++ b/apps/admin/src/animals/gender.tsx @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { Gender } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -20,7 +20,7 @@ export const GENDER_ICON: Record = }, } -export const SORTED_GENDERS = orderBy( +export const SORTED_GENDERS = orderEnumBy( Object.values(Gender), (gender) => GENDER_TRANSLATION[gender], ) diff --git a/apps/admin/src/animals/pick-up.ts b/apps/admin/src/animals/pick-up.ts index 3c742544..9a488344 100644 --- a/apps/admin/src/animals/pick-up.ts +++ b/apps/admin/src/animals/pick-up.ts @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { PickUpReason } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -21,7 +21,7 @@ export const PICK_UP_REASON_ICON: Record = { [PickUpReason.OTHER]: "icon-circle-question-solid", } -export const SORTED_PICK_UP_REASON = orderBy( +export const SORTED_PICK_UP_REASON = orderEnumBy( Object.values(PickUpReason), (pickUpReason) => PICK_UP_REASON_TRANSLATION[pickUpReason], ) diff --git a/apps/admin/src/animals/pictures/db.server.ts b/apps/admin/src/animals/pictures/db.server.ts index 50aa324a..224a2e82 100644 --- a/apps/admin/src/animals/pictures/db.server.ts +++ b/apps/admin/src/animals/pictures/db.server.ts @@ -1,5 +1,5 @@ import type { Animal } from "@animeaux/prisma/server" -import difference from "lodash.difference" +import { difference } from "es-toolkit/array" import { ActivityAction } from "#i/activity/action.js" import { Activity } from "#i/activity/db.server.js" diff --git a/apps/admin/src/animals/screening.ts b/apps/admin/src/animals/screening.ts index c46550f1..3d3e733c 100644 --- a/apps/admin/src/animals/screening.ts +++ b/apps/admin/src/animals/screening.ts @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { Gender, ScreeningResult } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -27,7 +27,7 @@ export const SCREENING_RESULT_ICON: Record = { [ScreeningResult.UNKNOWN]: "icon-circle-question-solid", } -export const SORTED_SCREENING_RESULTS = orderBy( +export const SORTED_SCREENING_RESULTS = orderEnumBy( Object.values(ScreeningResult), (result) => SCREENING_RESULT_TRANSLATION[result][Gender.MALE], ) diff --git a/apps/admin/src/animals/situation/health.ts b/apps/admin/src/animals/situation/health.ts index 8260de2e..2f6ca8cf 100644 --- a/apps/admin/src/animals/situation/health.ts +++ b/apps/admin/src/animals/situation/health.ts @@ -1,7 +1,7 @@ import type { Animal } from "@animeaux/prisma" import { Diagnosis, Species, Status } from "@animeaux/prisma" import type { SerializeFrom } from "@remix-run/node" -import difference from "lodash.difference" +import { difference } from "es-toolkit/array" import { DateTime } from "luxon" import type { SetNonNullable } from "type-fest" diff --git a/apps/admin/src/animals/species.tsx b/apps/admin/src/animals/species.tsx index fae8384f..ab116030 100644 --- a/apps/admin/src/animals/species.tsx +++ b/apps/admin/src/animals/species.tsx @@ -1,6 +1,6 @@ +import { orderEnumBy } from "@animeaux/core" import type { Animal, Breed, Color } from "@animeaux/prisma" import { Species } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import { isDefined } from "#i/core/is-defined.js" import type { IconName } from "#i/generated/icon.js" @@ -21,7 +21,7 @@ export const SPECIES_ICON: Record = { [Species.RODENT]: "icon-rabbit-solid", } -export const SORTED_SPECIES = orderBy( +export const SORTED_SPECIES = orderEnumBy( Object.values(Species), (species) => SPECIES_TRANSLATION[species], ) diff --git a/apps/admin/src/animals/status.tsx b/apps/admin/src/animals/status.tsx index fc4e01d6..395242b6 100644 --- a/apps/admin/src/animals/status.tsx +++ b/apps/admin/src/animals/status.tsx @@ -1,7 +1,6 @@ -import { cn } from "@animeaux/core" +import { cn, orderEnumBy } from "@animeaux/core" import { Status } from "@animeaux/prisma" -import difference from "lodash.difference" -import orderBy from "lodash.orderby" +import { difference } from "es-toolkit/array" import type { ChipColor } from "#i/core/data-display/chip.js" import { Chip } from "#i/core/data-display/chip.js" @@ -21,7 +20,7 @@ export const STATUS_TRANSLATION: Record = { [Status.TRANSFERRED]: "TransfΓ©rΓ©", } -export const SORTED_STATUS = orderBy( +export const SORTED_STATUS = orderEnumBy( Object.values(Status), (status) => STATUS_TRANSLATION[status], ) diff --git a/apps/admin/src/core/data-display/image.tsx b/apps/admin/src/core/data-display/image.tsx index 63da5aeb..3cf3a871 100644 --- a/apps/admin/src/core/data-display/image.tsx +++ b/apps/admin/src/core/data-display/image.tsx @@ -1,5 +1,5 @@ import { cn } from "@animeaux/core" -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import { forwardRef } from "react" import type { Merge } from "type-fest" @@ -25,16 +25,12 @@ export type ImageSize = (typeof IMAGE_SIZES)[number] // Larger to smaller. const SCREEN_SIZES = orderBy( (Object.entries(theme.screens) as [ScreenSize | "default", string][]).map( - ([name, width]) => - [name, Number(width.replace("px", ""))] as [ - ScreenSize | "default", - number, - ], + ([name, width]) => ({ name, width: Number(width.replace("px", "")) }), ), - ([_, width]) => width, - "desc", + [({ width }) => width], + ["desc"], ) - .map(([name]) => name) + .map(({ name }) => name) .concat("default") type AspectRatio = "none" | "1:1" | "4:3" diff --git a/apps/admin/src/routes/downloads.animals.tsx b/apps/admin/src/routes/downloads.animals.tsx index 5f375133..b3f8a307 100644 --- a/apps/admin/src/routes/downloads.animals.tsx +++ b/apps/admin/src/routes/downloads.animals.tsx @@ -8,7 +8,7 @@ import type { } from "@remix-run/node" import { defer } from "@remix-run/node" import { Await, useLoaderData } from "@remix-run/react" -import chunk from "lodash.chunk" +import { chunk } from "es-toolkit/array" import { Suspense } from "react" import { GENDER_ICON } from "#i/animals/gender.js" diff --git a/apps/admin/src/routes/resources.global-search/entity.ts b/apps/admin/src/routes/resources.global-search/entity.ts index 1ac0b8fb..7191808a 100644 --- a/apps/admin/src/routes/resources.global-search/entity.ts +++ b/apps/admin/src/routes/resources.global-search/entity.ts @@ -1,6 +1,6 @@ +import { orderEnumBy } from "@animeaux/core" import type { User } from "@animeaux/prisma" import { UserGroup } from "@animeaux/prisma" -import orderBy from "lodash.orderby" export namespace Entity { export const Enum = { @@ -17,7 +17,7 @@ export namespace Entity { [Enum.FOSTER_FAMILY]: "FA", } - export const values = orderBy( + export const values = orderEnumBy( Object.values(Enum), (entity) => translations[entity], ) diff --git a/apps/admin/src/show/divider-type/db.server.ts b/apps/admin/src/show/divider-type/db.server.ts index dc61c8f2..fceb9797 100644 --- a/apps/admin/src/show/divider-type/db.server.ts +++ b/apps/admin/src/show/divider-type/db.server.ts @@ -1,5 +1,5 @@ import type { Prisma } from "@animeaux/prisma/server" -import merge from "lodash.merge" +import { toMerged } from "es-toolkit/object" import type { Simplify } from "type-fest" import { prisma } from "#i/core/prisma.server.js" @@ -42,7 +42,7 @@ export class ShowDividerTypeDbDelegate { }> const dividerType = (await this.findUnique(id, { - select: merge({}, internalSelect, params.select), + select: toMerged(internalSelect, params.select), })) as Internal const usedCount = dividerType.exhibitors.reduce( @@ -94,7 +94,7 @@ export class ShowDividerTypeDbDelegate { }> const dividerTypes = (await this.findMany({ - select: merge({}, internalSelect, params.select), + select: toMerged(internalSelect, params.select), })) as Internal[] const dividerTypesWithAvailability = dividerTypes.map< diff --git a/apps/admin/src/show/exhibitors/activity-field/activity-field.ts b/apps/admin/src/show/exhibitors/activity-field/activity-field.ts index a843d2e5..283b306b 100644 --- a/apps/admin/src/show/exhibitors/activity-field/activity-field.ts +++ b/apps/admin/src/show/exhibitors/activity-field/activity-field.ts @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { ShowActivityField } from "@animeaux/prisma" -import orderBy from "lodash.orderby" export namespace ActivityField { export const Enum = ShowActivityField @@ -25,7 +25,7 @@ export namespace ActivityField { [Enum.TRAINING]: "Formation", } - export const values = orderBy( + export const values = orderEnumBy( Object.values(Enum), (field) => translation[field], ) diff --git a/apps/admin/src/show/exhibitors/activity-target/activity-target.ts b/apps/admin/src/show/exhibitors/activity-target/activity-target.ts index aa24825a..547c89d2 100644 --- a/apps/admin/src/show/exhibitors/activity-target/activity-target.ts +++ b/apps/admin/src/show/exhibitors/activity-target/activity-target.ts @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { ShowActivityTarget } from "@animeaux/prisma" -import orderBy from "lodash.orderby" export namespace ActivityTarget { export const Enum = ShowActivityTarget @@ -15,7 +15,7 @@ export namespace ActivityTarget { [Enum.WILDLIFE]: "Faune sauvage", } - export const values = orderBy( + export const values = orderEnumBy( Object.values(Enum), (field) => translation[field], ) diff --git a/apps/admin/src/show/exhibitors/applications/status.tsx b/apps/admin/src/show/exhibitors/applications/status.tsx index e7747e03..3ea78934 100644 --- a/apps/admin/src/show/exhibitors/applications/status.tsx +++ b/apps/admin/src/show/exhibitors/applications/status.tsx @@ -1,6 +1,5 @@ -import { cn } from "@animeaux/core" +import { cn, orderEnumBy } from "@animeaux/core" import { ShowExhibitorApplicationStatus } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" import { Icon } from "#i/generated/icon.js" @@ -15,7 +14,7 @@ export const TRANSLATION_BY_APPLICATION_STATUS: Record< [ShowExhibitorApplicationStatus.WAITING_LIST]: "Sur liste d’attente", } -export const SORTED_STATUSES = orderBy( +export const SORTED_STATUSES = orderEnumBy( Object.values(ShowExhibitorApplicationStatus), (status) => TRANSLATION_BY_APPLICATION_STATUS[status], ) diff --git a/apps/admin/src/show/stand-size/db.server.ts b/apps/admin/src/show/stand-size/db.server.ts index f6be06c2..00b4db8d 100644 --- a/apps/admin/src/show/stand-size/db.server.ts +++ b/apps/admin/src/show/stand-size/db.server.ts @@ -1,6 +1,6 @@ import { catchError } from "@animeaux/core" import { Prisma } from "@animeaux/prisma/server" -import merge from "lodash.merge" +import { toMerged } from "es-toolkit/object" import type { Simplify } from "type-fest" import { @@ -49,7 +49,7 @@ export class ShowStandSizeDbDelegate { }> const standSize = (await this.findUnique(id, { - select: merge({}, internalSelect, params.select), + select: toMerged(internalSelect, params.select), })) as Internal const bookedCount = standSize.exhibitors.length diff --git a/apps/admin/src/users/groups.tsx b/apps/admin/src/users/groups.tsx index e372a110..0ebf2e9f 100644 --- a/apps/admin/src/users/groups.tsx +++ b/apps/admin/src/users/groups.tsx @@ -1,7 +1,7 @@ +import { orderEnumBy } from "@animeaux/core" import type { User } from "@animeaux/prisma" import { UserGroup } from "@animeaux/prisma" -import intersection from "lodash.intersection" -import orderBy from "lodash.orderby" +import { intersection } from "es-toolkit/array" import type { IconName } from "#i/generated/icon.js" @@ -15,7 +15,7 @@ export const GROUP_TRANSLATION: Record = { [UserGroup.VOLUNTEER]: "BΓ©nΓ©vole", } -export const SORTED_GROUPS = orderBy( +export const SORTED_GROUPS = orderEnumBy( Object.values(UserGroup), (group) => GROUP_TRANSLATION[group], ) diff --git a/apps/show/Dockerfile b/apps/show/Dockerfile index e626f549..4cd53b92 100644 --- a/apps/show/Dockerfile +++ b/apps/show/Dockerfile @@ -36,6 +36,7 @@ COPY libs/core/package.json libs/core/ COPY libs/dev-tools/package.json libs/dev-tools/ COPY libs/file-storage/package.json libs/file-storage/ COPY libs/files-io/package.json libs/files-io/ +COPY libs/password/package.json libs/password/ COPY libs/prisma/package.json libs/prisma/ COPY libs/react-primitives/package.json libs/react-primitives/ COPY libs/search-params-io/package.json libs/search-params-io/ @@ -55,12 +56,17 @@ COPY libs/core libs/core COPY libs/dev-tools libs/dev-tools COPY libs/file-storage libs/file-storage COPY libs/files-io libs/files-io +COPY libs/password libs/password COPY libs/prisma libs/prisma COPY libs/react-primitives libs/react-primitives COPY libs/search-params-io libs/search-params-io COPY libs/zod-utils libs/zod-utils COPY apps/show apps/show +# Synchronise injected workspace packages after copying source code. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --frozen-lockfile --offline + # Build libs and app. RUN pnpm --filter show... build @@ -95,4 +101,8 @@ COPY libs/prisma/migrations libs/prisma/migrations COPY libs/prisma/prisma.config.ts libs/prisma/prisma.config.ts COPY libs/prisma/schema.prisma libs/prisma/schema.prisma +# Synchronise injected workspace packages after copying build artifacts. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --prod --frozen-lockfile --offline + CMD ["pnpm", "--filter", "show", "start"] diff --git a/apps/show/package.json b/apps/show/package.json index 6410b766..d0f5edbc 100644 --- a/apps/show/package.json +++ b/apps/show/package.json @@ -66,9 +66,7 @@ "express": "catalog:", "get-port": "catalog:", "isbot": "catalog:", - "lodash.chunk": "catalog:", - "lodash.groupby": "catalog:", - "lodash.orderby": "catalog:", + "es-toolkit": "catalog:", "lru-cache": "catalog:", "luxon": "catalog:", "morgan": "catalog:", @@ -90,9 +88,6 @@ "@remix-run/dev": "catalog:", "@types/compression": "catalog:", "@types/express": "catalog:", - "@types/lodash.chunk": "catalog:", - "@types/lodash.groupby": "catalog:", - "@types/lodash.orderby": "catalog:", "@types/luxon": "catalog:", "@types/morgan": "catalog:", "@types/node": "catalog:", diff --git a/apps/show/src/core/data-display/image.tsx b/apps/show/src/core/data-display/image.tsx index db4c585e..f46faa61 100644 --- a/apps/show/src/core/data-display/image.tsx +++ b/apps/show/src/core/data-display/image.tsx @@ -1,6 +1,6 @@ import { cn } from "@animeaux/core" import { blurhashToDataUri } from "@unpic/placeholder" -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import { forwardRef } from "react" import type { ImageData } from "#i/core/image/data.js" @@ -163,16 +163,12 @@ export function createImageUrl( // Larger to smaller. const SCREEN_SIZES = orderBy( (Object.entries(theme.screens) as [ScreenSize | "default", string][]).map( - ([name, width]) => - [name, Number(width.replace("px", ""))] as [ - ScreenSize | "default", - number, - ], + ([name, width]) => ({ name, width: Number(width.replace("px", "")) }), ), - ([_, width]) => width, - "desc", + [({ width }) => width], + ["desc"], ) - .map(([name]) => name) + .map(({ name }) => name) .concat("default") const ASPECT_RATIO_CLASS_NAME: Record = { diff --git a/apps/show/src/exhibitors/activity-field/activity-field.tsx b/apps/show/src/exhibitors/activity-field/activity-field.tsx index 171e1b6d..f14cce76 100644 --- a/apps/show/src/exhibitors/activity-field/activity-field.tsx +++ b/apps/show/src/exhibitors/activity-field/activity-field.tsx @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { ShowActivityField } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -27,7 +27,7 @@ export namespace ActivityField { [Enum.TRAINING]: "Formation", } - export const values = orderBy( + export const values = orderEnumBy( Object.values(Enum), (field) => translation[field], ) diff --git a/apps/show/src/exhibitors/activity-target/activity-target.tsx b/apps/show/src/exhibitors/activity-target/activity-target.tsx index f39d54c8..4a0f9ec9 100644 --- a/apps/show/src/exhibitors/activity-target/activity-target.tsx +++ b/apps/show/src/exhibitors/activity-target/activity-target.tsx @@ -1,5 +1,5 @@ +import { orderEnumBy } from "@animeaux/core" import { ShowActivityTarget } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import type { IconName } from "#i/generated/icon.js" @@ -13,7 +13,7 @@ export const ACTIVITY_TARGET_TRANSLATION: Record = { [ShowActivityTarget.WILDLIFE]: "Faune sauvage", } -export const SORTED_ACTIVITY_TARGETS = orderBy( +export const SORTED_ACTIVITY_TARGETS = orderEnumBy( Object.values(ShowActivityTarget), (field) => ACTIVITY_TARGET_TRANSLATION[field], ) diff --git a/apps/show/src/exhibitors/dogs-configuration/gender.ts b/apps/show/src/exhibitors/dogs-configuration/gender.ts index 7fef756f..425c72b4 100644 --- a/apps/show/src/exhibitors/dogs-configuration/gender.ts +++ b/apps/show/src/exhibitors/dogs-configuration/gender.ts @@ -1,12 +1,12 @@ +import { orderEnumBy } from "@animeaux/core" import { Gender } from "@animeaux/prisma" -import orderBy from "lodash.orderby" export const GENDER_TRANSLATION: Record = { [Gender.FEMALE]: "Femelle", [Gender.MALE]: "MΓ’le", } -export const SORTED_GENDERS = orderBy( +export const SORTED_GENDERS = orderEnumBy( Object.values(Gender), (gender) => GENDER_TRANSLATION[gender], ) diff --git a/apps/show/src/faq/item.tsx b/apps/show/src/faq/item.tsx index d8658c89..f6b8f1e6 100644 --- a/apps/show/src/faq/item.tsx +++ b/apps/show/src/faq/item.tsx @@ -1,5 +1,5 @@ import * as Collapsible from "@radix-ui/react-collapsible" -import chunk from "lodash.chunk" +import { chunk } from "es-toolkit/array" import { Children } from "react" import { diff --git a/apps/show/src/previous-editions/previous-edition.tsx b/apps/show/src/previous-editions/previous-edition.tsx index a378184c..8a9ea6c8 100644 --- a/apps/show/src/previous-editions/previous-edition.tsx +++ b/apps/show/src/previous-editions/previous-edition.tsx @@ -1,4 +1,4 @@ -import orderBy from "lodash.orderby" +import { orderEnumBy } from "@animeaux/core" export enum PreviousEdition { Y_2022 = "2022", @@ -7,10 +7,10 @@ export enum PreviousEdition { Y_2025 = "2025", } -export const SORTED_PREVIOUS_EDITIONS = orderBy( +export const SORTED_PREVIOUS_EDITIONS = orderEnumBy( Object.values(PreviousEdition), (edition) => edition, - ["desc"], + "desc", ) export const PREVIOUS_EDITION_PHOTOGRAPH: Partial< diff --git a/apps/show/src/routes/_layout.programme.$day/loader.server.ts b/apps/show/src/routes/_layout.programme.$day/loader.server.ts index 40cabcb2..743d64c8 100644 --- a/apps/show/src/routes/_layout.programme.$day/loader.server.ts +++ b/apps/show/src/routes/_layout.programme.$day/loader.server.ts @@ -1,6 +1,6 @@ import { safeParseRouteParam } from "@animeaux/zod-utils" import type { LoaderFunctionArgs } from "@remix-run/node" -import groupBy from "lodash.groupby" +import { groupBy } from "es-toolkit/array" import { notFound } from "#i/core/response.server.js" import { services } from "#i/core/services.server.js" diff --git a/apps/show/src/sponsors/service.server.ts b/apps/show/src/sponsors/service.server.ts index 9af867e8..f3e13742 100644 --- a/apps/show/src/sponsors/service.server.ts +++ b/apps/show/src/sponsors/service.server.ts @@ -1,4 +1,4 @@ -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import invariant from "tiny-invariant" import type { ServicePrisma } from "#i/core/prisma.service.server.js" @@ -53,6 +53,6 @@ export class ServiceSponsor { } }) - return orderBy(sponsors, (sponsor) => sponsor.name, "asc") + return orderBy(sponsors, [(sponsor) => sponsor.name], ["asc"]) } } diff --git a/apps/website/Dockerfile b/apps/website/Dockerfile index ccb61f95..1b305f3f 100644 --- a/apps/website/Dockerfile +++ b/apps/website/Dockerfile @@ -34,6 +34,7 @@ FROM base AS packages-json COPY package.json ./ COPY libs/core/package.json libs/core/ COPY libs/dev-tools/package.json libs/dev-tools/ +COPY libs/password/package.json libs/password/ COPY libs/prisma/package.json libs/prisma/ COPY libs/search-params-io/package.json libs/search-params-io/ COPY libs/zod-utils/package.json libs/zod-utils/ @@ -50,11 +51,16 @@ RUN pnpm install --frozen-lockfile --offline COPY libs/core libs/core COPY libs/dev-tools libs/dev-tools +COPY libs/password libs/password COPY libs/prisma libs/prisma COPY libs/search-params-io libs/search-params-io COPY libs/zod-utils libs/zod-utils COPY apps/website apps/website +# Synchronise injected workspace packages after copying source code. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --frozen-lockfile --offline + # Build libs and app. RUN pnpm --filter website... build @@ -89,4 +95,8 @@ COPY libs/prisma/migrations libs/prisma/migrations COPY libs/prisma/prisma.config.ts libs/prisma/prisma.config.ts COPY libs/prisma/schema.prisma libs/prisma/schema.prisma +# Synchronise injected workspace packages after copying build artifacts. +# See: docs/decisions/002-workspace-package-injection.md +RUN pnpm install --prod --frozen-lockfile --offline + CMD ["pnpm", "--filter", "website", "start"] diff --git a/apps/website/package.json b/apps/website/package.json index 22224f47..a1212811 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -42,7 +42,7 @@ "focus-trap": "catalog:", "history": "catalog:", "isbot": "catalog:", - "lodash.orderby": "catalog:", + "es-toolkit": "catalog:", "luxon": "catalog:", "react": "catalog:", "react-dom": "catalog:", @@ -58,7 +58,6 @@ "@animeaux/dev-tools": "workspace:*", "@remix-run/dev": "catalog:", "@types/body-scroll-lock": "catalog:", - "@types/lodash.orderby": "catalog:", "@types/luxon": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", diff --git a/apps/website/src/blog/data.server.ts b/apps/website/src/blog/data.server.ts index 2412362e..f5169979 100644 --- a/apps/website/src/blog/data.server.ts +++ b/apps/website/src/blog/data.server.ts @@ -1,4 +1,4 @@ -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import { DateTime } from "luxon" import { MarkdownLink } from "#i/core/data-display/markdown.js" @@ -441,6 +441,6 @@ Alors, qu’attendez-vousΒ ? C’est peut-Γͺtre vous le nouveau compagnon pour l [Contactez-nous](mailto:adoption@animeaux.org)Β !`, }, ], - (article) => DateTime.fromJSDate(article.publicationDate).toMillis(), - "desc", + [(article) => DateTime.fromJSDate(article.publicationDate).toMillis()], + ["desc"], ) diff --git a/apps/website/src/core/controllers/searchForm.tsx b/apps/website/src/core/controllers/searchForm.tsx index 743c040d..24f9d951 100644 --- a/apps/website/src/core/controllers/searchForm.tsx +++ b/apps/website/src/core/controllers/searchForm.tsx @@ -1,6 +1,10 @@ -import { ANIMAL_AGE_RANGE_BY_SPECIES, AnimalAge, cn } from "@animeaux/core" +import { + ANIMAL_AGE_RANGE_BY_SPECIES, + AnimalAge, + cn, + orderEnumBy, +} from "@animeaux/core" import { Species } from "@animeaux/prisma" -import orderBy from "lodash.orderby" import { useCallback, useEffect, useState } from "react" import { BaseLink } from "#i/core/base-link.js" @@ -10,12 +14,12 @@ import { } from "#i/core/translations.js" import { Icon } from "#i/generated/icon.js" -const SORTED_SPECIES = orderBy( +const SORTED_SPECIES = orderEnumBy( Object.values(Species), (species) => SPECIES_TRANSLATION_STANDALONE[species], ) -const SORTED_AGES = orderBy(Object.values(AnimalAge), (age) => +const SORTED_AGES = orderEnumBy(Object.values(AnimalAge), (age) => age === AnimalAge.JUNIOR ? 0 : age === AnimalAge.ADULT ? 1 : 2, ) diff --git a/apps/website/src/core/data-display/image.tsx b/apps/website/src/core/data-display/image.tsx index 09b948f3..bfabb6b9 100644 --- a/apps/website/src/core/data-display/image.tsx +++ b/apps/website/src/core/data-display/image.tsx @@ -1,5 +1,5 @@ import { cn } from "@animeaux/core" -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import invariant from "tiny-invariant" import type { IconProps } from "#i/generated/icon.js" @@ -19,16 +19,12 @@ export type ImageDescriptor = { // Larger to smaller. const SCREEN_SIZES = orderBy( (Object.entries(theme.screens) as [ScreenSize | "default", string][]).map( - ([name, width]) => - [name, Number(width.replace("px", ""))] as [ - ScreenSize | "default", - number, - ], + ([name, width]) => ({ name, width: Number(width.replace("px", "")) }), ), - ([_, width]) => width, - "desc", + [({ width }) => width], + ["desc"], ) - .map(([name]) => name) + .map(({ name }) => name) .concat("default") export type StaticImageProps = { diff --git a/apps/website/src/partners/data.server.ts b/apps/website/src/partners/data.server.ts index bb8a5960..cf4a8a01 100644 --- a/apps/website/src/partners/data.server.ts +++ b/apps/website/src/partners/data.server.ts @@ -1,4 +1,4 @@ -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" export type Partner = { id: string @@ -59,5 +59,6 @@ export const partners: Partner[] = orderBy( url: "https://www.super-logo.com", }, ], - (partner) => partner.name.toLowerCase(), + [(partner) => partner.name.toLowerCase()], + ["asc"], ) diff --git a/apps/website/src/routes/nommez-votre-animal.tsx b/apps/website/src/routes/nommez-votre-animal.tsx index d12fbf08..d46844a6 100644 --- a/apps/website/src/routes/nommez-votre-animal.tsx +++ b/apps/website/src/routes/nommez-votre-animal.tsx @@ -3,7 +3,7 @@ import { Gender } from "@animeaux/prisma" import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node" import { json } from "@remix-run/node" import { Form, useLoaderData, useSearchParams } from "@remix-run/react" -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" import { useEffect, useState } from "react" import invariant from "tiny-invariant" import { z } from "zod" @@ -54,7 +54,8 @@ export async function loader({ request }: LoaderFunctionArgs) { names = orderBy( pickRandom(names, ANIMAL_NAME_COUNT_PER_PAGE), - (name) => name.label, + [(name) => name.label], + ["asc"], ) return json({ names }) diff --git a/apps/website/src/sterilisation-agreements/data.server.ts b/apps/website/src/sterilisation-agreements/data.server.ts index b10d876a..ef26e827 100644 --- a/apps/website/src/sterilisation-agreements/data.server.ts +++ b/apps/website/src/sterilisation-agreements/data.server.ts @@ -1,4 +1,4 @@ -import orderBy from "lodash.orderby" +import { orderBy } from "es-toolkit/array" export type CityWithAgreements = { id: string @@ -30,5 +30,6 @@ export const citiesWithAgreements: CityWithAgreements[] = orderBy( image: "cities-with-agreements/villenoy-rrzty95tbnggot0s8tyj", }, ], - (city) => city.name.toLowerCase(), + [(city) => city.name.toLowerCase()], + ["asc"], ) diff --git a/docs/commit-message-guidelines.md b/docs/commit-message-guidelines.md new file mode 100644 index 00000000..124e87e3 --- /dev/null +++ b/docs/commit-message-guidelines.md @@ -0,0 +1,142 @@ +# Commit message guidelines + +The rules below are inspired by the [Angular convention][convention-angular]. + +## Overall structure + +Each commit message is structured in three sections, separated by a blank line: + +``` +
+ + + +