From 533db4dfe902334abf12d1e8590ff6a2978a32d9 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 15:32:27 +0200 Subject: [PATCH 01/14] feat(agent): install caveman skills and project documentation - Add 6 caveman AI communication skills (.agents/skills/) - Add AGENTS.md with mongodb-dynamic-api architecture guide - Add .github/copilot-instructions.md with universal coding rules No source code changes. Static configuration files only. --- .agents/skills/cavecrew/SKILL.md | 83 +++++++++++++++++++++++++ .agents/skills/caveman-commit/SKILL.md | 66 ++++++++++++++++++++ .agents/skills/caveman-help/SKILL.md | 60 ++++++++++++++++++ .agents/skills/caveman-review/SKILL.md | 56 +++++++++++++++++ .agents/skills/caveman-stats/SKILL.md | 11 ++++ .agents/skills/caveman/SKILL.md | 75 +++++++++++++++++++++++ .github/copilot-instructions.md | 21 +++++++ AGENTS.md | 84 ++++++++++++++++++++++++++ 8 files changed, 456 insertions(+) create mode 100644 .agents/skills/cavecrew/SKILL.md create mode 100644 .agents/skills/caveman-commit/SKILL.md create mode 100644 .agents/skills/caveman-help/SKILL.md create mode 100644 .agents/skills/caveman-review/SKILL.md create mode 100644 .agents/skills/caveman-stats/SKILL.md create mode 100644 .agents/skills/caveman/SKILL.md create mode 100644 .github/copilot-instructions.md create mode 100644 AGENTS.md diff --git a/.agents/skills/cavecrew/SKILL.md b/.agents/skills/cavecrew/SKILL.md new file mode 100644 index 0000000..656f69e --- /dev/null +++ b/.agents/skills/cavecrew/SKILL.md @@ -0,0 +1,83 @@ +--- +name: cavecrew +description: > + Decision guide for delegating to caveman-style subagents. Tells the main + thread WHEN to spawn `cavecrew-investigator` (locate code), `cavecrew-builder` + (1-2 file edit), or `cavecrew-reviewer` (diff review) instead of doing the + work inline or using vanilla `Explore`. Subagent output is caveman-compressed + so the tool-result injected back into main context is ~60% smaller — main + context lasts longer across long sessions. + Trigger: "delegate to subagent", "use cavecrew", "spawn investigator/builder/reviewer", + "save context", "compressed agent output". +--- + +Cavecrew = three subagent presets that emit caveman output. Same job as Anthropic defaults (`Explore`, edit-style agents, reviewer); difference is the tool-result they return is compressed, so main context shrinks per delegation. + +## When to use cavecrew vs alternatives + +| Task | Use | +|---|---| +| "Where is X defined / what calls Y / list uses of Z" | `cavecrew-investigator` | +| Same but you also want suggestions/architecture commentary | `Explore` (vanilla) | +| Surgical edit, ≤2 files, scope obvious | `cavecrew-builder` | +| New feature / 3+ files / cross-cutting refactor | Main thread or `feature-dev:code-architect` | +| Review diff, branch, or file for bugs | `cavecrew-reviewer` | +| Deep code review with rationale + alternatives | `Code Reviewer` (vanilla) | +| One-line answer you already know | Main thread, no subagent | + +Rule of thumb: **if you'd want the subagent's output in 1/3 the tokens, pick cavecrew. If you'd want prose, pick vanilla.** + +## Why this exists (the real win) + +Subagent tool results get injected into main context verbatim. A vanilla `Explore` that returns 2k tokens of prose costs 2k tokens of main-context budget every time. The same finding from `cavecrew-investigator` returns ~700 tokens. Across 20 delegations in one session that's the difference between context exhaustion and finishing the task. + +## Output contracts + +What main thread can rely on per agent: + +**`cavecrew-investigator`** +``` +
: +- path:line — `symbol` — short note +totals: . +``` +Or `No match.` Always file-path-first, line-number-attached, backticked symbols. Safe to grep with `path:\d+`. + +**`cavecrew-builder`** +``` +. +verified: . +``` +Or one of: `too-big.` / `needs-confirm.` / `ambiguous.` / `regressed.` (terminal first token). + +**`cavecrew-reviewer`** +``` +path:line: : . . +totals: N🔴 N🟡 N🔵 N❓ +``` +Or `No issues.` Findings sorted file → line ascending. + +## Chaining patterns + +**Locate → fix → verify** (most common): +1. `cavecrew-investigator` returns site list. +2. Main thread picks 1-2 sites, hands paths to `cavecrew-builder`. +3. `cavecrew-reviewer` audits the diff. + +**Parallel scout** (when investigation is broad): +Spawn 2-3 `cavecrew-investigator` calls in one message (different angles: defs vs callers vs tests). Aggregate in main thread. + +**Single-shot edit** (when site is already known): +Skip investigator. Hand exact path:line to `cavecrew-builder` directly. + +## What NOT to do + +- Don't use `cavecrew-builder` when you don't already know the file. Spawn investigator first or main thread will eat tokens passing context. +- Don't chain `cavecrew-investigator → cavecrew-builder` for a 5-file refactor. Builder will return `too-big.` and you'll have wasted a turn. +- Don't ask `cavecrew-reviewer` for "general feedback" — it returns findings only, no architecture opinions. Use `Code Reviewer` for that. +- Don't expect prose. Cavecrew output is structured, sometimes terse to the point of cryptic. If a human will read it directly, paraphrase. + +## Auto-clarity (inherited) + +Subagents drop caveman → normal English for security warnings, irreversible-action confirmations, and any output where fragment ambiguity could be misread. Resume caveman after. + diff --git a/.agents/skills/caveman-commit/SKILL.md b/.agents/skills/caveman-commit/SKILL.md new file mode 100644 index 0000000..62563d6 --- /dev/null +++ b/.agents/skills/caveman-commit/SKILL.md @@ -0,0 +1,66 @@ +--- +name: caveman-commit +description: > + Ultra-compressed commit message generator. Cuts noise from commit messages while preserving + intent and reasoning. Conventional Commits format. Subject ≤50 chars, body only when "why" + isn't obvious. Use when user says "write a commit", "commit message", "generate commit", + "/commit", or invokes /caveman-commit. Auto-triggers when staging changes. +--- + +Write commit messages terse and exact. Conventional Commits format. No fluff. Why over what. + +## Rules + +**Subject line:** +- `(): ` — `` optional +- Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore`, `build`, `ci`, `style`, `revert` +- Imperative mood: "add", "fix", "remove" — not "added", "adds", "adding" +- ≤50 chars when possible, hard cap 72 +- No trailing period +- Match project convention for capitalization after the colon + +**Body (only if needed):** +- Skip entirely when subject is self-explanatory +- Add body only for: non-obvious *why*, breaking changes, migration notes, linked issues +- Wrap at 72 chars +- Bullets `-` not `*` +- Reference issues/PRs at end: `Closes #42`, `Refs #17` + +**What NEVER goes in:** +- "This commit does X", "I", "we", "now", "currently" — the diff says what +- "As requested by..." — use Co-authored-by trailer +- "Generated with Claude Code" or any AI attribution +- Emoji (unless project convention requires) +- Restating the file name when scope already says it + +## Examples + +Diff: new endpoint for user profile with body explaining the why +- ❌ "feat: add a new endpoint to get user profile information from the database" +- ✅ + ``` + feat(api): add GET /users/:id/profile + + Mobile client needs profile data without the full user payload + to reduce LTE bandwidth on cold-launch screens. + + Closes #128 + ``` + +Diff: breaking API change +- ✅ + ``` + feat(api)!: rename /v1/orders to /v1/checkout + + BREAKING CHANGE: clients on /v1/orders must migrate to /v1/checkout + before 2026-06-01. Old route returns 410 after that date. + ``` + +## Auto-Clarity + +Always include body for: breaking changes, security fixes, data migrations, anything reverting a prior commit. Never compress these into subject-only — future debuggers need the context. + +## Boundaries + +Only generates the commit message. Does not run `git commit`, does not stage files, does not amend. Output the message as a code block ready to paste. "stop caveman-commit" or "normal mode": revert to verbose commit style. + diff --git a/.agents/skills/caveman-help/SKILL.md b/.agents/skills/caveman-help/SKILL.md new file mode 100644 index 0000000..c196616 --- /dev/null +++ b/.agents/skills/caveman-help/SKILL.md @@ -0,0 +1,60 @@ +--- +name: caveman-help +description: > + Quick-reference card for all caveman modes, skills, and commands. + One-shot display, not a persistent mode. Trigger: /caveman-help, + "caveman help", "what caveman commands", "how do I use caveman". +--- + +# Caveman Help + +Display this reference card when invoked. One-shot — do NOT change mode, write flag files, or persist anything. Output in caveman style. + +## Modes + +| Mode | Trigger | What change | +|------|---------|-------------| +| **Lite** | `/caveman lite` | Drop filler. Keep sentence structure. | +| **Full** | `/caveman` | Drop articles, filler, pleasantries, hedging. Fragments OK. Default. | +| **Ultra** | `/caveman ultra` | Extreme compression. Bare fragments. Tables over prose. | +| **Wenyan-Lite** | `/caveman wenyan-lite` | Classical Chinese style, light compression. | +| **Wenyan-Full** | `/caveman wenyan` | Full 文言文. Maximum classical terseness. | +| **Wenyan-Ultra** | `/caveman wenyan-ultra` | Extreme. Ancient scholar on a budget. | + +Mode stick until changed or session end. + +## Skills + +| Skill | Trigger | What it do | +|-------|---------|-----------| +| **caveman-commit** | `/caveman-commit` | Terse commit messages. Conventional Commits. ≤50 char subject. | +| **caveman-review** | `/caveman-review` | One-line PR comments: `L42: bug: user null. Add guard.` | +| **caveman-compress** | `/caveman:compress ` | Compress .md files to caveman prose. Saves ~46% input tokens. | +| **caveman-help** | `/caveman-help` | This card. | + +## Deactivate + +Say "stop caveman" or "normal mode". Resume anytime with `/caveman`. + +## Configure Default Mode + +Default mode = `full`. Change it: + +**Environment variable** (highest priority): +```bash +export CAVEMAN_DEFAULT_MODE=ultra +``` + +**Config file** (`~/.config/caveman/config.json`): +```json +{ "defaultMode": "lite" } +``` + +Set `"off"` to disable auto-activation on session start. User can still activate manually with `/caveman`. + +Resolution: env var > config file > `full`. + +## More + +Full docs: https://github.com/JuliusBrussee/caveman + diff --git a/.agents/skills/caveman-review/SKILL.md b/.agents/skills/caveman-review/SKILL.md new file mode 100644 index 0000000..9be288c --- /dev/null +++ b/.agents/skills/caveman-review/SKILL.md @@ -0,0 +1,56 @@ +--- +name: caveman-review +description: > + Ultra-compressed code review comments. Cuts noise from PR feedback while preserving + the actionable signal. Each comment is one line: location, problem, fix. Use when user + says "review this PR", "code review", "review the diff", "/review", or invokes + /caveman-review. Auto-triggers when reviewing pull requests. +--- + +Write code review comments terse and actionable. One line per finding. Location, problem, fix. No throat-clearing. + +## Rules + +**Format:** `L: . .` — or `:L: ...` when reviewing multi-file diffs. + +**Severity prefix (optional, when mixed):** +- `🔴 bug:` — broken behavior, will cause incident +- `🟡 risk:` — works but fragile (race, missing null check, swallowed error) +- `🔵 nit:` — style, naming, micro-optim. Author can ignore +- `❓ q:` — genuine question, not a suggestion + +**Drop:** +- "I noticed that...", "It seems like...", "You might want to consider..." +- "This is just a suggestion but..." — use `nit:` instead +- "Great work!", "Looks good overall but..." — say it once at the top, not per comment +- Restating what the line does — the reviewer can read the diff +- Hedging ("perhaps", "maybe", "I think") — if unsure use `q:` + +**Keep:** +- Exact line numbers +- Exact symbol/function/variable names in backticks +- Concrete fix, not "consider refactoring this" +- The *why* if the fix isn't obvious from the problem statement + +## Examples + +❌ "I noticed that on line 42 you're not checking if the user object is null before accessing the email property. This could potentially cause a crash if the user is not found in the database. You might want to add a null check here." + +✅ `L42: 🔴 bug: user can be null after .find(). Add guard before .email.` + +❌ "It looks like this function is doing a lot of things and might benefit from being broken up into smaller functions for readability." + +✅ `L88-140: 🔵 nit: 50-line fn does 4 things. Extract validate/normalize/persist.` + +❌ "Have you considered what happens if the API returns a 429? I think we should probably handle that case." + +✅ `L23: 🟡 risk: no retry on 429. Wrap in withBackoff(3).` + +## Auto-Clarity + +Drop terse mode for: security findings (CVE-class bugs need full explanation + reference), architectural disagreements (need rationale, not just a one-liner), and onboarding contexts where the author is new and needs the "why". In those cases write a normal paragraph, then resume terse for the rest. + +## Boundaries + +Reviews only — does not write the code fix, does not approve/request-changes, does not run linters. Output the comment(s) ready to paste into the PR. "stop caveman-review" or "normal mode": revert to verbose review style. + diff --git a/.agents/skills/caveman-stats/SKILL.md b/.agents/skills/caveman-stats/SKILL.md new file mode 100644 index 0000000..b839960 --- /dev/null +++ b/.agents/skills/caveman-stats/SKILL.md @@ -0,0 +1,11 @@ +--- +name: caveman-stats +description: > + Show real token usage and estimated savings for the current session. + Reads directly from the Claude Code session log — no AI estimation. + Triggers on /caveman-stats. Output is injected by the mode-tracker hook; + the model itself does not compute the numbers. +--- + +This skill is delivered by `hooks/caveman-stats.js` (read by `hooks/caveman-mode-tracker.js` on `/caveman-stats`). The model does not need to do anything when this skill fires — the hook returns `decision: "block"` with the formatted stats as the reason. The user sees the numbers immediately. + diff --git a/.agents/skills/caveman/SKILL.md b/.agents/skills/caveman/SKILL.md new file mode 100644 index 0000000..74c51bc --- /dev/null +++ b/.agents/skills/caveman/SKILL.md @@ -0,0 +1,75 @@ +--- +name: caveman +description: > + Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman + while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, + wenyan-lite, wenyan-full, wenyan-ultra. + Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", + "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested. +--- + +Respond terse like smart caveman. All technical substance stay. Only fluff die. + +## Persistence + +ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift. Still active if unsure. Off only: "stop caveman" / "normal mode". + +Default: **full**. Switch: `/caveman lite|full|ultra`. + +## Rules + +Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Technical terms exact. Code blocks unchanged. Errors quoted exact. + +Pattern: `[thing] [action] [reason]. [next step].` + +Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..." +Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:" + +## Intensity + +| Level | What change | +|-------|------------| +| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight | +| **full** | Drop articles, fragments OK, short synonyms. Classic caveman | +| **ultra** | Abbreviate prose words (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y), one word when one word enough. Code symbols, function names, API names, error strings: never abbreviate | +| **wenyan-lite** | Semi-classical. Drop filler/hedging but keep grammar structure, classical register | +| **wenyan-full** | Maximum classical terseness. Fully 文言文. 80-90% character reduction. Classical sentence patterns, verbs precede objects, subjects often omitted, classical particles (之/乃/為/其) | +| **wenyan-ultra** | Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse | + +Example — "Why React component re-render?" +- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`." +- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`." +- ultra: "Inline obj prop → new ref → re-render. `useMemo`." +- wenyan-lite: "組件頻重繪,以每繪新生對象參照故。以 useMemo 包之。" +- wenyan-full: "物出新參照,致重繪。useMemo .Wrap之。" +- wenyan-ultra: "新參照→重繪。useMemo Wrap。" + +Example — "Explain database connection pooling." +- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead." +- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead." +- ultra: "Pool = reuse DB conn. Skip handshake → fast under load." +- wenyan-full: "池reuse open connection。不每req新開。skip handshake overhead。" +- wenyan-ultra: "池reuse conn。skip handshake → fast。" + +## Auto-Clarity + +Drop caveman when: +- Security warnings +- Irreversible action confirmations +- Multi-step sequences where fragment order or omitted conjunctions risk misread +- Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` — order unclear without articles/conjunctions) +- User asks to clarify or repeats question + +Resume caveman after clear part done. + +Example — destructive op: +> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone. +> ```sql +> DROP TABLE users; +> ``` +> Caveman resume. Verify backup exist first. + +## Boundaries + +Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end. + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..4a0e303 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,21 @@ +> **🗣️ CAVEMAN MODE ALWAYS ON** — Réponds toujours en mode caveman (level: **full**) par défaut dans chaque message. Pas d'articles, fragments OK, synonymes courts, sans fioritures. Précision technique intacte. Désactivation uniquement si l'utilisateur dit "stop caveman" ou "normal mode". Reprendre caveman dès le message suivant. Consulter `.github/skills/caveman/SKILL.md` pour les règles complètes. + +> **🚫 INTERDICTION ABSOLUE de `as any`** — ne JAMAIS utiliser `as any`, `Promise`, `: any`, `[key: string]: any` ou tout autre usage du type `any`. Toujours créer une interface ou un type explicite. Si un type externe est manquant, déclarer une interface locale typée. Cette règle est non-négociable. + +> **🇬🇧 Commit messages = English only** — every commit subject and body must be written in English. No French, no mixed language. This applies to all scopes (feat, fix, chore, docs, refactor…). Non-negotiable. + +> **📝 Commits : toujours via `scripts/git-commit.cjs`** — NE JAMAIS utiliser `git commit -m "..."` directement sur Windows bash : les sauts de ligne sont squashés et commitlint rejette avec `body-leading-blank`. Utiliser **systématiquement** le helper : +> +> ```bash +> # Sans body (sujet seul) +> node scripts/git-commit.cjs "fix(scope): message court" +> +> # Avec body (saut de ligne via \n\n) +> node scripts/git-commit.cjs "feat(scope): message court\n\nDescription détaillée ici." +> +> # Amend +> node scripts/git-commit.cjs --amend "fix(scope): correction\n\nBody." +> ``` +> +> Découper en commits atomiques par scope (feat/fix/refactor/chore…). Format Conventional Commits obligatoire. Si `body-leading-blank` apparaît malgré tout, amender immédiatement avec le helper. + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..61956af --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +> **🗣️ CAVEMAN MODE ALWAYS ON** — Réponds toujours en mode caveman (level: **full**) par défaut dans chaque message. Pas d'articles, fragments OK, synonymes courts, sans fioritures. Précision technique intacte. Désactivation uniquement si l'utilisateur dit "stop caveman" ou "normal mode". Reprendre caveman dès le message suivant. Consulter `.github/skills/caveman/SKILL.md` pour les règles complètes. + +# AI Agent Guide — Base Template + +> Copy this file to your project root as `AGENTS.md` and fill in the project-specific sections below. + +## Universal Rules + +### 🚫 No `any` — Ever + +Never use `as any`, `Promise`, `: any`, `[key: string]: any`. Always create an explicit interface or type. If an external type is missing, declare a local typed interface. Non-negotiable. + +### 🇬🇧 Commit Messages = English Only + +Every commit subject and body must be written in English. No mixed language. Applies to all scopes (feat, fix, chore, docs, refactor…). + +### 📝 Commits: Always via `scripts/git-commit.cjs` + +NEVER use `git commit -m "..."` directly on Windows bash — newlines get squashed and commitlint rejects with `body-leading-blank`. Always use the helper: + +```bash +# Subject only +node scripts/git-commit.cjs "fix(scope): short message" + +# With body (newline via \n\n) +node scripts/git-commit.cjs "feat(scope): short message\n\nDetailed description here." + +# Amend +node scripts/git-commit.cjs --amend "fix(scope): correction\n\nBody." +``` + +Split into atomic commits per scope (feat/fix/refactor/chore…). Conventional Commits format required. If `body-leading-blank` appears, amend immediately with the helper. + +### 🗣️ Caveman Mode + +Default: **full**. Active every response. Off only when user says "stop caveman" or "normal mode". Resume next message. Full rules in `.github/skills/caveman/SKILL.md`. + +Levels: `/caveman lite` | `/caveman` (full) | `/caveman ultra` + +--- + +## Skills + +| Skill | Trigger | Purpose | +|-------|---------|---------| +| **caveman** | `/caveman [lite\|full\|ultra]` | Ultra-compressed communication (~75% token cut) | +| **cavecrew** | `use cavecrew` | Delegate to compressed subagents (investigator/builder/reviewer) | +| **caveman-commit** | `/caveman-commit` | Terse Conventional Commits generator | +| **caveman-review** | `/caveman-review` | One-line PR review comments | +| **caveman-help** | `/caveman-help` | Quick reference card | +| **caveman-stats** | `/caveman-stats` | Token usage stats (requires hooks) | + +Skills live in `.github/skills/` (GitHub Copilot Cloud) and `.agents/skills/` (local IDE). + +--- + +## Project Architecture + + + +| Path | Role | +|------|------| +| `` | `` | + +### Stack + + + +### Key Patterns & Conventions + + + +### Quick Commands + +```bash +# +``` + +--- + +## Deployment + + + From a2786b6c02b152c897d03f2f84c58e70cf0cb7ce Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 15:58:36 +0200 Subject: [PATCH 02/14] chore(deps): upgrade release-it to v20 for Node 24 compatibility Upgrade release-it 17.11.0 -> 20.0.1 (engines: ^20.19.0 || ^22.13.0 || >=24.0.0). Upgrade @release-it/bumper 6.0.1 -> 7.0.5. Upgrade @release-it/conventional-changelog 8.0.1 -> 11.0.0. Add scripts/git-commit.cjs helper. No breaking changes: all .release-it.json keys and CLI flags (--ci, --preRelease=beta) verified valid in v20. --- package-lock.json | 3550 ++++++++++++++++++---------------------- package.json | 6 +- scripts/git-commit.cjs | 57 + 3 files changed, 1694 insertions(+), 1919 deletions(-) create mode 100644 scripts/git-commit.cjs diff --git a/package-lock.json b/package-lock.json index 102ff5c..417446f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,9 +26,8 @@ "class-validator": "^0.14.1", "cookie-parser": "^1.4.7", "dotenv": "^16.4.5", - "lodash": "4.17.23", "mongodb-pipeline-builder": "^4.0.2", - "mongoose": "8.9.5", + "mongoose": "^8.9.5", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", @@ -41,8 +40,8 @@ "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", "@nestjs/testing": "^11.1.16", - "@release-it/bumper": "^6.0.1", - "@release-it/conventional-changelog": "^8.0.1", + "@release-it/bumper": "^7.0.5", + "@release-it/conventional-changelog": "^11.0.0", "@swc/cli": "^0.3.9", "@swc/core": "^1.4.0", "@types/bcrypt": "^5.0.2", @@ -64,7 +63,7 @@ "jest-sonar-reporter": "^2.0.0", "lcov-result-merger": "^5.0.1", "prettier": "^3.2.5", - "release-it": "^17.11.0", + "release-it": "^20.0.1", "source-map-support": "^0.5.21", "supertest": "^6.3.4", "ts-jest": "^29.1.2", @@ -835,6 +834,33 @@ "node": ">=0.1.90" } }, + "node_modules/@conventional-changelog/git-client": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.7.0.tgz", + "integrity": "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^1.0.0", + "@simple-libs/stream-utils": "^1.2.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.4.0" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1047,20 +1073,12 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@hutson/parse-repository-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz", - "integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "dev": true, + "license": "ISC" }, "node_modules/@inquirer/ansi": { "version": "1.0.2", @@ -1228,13 +1246,6 @@ } } }, - "node_modules/@inquirer/external-editor/node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -3025,249 +3036,235 @@ } }, "node_modules/@octokit/auth-token": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", - "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/core": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", - "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/auth-token": "^4.0.0", - "@octokit/graphql": "^7.1.0", - "@octokit/request": "^8.4.1", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.0.0", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/endpoint": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", - "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/graphql": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", - "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/request": "^8.4.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/openapi-types": { - "version": "24.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", - "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", "dev": true, "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", - "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.5.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "5" + "@octokit/core": ">=6" } }, "node_modules/@octokit/plugin-request-log": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", - "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "5" + "@octokit/core": ">=6" } }, "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", - "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.5.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" }, "peerDependencies": { - "@octokit/core": "^5" + "@octokit/core": ">=6" } }, "node_modules/@octokit/request": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", - "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.9.tgz", + "integrity": "sha512-o8Bi3f608eyM+7BmBiUWxFsdjLb3/ym1cQek5LZOv9KkZcxRrHCPhhRzm6xjO6HVZ85ItD6+sTsjxo821SVa/A==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/endpoint": "^9.0.6", - "@octokit/request-error": "^5.1.1", - "@octokit/types": "^13.1.0", - "universal-user-agent": "^6.0.0" + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "content-type": "^2.0.0", + "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/request-error": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", - "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/types": "^13.1.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "@octokit/types": "^16.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/request/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/@octokit/rest": { - "version": "20.1.1", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.1.tgz", - "integrity": "sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==", + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/core": "^5.0.2", - "@octokit/plugin-paginate-rest": "11.3.1", - "@octokit/plugin-request-log": "^4.0.0", - "@octokit/plugin-rest-endpoint-methods": "13.2.2" + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/types": { - "version": "13.10.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", - "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", "dev": true, "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^24.2.0" + "@octokit/openapi-types": "^27.0.0" } }, - "node_modules/@pkgr/core": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", - "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "node_modules/@phun-ky/typeof": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@phun-ky/typeof/-/typeof-2.0.3.tgz", + "integrity": "sha512-oeQJs1aa8Ghke8JIK9yuq/+KjMiaYeDZ38jx7MhkXncXlUKjqQ3wEm2X3qCKyjo+ZZofZj+WsEEiqkTtRuE2xQ==", "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + "node": "^20.9.0 || >=22.0.0", + "npm": ">=10.8.2" }, "funding": { - "url": "https://opencollective.com/pkgr" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.22.0" + "url": "https://github.com/phun-ky/typeof?sponsor=1" } }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true, - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", - "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://opencollective.com/pkgr" } }, "node_modules/@release-it/bumper": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@release-it/bumper/-/bumper-6.0.1.tgz", - "integrity": "sha512-yeQsbGNMzzN0c/5JV1awXP6UHX/kJamXCKR6/daS0YQfj98SZXAcLn3JEq+qfK/Jq/cnATnlz5r6UY0cfBkm1A==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@release-it/bumper/-/bumper-7.0.5.tgz", + "integrity": "sha512-HCFMqDHreLYg4jjTWL//pW1GzZZMn3p7HDbwS2y7y5m0L6p8hEaOEixC3tEzwyVV7VP1VGjqxMvxfa360q8+Tg==", "dev": true, + "license": "MIT", "dependencies": { - "@iarna/toml": "^2.2.5", + "@iarna/toml": "^3.0.0", + "cheerio": "^1.0.0", "detect-indent": "7.0.1", - "fast-glob": "^3.3.2", - "ini": "^4.1.1", + "fast-glob": "^3.3.3", + "ini": "^5.0.0", "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", - "semver": "^7.3.7" + "semver": "^7.7.1" }, "engines": { - "node": ">=18" + "node": "^20.9.0 || >=22.0.0" }, "peerDependencies": { - "release-it": "^17.0.0" + "release-it": ">=18.0.0 || >=19.0.0" } }, "node_modules/@release-it/bumper/node_modules/argparse": { @@ -3288,22 +3285,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@release-it/bumper/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@release-it/conventional-changelog": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@release-it/conventional-changelog/-/conventional-changelog-8.0.1.tgz", - "integrity": "sha512-pwc9jaBYDaSX5TXw6rEnPfqDkKJN2sFBhYpON1kBi9T3sA9EOBncC4ed0Bv3L1ciNb6eqEJXPfp+tQMqVlv/eg==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@release-it/conventional-changelog/-/conventional-changelog-11.0.0.tgz", + "integrity": "sha512-3/WyzObY+y+EejBt+J2vcojFPLUiGu14liRiaPWc0bNVluhRIsADeJ785Iq5ynnhdd1zcjMNh5lBmM/J6/KXig==", "dev": true, + "license": "MIT", "dependencies": { + "@conventional-changelog/git-client": "^2.7.0", "concat-stream": "^2.0.0", - "conventional-changelog": "^5.1.0", - "conventional-recommended-bump": "^9.0.0", - "semver": "^7.5.4" + "conventional-changelog": "^7.2.0", + "conventional-changelog-angular": "^8.3.1", + "conventional-changelog-conventionalcommits": "^9.3.1", + "conventional-recommended-bump": "^11.2.0", + "semver": "^7.7.4" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { - "release-it": "^17.0.0" + "release-it": "^18.0.0 || ^19.0.0 || ^20.0.0" + } + }, + "node_modules/@release-it/conventional-changelog/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@scarf/scarf": { @@ -3313,6 +3340,48 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@simple-libs/child-process-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", + "integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/hosted-git-info": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@simple-libs/hosted-git-info/-/hosted-git-info-1.0.2.tgz", + "integrity": "sha512-aAmGQdMH+ZinytKuA2832u0ATeOFNYNk4meBEXtB5xaPotUgggYNhq5tYU/v17wEbmTW5P9iHNqNrFyrhnqBAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", + "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3331,19 +3400,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -3728,13 +3784,6 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -4034,7 +4083,15 @@ "version": "2.4.4", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-path": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", + "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/passport": { "version": "1.0.16", @@ -4512,19 +4569,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", @@ -4843,16 +4887,10 @@ "node": ">=0.4.0" } }, - "node_modules/add-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", - "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", - "dev": true - }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-8.0.0.tgz", + "integrity": "sha512-QT8i0hCz6C/KQ+KTAbSNwCHDGdmUJl2tp2ZpNlGSWCfhUNVbYG2WLE3MdZGBAgXPV4GAvjGMxo+C1hroyxmZEg==", "dev": true, "license": "MIT", "engines": { @@ -4928,16 +4966,6 @@ "ajv": "^6.9.1" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5092,7 +5120,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/array-timsort": { "version": "1.0.3", @@ -5135,17 +5164,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/atomically": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", - "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-fs": "^2.0.0", - "when-exit": "^2.1.4" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -5380,9 +5398,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", - "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "dev": true, "license": "MIT", "engines": { @@ -5408,9 +5426,9 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node_modules/before-after-hook": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", - "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", "dev": true, "license": "Apache-2.0" }, @@ -5593,159 +5611,33 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, - "license": "MIT", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "fill-range": "^7.1.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/boxen/node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/boxen/node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" + "engines": { + "node": ">=8" } }, "node_modules/browserslist": { @@ -5852,6 +5744,7 @@ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, + "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" }, @@ -5882,6 +5775,78 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz", + "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^17.2.3", + "exsolve": "^1.0.8", + "giget": "^2.0.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.0.0", + "pkg-types": "^2.3.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/cache-manager": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-6.4.3.tgz", @@ -6035,12 +6000,56 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -6074,6 +6083,16 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", @@ -6095,19 +6114,6 @@ "validator": "^13.9.0" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -6321,23 +6327,12 @@ "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, + "license": "MIT", "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, - "node_modules/compare-func/node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -6367,42 +6362,12 @@ "typedarray": "^0.0.6" } }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/configstore": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", - "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "atomically": "^2.0.3", - "dot-prop": "^9.0.0", - "graceful-fs": "^4.2.11", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", @@ -6440,212 +6405,130 @@ } }, "node_modules/conventional-changelog": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-5.1.0.tgz", - "integrity": "sha512-aWyE/P39wGYRPllcCEZDxTVEmhyLzTc9XA6z6rVfkuCD2UBnhV/sgSOKbQrEG5z9mEZJjnopjgQooTKxEg8mAg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-7.2.0.tgz", + "integrity": "sha512-BEdgG+vPl53EVlTTk9sZ96aagFp0AQ5pw/ggiQMy2SClLbTo1r0l+8dSg79gkLOO5DS1Lswuhp5fWn6RwE+ivg==", "dev": true, + "license": "MIT", "dependencies": { - "conventional-changelog-angular": "^7.0.0", - "conventional-changelog-atom": "^4.0.0", - "conventional-changelog-codemirror": "^4.0.0", - "conventional-changelog-conventionalcommits": "^7.0.2", - "conventional-changelog-core": "^7.0.0", - "conventional-changelog-ember": "^4.0.0", - "conventional-changelog-eslint": "^5.0.0", - "conventional-changelog-express": "^4.0.0", - "conventional-changelog-jquery": "^5.0.0", - "conventional-changelog-jshint": "^4.0.0", - "conventional-changelog-preset-loader": "^4.1.0" + "@conventional-changelog/git-client": "^2.6.0", + "@simple-libs/hosted-git-info": "^1.0.2", + "@types/normalize-package-data": "^2.4.4", + "conventional-changelog-preset-loader": "^5.0.0", + "conventional-changelog-writer": "^8.3.0", + "conventional-commits-parser": "^6.3.0", + "fd-package-json": "^2.0.0", + "meow": "^13.0.0", + "normalize-package-data": "^7.0.0" + }, + "bin": { + "conventional-changelog": "dist/cli/index.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/conventional-changelog-angular": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", - "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.1.tgz", + "integrity": "sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==", "dev": true, + "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-atom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-4.0.0.tgz", - "integrity": "sha512-q2YtiN7rnT1TGwPTwjjBSIPIzDJCRE+XAUahWxnh+buKK99Kks4WLMHoexw38GXx9OUxAsrp44f9qXe5VEMYhw==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-codemirror": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-4.0.0.tgz", - "integrity": "sha512-hQSojc/5imn1GJK3A75m9hEZZhc3urojA5gMpnar4JHmgLnuM3CUIARPpEk86glEKr3c54Po3WV/vCaO/U8g3Q==", - "dev": true, - "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/conventional-changelog-conventionalcommits": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", - "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz", + "integrity": "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==", "dev": true, + "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/conventional-changelog-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-7.0.0.tgz", - "integrity": "sha512-UYgaB1F/COt7VFjlYKVE/9tTzfU3VUq47r6iWf6lM5T7TlOxr0thI63ojQueRLIpVbrtHK4Ffw+yQGduw2Bhdg==", + "node_modules/conventional-changelog-preset-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz", + "integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==", "dev": true, - "dependencies": { - "@hutson/parse-repository-url": "^5.0.0", - "add-stream": "^1.0.0", - "conventional-changelog-writer": "^7.0.0", - "conventional-commits-parser": "^5.0.0", - "git-raw-commits": "^4.0.0", - "git-semver-tags": "^7.0.0", - "hosted-git-info": "^7.0.0", - "normalize-package-data": "^6.0.0", - "read-pkg": "^8.0.0", - "read-pkg-up": "^10.0.0" - }, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/conventional-changelog-ember": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-4.0.0.tgz", - "integrity": "sha512-D0IMhwcJUg1Y8FSry6XAplEJcljkHVlvAZddhhsdbL1rbsqRsMfGx/PIkPYq0ru5aDgn+OxhQ5N5yR7P9mfsvA==", + "node_modules/conventional-changelog-writer": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz", + "integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==", "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^1.2.0", + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/conventional-changelog-eslint": { + "node_modules/conventional-commits-filter": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-5.0.0.tgz", - "integrity": "sha512-6JtLWqAQIeJLn/OzUlYmzd9fKeNSWmQVim9kql+v4GrZwLx807kAJl3IJVc3jTYfVKWLxhC3BGUxYiuVEcVjgA==", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/conventional-changelog-express": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-4.0.0.tgz", - "integrity": "sha512-yWyy5c7raP9v7aTvPAWzqrztACNO9+FEI1FSYh7UP7YT1AkWgv5UspUeB5v3Ibv4/o60zj2o9GF2tqKQ99lIsw==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-jquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-5.0.0.tgz", - "integrity": "sha512-slLjlXLRNa/icMI3+uGLQbtrgEny3RgITeCxevJB+p05ExiTgHACP5p3XiMKzjBn80n+Rzr83XMYfRInEtCPPw==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-jshint": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-4.0.0.tgz", - "integrity": "sha512-LyXq1bbl0yG0Ai1SbLxIk8ZxUOe3AjnlwE6sVRQmMgetBk+4gY9EO3d00zlEt8Y8gwsITytDnPORl8al7InTjg==", - "dev": true, - "dependencies": { - "compare-func": "^2.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-preset-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-4.1.0.tgz", - "integrity": "sha512-HozQjJicZTuRhCRTq4rZbefaiCzRM2pr6u2NL3XhrmQm4RMnDXfESU6JKu/pnKwx5xtdkYfNCsbhN5exhiKGJA==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-writer": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-7.0.1.tgz", - "integrity": "sha512-Uo+R9neH3r/foIvQ0MKcsXkX642hdm9odUp7TqgFS7BsalTcjzRlIfWZrZR1gbxOozKucaKt5KAbjW8J8xRSmA==", - "dev": true, - "dependencies": { - "conventional-commits-filter": "^4.0.0", - "handlebars": "^4.7.7", - "json-stringify-safe": "^5.0.1", - "meow": "^12.0.1", - "semver": "^7.5.2", - "split2": "^4.0.0" - }, - "bin": { - "conventional-changelog-writer": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-commits-filter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-4.0.0.tgz", - "integrity": "sha512-rnpnibcSOdFcdclpFwWa+pPlZJhXE7l+XK04zxhbWrhgpR96h33QLz8hITTXbcYICxVr3HZFtbtUAQ+4LdBo9A==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-commits-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", - "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "node_modules/conventional-commits-parser": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.4.0.tgz", + "integrity": "sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==", "dev": true, + "license": "MIT", "dependencies": { - "is-text-path": "^2.0.0", - "JSONStream": "^1.3.5", - "meow": "^12.0.1", - "split2": "^4.0.0" + "@simple-libs/stream-utils": "^1.2.0", + "meow": "^13.0.0" }, "bin": { - "conventional-commits-parser": "cli.mjs" + "conventional-commits-parser": "dist/cli/index.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/conventional-recommended-bump": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-9.0.0.tgz", - "integrity": "sha512-HR1yD0G5HgYAu6K0wJjLd7QGRK8MQDqqj6Tn1n/ja1dFwBCE6QmV+iSgQ5F7hkx7OUR/8bHpxJqYtXj2f/opPQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-11.2.0.tgz", + "integrity": "sha512-lqIdmw330QdMBgfL0e6+6q5OMKyIpy4OZNmepit6FS3GldhkG+70drZjuZ0A5NFpze5j85dlYs3GabQXl6sMHw==", "dev": true, + "license": "MIT", "dependencies": { - "conventional-changelog-preset-loader": "^4.1.0", - "conventional-commits-filter": "^4.0.0", - "conventional-commits-parser": "^5.0.0", - "git-raw-commits": "^4.0.0", - "git-semver-tags": "^7.0.0", - "meow": "^12.0.1" + "@conventional-changelog/git-client": "^2.5.1", + "conventional-changelog-preset-loader": "^5.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.1.0", + "meow": "^13.0.0" }, "bin": { - "conventional-recommended-bump": "cli.mjs" + "conventional-recommended-bump": "dist/cli/index.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/convert-source-map": { @@ -6916,22 +6799,40 @@ "node": ">= 8" } }, - "node_modules/dargs": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">= 6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/fb55" } }, "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-7.0.0.tgz", + "integrity": "sha512-CuRUx0TXGSbbWdEci3VK/XOZGP3n0P4pIKpsqpVtBqaIIuj3GKK8H45oAqA4Rg8FHipc+CzRdUzmD4YQXxv66Q==", "dev": true, "license": "MIT", "engines": { @@ -6995,16 +6896,6 @@ } } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -7021,10 +6912,11 @@ } }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, + "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" @@ -7037,10 +6929,11 @@ } }, "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -7074,6 +6967,7 @@ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -7081,10 +6975,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-6.0.0.tgz", + "integrity": "sha512-j5MdXdefrecJeSqTpUrgZd4fBsD2IxZx0JlJD+n1Q7+aTf7/HcyXSfHsicPW6ekPurX159v1ZYla6OJgSPh2Dw==", "dev": true, "license": "MIT", "dependencies": { @@ -7094,6 +6995,9 @@ }, "engines": { "node": ">= 14" + }, + "peerDependencies": { + "quickjs-wasi": "^0.0.1" } }, "node_modules/delayed-stream": { @@ -7119,12 +7023,12 @@ "node": ">= 0.8" } }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "dev": true, - "license": "ISC" + "license": "MIT" }, "node_modules/detect-indent": { "version": "7.0.1", @@ -7171,20 +7075,76 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^4.18.2" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" }, "engines": { - "node": ">=18" + "node": ">= 4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/dotenv": { @@ -7260,6 +7220,20 @@ "node": ">= 0.8" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -7389,13 +7363,17 @@ "node": ">=10.13.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/error-ex": { @@ -7453,19 +7431,6 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -7801,6 +7766,19 @@ "node": ">=0.10.0" } }, + "node_modules/eta": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/eta/-/eta-4.5.1.tgz", + "integrity": "sha512-EaNCGm+8XEIU7YNcc+THptWAO5NfKBHHARxt+wxZljj9bTr/+arRoOm9/MpGt4n6xn9fLnPFRSoLD0WFYGFUxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/bgub/eta?sponsor=1" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -7990,6 +7968,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, "node_modules/ext-list": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", @@ -8015,20 +8000,22 @@ "node": ">=4" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -8044,16 +8031,17 @@ "license": "Apache-2.0" }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -8076,6 +8064,23 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -8093,6 +8098,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -8111,6 +8126,16 @@ "bser": "2.1.1" } }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -8189,10 +8214,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -8601,9 +8627,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", "dev": true, "license": "MIT", "engines": { @@ -8669,70 +8695,57 @@ } }, "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-7.0.0.tgz", + "integrity": "sha512-ZsC7KQxm1Hra8yO0RvMZ4lGJT7vnBtSNpEHKq39MPN7vjuvCiu1aQ8rkXUaIXG1y/TSDez97Gmv04ibnYqCp/A==", "dev": true, "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", + "data-uri-to-buffer": "7.0.0", "debug": "^4.3.4" }, "engines": { "node": ">= 14" } }, - "node_modules/git-raw-commits": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "dev": true, - "dependencies": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" - }, - "bin": { - "git-raw-commits": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/git-semver-tags": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-7.0.1.tgz", - "integrity": "sha512-NY0ZHjJzyyNXHTDZmj+GG7PyuAKtMsyWSwh07CR2hOZFa+/yoTsXci/nF2obzL8UDhakFNkD9gNdt/Ed+cxh2Q==", + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", "dev": true, + "license": "MIT", "dependencies": { - "meow": "^12.0.1", - "semver": "^7.5.2" + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" }, "bin": { - "git-semver-tags": "cli.mjs" - }, - "engines": { - "node": ">=16" + "giget": "dist/cli.mjs" } }, "node_modules/git-up": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", - "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", + "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", "dev": true, + "license": "MIT", "dependencies": { "is-ssh": "^1.4.0", - "parse-url": "^8.1.0" + "parse-url": "^9.2.0" } }, "node_modules/git-url-parse": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-14.0.0.tgz", - "integrity": "sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==", + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", + "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", "dev": true, + "license": "MIT", "dependencies": { - "git-up": "^7.0.0" + "git-up": "^8.1.0" } }, "node_modules/glob": { @@ -8770,79 +8783,16 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, "engines": { - "node": ">=18" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/got": { @@ -8877,10 +8827,11 @@ "dev": true }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -8945,25 +8896,24 @@ } }, "node_modules/hosted-git-info": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", - "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", + "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^10.0.1" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/html-escaper": { "version": "2.0.2", @@ -8971,6 +8921,39 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -8998,13 +8981,13 @@ } }, "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-8.0.0.tgz", + "integrity": "sha512-7pose0uGgrCJeH2Qh4JcNhWZp3u/oNrWjNYDK4ydOLxOpTw8V8ogHFAmkz0VWq96JBFj4umVJpvmQi287rSYLg==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", + "agent-base": "8.0.0", "debug": "^4.3.4" }, "engines": { @@ -9025,14 +9008,14 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-8.0.0.tgz", + "integrity": "sha512-YYeW+iCnAS3xhvj2dvVoWgsbca3RfQy/IlaNHHOtDmU0jMqPI9euIq3Y9BJETdxk16h9NHHCKqp/KB9nIMStCQ==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "agent-base": "8.0.0", + "debug": "^4.3.4" }, "engines": { "node": ">= 14" @@ -9048,13 +9031,13 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -9147,56 +9130,13 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/inquirer": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.2.tgz", - "integrity": "sha512-+ynEbhWKhyomnaX0n2aLIMSkgSlGB5RrWbNXnEqj6mdaIydu6y40MdBjL38SAB0JcdmOaIaMua1azdjLEr3sdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/figures": "^1.0.3", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/inquirer/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/ip-address": { @@ -9241,6 +9181,7 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -9289,17 +9230,14 @@ "node": ">=0.10.0" } }, - "node_modules/is-in-ci": { + "node_modules/is-in-ssh": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", "dev": true, "license": "MIT", - "bin": { - "is-in-ci": "cli.js" - }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9310,6 +9248,7 @@ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, @@ -9323,23 +9262,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-installed-globally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", - "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-directory": "^4.0.1", - "is-path-inside": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -9349,24 +9271,12 @@ "node": ">=8" } }, - "node_modules/is-npm": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", - "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9376,21 +9286,9 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/is-plain-obj": { @@ -9409,10 +9307,11 @@ "license": "MIT" }, "node_modules/is-ssh": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", - "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", "dev": true, + "license": "MIT", "dependencies": { "protocols": "^2.0.1" } @@ -9426,18 +9325,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-text-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", - "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", - "dev": true, - "dependencies": { - "text-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -9451,10 +9338,11 @@ } }, "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, + "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, @@ -11278,6 +11166,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11335,11 +11233,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", @@ -11372,31 +11271,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -11467,35 +11341,6 @@ "node": ">=6" } }, - "node_modules/ky": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", - "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, - "node_modules/latest-version": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "package-json": "^10.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lcov-result-merger": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/lcov-result-merger/-/lcov-result-merger-5.0.1.tgz", @@ -11751,6 +11596,13 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -11868,6 +11720,19 @@ "yallist": "^2.1.2" } }, + "node_modules/macos-release": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-3.4.0.tgz", + "integrity": "sha512-wpGPwyg/xrSp4H4Db4xYSeAr6+cFQGHfspHzDUdYxswDnUW0L5Ov63UuJiSr8NMSpyaChO4u1n0MXUvVPtrN6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -11946,12 +11811,13 @@ "license": "MIT" }, "node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16.10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11994,12 +11860,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -12323,9 +12190,9 @@ "dev": true }, "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", "dev": true, "license": "MIT", "engines": { @@ -12416,6 +12283,13 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "dev": true, + "license": "MIT" + }, "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -12475,18 +12349,18 @@ } }, "node_modules/normalize-package-data": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", - "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", + "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", + "hosted-git-info": "^8.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-path": { @@ -12542,6 +12416,44 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nypm": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.6.tgz", + "integrity": "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.1.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -12570,6 +12482,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -12606,18 +12525,21 @@ } }, "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "dev": true, + "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12752,14 +12674,21 @@ "node": ">=4" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/os-name": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-7.0.0.tgz", + "integrity": "sha512-/HfRU/lPPr4T2VigM+cvM3cU77es+XF4OEAa4aE5zpdvrxHGD2NmH0AFIWpMNAb+CsZL45rlcIO49Re0ZcRseg==", "dev": true, "license": "MIT", + "dependencies": { + "macos-release": "^3.4.0", + "windows-release": "^7.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-cancelable": { @@ -12820,56 +12749,40 @@ } }, "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-8.0.0.tgz", + "integrity": "sha512-HyCoVbyQ/nbVlQ/R6wBu0YXhbG2oAnEK5BQ3xMyj1OffQmU5NoOnpLzgPlKHaobUzz5NK0+AZHby4TdydAEBUA==", "dev": true, "license": "MIT", "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", + "agent-base": "8.0.0", "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" + "get-uri": "7.0.0", + "http-proxy-agent": "8.0.0", + "https-proxy-agent": "8.0.0", + "pac-resolver": "8.0.0", + "quickjs-wasi": "^0.0.1", + "socks-proxy-agent": "9.0.0" }, "engines": { "node": ">= 14" } }, "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-8.0.0.tgz", + "integrity": "sha512-SVNzOxVq2zuTew3WAt7U8UghwzJzuWYuJryd3y8FxyLTZdjVoCzY8kLP39PpEqQCDvlMWdQXwViu0sYT3eiU2w==", "dev": true, "license": "MIT", "dependencies": { - "degenerator": "^5.0.0", + "degenerator": "6.0.0", "netmask": "^2.0.2" }, "engines": { "node": ">= 14" - } - }, - "node_modules/package-json": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ky": "^1.2.0", - "registry-auth-token": "^5.0.2", - "registry-url": "^6.0.1", - "semver": "^7.6.0" }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "quickjs-wasi": "^0.0.1" } }, "node_modules/parent-module": { @@ -12903,21 +12816,80 @@ } }, "node_modules/parse-path": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", - "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", + "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", "dev": true, + "license": "MIT", "dependencies": { "protocols": "^2.0.0" } }, "node_modules/parse-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", - "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", + "integrity": "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==", "dev": true, + "license": "MIT", "dependencies": { + "@types/parse-path": "^7.0.0", "parse-path": "^7.0.0" + }, + "engines": { + "node": ">=14.13.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/parseurl": { @@ -13052,6 +13024,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", @@ -13070,6 +13049,13 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -13181,6 +13167,18 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -13191,6 +13189,19 @@ "node": ">=4" } }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13267,18 +13278,12 @@ "node": ">= 6" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "dev": true, - "license": "ISC" - }, "node_modules/protocols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", - "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", + "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", + "dev": true, + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -13294,20 +13299,20 @@ } }, "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-7.0.0.tgz", + "integrity": "sha512-okTgt79rHTvMHkr/Ney5rZpgCHh3g1g3tI5uhkgN5b7OeI3n0Q/ui1uv9OdrnZNJM9WIZJqZPh/UJs+YtO/TMQ==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "8.0.0", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", + "http-proxy-agent": "8.0.0", + "https-proxy-agent": "8.0.0", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", + "pac-proxy-agent": "8.0.0", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" + "socks-proxy-agent": "9.0.0" }, "engines": { "node": ">= 14" @@ -13354,22 +13359,6 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", - "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -13433,6 +13422,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/quickjs-wasi": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-0.0.1.tgz", + "integrity": "sha512-fBWNLTBkxkLAhe1AzF1hyXEvuA+N+vV1WMP2D6iiMUblvmOt8Pp5t8zUcgvz7aYA1ldUdxDlgUse15dmcKjkNg==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -13473,37 +13469,15 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" } }, "node_modules/react-is": { @@ -13512,295 +13486,431 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, - "node_modules/read-pkg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", - "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", - "dev": true, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^6.0.0", - "parse-json": "^7.0.0", - "type-fest": "^4.2.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/read-pkg-up": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", - "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", "dev": true, "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^8.1.0", - "type-fest": "^4.2.0" + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=16" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" + }, + "node_modules/release-it": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/release-it/-/release-it-20.0.1.tgz", + "integrity": "sha512-3ob1P1aV+3+ZOoR7qgobfYyMlQbpitzOK09iKTtQ145vFi4rWxlRTgHwtVl8kokCvqiF/cJPxRlfcmZmF5aDJA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/webpro" + } + ], + "license": "MIT", "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" + "@inquirer/prompts": "8.4.2", + "@octokit/rest": "22.0.1", + "@phun-ky/typeof": "2.0.3", + "async-retry": "1.3.3", + "c12": "3.3.3", + "ci-info": "^4.4.0", + "defu": "^6.1.7", + "eta": "4.5.1", + "git-url-parse": "16.1.0", + "issue-parser": "7.0.1", + "lodash.merge": "4.6.2", + "mime-types": "3.0.2", + "new-github-release-url": "2.0.0", + "open": "11.0.0", + "ora": "9.3.0", + "os-name": "7.0.0", + "proxy-agent": "7.0.0", + "semver": "7.7.4", + "tinyglobby": "0.2.15", + "undici": "7.24.5", + "url-join": "5.0.0", + "wildcard-match": "5.1.4", + "yargs-parser": "22.0.0" }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "bin": { + "release-it": "bin/release-it.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/release-it/node_modules/@inquirer/ansi": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.5.tgz", + "integrity": "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==", "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/release-it/node_modules/@inquirer/checkbox": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.5.tgz", + "integrity": "sha512-Jmf9tgBHIEK5SAOB7swYfStqmtkZb00xOTpSQmkoGEpdxOTpJi9RS0A8bkfDPHTTItZRJrRdZrEMu25wyj0VfQ==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.10", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "node_modules/release-it/node_modules/@inquirer/confirm": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.13.tgz", + "integrity": "sha512-wkGPC7yJ5WJk1DJ5SX7fzk+gfj4BM8cf5dDDi71B/551xHrdsZVRJOC0WyikXd0pEsb/9cLniuE4atbsMqmFkw==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^4.0.0" + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "node_modules/release-it/node_modules/@inquirer/core": { + "version": "11.1.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.10.tgz", + "integrity": "sha512-a4Q5BXHQAHa9eO202sTaFCHFYVB3x5fauDuThEAdZ9gfn76pSxiKU7wWcEH0N1O0XmQvNfQNU6QXpiRxmYQx+A==", "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.5", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg-up/node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "node_modules/release-it/node_modules/@inquirer/editor": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.1.2.tgz", + "integrity": "sha512-Y3Nor7S/DhIPo+8Ym/dSY4efwKI4BsflKDwXh0jNeXJsSF3dteS/3Yf+z4wkibVZDvYMyCgknSTQlNahfunGHg==", "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.10", + "@inquirer/external-editor": "^3.0.0", + "@inquirer/type": "^4.0.5" + }, "engines": { - "node": ">=12.20" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", - "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "node_modules/release-it/node_modules/@inquirer/expand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.14.tgz", + "integrity": "sha512-qyY9zcIX2eKYwaAUiQo9zORd61Lc3sXeM72fVbeHkYnDkqfr8/armcRbmVAIrExeJhI2puk+uomeKtWrpUVUmQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/read-pkg/node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg/node_modules/parse-json": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", - "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "node_modules/release-it/node_modules/@inquirer/external-editor": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.0.tgz", + "integrity": "sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.21.4", - "error-ex": "^1.3.2", - "json-parse-even-better-errors": "^3.0.0", - "lines-and-columns": "^2.0.3", - "type-fest": "^3.8.0" + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" }, "engines": { - "node": ">=16" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/read-pkg/node_modules/parse-json/node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "node_modules/release-it/node_modules/@inquirer/figures": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.5.tgz", + "integrity": "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/release-it/node_modules/@inquirer/input": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.13.tgz", + "integrity": "sha512-0l0jCHlJnXIV8CTxwQC0C+5Ziq8WP22edWgmciW2xYvoeoSck4v5FvCS1ctKdqLLR0dUo93uAHgWHywgBSoRyw==", + "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": ">= 6" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "node_modules/release-it/node_modules/@inquirer/number": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.13.tgz", + "integrity": "sha512-WHmkYnnJAou5gx7RgcvAfUggnHNM1zWfoh0dFPl3dxVssuqt+dK5rIbaOYQXNyOegvFnopbKupjnhw2O8gANNg==", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "^3.6.0" + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": ">=8" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "node_modules/release-it/node_modules/@inquirer/password": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.13.tgz", + "integrity": "sha512-XDGu64ROHZjOOXLAANvJN7iIxWKhOSCG5VakrZ5kaScVR+snVJCFglD/hL3/677awtWcu4pXoWa280CDIYcBeg==", "dev": true, + "license": "MIT", "dependencies": { - "resolve": "^1.1.6" + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": ">= 0.10" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/reflect-metadata": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", - "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" - }, - "node_modules/registry-auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", - "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "node_modules/release-it/node_modules/@inquirer/prompts": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.4.2.tgz", + "integrity": "sha512-XJmn/wY4AX56l1BRU+ZjDrFtg9+2uBEi4JvJQj82kwJDQKiPgSn4CEsbfGGygS4Gw6rkL4W18oATjfVfaqub2Q==", "dev": true, "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^3.0.2" + "@inquirer/checkbox": "^5.1.4", + "@inquirer/confirm": "^6.0.12", + "@inquirer/editor": "^5.1.1", + "@inquirer/expand": "^5.0.13", + "@inquirer/input": "^5.0.12", + "@inquirer/number": "^4.0.12", + "@inquirer/password": "^5.0.12", + "@inquirer/rawlist": "^5.2.8", + "@inquirer/search": "^4.1.8", + "@inquirer/select": "^5.1.4" }, "engines": { - "node": ">=14" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "node_modules/release-it/node_modules/@inquirer/rawlist": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.9.tgz", + "integrity": "sha512-a1ErXEfgjfPYpyQ89dp+7n2IISjH9oQg3ygvF5adz8B7aHn4n2PjEgu1wpVTp69K3bj3lVLxP0qJ2b1clk1Whw==", "dev": true, "license": "MIT", "dependencies": { - "rc": "1.2.8" + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "engines": { - "node": ">=12" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/release-it": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/release-it/-/release-it-17.11.0.tgz", - "integrity": "sha512-qQGgfMbUZ3/vpXUPmngsgjFObOLjlkwtiozHUYen9fo9AEGciXjG1ZpGr+FNmuBT8R7TOSY+x/s84wOCRKJjbA==", + "node_modules/release-it/node_modules/@inquirer/search": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.9.tgz", + "integrity": "sha512-ZlbM28Q9lmLkFPNAIv+ZuY530n5Km8U1WW48oYEvDhe9yc2uL3m3t+JSdRUkQlk5fuIuskgiIVjcb7czFzQpuA==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/webpro" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/webpro" + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.10", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true } - ], + } + }, + "node_modules/release-it/node_modules/@inquirer/select": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.5.tgz", + "integrity": "sha512-6SRg6kHfK/sjLXOsuqNebuir+sjwrf/iWuRUnXgB2slzEewppI1WfzeS16XxDcOQmXBruMmmB9Cgrz7wsAxqMg==", + "dev": true, "license": "MIT", "dependencies": { - "@iarna/toml": "2.2.5", - "@octokit/rest": "20.1.1", - "async-retry": "1.3.3", - "chalk": "5.4.1", - "ci-info": "^4.1.0", - "cosmiconfig": "9.0.0", - "execa": "8.0.0", - "git-url-parse": "14.0.0", - "globby": "14.0.2", - "inquirer": "9.3.2", - "issue-parser": "7.0.1", - "lodash": "4.17.21", - "mime-types": "2.1.35", - "new-github-release-url": "2.0.0", - "open": "10.1.0", - "ora": "8.1.1", - "os-name": "5.1.0", - "proxy-agent": "6.5.0", - "semver": "7.6.3", - "shelljs": "0.8.5", - "update-notifier": "7.3.1", - "url-join": "5.0.0", - "wildcard-match": "5.1.4", - "yargs-parser": "21.1.1" + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.10", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" }, - "bin": { - "release-it": "bin/release-it.js" + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/release-it/node_modules/@inquirer/type": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", + "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || ^22.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/release-it/node_modules/ansi-regex": { @@ -13816,16 +13926,10 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/release-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/release-it/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -13867,84 +13971,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "node_modules/release-it/node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", "dev": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18.20" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/release-it/node_modules/execa": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.0.tgz", - "integrity": "sha512-CTNS0BcKBcoOsawKBlpcKNmK4Kjuyz5jVLhf+PUsHGMqiKMVTa4cN3U7r7bRY8KTpfOGpXMo27fdy0dYVg2pqA==", + "node_modules/release-it/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/release-it/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/release-it/node_modules/is-interactive": { @@ -13960,19 +14014,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/release-it/node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -13986,34 +14027,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/release-it/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, "node_modules/release-it/node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" }, "engines": { "node": ">=18" @@ -14022,124 +14044,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "node_modules/release-it/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/macos-release": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-3.2.0.tgz", - "integrity": "sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/release-it/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/release-it/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/release-it/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/release-it/node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, + "license": "ISC", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/release-it/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/release-it/node_modules/ora": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", - "integrity": "sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", + "chalk": "^5.6.2", "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", + "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/os-name": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-5.1.0.tgz", - "integrity": "sha512-YEIoAnM6zFmzw3PQ201gCVCIWbXNyKObGlVvpAVvraAeOHnlYVKFssbA/riRX5R40WA6kKrZ7Dr7dWzO3nKSeQ==", - "dev": true, - "dependencies": { - "macos-release": "^3.1.0", - "windows-release": "^5.0.1" + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.1", + "string-width": "^8.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -14162,211 +14137,70 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/restore-cursor/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/release-it/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/release-it/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/release-it/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/windows-release": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-5.1.1.tgz", - "integrity": "sha512-NMD00arvqcq2nwqc5Q6KtrSRHK+fVD31erE5FEMahAw5PmVCgD7MUXodq3pdZSUkqA9Cda2iWx6s1XYwiJWRmw==", - "dev": true, - "dependencies": { - "execa": "^5.1.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/windows-release/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/release-it/node_modules/windows-release/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/windows-release/node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/release-it/node_modules/windows-release/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/release-it/node_modules/windows-release/node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/release-it/node_modules/windows-release/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/release-it/node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/release-it/node_modules/windows-release/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "node_modules/release-it/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/release-it/node_modules/windows-release/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/release-it/node_modules/undici": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz", + "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.18.1" } }, - "node_modules/release-it/node_modules/windows-release/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/release-it/node_modules/windows-release/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "node_modules/release-it/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=6" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/require-directory": { @@ -14534,10 +14368,11 @@ "license": "MIT" }, "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -14545,16 +14380,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -14630,9 +14455,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -14793,23 +14618,6 @@ "node": ">=0.10.0" } }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -15077,13 +14885,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-9.0.0.tgz", + "integrity": "sha512-fFlbMlfsXhK02ZB8aZY7Hwxh/IHBV9b1Oq9bvBk6tkFWXvdAxUgA0wbw/NYR5liU3Y5+KI6U4FH3kYJt9QYv0w==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "8.0.0", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -15148,6 +14956,7 @@ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -15157,32 +14966,26 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true + "dev": true, + "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, + "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", - "dev": true - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", "dev": true, - "engines": { - "node": ">= 10.x" - } + "license": "CC0-1.0" }, "node_modules/sprintf-js": { "version": "1.0.3", @@ -15221,9 +15024,9 @@ } }, "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", "dev": true, "license": "MIT", "engines": { @@ -15359,23 +15162,6 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/stubborn-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", - "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "stubborn-utils": "^1.0.1" - } - }, - "node_modules/stubborn-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", - "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", - "dev": true, - "license": "MIT" - }, "node_modules/superagent": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", @@ -15718,24 +15504,16 @@ "node": ">=8" } }, - "node_modules/text-extensions": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", - "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -15766,19 +15544,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -15799,6 +15564,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -16217,19 +15983,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -16289,10 +16042,11 @@ } }, "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -16324,28 +16078,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=20.18.1" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/universal-user-agent": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", - "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", "dev": true, "license": "ISC" }, @@ -16398,44 +16149,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", - "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^8.0.1", - "chalk": "^5.3.0", - "configstore": "^7.0.0", - "is-in-ci": "^1.0.0", - "is-installed-globally": "^1.0.0", - "is-npm": "^6.0.0", - "latest-version": "^9.0.0", - "pupa": "^3.1.0", - "semver": "^7.6.3", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -16492,6 +16205,7 @@ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, + "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -16514,6 +16228,16 @@ "node": ">= 0.8" } }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -16722,6 +16446,30 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/whatwg-url": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", @@ -16735,13 +16483,6 @@ "node": ">=18" } }, - "node_modules/when-exit": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", - "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -16762,88 +16503,48 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/widest-line/node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "node_modules/wildcard-match": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", + "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/widest-line/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/windows-release": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-7.1.1.tgz", + "integrity": "sha512-0GBwC9WmR8Bm3WYiz3FC391054BsFHZ2gzBVdYj9uj5eIVYzbn/YPYCYW9SWdh9vwnLuzpn1UGwJKiMG4F236w==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "powershell-utils": "^0.2.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/windows-release/node_modules/powershell-utils": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz", + "integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wildcard-match": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", - "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==", - "dev": true, - "license": "ISC" - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "6.2.0", @@ -16930,14 +16631,18 @@ } } }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "dev": true, "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -17029,6 +16734,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", diff --git a/package.json b/package.json index ea36ec0..18e6ccd 100644 --- a/package.json +++ b/package.json @@ -71,8 +71,8 @@ "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", "@nestjs/testing": "^11.1.16", - "@release-it/bumper": "^6.0.1", - "@release-it/conventional-changelog": "^8.0.1", + "@release-it/bumper": "^7.0.5", + "@release-it/conventional-changelog": "^11.0.0", "@swc/cli": "^0.3.9", "@swc/core": "^1.4.0", "@types/bcrypt": "^5.0.2", @@ -94,7 +94,7 @@ "jest-sonar-reporter": "^2.0.0", "lcov-result-merger": "^5.0.1", "prettier": "^3.2.5", - "release-it": "^17.11.0", + "release-it": "^20.0.1", "source-map-support": "^0.5.21", "supertest": "^6.3.4", "ts-jest": "^29.1.2", diff --git a/scripts/git-commit.cjs b/scripts/git-commit.cjs new file mode 100644 index 0000000..0ec18f0 --- /dev/null +++ b/scripts/git-commit.cjs @@ -0,0 +1,57 @@ +#!/usr/bin/env node +/** + * git-commit.cjs — Wrapper de commit fiable sur Windows bash. + * + * Problème : `git commit -m "subject\n\nbody"` sur Windows bash squash les + * sauts de ligne → commitlint rejette avec `body-leading-blank`. + * + * Solution : écrire le message dans un fichier temporaire avec des newlines + * Unix explicits, puis utiliser `git commit --file=`. + * + * Usage : + * node scripts/git-commit.cjs [--amend] + * + * Le message doit contenir une vraie ligne vide entre le sujet et le body. + * Utiliser \n dans la chaîne JS — Node.js gère les newlines correctement. + * + * Exemples (depuis npm scripts ou terminal) : + * node scripts/git-commit.cjs "feat(scope): titre\n\nBody détaillé ici." + * node scripts/git-commit.cjs --amend "fix(scope): correction\n\nBody." + */ + +'use strict'; + +const { execSync } = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const args = process.argv.slice(2); +const amend = args[0] === '--amend'; +const msgArg = amend ? args[1] : args[0]; + +if (!msgArg) { + console.error('Usage: node scripts/git-commit.cjs [--amend] ""'); + process.exit(1); +} + +// Interpréter les \n littéraux éventuellement présents dans la chaîne +const message = msgArg.replace(/\\n/g, '\n'); + +// Écrire dans un fichier temporaire avec newlines Unix +const tmpFile = path.join(os.tmpdir(), `git-commit-msg-${Date.now()}.txt`); +fs.writeFileSync(tmpFile, message.endsWith('\n') ? message : message + '\n', { encoding: 'utf8' }); + +try { + // Stage all changes (tracked + untracked) before committing + if (!amend) { + execSync('git add -A', { stdio: 'inherit' }); + } + const cmd = amend + ? `git commit --amend --file="${tmpFile}"` + : `git commit --file="${tmpFile}"`; + execSync(cmd, { stdio: 'inherit' }); +} finally { + fs.unlinkSync(tmpFile); +} + From f59e28e9a4ba08f40b6f66839bd23b3954f8a37e Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 16:05:48 +0200 Subject: [PATCH 03/14] chore(deps): pin @nestjs/core to 11.1.18 --- package-lock.json | 20 +++++++++++++++----- package.json | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 417446f..22b516a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@nestjs/cache-manager": "^3.0.1", "@nestjs/common": "^11.1.13", - "@nestjs/core": "^11.1.13", + "@nestjs/core": "11.1.18", "@nestjs/jwt": "^11.0.2", "@nestjs/mongoose": "^11.0.4", "@nestjs/passport": "^11.0.5", @@ -2649,16 +2649,16 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.13", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.13.tgz", - "integrity": "sha512-Tq9EIKiC30EBL8hLK93tNqaToy0hzbuVGYt29V8NhkVJUsDzlmiVf6c3hSPtzx2krIUVbTgQ2KFeaxr72rEyzQ==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.18.tgz", + "integrity": "sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "8.3.0", + "path-to-regexp": "8.4.2", "tslib": "2.8.1", "uid": "2.0.2" }, @@ -2689,6 +2689,16 @@ } } }, + "node_modules/@nestjs/core/node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@nestjs/jwt": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", diff --git a/package.json b/package.json index 18e6ccd..cc06327 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "dependencies": { "@nestjs/cache-manager": "^3.0.1", "@nestjs/common": "^11.1.13", - "@nestjs/core": "^11.1.13", + "@nestjs/core": "11.1.18", "@nestjs/jwt": "^11.0.2", "@nestjs/mongoose": "^11.0.4", "@nestjs/passport": "^11.0.5", From 3044a50fdbbeb018d7469ebf5221f5eae3637191 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 17:29:19 +0200 Subject: [PATCH 04/14] feat(auth): implement refreshTokenOnUpdate and operation context Add refreshTokenOnUpdate optional flag to DynamicApiUpdateAccountOptions. When true, updateAccount returns a fresh { accessToken, refreshToken } instead of the updated entity, allowing clients to refresh their JWT seamlessly after a profile change. Add DynamicApiGlobalState.refreshTokenOnUpdate propagated from useAuth.updateAccount.refreshTokenOnUpdate via buildStateFromOptions. Add AsyncLocalStorage-based auth operation context (register / login / updateAccount) exposed as getAuthOperationContext() for use in custom class-validator decorators. Update AuthService.updateAccount return type to Promise. Update AuthController and AuthGateway interfaces accordingly. Wrap login, register, and updateAccount service calls with authOperationStorage.run() in both HTTP controller and WebSocket gateway mixins. --- libs/dynamic-api/src/dynamic-api.module.ts | 1 + .../dynamic-api-global-state.interface.ts | 1 + .../modules/auth/auth-operation-context.ts | 27 ++++++++++++++ .../src/modules/auth/auth.helper.ts | 1 + libs/dynamic-api/src/modules/auth/index.ts | 1 + .../interfaces/auth-controller.interface.ts | 2 +- .../auth/interfaces/auth-gateway.interface.ts | 2 +- .../auth/interfaces/auth-options.interface.ts | 6 ++++ .../auth/interfaces/auth-service.interface.ts | 2 +- .../auth/mixins/auth-controller.mixin.ts | 36 ++++++++++++++++--- .../modules/auth/mixins/auth-gateway.mixin.ts | 13 ++++--- .../auth/services/base-auth.service.ts | 10 +++++- 12 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 libs/dynamic-api/src/modules/auth/auth-operation-context.ts diff --git a/libs/dynamic-api/src/dynamic-api.module.ts b/libs/dynamic-api/src/dynamic-api.module.ts index c54c5e7..e716d0e 100644 --- a/libs/dynamic-api/src/dynamic-api.module.ts +++ b/libs/dynamic-api/src/dynamic-api.module.ts @@ -287,6 +287,7 @@ export class DynamicApiModule { jwtRefreshTokenExpiresIn: useAuth.jwt?.refreshTokenExpiresIn ?? '7d', jwtRefreshSecret: useAuth.jwt?.refreshSecret, jwtRefreshUseCookie: useAuth.refreshToken?.useCookie ?? false, + refreshTokenOnUpdate: useAuth.updateAccount?.refreshTokenOnUpdate ?? false, gatewayOptions: initializeConfigFromOptions(webSocket), } : {} ), diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-global-state.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-global-state.interface.ts index 089de2e..276a0eb 100644 --- a/libs/dynamic-api/src/interfaces/dynamic-api-global-state.interface.ts +++ b/libs/dynamic-api/src/interfaces/dynamic-api-global-state.interface.ts @@ -32,6 +32,7 @@ interface DynamicApiGlobalState { jwtRefreshTokenExpiresIn: string | number | undefined; jwtRefreshSecret: string | undefined; jwtRefreshUseCookie: boolean | undefined; + refreshTokenOnUpdate: boolean; routesConfig: RoutesConfig; gatewayOptions: GatewayMetadata | undefined; broadcastGatewayOptions: GatewayMetadata | undefined; diff --git a/libs/dynamic-api/src/modules/auth/auth-operation-context.ts b/libs/dynamic-api/src/modules/auth/auth-operation-context.ts new file mode 100644 index 0000000..76353e0 --- /dev/null +++ b/libs/dynamic-api/src/modules/auth/auth-operation-context.ts @@ -0,0 +1,27 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; + +/** + * The context of the current auth operation. + * Populated automatically before each auth service call. + * Useful in custom class-validator decorators to apply operation-specific rules. + * + * @example + * import { getAuthOperationContext } from '@dynamic-api'; + * + * export function IsRequiredOnRegister(): PropertyDecorator { + * return ValidateIf(() => getAuthOperationContext() === 'register'); + * } + */ +type AuthOperationContext = 'register' | 'login' | 'updateAccount'; + +const authOperationStorage = new AsyncLocalStorage(); + +/** + * Returns the current auth operation context if called within an auth request pipeline, + * or `undefined` outside of it. + */ +const getAuthOperationContext = (): AuthOperationContext | undefined => + authOperationStorage.getStore(); + +export { AuthOperationContext, authOperationStorage, getAuthOperationContext }; + diff --git a/libs/dynamic-api/src/modules/auth/auth.helper.ts b/libs/dynamic-api/src/modules/auth/auth.helper.ts index b385bdf..f9a1aa2 100644 --- a/libs/dynamic-api/src/modules/auth/auth.helper.ts +++ b/libs/dynamic-api/src/modules/auth/auth.helper.ts @@ -114,6 +114,7 @@ function createAuthServiceProvider( protected loginField = loginField; protected passwordField = passwordField; protected refreshTokenField = refreshToken?.refreshTokenField; + protected refreshTokenOnUpdate = DynamicApiModule.state.get('refreshTokenOnUpdate') ?? false; protected beforeRegisterCallback = register?.beforeSaveCallback; protected registerCallback = register?.callback; diff --git a/libs/dynamic-api/src/modules/auth/index.ts b/libs/dynamic-api/src/modules/auth/index.ts index 97e4930..4383458 100644 --- a/libs/dynamic-api/src/modules/auth/index.ts +++ b/libs/dynamic-api/src/modules/auth/index.ts @@ -1,5 +1,6 @@ export * from './auth-broadcast.helper'; export * from './auth-events.constants'; +export * from './auth-operation-context'; export * from './guards'; export * from './interfaces'; export * from './mixins'; diff --git a/libs/dynamic-api/src/modules/auth/interfaces/auth-controller.interface.ts b/libs/dynamic-api/src/modules/auth/interfaces/auth-controller.interface.ts index c559a39..62253f5 100644 --- a/libs/dynamic-api/src/modules/auth/interfaces/auth-controller.interface.ts +++ b/libs/dynamic-api/src/modules/auth/interfaces/auth-controller.interface.ts @@ -10,7 +10,7 @@ interface AuthController { login(req: { user: Entity }, body: Body, res: Response): Promise; register(body: Body, res: Response): Promise; getAccount(req: { user: Entity; headers: Record }): Promise>; - updateAccount(req: { user: Entity; headers: Record }, body: Body): Promise; + updateAccount(req: { user: Entity; headers: Record }, body: Body, res: Response): Promise; resetPassword(body: ResetPasswordDto): Promise; changePassword(body: ChangePasswordDto): Promise; refreshToken(req: { user: Entity; headers: Record; cookies: Record }, res: Response): Promise; diff --git a/libs/dynamic-api/src/modules/auth/interfaces/auth-gateway.interface.ts b/libs/dynamic-api/src/modules/auth/interfaces/auth-gateway.interface.ts index 3d1a455..719f2be 100644 --- a/libs/dynamic-api/src/modules/auth/interfaces/auth-gateway.interface.ts +++ b/libs/dynamic-api/src/modules/auth/interfaces/auth-gateway.interface.ts @@ -9,7 +9,7 @@ interface AuthGateway { login(socket: ExtendedSocket, body: Body): GatewayResponse; register(socket: ExtendedSocket, body: Body): GatewayResponse; getAccount(socket: ExtendedSocket): GatewayResponse>; - updateAccount(socket: ExtendedSocket, body: Body): GatewayResponse>; + updateAccount(socket: ExtendedSocket, body: Body): GatewayResponse | LoginResponse>; resetPassword(body: ResetPasswordDto): GatewayResponse; changePassword(body: ChangePasswordDto): GatewayResponse; refreshToken(socket: ExtendedSocket): GatewayResponse; diff --git a/libs/dynamic-api/src/modules/auth/interfaces/auth-options.interface.ts b/libs/dynamic-api/src/modules/auth/interfaces/auth-options.interface.ts index af9becf..1800988 100644 --- a/libs/dynamic-api/src/modules/auth/interfaces/auth-options.interface.ts +++ b/libs/dynamic-api/src/modules/auth/interfaces/auth-options.interface.ts @@ -69,6 +69,12 @@ type DynamicApiUpdateAccountOptions = { additionalFieldsToExclude?: (keyof Entity)[]; useInterceptors?: Type[]; broadcast?: DynamicApiAuthBroadcastConfig; + /** + * When true, the updateAccount endpoint returns a fresh `{ accessToken, refreshToken }` + * instead of the updated entity, so the client can seamlessly refresh its JWT after + * a profile change without a separate /refresh-token call. + */ + refreshTokenOnUpdate?: boolean; }; type DynamicApiResetPasswordOptions = { diff --git a/libs/dynamic-api/src/modules/auth/interfaces/auth-service.interface.ts b/libs/dynamic-api/src/modules/auth/interfaces/auth-service.interface.ts index a9475a8..ffd62c7 100644 --- a/libs/dynamic-api/src/modules/auth/interfaces/auth-service.interface.ts +++ b/libs/dynamic-api/src/modules/auth/interfaces/auth-service.interface.ts @@ -11,7 +11,7 @@ interface AuthService { getAccount(user: Entity): Promise; - updateAccount(user: Entity, update: Partial): Promise; + updateAccount(user: Entity, update: Partial): Promise; resetPassword(email: string): Promise; diff --git a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts index 803dee7..5c319dc 100644 --- a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts +++ b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts @@ -9,6 +9,7 @@ import { EntityBodyMixin } from '../../../mixins'; import { BaseEntity } from '../../../models'; import { DynamicApiBroadcastService } from '../../../services'; import { buildAuthBroadcastData } from '../auth-broadcast.helper'; +import { authOperationStorage } from '../auth-operation-context'; import { AUTH_GET_ACCOUNT_BROADCAST_EVENT, AUTH_LOGIN_BROADCAST_EVENT, @@ -18,7 +19,7 @@ import { import { ChangePasswordDto } from '../dtos/change-password.dto'; import { ResetPasswordDto } from '../dtos/reset-password.dto'; import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard, ResetPasswordGuard } from '../guards'; -import { AuthController, AuthControllerConstructor, AuthService, DynamicApiGetAccountOptions, DynamicApiLoginOptions, DynamicApiRefreshTokenOptions, DynamicApiRegisterOptions, DynamicApiResetPasswordOptions, DynamicApiUpdateAccountOptions } from '../interfaces'; +import { AuthController, AuthControllerConstructor, AuthService, DynamicApiGetAccountOptions, DynamicApiLoginOptions, DynamicApiRefreshTokenOptions, DynamicApiRegisterOptions, DynamicApiResetPasswordOptions, DynamicApiUpdateAccountOptions, LoginResponse } from '../interfaces'; import { AuthPoliciesGuardMixin } from './auth-policies-guard.mixin'; const REFRESH_TOKEN_COOKIE = 'refreshToken'; @@ -48,6 +49,7 @@ function AuthControllerMixin( { useInterceptors: updateAccountUseInterceptors = [], broadcast: updateAccountBroadcastConfig, + refreshTokenOnUpdate = false, ...updateAccountOptions }: DynamicApiUpdateAccountOptions = {}, { @@ -217,7 +219,7 @@ function AuthControllerMixin( @Body() _: AuthLoginDto, @Res({ passthrough: true }) res: Response, ) { - const result = await this.service.login(req.user); + const result = await authOperationStorage.run('login', async () => this.service.login(req.user)); if (loginBroadcastConfig) { const broadcastData = buildAuthBroadcastData(req.user, loginBroadcastConfig.fields); @@ -246,7 +248,7 @@ function AuthControllerMixin( @Body() body: AuthRegisterDto, @Res({ passthrough: true }) res: Response, ) { - const result = await this.service.register(body); + const result = await authOperationStorage.run('register', async () => this.service.register(body)); if (registerBroadcastConfig && this.jwtService) { const decoded = this.jwtService.decode(result.accessToken); @@ -279,15 +281,39 @@ function AuthControllerMixin( @RouteDecoratorsHelper(authUpdateAccountDecorators) @HttpCode(HttpStatus.OK) - @ApiOkResponse({ type: AuthUserPresenter }) + @ApiOkResponse({ type: refreshTokenOnUpdate ? AuthPresenter : AuthUserPresenter }) @UseInterceptors(...updateAccountUseInterceptors) @Patch('account') async updateAccount( @Request() req: { user: Entity; headers: Record }, @Body() body: AuthUpdateAccountDto, + @Res({ passthrough: true }) res: Response, ) { const user = this.extractUserFromToken(req) ?? req.user; - const account = await this.service.updateAccount(user, body); + const result = await authOperationStorage.run('updateAccount', async () => this.service.updateAccount(user, body)); + + if (refreshTokenOnUpdate && result && 'accessToken' in result) { + const tokenResult = result as LoginResponse; + + if (updateAccountBroadcastConfig) { + const broadcastData = buildAuthBroadcastData(user, updateAccountBroadcastConfig.fields); + this.broadcastService?.broadcastFromHttp( + updateAccountBroadcastConfig.eventName ?? AUTH_UPDATE_ACCOUNT_BROADCAST_EVENT, + [broadcastData], + updateAccountBroadcastConfig, + ); + } + + if (useCookie) { + res.cookie(REFRESH_TOKEN_COOKIE, tokenResult.refreshToken, COOKIE_OPTIONS); + const { refreshToken: _rt, ...bodyResult } = tokenResult; + return bodyResult; + } + + return tokenResult; + } + + const account = result as Entity; if (updateAccountBroadcastConfig) { const broadcastData = buildAuthBroadcastData(account, updateAccountBroadcastConfig.fields); diff --git a/libs/dynamic-api/src/modules/auth/mixins/auth-gateway.mixin.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-gateway.mixin.ts index 6a7cd20..4215232 100644 --- a/libs/dynamic-api/src/modules/auth/mixins/auth-gateway.mixin.ts +++ b/libs/dynamic-api/src/modules/auth/mixins/auth-gateway.mixin.ts @@ -8,6 +8,7 @@ import { ExtendedSocket } from '../../../interfaces'; import { EntityBodyMixin } from '../../../mixins'; import { BaseEntity } from '../../../models'; import { buildAuthBroadcastData } from '../auth-broadcast.helper'; +import { authOperationStorage } from '../auth-operation-context'; import { AUTH_CHANGE_PASSWORD_EVENT, AUTH_GET_ACCOUNT_BROADCAST_EVENT, @@ -159,10 +160,12 @@ function AuthGatewayMixin( @ConnectedSocket() socket: ExtendedSocket, @MessageBody() body: AuthUpdateAccountDto, ) { - const account = socket.user ? await this.service.updateAccount(socket.user, body) : undefined; + const account = socket.user + ? await authOperationStorage.run('updateAccount', async () => this.service.updateAccount(socket.user, body)) + : undefined; - if (updateAccountBroadcastConfig && account) { - const broadcastData = buildAuthBroadcastData(account, updateAccountBroadcastConfig.fields); + if (updateAccountBroadcastConfig && account && !('accessToken' in account)) { + const broadcastData = buildAuthBroadcastData(account as Partial, updateAccountBroadcastConfig.fields); this.broadcastIfNeeded( socket, updateAccountBroadcastConfig.eventName ?? AUTH_UPDATE_ACCOUNT_BROADCAST_EVENT, @@ -192,7 +195,7 @@ function AuthGatewayMixin( throw new WsException('Access denied'); } - const result = await this.service.login(socket.user); + const result = await authOperationStorage.run('login', async () => this.service.login(socket.user)); if (loginBroadcastConfig) { const broadcastData = buildAuthBroadcastData(socket.user, loginBroadcastConfig.fields); @@ -216,7 +219,7 @@ function AuthGatewayMixin( ) { this.addUserToSocket(socket, !registerProtected && !registerAbilityPredicate); - const result = await this.service.register(data); + const result = await authOperationStorage.run('register', async () => this.service.register(data)); if (registerBroadcastConfig) { const decoded = this.jwtService.decode(result.accessToken); diff --git a/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts b/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts index b145138..698a9d4 100644 --- a/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts +++ b/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts @@ -23,6 +23,9 @@ export abstract class BaseAuthService extends BaseSer protected resetPasswordOptions: DynamicApiResetPasswordOptions | undefined; protected refreshTokenField: keyof Entity | undefined; + /** refreshTokenOnUpdate */ + protected refreshTokenOnUpdate = false; + private resetPasswordCallbackMethods: DynamicApiResetPasswordCallbackMethods | undefined; private readonly logger = new MongoDBDynamicApiLogger('AuthService'); @@ -139,7 +142,7 @@ export abstract class BaseAuthService extends BaseSer return this.buildUserFields(user, fieldsToBuild); } - protected async updateAccount({ id }: Entity, update: Partial): Promise { + protected async updateAccount({ id }: Entity, update: Partial): Promise { this.logger.debug('Updating account', { userId: id, update }); this.verifyArguments(id, update); @@ -161,6 +164,11 @@ export abstract class BaseAuthService extends BaseSer await this.updateAccountCallback(instance, this.callbackMethods); } + if (this.refreshTokenOnUpdate) { + const updatedUser = await this.model.findOne({ _id: id }).lean().exec(); + return this.login({ ...updatedUser, id: updatedUser._id.toString() } as Entity, true); + } + return this.getAccount({ id } as Entity); } From 7f80c34d85c1c51aeef31e5aa38dcc27dab311f0 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 17:36:49 +0200 Subject: [PATCH 05/14] docs(auth): document refreshTokenOnUpdate and getAuthOperationContext Add refreshTokenOnUpdate option to updateAccount configuration block. Document conditional response shape (entity vs token pair) with examples, use-cases, cookie-mode compatibility, and broadcast behaviour note. Add Auth Operation Context section documenting getAuthOperationContext() AsyncLocalStorage helper, including operation-aware validator examples, a reusable OnAuthOperation/ExceptAuthOperation decorator pattern, and a note on the zero-overhead AsyncLocalStorage implementation. Update table of contents and Generated Endpoints section (update-account endpoint now shows both response shapes). --- README/authentication.md | 215 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 2 deletions(-) diff --git a/README/authentication.md b/README/authentication.md index 8b81076..51acdc6 100644 --- a/README/authentication.md +++ b/README/authentication.md @@ -21,6 +21,8 @@ JWT authentication is built-in and provides secure, **dual-token** (access + ref - [Custom Passport Strategy (`useStrategy`)](#custom-passport-strategy-usestrategy) - [JWT Payload Customization](#jwt-payload-customization) - [Update Account Configuration](#update-account-configuration) + - [Automatic Token Refresh on Update (`refreshTokenOnUpdate`)](#automatic-token-refresh-on-update-refreshtokenonupdate) ⭐ *New* + - [Auth Operation Context (`getAuthOperationContext`)](#auth-operation-context-getauthoperationcontext) ⭐ *New* - [Reset Password Configuration](#reset-password-configuration) - [Callbacks and Lifecycle Hooks](#callbacks-and-lifecycle-hooks) - [Interceptors](#interceptors) @@ -190,6 +192,12 @@ DynamicApiModule.forRoot('mongodb-uri', { fields?: (keyof Entity)[]; // Fields to include (default: all) rooms?: string | string[] | ((data: Partial) => string | string[]); // Target rooms }; + /** + * When true, PATCH /auth/account returns { accessToken, refreshToken } + * instead of the updated entity, so the client refreshes its JWT in one + * round-trip after a profile change. + */ + refreshTokenOnUpdate?: boolean; // Default: false }, // Reset Password Configuration @@ -384,7 +392,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... } ``` -**Response (200 OK):** +**Response (200 OK) — default (`refreshTokenOnUpdate` not set):** ```json { "id": "507f1f77bcf86cd799439011", @@ -395,7 +403,15 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... } ``` -**Note:** By default, `loginField` (email/username) and `passwordField` are excluded from updates. Use `additionalFieldsToExclude` to exclude more fields. +**Response (200 OK) — with `refreshTokenOnUpdate: true`:** +```json +{ + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +**Note:** By default, `loginField` (email/username) and `passwordField` are excluded from updates. Use `additionalFieldsToExclude` to exclude more fields. When `refreshTokenOnUpdate: true`, the response shape switches to a token pair so the client does not need a separate `/auth/refresh-token` call after updating profile data that is embedded in the JWT. ### 5. Reset/Change Password @@ -896,6 +912,74 @@ DynamicApiModule.forRoot('mongodb-uri', { **Note:** By default, `loginField` and `passwordField` are automatically excluded from updates. +### Automatic Token Refresh on Update (`refreshTokenOnUpdate`) + +> ⭐ **New option** — eliminates the need for a client-side `/auth/refresh-token` call after a profile change. + +**Problem:** When a user updates profile data that is **embedded in their JWT payload** (e.g., `name`, `role`, or any field in `login.additionalFields`), the access token becomes stale. The standard workaround is to call `/auth/refresh-token` immediately after `/auth/account` — an extra round-trip that can cause race conditions in SPA/mobile clients. + +**Solution:** Set `refreshTokenOnUpdate: true` to make `PATCH /auth/account` return a **fresh token pair** instead of the updated entity. The client receives a new `{ accessToken, refreshToken }` in a single request. + +```typescript +DynamicApiModule.forRoot('mongodb-uri', { + useAuth: { + userEntity: User, + login: { + additionalFields: ['role', 'name'], // These fields are in the JWT — get stale after update + }, + updateAccount: { + refreshTokenOnUpdate: true, // ← PATCH /auth/account now returns { accessToken, refreshToken } + }, + }, +}) +``` + +**Response comparison:** + +| `refreshTokenOnUpdate` | `PATCH /auth/account` response | +|---|---| +| `false` (default) | `{ id, email, name, ... }` — updated entity | +| `true` | `{ accessToken, refreshToken }` — fresh token pair | + +**When to use:** + +✅ Use `refreshTokenOnUpdate: true` when: +- Profile fields that change (`name`, `role`, `avatar`, etc.) are included in `login.additionalFields` and therefore embedded in the JWT +- You want one round-trip instead of two (`PATCH /account` → `/refresh-token`) +- Your client needs the new token immediately (e.g., role change must take effect before the next request) + +❌ Keep default (`false`) when: +- Updated fields are **not** in the JWT payload (e.g., `address`, `bio`, `preferences`) +- Your client already has a silent-refresh interceptor and the extra call is acceptable +- You need the updated entity shape in the response for UI state management + +**Cookie mode compatibility:** + +`refreshTokenOnUpdate` is fully compatible with `useCookie: true`. When both are enabled, the new `refreshToken` is set as an httpOnly cookie and **not** included in the response body: + +```typescript +updateAccount: { refreshTokenOnUpdate: true } +refreshToken: { useCookie: true } +// → Response body: { "accessToken": "eyJ..." } +// → Cookie: Set-Cookie: refreshToken=eyJ...; HttpOnly; ... +``` + +**Broadcast behaviour:** + +When `refreshTokenOnUpdate: true`, the broadcast payload (if `broadcast` is configured) uses the **original user identity** from the request (not the token result) so field filtering still works as expected: + +```typescript +updateAccount: { + refreshTokenOnUpdate: true, + broadcast: { + enabled: true, + fields: ['id', 'email', 'name'], // Works normally — not affected by refreshTokenOnUpdate + }, +}, +``` + +--- + ### Reset Password Configuration Enable password reset functionality with email notifications: @@ -1401,6 +1485,133 @@ socket.on('admin-logged-in', (data) => { --- +### Auth Operation Context (`getAuthOperationContext`) + +> ⭐ **New utility** — enables operation-aware validation in `class-validator` decorators. + +MDA automatically tracks which auth operation is currently executing (`'register'`, `'login'`, or `'updateAccount'`) using Node.js `AsyncLocalStorage`. This context is available anywhere in the request pipeline via the exported `getAuthOperationContext()` helper — including inside custom `class-validator` decorators. + +**Import:** + +```typescript +import { getAuthOperationContext } from 'mongodb-dynamic-api'; +// Returns: 'register' | 'login' | 'updateAccount' | undefined +``` + +**Type:** + +```typescript +type AuthOperationContext = 'register' | 'login' | 'updateAccount'; +``` + +Returns `undefined` when called outside of an auth request pipeline (e.g., in a background job). + +#### Use cases + +##### Only validate a field on registration + +```typescript +import { ValidateIf, IsNotEmpty } from 'class-validator'; +import { getAuthOperationContext } from 'mongodb-dynamic-api'; + +@Schema({ collection: 'users' }) +export class User extends BaseEntity { + @IsEmail() + @Prop({ type: String, required: true, unique: true }) + email: string; + + @Prop({ type: String, required: true }) + password: string; + + // referralCode is only mandatory when registering — optional elsewhere + @ValidateIf(() => getAuthOperationContext() === 'register') + @IsNotEmpty({ message: 'Referral code is required at registration' }) + @Prop({ type: String }) + referralCode?: string; +} +``` + +##### Apply different validation rules per operation + +```typescript +import { ValidateIf, MinLength, IsOptional } from 'class-validator'; +import { getAuthOperationContext } from 'mongodb-dynamic-api'; + +@Schema({ collection: 'users' }) +export class User extends BaseEntity { + @IsEmail() + @Prop({ type: String, required: true }) + email: string; + + // On register: password must be strong (min 12 chars) + // On updateAccount: password is optional (user may not want to change it) + @ValidateIf(() => getAuthOperationContext() !== 'updateAccount') + @MinLength(12, { message: 'Password must be at least 12 characters on registration' }) + @Prop({ type: String, required: true }) + password: string; +} +``` + +##### Custom decorator helper + +Create a reusable decorator to avoid repeating `ValidateIf(() => getAuthOperationContext() === '...')`: + +```typescript +// src/validators/auth-operation.decorators.ts +import { ValidateIf } from 'class-validator'; +import { getAuthOperationContext, AuthOperationContext } from 'mongodb-dynamic-api'; + +/** Only apply the following validators during the specified auth operation(s). */ +export function OnAuthOperation(...operations: AuthOperationContext[]): PropertyDecorator { + return ValidateIf(() => { + const ctx = getAuthOperationContext(); + return ctx !== undefined && operations.includes(ctx); + }); +} + +/** Skip the following validators during the specified auth operation(s). */ +export function ExceptAuthOperation(...operations: AuthOperationContext[]): PropertyDecorator { + return ValidateIf(() => { + const ctx = getAuthOperationContext(); + return ctx === undefined || !operations.includes(ctx); + }); +} +``` + +Usage: + +```typescript +import { OnAuthOperation, ExceptAuthOperation } from './validators/auth-operation.decorators'; +import { IsNotEmpty, IsOptional, MinLength } from 'class-validator'; + +@Schema() +export class User extends BaseEntity { + @IsEmail() + @Prop({ type: String, required: true }) + email: string; + + @OnAuthOperation('register') // ← only validated on POST /auth/register + @IsNotEmpty() + @MinLength(8) + @Prop({ type: String, required: true }) + password: string; + + @OnAuthOperation('register') // ← terms acceptance only required at registration + @IsNotEmpty() + @Prop({ type: Boolean }) + acceptedTerms?: boolean; + + @ExceptAuthOperation('updateAccount') // ← validated everywhere except PATCH /auth/account + @IsNotEmpty() + @Prop({ type: String }) + requiredOnRegisterAndLogin?: string; +} +``` + +> **Note:** `getAuthOperationContext()` uses `AsyncLocalStorage` (Node.js native) and has **zero performance overhead** — no request-scoped DI or additional middleware is involved. It returns `undefined` outside of an auth request, so validators that run in other contexts (e.g., CRUD routes) are unaffected. + +--- + ### Refresh Token Configuration ⭐ *New in v4* Configure the refresh token behaviour via the `refreshToken` option in `useAuth`: From 62cb9d75d957544baf65cec072399acdfb16581b Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 17:48:42 +0200 Subject: [PATCH 06/14] test(auth): add tests for authOperationContext, refreshTokenOnUpdate and fix missing res arg - Add auth-operation-context.spec.ts: full coverage of AsyncLocalStorage context isolation - Add updateAccount refreshTokenOnUpdate=true tests in base-auth.service.spec.ts - Add updateAccount refreshTokenOnUpdate tests + authOperationStorage context integration tests in auth-controller.mixin.spec.ts - Fix pre-existing TS errors: pass missing res arg to updateAccount calls in broadcast tests --- .../auth/auth-operation-context.spec.ts | 68 ++++++++ .../auth/mixins/auth-controller.mixin.spec.ts | 153 +++++++++++++++++- .../auth/services/base-auth.service.spec.ts | 52 ++++++ 3 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 libs/dynamic-api/src/modules/auth/auth-operation-context.spec.ts diff --git a/libs/dynamic-api/src/modules/auth/auth-operation-context.spec.ts b/libs/dynamic-api/src/modules/auth/auth-operation-context.spec.ts new file mode 100644 index 0000000..2c066de --- /dev/null +++ b/libs/dynamic-api/src/modules/auth/auth-operation-context.spec.ts @@ -0,0 +1,68 @@ +import { AuthOperationContext, authOperationStorage, getAuthOperationContext } from './auth-operation-context'; + +describe('auth-operation-context', () => { + describe('getAuthOperationContext', () => { + it('should return undefined outside of any storage run', () => { + expect(getAuthOperationContext()).toBeUndefined(); + }); + + it('should return "login" inside authOperationStorage.run("login", ...)', async () => { + let capturedContext: AuthOperationContext | undefined; + + await authOperationStorage.run('login', async () => { + capturedContext = getAuthOperationContext(); + }); + + expect(capturedContext).toBe('login'); + }); + + it('should return "register" inside authOperationStorage.run("register", ...)', async () => { + let capturedContext: AuthOperationContext | undefined; + + await authOperationStorage.run('register', async () => { + capturedContext = getAuthOperationContext(); + }); + + expect(capturedContext).toBe('register'); + }); + + it('should return "updateAccount" inside authOperationStorage.run("updateAccount", ...)', async () => { + let capturedContext: AuthOperationContext | undefined; + + await authOperationStorage.run('updateAccount', async () => { + capturedContext = getAuthOperationContext(); + }); + + expect(capturedContext).toBe('updateAccount'); + }); + + it('should return undefined again after storage run completes', async () => { + await authOperationStorage.run('login', async () => { + // inside run + }); + + expect(getAuthOperationContext()).toBeUndefined(); + }); + + it('should isolate contexts between concurrent async runs', async () => { + const results: (AuthOperationContext | undefined)[] = []; + + const run1 = authOperationStorage.run('login', async () => { + await new Promise((resolve) => setTimeout(resolve, 10)); + results.push(getAuthOperationContext()); + }); + + const run2 = authOperationStorage.run('register', async () => { + await new Promise((resolve) => setTimeout(resolve, 5)); + results.push(getAuthOperationContext()); + }); + + await Promise.all([run1, run2]); + + expect(results).toContain('login'); + expect(results).toContain('register'); + expect(results).toHaveLength(2); + }); + }); +}); + diff --git a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.spec.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.spec.ts index d370a9b..7f79bb7 100644 --- a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.spec.ts +++ b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.spec.ts @@ -3,6 +3,7 @@ import { JwtService } from '@nestjs/jwt'; import { Response } from 'express'; import { BaseEntity } from '../../../models'; import { DynamicApiBroadcastService } from '../../../services'; +import { authOperationStorage, getAuthOperationContext } from '../auth-operation-context'; import { AuthService } from '../interfaces'; import { AuthControllerMixin } from './auth-controller.mixin'; @@ -200,7 +201,7 @@ describe('AuthControllerMixin', () => { jwtService.decode.mockReturnValueOnce(decodedUser); const controller = new AuthController(service, undefined, jwtService); - await controller.updateAccount({ user: new TestEntity(), headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user: new TestEntity(), headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(jwtService.decode).toHaveBeenCalledWith('fake-token'); expect(service.updateAccount).toHaveBeenCalledWith( @@ -220,10 +221,150 @@ describe('AuthControllerMixin', () => { const controller = new AuthController(service); const user = new TestEntity(); - await controller.updateAccount({ user, headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user, headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(service.updateAccount).toHaveBeenCalledWith(user, {}); }); + + describe('with refreshTokenOnUpdate = true', () => { + it('should return LoginResponse when service returns accessToken', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + undefined, + undefined, + { refreshTokenOnUpdate: true }, + ); + const controller = new AuthController(service); + const user = new TestEntity(); + const fakeRes = { cookie: jest.fn() }; + service.updateAccount.mockResolvedValueOnce({ accessToken: 'at', refreshToken: 'rt' }); + + const result = await controller.updateAccount( + { user, headers: {} as Record }, + {}, + fakeRes as unknown as Response, + ); + + expect(result).toEqual({ accessToken: 'at', refreshToken: 'rt' }); + expect(fakeRes.cookie).not.toHaveBeenCalled(); + }); + + it('should set cookie and strip refreshToken when useCookie + refreshTokenOnUpdate', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + undefined, + undefined, + { refreshTokenOnUpdate: true }, + undefined, + { useCookie: true }, + ); + const controller = new AuthController(service); + const user = new TestEntity(); + const fakeRes = { cookie: jest.fn() }; + service.updateAccount.mockResolvedValueOnce({ accessToken: 'at', refreshToken: 'rt' }); + + const result = await controller.updateAccount( + { user, headers: {} as Record }, + {}, + fakeRes as unknown as Response, + ); + + expect(fakeRes.cookie).toHaveBeenCalledWith('refreshToken', 'rt', expect.objectContaining({ httpOnly: true })); + expect(result).toEqual({ accessToken: 'at' }); + }); + + it('should return account entity when service returns entity (no accessToken)', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + undefined, + undefined, + { refreshTokenOnUpdate: true }, + ); + const controller = new AuthController(service); + const user = new TestEntity(); + const fakeRes = { cookie: jest.fn() }; + const fakeAccount = Object.assign(new TestEntity(), { id: 'acc-id', loginField: 'test@test.co' }); + service.updateAccount.mockResolvedValueOnce(fakeAccount); + + const result = await controller.updateAccount( + { user, headers: {} as Record }, + {}, + fakeRes as unknown as Response, + ); + + expect(result).toEqual(fakeAccount); + }); + }); + }); + + describe('authOperationStorage context', () => { + it('should run login in "login" context', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + ); + const controller = new AuthController(service); + const fakeRes = { cookie: jest.fn() }; + let capturedContext: string | undefined; + + service.login.mockImplementationOnce(async () => { + capturedContext = getAuthOperationContext(); + return { accessToken: 'at', refreshToken: 'rt' }; + }); + + await controller.login({ user: new TestEntity() }, {}, fakeRes as unknown as Response); + + expect(capturedContext).toBe('login'); + }); + + it('should run register in "register" context', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + ); + const controller = new AuthController(service); + const fakeRes = { cookie: jest.fn() }; + let capturedContext: string | undefined; + + service.register.mockImplementationOnce(async () => { + capturedContext = getAuthOperationContext(); + return { accessToken: 'at', refreshToken: 'rt' }; + }); + + await controller.register({} as Parameters[0], fakeRes as unknown as Response); + + expect(capturedContext).toBe('register'); + }); + + it('should run updateAccount in "updateAccount" context', async () => { + const AuthController = AuthControllerMixin( + TestEntity, + { loginField: 'loginField', passwordField: 'passwordField' }, + ); + const controller = new AuthController(service); + const fakeRes = { cookie: jest.fn() }; + let capturedContext: string | undefined; + + service.updateAccount.mockImplementationOnce(async () => { + capturedContext = getAuthOperationContext(); + return new TestEntity(); + }); + + await controller.updateAccount( + { user: new TestEntity(), headers: {} as Record }, + {}, + fakeRes as unknown as Response, + ); + + expect(capturedContext).toBe('updateAccount'); + }); + + it('should return undefined context outside of any auth operation', () => { + expect(getAuthOperationContext()).toBeUndefined(); + }); }); describe('login', () => { @@ -662,7 +803,7 @@ describe('AuthControllerMixin', () => { ); const controller = new AuthController(service, broadcastService, jwtService); - await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(broadcastService.broadcastFromHttp).toHaveBeenCalledWith( 'auth-update-account-broadcast', @@ -681,7 +822,7 @@ describe('AuthControllerMixin', () => { ); const controller = new AuthController(service, broadcastService, jwtService); - await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(broadcastService.broadcastFromHttp).toHaveBeenCalledWith( 'auth-update-account-broadcast', @@ -700,7 +841,7 @@ describe('AuthControllerMixin', () => { ); const controller = new AuthController(service, broadcastService, jwtService); - await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(broadcastService.broadcastFromHttp).toHaveBeenCalledWith( 'custom-update-account', @@ -719,7 +860,7 @@ describe('AuthControllerMixin', () => { ); const controller = new AuthController(service, broadcastService, jwtService); - await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}); + await controller.updateAccount({ user: fakeUser, headers: { authorization: 'Bearer fake-token' } }, {}, { cookie: jest.fn() } as unknown as Response); expect(broadcastService.broadcastFromHttp).not.toHaveBeenCalled(); }); diff --git a/libs/dynamic-api/src/modules/auth/services/base-auth.service.spec.ts b/libs/dynamic-api/src/modules/auth/services/base-auth.service.spec.ts index 4dfe9f7..8894391 100644 --- a/libs/dynamic-api/src/modules/auth/services/base-auth.service.spec.ts +++ b/libs/dynamic-api/src/modules/auth/services/base-auth.service.spec.ts @@ -631,6 +631,58 @@ describe('BaseAuthService', () => { service['callbackMethods'], ); }); + + describe('with refreshTokenOnUpdate = true', () => { + let spyLogin: jest.SpyInstance; + + beforeEach(() => { + service['refreshTokenOnUpdate'] = true; + service['beforeUpdateAccountCallback'] = undefined; + service['updateAccountCallback'] = undefined; + spyLogin = jest.spyOn(service, 'login').mockResolvedValueOnce({ accessToken, refreshToken: 'fresh-rt' }); + }); + + afterEach(() => { + service['refreshTokenOnUpdate'] = false; + }); + + it('should return LoginResponse when refreshTokenOnUpdate is true', async () => { + exec.mockResolvedValueOnce(undefined).mockResolvedValueOnce({ ...fakeUser, _id: fakeUserId }); + + const result = await service['updateAccount']({ id: fakeUserId } as User, update); + + expect(model.updateOne).toHaveBeenCalledWith({ _id: fakeUserId }, { $set: update }); + expect(spyLogin).toHaveBeenCalledWith( + { ...fakeUser, _id: fakeUserId, id: fakeUserId }, + true, + ); + expect(result).toEqual({ accessToken, refreshToken: 'fresh-rt' }); + expect(spyGetAccount).not.toHaveBeenCalled(); + }); + + it('should not call getAccount when refreshTokenOnUpdate is true', async () => { + exec.mockResolvedValueOnce(undefined).mockResolvedValueOnce({ ...fakeUser, _id: fakeUserId }); + + await service['updateAccount']({ id: fakeUserId } as User, update); + + expect(spyGetAccount).not.toHaveBeenCalled(); + }); + + it('should still call updateAccountCallback before refreshing token', async () => { + service['updateAccountCallback'] = updateAccountCallback; + exec + .mockResolvedValueOnce(undefined) // updateOne + .mockResolvedValueOnce({ ...fakeUser, _id: fakeUserId }) // findOne for callback + .mockResolvedValueOnce({ ...fakeUser, _id: fakeUserId }); // findOne for login + spyBuildInstance.mockReturnValueOnce(fakeUserInstance); + + await service['updateAccount']({ id: fakeUserId } as User, update); + + expect(updateAccountCallback).toHaveBeenCalledTimes(1); + expect(updateAccountCallback).toHaveBeenCalledWith(fakeUserInstance, service['callbackMethods']); + expect(spyLogin).toHaveBeenCalledTimes(1); + }); + }); }); describe('resetPassword', () => { From 4be547c455a617f0270e337d09c84277ad9c74cc Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 18:59:24 +0200 Subject: [PATCH 07/14] test(auth): add e2e integration tests for refreshTokenOnUpdate and getAuthOperationContext New auth-api-update-account.e2e-spec.ts covers: - PATCH /auth/account returns entity by default (refreshTokenOnUpdate: false) - PATCH /auth/account returns fresh token pair when refreshTokenOnUpdate: true - new accessToken is usable after update - getAuthOperationContext() returns correct value inside each auth callback - getAuthOperationContext() returns undefined outside auth pipeline Also: - export getAuthOperationContext from src/index.ts (public API) - fix state toStrictEqual assertions in auth-api-basic and websockets-auth-basic to include refreshTokenOnUpdate: false --- libs/dynamic-api/src/index.ts | 1 + .../test/for-root/auth-api-basic.e2e-spec.ts | 4 +- .../auth-api-update-account.e2e-spec.ts | 177 ++++++++++++++++++ .../websockets-auth-basic.e2e-spec.ts | 1 + 4 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 libs/dynamic-api/test/for-root/auth-api-update-account.e2e-spec.ts diff --git a/libs/dynamic-api/src/index.ts b/libs/dynamic-api/src/index.ts index 443011d..ef7868e 100644 --- a/libs/dynamic-api/src/index.ts +++ b/libs/dynamic-api/src/index.ts @@ -14,3 +14,4 @@ export * from './routes'; export * from './services'; export * from './utils'; export * from './dynamic-api.module'; +export { AuthOperationContext, getAuthOperationContext } from './modules/auth/auth-operation-context'; diff --git a/libs/dynamic-api/test/for-root/auth-api-basic.e2e-spec.ts b/libs/dynamic-api/test/for-root/auth-api-basic.e2e-spec.ts index 90e299c..40615f4 100644 --- a/libs/dynamic-api/test/for-root/auth-api-basic.e2e-spec.ts +++ b/libs/dynamic-api/test/for-root/auth-api-basic.e2e-spec.ts @@ -22,7 +22,7 @@ describe('DynamicApiModule forRoot - Authentication API Basic (e2e)', () => { const UserEntity = createBasicUserEntity(); let app: INestApplication; - +- beforeEach(async () => { app = await initModule({ useAuth: { userEntity: UserEntity } }); }); @@ -44,6 +44,7 @@ describe('DynamicApiModule forRoot - Authentication API Basic (e2e)', () => { jwtRefreshTokenExpiresIn: '7d', jwtRefreshSecret: undefined, jwtRefreshUseCookie: false, + refreshTokenOnUpdate: false, jwtSecret: 'dynamic-api-jwt-secret', routesConfig: { defaults: [ @@ -271,6 +272,7 @@ describe('DynamicApiModule forRoot - Authentication API Basic (e2e)', () => { jwtRefreshTokenExpiresIn: '7d', jwtRefreshSecret: undefined, jwtRefreshUseCookie: false, + refreshTokenOnUpdate: false, jwtSecret: 'test-secret', routesConfig: { defaults: [ diff --git a/libs/dynamic-api/test/for-root/auth-api-update-account.e2e-spec.ts b/libs/dynamic-api/test/for-root/auth-api-update-account.e2e-spec.ts new file mode 100644 index 0000000..cf15c88 --- /dev/null +++ b/libs/dynamic-api/test/for-root/auth-api-update-account.e2e-spec.ts @@ -0,0 +1,177 @@ +import mongoose from 'mongoose'; +import { DynamicApiModule, getAuthOperationContext } from '../../src'; +import { closeTestingApp, server } from '../e2e.setup'; +import 'dotenv/config'; +import { createBroadcastUserEntity, initModule } from '../shared'; + +describe('DynamicApiModule forRoot - PATCH /auth/account (e2e)', () => { + beforeEach(() => { + DynamicApiModule.state['resetState'](); + }); + + afterEach(async () => { + await closeTestingApp(mongoose.connections); + }); + + // ───────────────────────────────────────────────────────────────────────────── + // Default behaviour — refreshTokenOnUpdate: false (not set) + // ───────────────────────────────────────────────────────────────────────────── + describe('default behaviour (refreshTokenOnUpdate not set)', () => { + const UserEntity = createBroadcastUserEntity(); + let accessToken: string; + + beforeEach(async () => { + await initModule({ useAuth: { userEntity: UserEntity } }); + const { body } = await server.post('/auth/register', { email: 'update@test.co', password: 'pass' }); + accessToken = body.accessToken; + }); + + it('should return 401 when access token is missing', async () => { + const { body, status } = await server.patch('/auth/account', { name: 'New Name' }); + + expect(status).toBe(401); + expect(body).toEqual({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('should return the updated entity (not a token pair)', async () => { + const { body, status } = await server.patch('/auth/account', { name: 'New Name' }, { authToken: accessToken }); + + expect(status).toBe(200); + expect(body).toHaveProperty('id'); + expect(body).toHaveProperty('email', 'update@test.co'); + expect(body).not.toHaveProperty('accessToken'); + expect(body).not.toHaveProperty('refreshToken'); + }); + }); + + // ───────────────────────────────────────────────────────────────────────────── + // refreshTokenOnUpdate: true — should return a fresh token pair + // ───────────────────────────────────────────────────────────────────────────── + describe('refreshTokenOnUpdate: true', () => { + const UserEntity = createBroadcastUserEntity(); + let firstAccessToken: string; + + beforeEach(async () => { + await initModule({ + useAuth: { + userEntity: UserEntity, + updateAccount: { refreshTokenOnUpdate: true }, + }, + }); + const { body } = await server.post('/auth/register', { email: 'refresh@test.co', password: 'pass' }); + firstAccessToken = body.accessToken; + }); + + it('should return 401 when access token is missing', async () => { + const { body, status } = await server.patch('/auth/account', { name: 'X' }); + + expect(status).toBe(401); + expect(body).toEqual({ message: 'Unauthorized', statusCode: 401 }); + }); + + it('should return a fresh token pair instead of the entity', async () => { + const { body, status } = await server.patch( + '/auth/account', + { name: 'Updated Name' }, + { authToken: firstAccessToken }, + ); + + expect(status).toBe(200); + expect(body).toEqual({ + accessToken: expect.any(String), + refreshToken: expect.any(String), + }); + }); + + it('new accessToken should be usable to fetch account', async () => { + const { body: tokenPair } = await server.patch( + '/auth/account', + { name: 'Updated Name' }, + { authToken: firstAccessToken }, + ); + + const { body: account, status } = await server.get('/auth/account', { authToken: tokenPair.accessToken }); + + expect(status).toBe(200); + expect(account).toHaveProperty('id'); + expect(account).toHaveProperty('email', 'refresh@test.co'); + }); + }); + + // ───────────────────────────────────────────────────────────────────────────── + // getAuthOperationContext — AsyncLocalStorage propagation + // ───────────────────────────────────────────────────────────────────────────── + describe('getAuthOperationContext', () => { + it('should expose "updateAccount" context inside updateAccount callback', async () => { + let capturedContext: string | undefined; + const UserEntity = createBroadcastUserEntity(); + + await initModule({ + useAuth: { + userEntity: UserEntity, + updateAccount: { + callback: async () => { + capturedContext = getAuthOperationContext(); + }, + }, + }, + }); + + const { body: { accessToken } } = await server.post('/auth/register', { + email: 'ctx-update@test.co', + password: 'pass', + }); + await server.patch('/auth/account', { name: 'Ctx Test' }, { authToken: accessToken }); + + expect(capturedContext).toBe('updateAccount'); + }); + + it('should expose "login" context inside login callback', async () => { + let capturedContext: string | undefined; + const UserEntity = createBroadcastUserEntity(); + + await initModule({ + useAuth: { + userEntity: UserEntity, + login: { + callback: async () => { + capturedContext = getAuthOperationContext(); + }, + }, + }, + }); + + await server.post('/auth/register', { email: 'ctx-login@test.co', password: 'pass' }); + await server.post('/auth/login', { email: 'ctx-login@test.co', password: 'pass' }); + + expect(capturedContext).toBe('login'); + }); + + it('should expose "register" context inside register callback', async () => { + let capturedContext: string | undefined; + const UserEntity = createBroadcastUserEntity(); + + await initModule({ + useAuth: { + userEntity: UserEntity, + register: { + callback: async () => { + capturedContext = getAuthOperationContext(); + }, + }, + }, + }); + + await server.post('/auth/register', { email: 'ctx-register@test.co', password: 'pass' }); + + expect(capturedContext).toBe('register'); + }); + + it('should return undefined when called outside an auth operation', () => { + expect(getAuthOperationContext()).toBeUndefined(); + }); + }); +}); + + + diff --git a/libs/dynamic-api/test/for-root/websockets-auth-basic.e2e-spec.ts b/libs/dynamic-api/test/for-root/websockets-auth-basic.e2e-spec.ts index 818f082..d0ceb0e 100644 --- a/libs/dynamic-api/test/for-root/websockets-auth-basic.e2e-spec.ts +++ b/libs/dynamic-api/test/for-root/websockets-auth-basic.e2e-spec.ts @@ -251,6 +251,7 @@ describe('DynamicApiModule forRoot - Websockets Authentication Basic (e2e)', () jwtRefreshTokenExpiresIn: '7d', jwtRefreshSecret: undefined, jwtRefreshUseCookie: false, + refreshTokenOnUpdate: false, jwtSecret: 'test-secret', routesConfig: { defaults: [ From 0e3c41bbf2a3f05b9c8e6bf761c88ee22d7869e9 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 19:07:05 +0200 Subject: [PATCH 08/14] fix(dynamic-api-config): add missing refreshTokenOnUpdate field in spec config --- .../modules/dynamic-api-config/dynamic-api-config.module.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/dynamic-api/src/modules/dynamic-api-config/dynamic-api-config.module.spec.ts b/libs/dynamic-api/src/modules/dynamic-api-config/dynamic-api-config.module.spec.ts index 6b7a0f8..4569be0 100644 --- a/libs/dynamic-api/src/modules/dynamic-api-config/dynamic-api-config.module.spec.ts +++ b/libs/dynamic-api/src/modules/dynamic-api-config/dynamic-api-config.module.spec.ts @@ -16,6 +16,7 @@ describe('DynamicApiConfigModule', () => { jwtRefreshTokenExpiresIn: 0, jwtRefreshSecret: undefined, jwtRefreshUseCookie: undefined, + refreshTokenOnUpdate: false, routesConfig: undefined, uri: '', gatewayOptions: undefined, From dad0a7ae70ecd8384a900a61fb8cc59530b2855b Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 19:18:11 +0200 Subject: [PATCH 09/14] fix(auth): remove unnecessary type assertions flagged by SonarQube Drop redundant 'as LoginResponse' in auth-controller.mixin and two 'as Entity' casts in base-auth.service; TypeScript already infers the correct types from context. --- .../src/modules/auth/mixins/auth-controller.mixin.ts | 2 +- .../src/modules/auth/services/base-auth.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts index 5c319dc..8886c09 100644 --- a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts +++ b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts @@ -293,7 +293,7 @@ function AuthControllerMixin( const result = await authOperationStorage.run('updateAccount', async () => this.service.updateAccount(user, body)); if (refreshTokenOnUpdate && result && 'accessToken' in result) { - const tokenResult = result as LoginResponse; + const tokenResult = result; if (updateAccountBroadcastConfig) { const broadcastData = buildAuthBroadcastData(user, updateAccountBroadcastConfig.fields); diff --git a/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts b/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts index 698a9d4..2ee2e92 100644 --- a/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts +++ b/libs/dynamic-api/src/modules/auth/services/base-auth.service.ts @@ -52,7 +52,7 @@ export abstract class BaseAuthService extends BaseSer return null; } - return { ...user, id: user._id.toString() } as Entity; + return { ...user, id: user._id.toString() }; } protected async login(user: Entity, fromMember = false) { @@ -166,7 +166,7 @@ export abstract class BaseAuthService extends BaseSer if (this.refreshTokenOnUpdate) { const updatedUser = await this.model.findOne({ _id: id }).lean().exec(); - return this.login({ ...updatedUser, id: updatedUser._id.toString() } as Entity, true); + return this.login({ ...updatedUser, id: updatedUser._id.toString() }, true); } return this.getAccount({ id } as Entity); From d01b52a588ba138b38b708c52e7c660b8c53d897 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 19:44:04 +0200 Subject: [PATCH 10/14] feat(decorators): add @DerivedField and @ProtectedField decorators Introduce @DerivedField(computeFn, opts?) to declare server-computed fields directly on entity classes. Supports on:'save'|'read'|'both' (default 'save'). compute is applied after beforeSaveCallback and before DB write; 'read' fields are computed in buildInstance. Snapshot isolation prevents circular deps. Introduce @ProtectedField() to auto-exclude entity fields from all mutating body DTOs (CreateOne, CreateMany, UpdateOne, UpdateMany, ReplaceOne, DuplicateOne, DuplicateMany) via EntityBodyMixin auto-scan. Add DERIVED_FIELD_METADATA, DERIVED_FIELD_KEYS_METADATA, DerivedFieldOptions, DerivedFieldMeta, PROTECTED_FIELD_METADATA types to public API. --- README/callbacks.md | 23 ++ README/entities.md | 139 ++++++++++ README/route-config.md | 80 ++++++ .../derived-field.decorator.spec.ts | 80 ++++++ .../src/decorators/derived-field.decorator.ts | 37 +++ libs/dynamic-api/src/decorators/index.ts | 2 + .../protected-field.decorator.spec.ts | 47 ++++ .../decorators/protected-field.decorator.ts | 17 ++ .../src/helpers/from-user.helper.spec.ts | 63 +++++ .../src/helpers/from-user.helper.ts | 35 +++ libs/dynamic-api/src/helpers/index.ts | 1 + .../dynamic-api-route-config.interface.ts | 17 +- .../src/mixins/entity-body.mixin.spec.ts | 33 +++ .../src/mixins/entity-body.mixin.ts | 12 +- .../create-many/base-create-many.service.ts | 4 +- .../create-many-controller.mixin.ts | 10 +- .../base-create-one.service.spec.ts | 23 ++ .../create-one/base-create-one.service.ts | 4 +- .../create-one/create-one-controller.mixin.ts | 9 +- .../base-duplicate-many.service.ts | 4 +- .../duplicate-many-controller.mixin.ts | 9 +- .../base-duplicate-one.service.ts | 4 +- .../duplicate-one-controller.mixin.ts | 9 +- .../replace-one/base-replace-one.service.ts | 4 +- .../replace-one-controller.mixin.ts | 9 +- .../update-many/base-update-many.service.ts | 8 +- .../update-many-controller.mixin.ts | 9 +- .../update-one/base-update-one.service.ts | 4 +- .../update-one/update-one-controller.mixin.ts | 9 +- .../src/services/base/base.service.spec.ts | 95 +++++++ .../src/services/base/base.service.ts | 40 ++- ...ved-fields-protected-from-user.e2e-spec.ts | 251 ++++++++++++++++++ 32 files changed, 1059 insertions(+), 32 deletions(-) create mode 100644 libs/dynamic-api/src/decorators/derived-field.decorator.spec.ts create mode 100644 libs/dynamic-api/src/decorators/derived-field.decorator.ts create mode 100644 libs/dynamic-api/src/decorators/protected-field.decorator.spec.ts create mode 100644 libs/dynamic-api/src/decorators/protected-field.decorator.ts create mode 100644 libs/dynamic-api/src/helpers/from-user.helper.spec.ts create mode 100644 libs/dynamic-api/src/helpers/from-user.helper.ts create mode 100644 libs/dynamic-api/test/for-feature/derived-fields-protected-from-user.e2e-spec.ts diff --git a/README/callbacks.md b/README/callbacks.md index 5346ddf..1475389 100644 --- a/README/callbacks.md +++ b/README/callbacks.md @@ -49,6 +49,29 @@ Both `beforeSaveCallback` and `callback` (after save) **receive the authenticate --- +## Mutation Pipeline Order + +For every mutating route (`CreateOne`, `UpdateOne`, `ReplaceOne`, etc.), the following pipeline is executed **in order**: + +``` +1. fromUser injection (controller layer — injects JWT claims into body) + ↓ +2. beforeSaveCallback (your hook — transform / validate / enrich) + ↓ +3. @DerivedField computation (service layer — computes server-side values, on:'save') + ↓ +4. DB write (model.create / model.findOneAndUpdate / …) + ↓ +5. buildInstance + @DerivedField on:'read' computation + ↓ +6. afterSave callback (your hook — side effects, audit logs, notifications) +``` + +> ✅ `beforeSaveCallback` always sees the enriched body **after** `fromUser` injection, so you can use `context.toCreate.createdBy` directly. +> ✅ `@DerivedField(fn, { on:'save' })` fields are always computed **after** your callback, so your callback can modify source fields (e.g. `firstName`) and the derived field (e.g. `fullName`) will still be correct. + +--- + ## Overview | Property | Hook point | Purpose | Returns | diff --git a/README/entities.md b/README/entities.md index 9a59973..827f724 100644 --- a/README/entities.md +++ b/README/entities.md @@ -470,6 +470,145 @@ export class Order extends BaseEntity { - 🔧 **[Schema Options](./schema-options.md)** - Configure indexes, hooks, and custom initialization - ✅ **[Validation](./validation.md)** - Validate entity data - 📚 **[Swagger UI](./swagger-ui.md)** - API documentation +- 🔑 **[Route Config](./route-config.md)** - `fromUser` injection, callbacks, DTOs + +--- + +## Field Decorators + +Two property-level decorators let you declare computed and protected fields directly on the entity class, without writing any custom DTO or `beforeSaveCallback`. + +### `@DerivedField(computeFn, options?)` + +Marks a field as **server-computed**. The `computeFn` receives a snapshot of the entity being saved/read and must return the derived value. The original body value for this field is always overwritten. + +#### Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `on` | `'save' \| 'read' \| 'both'` | `'save'` | When to compute: before DB write, after DB read, or both | + +#### Behaviour + +| `on` value | Computed… | Persisted to DB | +|-----------|-----------|-----------------| +| `'save'` | Before DB write (after `beforeSaveCallback`) | ✅ Yes | +| `'read'` | After DB read (in `buildInstance`) | ❌ No | +| `'both'` | At both moments | ✅ Yes (save value) | + +> 💡 All `computeFn` calls receive the entity snapshot **before** any mutation from other derived fields, preventing circular computation. + +#### Example + +```typescript +import { Prop, Schema } from '@nestjs/mongoose'; +import { BaseEntity, DerivedField } from 'mongodb-dynamic-api'; +import { ApiProperty } from '@nestjs/swagger'; + +@Schema({ collection: 'articles' }) +export class Article extends BaseEntity { + @ApiProperty({ example: 'John' }) + @Prop({ type: String, required: true }) + firstName: string; + + @ApiProperty({ example: 'Doe' }) + @Prop({ type: String, required: true }) + lastName: string; + + // Computed on save and persisted + @ApiProperty({ example: 'John Doe' }) + @Prop({ type: String }) + @DerivedField
((e) => `${e.firstName} ${e.lastName}`) + fullName: string; + + // Computed only on read (never persisted) + @ApiProperty({ example: 'Hi, my name is John' }) + @Prop({ type: String }) + @DerivedField
((e) => `Hi, my name is ${e.firstName}`, { on: 'read' }) + greeting: string; + + // Re-computed at every read AND save + @ApiProperty({ example: 'JOHN DOE' }) + @Prop({ type: String }) + @DerivedField
((e) => `${e.firstName} ${e.lastName}`.toUpperCase(), { on: 'both' }) + displayName: string; +} +``` + +#### POST /articles + +**Request body:** +```json +{ "firstName": "John", "lastName": "Doe" } +``` + +**Response (201):** +```json +{ + "id": "...", + "firstName": "John", + "lastName": "Doe", + "fullName": "John Doe", + "displayName": "JOHN DOE", + "createdAt": "...", + "updatedAt": "..." +} +``` + +--- + +### `@ProtectedField()` + +Marks a field as **server-only**. It is automatically excluded from all mutating body DTOs (`CreateOne`, `CreateMany`, `UpdateOne`, `UpdateMany`, `ReplaceOne`, `DuplicateOne`, `DuplicateMany`) without needing manual `additionalKeysToExclude` lists. Clients cannot set or override this field. + +> ⚠️ If you supply a custom `dTOs.body`, `@ProtectedField` auto-exclusion is **bypassed** — manage exclusions manually in that case. + +#### Example + +```typescript +import { Prop, Schema } from '@nestjs/mongoose'; +import { BaseEntity, ProtectedField } from 'mongodb-dynamic-api'; +import { ApiProperty } from '@nestjs/swagger'; + +@Schema({ collection: 'products' }) +export class Product extends BaseEntity { + @ApiProperty({ example: 'Laptop' }) + @Prop({ type: String, required: true }) + name: string; + + // Stored internally, never writable by clients + @Prop({ type: String }) + @ProtectedField() + internalSku: string; + + // Set server-side via fromUser (see Route Config docs) + @Prop({ type: String }) + @ProtectedField() + createdBy: string; +} +``` + +**POST /products body** (`internalSku` and `createdBy` are stripped from the DTO): +```json +{ "name": "Laptop" } +``` + +#### Combo pattern: `@ProtectedField` + `fromUser` + +Mark a field `@ProtectedField()` **and** map it in `fromUser` on the route → clients cannot submit the value, but the server auto-injects it from the JWT: + +```typescript +// Entity +@ProtectedField() +@Prop({ type: String }) +createdBy: string; + +// Route config +{ + type: 'CreateOne', + fromUser: { createdBy: 'email' }, +} +``` --- diff --git a/README/route-config.md b/README/route-config.md index 30ebe22..69effc7 100644 --- a/README/route-config.md +++ b/README/route-config.md @@ -38,6 +38,7 @@ Each route in `DynamicApiModule.forFeature` can be finely configured through the - [abilityPredicate](#abilitypredicate) - [isArrayResponse](#isarrayresponse) - [useInterceptors](#useinterceptors) + - [fromUser](#fromuser) - [webSocket](#websocket) - [eventName](#eventname) - [broadcast](#broadcast) @@ -810,6 +811,85 @@ routes: [ --- +### fromUser + +Automatically injects values from the **authenticated user's JWT payload** (`req.user`) into the body before the `beforeSaveCallback` and persistence. Supported on all mutating routes: `CreateOne`, `CreateMany`, `UpdateOne`, `UpdateMany`, `ReplaceOne`, `DuplicateOne`, `DuplicateMany`. + +> 📌 **Execution order:** `fromUser` injection → `beforeSaveCallback` → `@DerivedField` computation → DB write. + +#### Signature + +```typescript +type FromUserMap = Partial< + Record unknown)> +>; +``` + +| Source type | Description | +|-------------|-------------| +| `string` | Key name on `req.user` — value is read as `req.user[key]` | +| `(user) => value` | Extractor function — receives `req.user` and returns the value | + +> ⚠️ If `req.user` is `undefined` or `null` (unauthenticated route), `fromUser` is silently skipped. + +#### Example + +```typescript +// Entity +@Schema({ collection: 'posts' }) +export class Post extends BaseEntity { + @Prop({ type: String, required: true }) + title: string; + + @Prop({ type: String }) + @ProtectedField() // Cannot be set by the client + createdBy: string; + + @Prop({ type: String }) + @ProtectedField() + tenantId: string; +} +``` + +```typescript +// Module +DynamicApiModule.forFeature({ + entity: Post, + controllerOptions: { path: 'posts' }, + routes: [ + { + type: 'CreateOne', + fromUser: { + // Inject req.user.email into createdBy + createdBy: 'email', + // Inject computed value from req.user + tenantId: (user) => (user as JwtPayload).tenantId, + }, + }, + ], +}) +``` + +**POST /posts** (JWT: `{ email: "alice@co.com", tenantId: "tenant-42" }`) + +Request body (client sends): +```json +{ "title": "Hello World" } +``` + +Persisted document: +```json +{ + "title": "Hello World", + "createdBy": "alice@co.com", + "tenantId": "tenant-42" +} +``` + +> 💡 Combine `@ProtectedField()` with `fromUser` for the most secure pattern: the field is excluded from the DTO (client cannot submit it) **and** automatically filled from JWT. See [Entities docs](./entities.md#combo-pattern-protectedfield--fromuser). + +--- + ## Examples ### Complete Route Configuration diff --git a/libs/dynamic-api/src/decorators/derived-field.decorator.spec.ts b/libs/dynamic-api/src/decorators/derived-field.decorator.spec.ts new file mode 100644 index 0000000..7d36c7b --- /dev/null +++ b/libs/dynamic-api/src/decorators/derived-field.decorator.spec.ts @@ -0,0 +1,80 @@ +import 'reflect-metadata'; +import { DERIVED_FIELD_KEYS_METADATA, DERIVED_FIELD_METADATA, DerivedField, DerivedFieldMeta } from './derived-field.decorator'; + +class TestEntity { + name: string; + slug: string; + score: number; +} + +describe('DerivedField', () => { + describe('metadata storage', () => { + it('should store computeFn and default on:"save" on the property', () => { + const computeFn = (e: Partial) => e.name?.toLowerCase(); + + DerivedField(computeFn)(TestEntity.prototype, 'slug'); + + const meta: DerivedFieldMeta = Reflect.getMetadata( + DERIVED_FIELD_METADATA, + TestEntity.prototype, + 'slug', + ); + + expect(meta).toBeDefined(); + expect(meta.computeFn).toBe(computeFn); + expect(meta.on).toBe('save'); + }); + + it('should respect custom on option', () => { + const computeFn = (e: Partial) => e.name?.length ?? 0; + + DerivedField(computeFn, { on: 'read' })(TestEntity.prototype, 'score'); + + const meta: DerivedFieldMeta = Reflect.getMetadata( + DERIVED_FIELD_METADATA, + TestEntity.prototype, + 'score', + ); + + expect(meta.on).toBe('read'); + }); + + it('should append the property key to the DERIVED_FIELD_KEYS_METADATA list', () => { + class FreshEntity { + a: string; + b: string; + } + + const fn = () => 'x'; + DerivedField(fn)(FreshEntity.prototype, 'a'); + DerivedField(fn)(FreshEntity.prototype, 'b'); + + const keys: string[] = Reflect.getMetadata(DERIVED_FIELD_KEYS_METADATA, FreshEntity.prototype); + expect(keys).toContain('a'); + expect(keys).toContain('b'); + }); + }); + + describe('computeFn behaviour', () => { + it('computeFn receives entity snapshot and returns derived value', () => { + class Post { + firstName: string; + lastName: string; + fullName: string; + } + + const fullNameFn = (e: Partial) => `${e.firstName} ${e.lastName}`; + DerivedField(fullNameFn)(Post.prototype, 'fullName'); + + const meta: DerivedFieldMeta = Reflect.getMetadata( + DERIVED_FIELD_METADATA, + Post.prototype, + 'fullName', + ); + + expect(meta.computeFn({ firstName: 'John', lastName: 'Doe' })).toBe('John Doe'); + }); + }); +}); + + diff --git a/libs/dynamic-api/src/decorators/derived-field.decorator.ts b/libs/dynamic-api/src/decorators/derived-field.decorator.ts new file mode 100644 index 0000000..b7b610d --- /dev/null +++ b/libs/dynamic-api/src/decorators/derived-field.decorator.ts @@ -0,0 +1,37 @@ +const DERIVED_FIELD_METADATA = 'dynamic-api-module:derived-field'; +const DERIVED_FIELD_KEYS_METADATA = 'dynamic-api-module:derived-field-keys'; + +interface DerivedFieldOptions { + on?: 'save' | 'read' | 'both'; +} + +interface DerivedFieldMeta { + computeFn: (entity: Partial) => unknown; + on: 'save' | 'read' | 'both'; +} + +function DerivedField( + computeFn: (entity: Partial) => unknown, + options?: DerivedFieldOptions, +): PropertyDecorator { + return (target: object, propertyKey: string | symbol) => { + const existing: (string | symbol)[] = + Reflect.getMetadata(DERIVED_FIELD_KEYS_METADATA, target) ?? []; + + Reflect.defineMetadata( + DERIVED_FIELD_KEYS_METADATA, + [...existing, propertyKey], + target, + ); + + Reflect.defineMetadata( + DERIVED_FIELD_METADATA, + { computeFn, on: options?.on ?? 'save' } satisfies DerivedFieldMeta, + target, + propertyKey, + ); + }; +} + +export { DERIVED_FIELD_METADATA, DERIVED_FIELD_KEYS_METADATA, DerivedField, DerivedFieldMeta, DerivedFieldOptions }; + diff --git a/libs/dynamic-api/src/decorators/index.ts b/libs/dynamic-api/src/decorators/index.ts index a1e15b0..79a728e 100644 --- a/libs/dynamic-api/src/decorators/index.ts +++ b/libs/dynamic-api/src/decorators/index.ts @@ -1,5 +1,7 @@ export * from './api-endpoint-visibility.decorator'; +export * from './derived-field.decorator'; export * from './disable-cache.decorator'; +export * from './protected-field.decorator'; export * from './public.decorator'; export * from './schema-options.decorator'; export * from './validator-pipe.decorator'; diff --git a/libs/dynamic-api/src/decorators/protected-field.decorator.spec.ts b/libs/dynamic-api/src/decorators/protected-field.decorator.spec.ts new file mode 100644 index 0000000..3320505 --- /dev/null +++ b/libs/dynamic-api/src/decorators/protected-field.decorator.spec.ts @@ -0,0 +1,47 @@ +import 'reflect-metadata'; +import { PROTECTED_FIELD_METADATA, ProtectedField } from './protected-field.decorator'; + +describe('ProtectedField', () => { + describe('metadata storage', () => { + it('should store the property key in the PROTECTED_FIELD_METADATA list on the prototype', () => { + class MyEntity { + public: string; + secret: string; + } + + ProtectedField()(MyEntity.prototype, 'secret'); + + const keys: string[] = Reflect.getMetadata(PROTECTED_FIELD_METADATA, MyEntity.prototype); + expect(keys).toContain('secret'); + expect(keys).not.toContain('public'); + }); + + it('should accumulate multiple protected fields', () => { + class AnotherEntity { + visible: string; + passwordHash: string; + internalCode: string; + } + + ProtectedField()(AnotherEntity.prototype, 'passwordHash'); + ProtectedField()(AnotherEntity.prototype, 'internalCode'); + + const keys: string[] = Reflect.getMetadata(PROTECTED_FIELD_METADATA, AnotherEntity.prototype); + expect(keys).toContain('passwordHash'); + expect(keys).toContain('internalCode'); + expect(keys).not.toContain('visible'); + expect(keys).toHaveLength(2); + }); + + it('should return undefined for a class with no @ProtectedField', () => { + class CleanEntity { + name: string; + } + + const keys = Reflect.getMetadata(PROTECTED_FIELD_METADATA, CleanEntity.prototype); + expect(keys).toBeUndefined(); + }); + }); +}); + + diff --git a/libs/dynamic-api/src/decorators/protected-field.decorator.ts b/libs/dynamic-api/src/decorators/protected-field.decorator.ts new file mode 100644 index 0000000..e1aaee1 --- /dev/null +++ b/libs/dynamic-api/src/decorators/protected-field.decorator.ts @@ -0,0 +1,17 @@ +const PROTECTED_FIELD_METADATA = 'dynamic-api-module:protected-field'; + +function ProtectedField(): PropertyDecorator { + return (target: object, propertyKey: string | symbol) => { + const existing: (string | symbol)[] = + Reflect.getMetadata(PROTECTED_FIELD_METADATA, target) ?? []; + + Reflect.defineMetadata( + PROTECTED_FIELD_METADATA, + [...existing, propertyKey], + target, + ); + }; +} + +export { PROTECTED_FIELD_METADATA, ProtectedField }; + diff --git a/libs/dynamic-api/src/helpers/from-user.helper.spec.ts b/libs/dynamic-api/src/helpers/from-user.helper.spec.ts new file mode 100644 index 0000000..cb08bd3 --- /dev/null +++ b/libs/dynamic-api/src/helpers/from-user.helper.spec.ts @@ -0,0 +1,63 @@ +import { BaseEntity } from '../models'; +import { applyFromUser } from './from-user.helper'; + +class TestEntity extends BaseEntity { + name: string; + createdBy: string; + tenantId: string; +} + +describe('applyFromUser', () => { + const partial: Partial = { name: 'foo' }; + const user = { email: 'alice@example.com', tid: 'tenant-42' }; + + it('should return partial unchanged when fromUser is undefined', () => { + expect(applyFromUser(partial, undefined, user)).toEqual(partial); + }); + + it('should return partial unchanged when user is nullish', () => { + expect(applyFromUser(partial, { createdBy: 'email' }, undefined)).toEqual(partial); + expect(applyFromUser(partial, { createdBy: 'email' }, null)).toEqual(partial); + }); + + it('should inject a string claim from user', () => { + const result = applyFromUser(partial, { createdBy: 'email' }, user); + expect(result.createdBy).toBe('alice@example.com'); + expect(result.name).toBe('foo'); + }); + + it('should inject value from extractor function', () => { + const result = applyFromUser( + partial, + { tenantId: (u: unknown) => (u as typeof user).tid }, + user, + ); + expect(result.tenantId).toBe('tenant-42'); + }); + + it('should inject multiple fields', () => { + const result = applyFromUser( + partial, + { + createdBy: 'email', + tenantId: (u: unknown) => (u as typeof user).tid, + }, + user, + ); + expect(result.createdBy).toBe('alice@example.com'); + expect(result.tenantId).toBe('tenant-42'); + }); + + it('should not mutate the original partial', () => { + const original: Partial = { name: 'bar' }; + applyFromUser(original, { createdBy: 'email' }, user); + expect(original).not.toHaveProperty('createdBy'); + }); + + it('should skip field if claim key does not exist on user', () => { + const result = applyFromUser(partial, { createdBy: 'nonExistentClaim' }, user); + expect(result.createdBy).toBeUndefined(); + }); +}); + + diff --git a/libs/dynamic-api/src/helpers/from-user.helper.ts b/libs/dynamic-api/src/helpers/from-user.helper.ts new file mode 100644 index 0000000..747d115 --- /dev/null +++ b/libs/dynamic-api/src/helpers/from-user.helper.ts @@ -0,0 +1,35 @@ +import { BaseEntity } from '../models'; +import { FromUserMap } from '../interfaces'; + +/** + * Injects values from the authenticated user (JWT payload) into a partial entity. + * Fields mapped to a string are resolved from `user[string]`. + * Fields mapped to a function receive the whole user object. + * If `user` is nullish, the partial is returned unchanged. + */ +function applyFromUser( + partial: Partial, + fromUser: FromUserMap | undefined, + user: unknown, +): Partial { + if (!fromUser || !user) { + return partial; + } + + const result: Partial = { ...partial }; + + for (const field of Object.keys(fromUser) as (keyof Entity)[]) { + const source = fromUser[field]; + + if (typeof source === 'function') { + result[field] = source(user) as Entity[typeof field]; + } else if (typeof source === 'string' && typeof user === 'object' && user !== null) { + result[field] = (user as Record)[source] as Entity[typeof field]; + } + } + + return result; +} + +export { applyFromUser }; + diff --git a/libs/dynamic-api/src/helpers/index.ts b/libs/dynamic-api/src/helpers/index.ts index b11aade..de44245 100644 --- a/libs/dynamic-api/src/helpers/index.ts +++ b/libs/dynamic-api/src/helpers/index.ts @@ -1,5 +1,6 @@ export * from './controller-ability-predicates.helper'; export * from './format.helper'; +export * from './from-user.helper'; export * from './lodash.helper'; export * from './mixin-data.helper'; export * from './repository.helper'; diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts index bf4332e..2c8a569 100644 --- a/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts +++ b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts @@ -8,6 +8,20 @@ import { AnyBeforeSaveCallback } from './dynamic-api-service-before-save-callbac import { AfterSaveCallback } from './dynamic-api-service-callback.interface'; import { DynamicApiWebSocketOptions } from './dynamic-api-web-socket.interface'; +/** + * Maps entity fields to JWT claim names or extractor functions. + * When a request is processed, the mapped values are injected into the body + * before validation and persistence. + * + * @example + * // Inject `req.user.email` into `createdBy`, run a function for `tenantId` + * fromUser: { + * createdBy: 'email', + * tenantId: (user) => (user as JwtPayload).tenantId, + * } + */ +type FromUserMap = Partial unknown)>>; + interface DynamicApiRouteConfig { type: RouteType; isPublic?: boolean; @@ -25,6 +39,7 @@ interface DynamicApiRouteConfig { broadcast?: DynamicApiBroadcastConfig; isArrayResponse?: boolean; useInterceptors?: Type[]; + fromUser?: FromUserMap; } /** @@ -32,4 +47,4 @@ interface DynamicApiRouteConfig { */ type DynamicAPIRouteConfig = DynamicApiRouteConfig; -export { DynamicApiRouteConfig, DynamicAPIRouteConfig }; +export { DynamicApiRouteConfig, DynamicAPIRouteConfig, FromUserMap }; diff --git a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts index acc0e22..a536a16 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts @@ -1,4 +1,5 @@ import { Type } from '@nestjs/common'; +import { ProtectedField } from '../decorators/protected-field.decorator'; import { BaseEntity, SoftDeletableEntity } from '../models'; import { EntityBodyMixin } from './entity-body.mixin'; @@ -12,6 +13,14 @@ class DeletableEntity extends SoftDeletableEntity { unit = 'test'; } +class EntityWithProtected extends BaseEntity { + name = 'visible'; + passwordHash = 'secret'; + internalCode = 'int-001'; +} +ProtectedField()(EntityWithProtected.prototype, 'passwordHash'); +ProtectedField()(EntityWithProtected.prototype, 'internalCode'); + describe('EntityBodyMixin', () => { let body: Type; const additionalKeysToExclude = ['additionalKey'] as (keyof Entity)[]; @@ -32,4 +41,28 @@ describe('EntityBodyMixin', () => { body = EntityBodyMixin(Entity, true, additionalKeysToExclude); expect(new body()).toEqual({ unit: 'test'}); }); + + it('should auto-exclude @ProtectedField keys', () => { + body = EntityBodyMixin(EntityWithProtected); + const instance = new body(); + expect(instance).toHaveProperty('name'); + expect(instance).not.toHaveProperty('passwordHash'); + expect(instance).not.toHaveProperty('internalCode'); + }); + + it('should combine @ProtectedField auto-exclusion with manual additionalKeysToExclude', () => { + class Combo extends BaseEntity { + pub = 'visible'; + manual = 'manual-excluded'; + secret = 'auto-excluded'; + } + ProtectedField()(Combo.prototype, 'secret'); + + body = EntityBodyMixin(Combo, false, ['manual'] as (keyof Combo)[]); + const instance = new body(); + expect(instance).toHaveProperty('pub'); + expect(instance).not.toHaveProperty('manual'); + expect(instance).not.toHaveProperty('secret'); + }); }); + diff --git a/libs/dynamic-api/src/mixins/entity-body.mixin.ts b/libs/dynamic-api/src/mixins/entity-body.mixin.ts index 92a9b64..3728358 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.ts @@ -1,5 +1,6 @@ import { Type } from '@nestjs/common'; import { OmitType, PartialType } from '@nestjs/swagger'; +import { PROTECTED_FIELD_METADATA } from '../decorators/protected-field.decorator'; import { BaseEntity } from '../models'; const baseEntityKeysToExclude = () => @@ -13,13 +14,22 @@ const baseEntityKeysToExclude = () => '__v', ] as (keyof Entity)[]; +function getProtectedFieldKeys(entity: Type): (keyof Entity)[] { + return ( + (Reflect.getMetadata(PROTECTED_FIELD_METADATA, entity.prototype) as (keyof Entity)[] | undefined) ?? [] + ); +} + function EntityBodyMixin( entity: Type, optional = false, additionalKeysToExclude?: (keyof Entity)[], ) { + const protectedKeys = getProtectedFieldKeys(entity); + const keysToExclude = [ ...baseEntityKeysToExclude(), + ...protectedKeys, ...(additionalKeysToExclude ?? []), ]; @@ -29,4 +39,4 @@ function EntityBodyMixin( return optional ? PartialType(EntityBody) : EntityBody; } -export { baseEntityKeysToExclude, EntityBodyMixin }; +export { baseEntityKeysToExclude, EntityBodyMixin, getProtectedFieldKeys }; diff --git a/libs/dynamic-api/src/routes/create-many/base-create-many.service.ts b/libs/dynamic-api/src/routes/create-many/base-create-many.service.ts index 12ff37e..d692e86 100644 --- a/libs/dynamic-api/src/routes/create-many/base-create-many.service.ts +++ b/libs/dynamic-api/src/routes/create-many/base-create-many.service.ts @@ -28,7 +28,7 @@ export abstract class BaseCreateManyService async createMany(partials: Partial[], user?: unknown): Promise { try { - const toCreate = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( undefined, { toCreate: cloneDeep(partials) }, @@ -37,6 +37,8 @@ export abstract class BaseCreateManyService ) : cloneDeep(partials); + const toCreate = afterCallback.map((p) => this.applyDerivedFields(p, 'save')); + const created = await this.model.create( toCreate.map((p) => plainToInstance(this.entity, p)), ); diff --git a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts index f480458..2437918 100644 --- a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts @@ -1,6 +1,6 @@ import { BadRequestException, Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -13,7 +13,7 @@ import { CreateManyService } from './create-many-service.interface'; function CreateManyControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): CreateManyControllerConstructor { const { @@ -93,7 +93,11 @@ function CreateManyControllerMixin( CreateManyBody as Mappable ).toEntities; - const list = await this.service.createMany(toEntities ? toEntities(body) : toCreateList, req?.user); + const rawList = toEntities ? toEntities(body) : toCreateList; + const list = await this.service.createMany( + rawList.map((p) => applyFromUser(p, fromUser, req?.user)), + req?.user, + ); const fromEntities = ( CreateManyPresenter as Mappable diff --git a/libs/dynamic-api/src/routes/create-one/base-create-one.service.spec.ts b/libs/dynamic-api/src/routes/create-one/base-create-one.service.spec.ts index 2c32ed4..d58dcd4 100644 --- a/libs/dynamic-api/src/routes/create-one/base-create-one.service.spec.ts +++ b/libs/dynamic-api/src/routes/create-one/base-create-one.service.spec.ts @@ -130,5 +130,28 @@ describe('BaseCreateOneService', () => { fakeUser, ); }); + + it('should call applyDerivedFields with "save" trigger after beforeSaveCallback', async () => { + service = initService(created); + const applyDerivedFieldsSpy = jest + .spyOn(service as unknown as { applyDerivedFields: jest.Mock }, 'applyDerivedFields') + .mockImplementation((p) => p); + + await service.createOne(toCreate); + + expect(applyDerivedFieldsSpy).toHaveBeenCalledWith(toCreate, 'save'); + }); + + it('should apply applyDerivedFields result to the entity persisted', async () => { + service = initService(created); + const withDerived = { ...toCreate, slug: 'test-slug' }; + jest + .spyOn(service as unknown as { applyDerivedFields: jest.Mock }, 'applyDerivedFields') + .mockReturnValue(withDerived); + + await service.createOne(toCreate); + + expect(modelMock.create).toHaveBeenCalledWith(expect.objectContaining({ slug: 'test-slug' })); + }); }); }); diff --git a/libs/dynamic-api/src/routes/create-one/base-create-one.service.ts b/libs/dynamic-api/src/routes/create-one/base-create-one.service.ts index b18e7b9..375f628 100644 --- a/libs/dynamic-api/src/routes/create-one/base-create-one.service.ts +++ b/libs/dynamic-api/src/routes/create-one/base-create-one.service.ts @@ -26,7 +26,7 @@ export abstract class BaseCreateOneService async createOne(partial: Partial, user?: unknown): Promise { try { - const toCreate = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( undefined, { toCreate: cloneDeep(partial) }, @@ -35,6 +35,8 @@ export abstract class BaseCreateOneService ) : cloneDeep(partial); + const toCreate = this.applyDerivedFields(afterCallback, 'save'); + const { _id } = await this.model.create(plainToInstance(this.entity, toCreate)); const document = await this.model.findOne({ _id }).lean().exec(); diff --git a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts index 3eee030..936d8a3 100644 --- a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts @@ -1,6 +1,6 @@ import { Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -11,7 +11,7 @@ import { CreateOneService } from './create-one-service.interface'; function CreateOneControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): CreateOneControllerConstructor { const { @@ -80,7 +80,10 @@ function CreateOneControllerMixin( CreateOneBody as Mappable ).toEntity; - const entity = await this.service.createOne(toEntity ? toEntity(body) : body as Partial, req?.user); + const rawPartial = toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + + const entity = await this.service.createOne(partial, req?.user); const fromEntity = ( CreateOnePresenter as Mappable diff --git a/libs/dynamic-api/src/routes/duplicate-many/base-duplicate-many.service.ts b/libs/dynamic-api/src/routes/duplicate-many/base-duplicate-many.service.ts index f193b55..60ffccf 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/base-duplicate-many.service.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/base-duplicate-many.service.ts @@ -56,7 +56,7 @@ export abstract class BaseDuplicateManyService ...partial, })); - const toCreateList = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( toDuplicateList, { ids, override: partial ? cloneDeep(partial) : undefined }, @@ -65,6 +65,8 @@ export abstract class BaseDuplicateManyService ) : baseDataList; + const toCreateList = afterCallback.map((d) => this.applyDerivedFields(d, 'save')); + const duplicatedList = await this.model.create(toCreateList.map((d) => plainToInstance( this.entity, d, diff --git a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts index 40de32d..e9e5986 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts @@ -1,6 +1,6 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -11,7 +11,7 @@ import { DuplicateManyService } from './duplicate-many-service.interface'; function DuplicateManyControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): DuplicateManyControllerConstructor { const { @@ -88,9 +88,12 @@ function DuplicateManyControllerMixin( DuplicateManyBody as Mappable ).toEntity; + const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + const list = await this.service.duplicateMany( ids, - !isEmpty(body) && toEntity ? toEntity(body) : body as Partial, + partial, req?.user, ); diff --git a/libs/dynamic-api/src/routes/duplicate-one/base-duplicate-one.service.ts b/libs/dynamic-api/src/routes/duplicate-one/base-duplicate-one.service.ts index 9df3c43..3951bd5 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/base-duplicate-one.service.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/base-duplicate-one.service.ts @@ -52,7 +52,7 @@ export abstract class BaseDuplicateOneService ...partial, }; - const toCreate = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( this.addDocumentId(toDuplicate), { id, override: partial ? cloneDeep(partial) : undefined }, @@ -61,6 +61,8 @@ export abstract class BaseDuplicateOneService ) : baseData; + const toCreate = this.applyDerivedFields(afterCallback, 'save'); + const { _id } = await this.model.create(plainToInstance(this.entity, toCreate)); const document = await this.model.findOne({ _id }).lean().exec(); diff --git a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts index b45f9d6..ce7d997 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -12,7 +12,7 @@ import { DuplicateOneService } from './duplicate-one-service.interface'; function DuplicateOneControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): DuplicateOneControllerConstructor { const { @@ -86,9 +86,12 @@ function DuplicateOneControllerMixin( DuplicateOneBody as Mappable ).toEntity; + const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + const entity = await this.service.duplicateOne( id, - !isEmpty(body) && toEntity ? toEntity(body) : body as Partial, + partial, req?.user, ); diff --git a/libs/dynamic-api/src/routes/replace-one/base-replace-one.service.ts b/libs/dynamic-api/src/routes/replace-one/base-replace-one.service.ts index c614074..12a3ba5 100644 --- a/libs/dynamic-api/src/routes/replace-one/base-replace-one.service.ts +++ b/libs/dynamic-api/src/routes/replace-one/base-replace-one.service.ts @@ -42,7 +42,7 @@ export abstract class BaseReplaceOneService this.handleDocumentNotFound(); } - const replacement = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( this.addDocumentId(existingDocument), { id, replacement: cloneDeep(partial) }, @@ -51,6 +51,8 @@ export abstract class BaseReplaceOneService ) : cloneDeep(partial); + const replacement = this.applyDerivedFields(afterCallback, 'save'); + const document = await this.model .findOneAndReplace( { _id: id }, diff --git a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts index d753015..7d027d8 100644 --- a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; -import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -12,7 +12,7 @@ import { ReplaceOneService } from './replace-one-service.interface'; function ReplaceOneControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): ReplaceOneControllerConstructor { const { @@ -86,7 +86,10 @@ function ReplaceOneControllerMixin( ReplaceOneBody as Mappable ).toEntity; - const entity = await this.service.replaceOne(id, toEntity ? toEntity(body) : body as Partial, req?.user); + const rawPartial = toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + + const entity = await this.service.replaceOne(id, partial, req?.user); const fromEntity = ( ReplaceOnePresenter as Mappable diff --git a/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts b/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts index 85819af..dde40c3 100644 --- a/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts +++ b/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts @@ -37,8 +37,10 @@ export abstract class BaseUpdateManyService user, ); + const updatesWithDerived = updates.map((u) => this.applyDerivedFields(u, 'save')); + await Promise.all( - updates.map((update, index) => + updatesWithDerived.map((update, index) => this.model .findByIdAndUpdate( toUpdateList[index]._id, @@ -50,6 +52,8 @@ export abstract class BaseUpdateManyService ), ); } else { + const partialWithDerived = this.applyDerivedFields(partial, 'save'); + await this.model .updateMany( { @@ -58,7 +62,7 @@ export abstract class BaseUpdateManyService this.isSoftDeletable ? { isDeleted: false } : undefined ), }, - partial, + partialWithDerived, ) .lean() .exec(); diff --git a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts index 7f0e9a7..c8fdf46 100644 --- a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts @@ -1,6 +1,6 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -11,7 +11,7 @@ import { UpdateManyService } from './update-many-service.interface'; function UpdateManyControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): UpdateManyControllerConstructor { const { @@ -92,7 +92,10 @@ function UpdateManyControllerMixin( UpdateManyBody as Mappable ).toEntity; - const list = await this.service.updateMany(ids, toEntity ? toEntity(body) : body as Partial, req?.user); + const rawPartial = toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + + const list = await this.service.updateMany(ids, partial, req?.user); const fromEntities = ( UpdateManyPresenter as Mappable diff --git a/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts b/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts index 9cdca96..7b3a32d 100644 --- a/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts +++ b/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts @@ -37,7 +37,7 @@ export abstract class BaseUpdateOneService this.handleDocumentNotFound(); } - const update = this.beforeSaveCallback + const afterCallback = this.beforeSaveCallback ? await this.beforeSaveCallback( this.addDocumentId(document), { id, update: cloneDeep(partial) }, @@ -46,6 +46,8 @@ export abstract class BaseUpdateOneService ) : cloneDeep(partial); + const update = this.applyDerivedFields(afterCallback, 'save'); + const updatedDocument = await this.model .findOneAndUpdate( { _id: id }, diff --git a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts index d10c98d..73ce1ac 100644 --- a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; @@ -12,7 +12,7 @@ import { UpdateOneService } from './update-one-service.interface'; function UpdateOneControllerMixin( entity: Type, controllerOptions: DynamicApiControllerOptions, - { dTOs, useInterceptors = [], broadcast: broadcastConfig, ...routeConfig }: DynamicAPIRouteConfig, + { dTOs, useInterceptors = [], broadcast: broadcastConfig, fromUser, ...routeConfig }: DynamicAPIRouteConfig, version?: string, ): UpdateOneControllerConstructor { const { @@ -90,7 +90,10 @@ function UpdateOneControllerMixin( UpdateOneBody as Mappable ).toEntity; - const entity = await this.service.updateOne(id, toEntity ? toEntity(body) : body as Partial, req?.user); + const rawPartial = toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(rawPartial, fromUser, req?.user); + + const entity = await this.service.updateOne(id, partial, req?.user); const fromEntity = ( UpdateOnePresenter as Mappable diff --git a/libs/dynamic-api/src/services/base/base.service.spec.ts b/libs/dynamic-api/src/services/base/base.service.spec.ts index c6a327c..127876f 100644 --- a/libs/dynamic-api/src/services/base/base.service.spec.ts +++ b/libs/dynamic-api/src/services/base/base.service.spec.ts @@ -675,4 +675,99 @@ describe('BaseService', () => { expect(result).toEqual({ id: fakeId, ...data }); }); }); + + describe('applyDerivedFields', () => { + class DerivedEntity extends BaseEntity { + firstName: string; + lastName: string; + fullName: string; + score: number; + displayScore: string; + } + + // Register derived field metadata manually (simulating @DerivedField decorator) + const fullNameFn = (e: Partial) => `${e.firstName} ${e.lastName}`; + const scoreFn = (e: Partial) => `Score: ${e.score}`; + const readOnlyFn = (e: Partial) => `readonly:${e.firstName}`; + + beforeEach(() => { + // Reset metadata for a fresh state per test + Reflect.defineMetadata('dynamic-api-module:derived-field-keys', [], DerivedEntity.prototype); + }); + + it('should return partial unchanged when no derived keys registered', () => { + const service = new TestService({} as unknown as Model); + (service as unknown as { entity: typeof TestEntity }).entity = TestEntity; + const partial = { name: 'test' }; + expect(service['applyDerivedFields'](partial, 'save')).toEqual(partial); + }); + + it('should apply save-triggered derived field', () => { + Reflect.defineMetadata('dynamic-api-module:derived-field-keys', ['fullName'], DerivedEntity.prototype); + Reflect.defineMetadata('dynamic-api-module:derived-field', { computeFn: fullNameFn, on: 'save' }, DerivedEntity.prototype, 'fullName'); + + class DerivedService extends BaseService { + protected entity = DerivedEntity; + constructor() { super({} as unknown as Model); } + } + const svc = new DerivedService(); + const result = svc['applyDerivedFields']({ firstName: 'John', lastName: 'Doe' }, 'save'); + expect(result.fullName).toBe('John Doe'); + }); + + it('should NOT apply save field when trigger is read', () => { + Reflect.defineMetadata('dynamic-api-module:derived-field-keys', ['fullName'], DerivedEntity.prototype); + Reflect.defineMetadata('dynamic-api-module:derived-field', { computeFn: fullNameFn, on: 'save' }, DerivedEntity.prototype, 'fullName'); + + class DerivedService2 extends BaseService { + protected entity = DerivedEntity; + constructor() { super({} as unknown as Model); } + } + const svc = new DerivedService2(); + const result = svc['applyDerivedFields']({ firstName: 'John', lastName: 'Doe' }, 'read'); + expect(result.fullName).toBeUndefined(); + }); + + it('should apply both-triggered field on save and read', () => { + class BothEntity extends BaseEntity { + val: number; + double: number; + } + const doubleFn = (e: Partial) => (e.val ?? 0) * 2; + Reflect.defineMetadata('dynamic-api-module:derived-field-keys', ['double'], BothEntity.prototype); + Reflect.defineMetadata('dynamic-api-module:derived-field', { computeFn: doubleFn, on: 'both' }, BothEntity.prototype, 'double'); + + class BothService extends BaseService { + protected entity = BothEntity; + constructor() { super({} as unknown as Model); } + } + const svc = new BothService(); + + expect(svc['applyDerivedFields']({ val: 5 }, 'save').double).toBe(10); + expect(svc['applyDerivedFields']({ val: 3 }, 'read').double).toBe(6); + }); + + it('should use snapshot (not mutated result) for all computeFns', () => { + class SnapshotEntity extends BaseEntity { + x: number; + y: number; + sumXY: number; + diff: number; + } + const sumFn = (e: Partial) => (e.x ?? 0) + (e.y ?? 0); + const diffFn = (e: Partial) => (e.x ?? 0) - (e.y ?? 0); + Reflect.defineMetadata('dynamic-api-module:derived-field-keys', ['sumXY', 'diff'], SnapshotEntity.prototype); + Reflect.defineMetadata('dynamic-api-module:derived-field', { computeFn: sumFn, on: 'save' }, SnapshotEntity.prototype, 'sumXY'); + Reflect.defineMetadata('dynamic-api-module:derived-field', { computeFn: diffFn, on: 'save' }, SnapshotEntity.prototype, 'diff'); + + class SnapshotService extends BaseService { + protected entity = SnapshotEntity; + constructor() { super({} as unknown as Model); } + } + const svc = new SnapshotService(); + const result = svc['applyDerivedFields']({ x: 10, y: 3 }, 'save'); + expect(result.sumXY).toBe(13); + expect(result.diff).toBe(7); + }); + }); }); diff --git a/libs/dynamic-api/src/services/base/base.service.ts b/libs/dynamic-api/src/services/base/base.service.ts index e741faa..921193a 100644 --- a/libs/dynamic-api/src/services/base/base.service.ts +++ b/libs/dynamic-api/src/services/base/base.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, ConflictException, ForbiddenException, HttpExcepti import { plainToInstance } from 'class-transformer'; import { PipelineStage } from 'mongodb-pipeline-builder'; import { FilterQuery, Model, PipelineStage as MongoosePipelineStage, Schema, UpdateQuery, UpdateWithAggregationPipeline } from 'mongoose'; +import { DERIVED_FIELD_KEYS_METADATA, DERIVED_FIELD_METADATA, DerivedFieldMeta } from '../../decorators'; import { AbilityPredicate, AuthAbilityPredicate, DeleteResult, DynamicApiCallbackMethods, UpdateResult } from '../../interfaces'; import { MongoDBDynamicApiLogger } from '../../logger'; import { BaseEntity, SoftDeletableEntity } from '../../models'; @@ -203,7 +204,7 @@ export abstract class BaseService { ...rest } = document as unknown as SoftDeletableEntity; - return plainToInstance(this.entity, { + const instance = plainToInstance(this.entity, { ...rest as Partial, ...( _id && !id ? { id: _id?.toString() } : {} @@ -213,6 +214,43 @@ export abstract class BaseService { isDeleted ? { deletedAt } : {} ), }); + + return this.applyDerivedFields(instance as unknown as Partial, 'read') as Entity; + } + + /** + * Applies `@DerivedField` computed values to a partial entity snapshot. + * Only fields whose `on` option matches `trigger` (or is `'both'`) are computed. + * The `computeFn` receives the snapshot **before** any mutation to avoid circular deps. + */ + protected applyDerivedFields(partial: Partial, trigger: 'save' | 'read'): Partial { + if (!this.entity?.prototype) { + return partial; + } + + const keys: (string | symbol)[] = + Reflect.getMetadata(DERIVED_FIELD_KEYS_METADATA, this.entity.prototype) ?? []; + + if (!keys.length) { + return partial; + } + + const snapshot = { ...partial }; + const result = { ...partial }; + + for (const key of keys) { + const meta = Reflect.getMetadata( + DERIVED_FIELD_METADATA, + this.entity.prototype, + key, + ) as DerivedFieldMeta | undefined; + + if (meta && (meta.on === trigger || meta.on === 'both')) { + result[key as keyof Entity] = meta.computeFn(snapshot) as Entity[keyof Entity]; + } + } + + return result; } protected handleAbilityPredicate(document: Entity, authAbilityPredicate?: AuthAbilityPredicate) { diff --git a/libs/dynamic-api/test/for-feature/derived-fields-protected-from-user.e2e-spec.ts b/libs/dynamic-api/test/for-feature/derived-fields-protected-from-user.e2e-spec.ts new file mode 100644 index 0000000..3a0c4d5 --- /dev/null +++ b/libs/dynamic-api/test/for-feature/derived-fields-protected-from-user.e2e-spec.ts @@ -0,0 +1,251 @@ +/** + * E2E tests for @DerivedField, @ProtectedField and fromUser + * + * Strategy + * --------- + * @DerivedField — POST creates an entity whose `fullName` is computed + * server-side from firstName+lastName (on:'save') + * and `shortBio` is computed on read (on:'read'). + * @ProtectedField — `internalCode` is not accepted in POST/PATCH body + * (OmitType removes it from the DTO); trying to send it + * has no effect. + * fromUser — tested via a dedicated `describe` using a + * beforeSaveCallback that checks the field value was + * injected before the callback runs (no JWT needed here + * — we stub `req.user` via a custom interceptor applied + * at route level using `useInterceptors`). + */ + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Prop, Schema } from '@nestjs/mongoose'; +import mongoose from 'mongoose'; +import { Observable } from 'rxjs'; +import { + BaseEntity, + DerivedField, + DynamicApiModule, + DynamicAPISchemaOptions, + ProtectedField, +} from '../../src'; +import { closeTestingApp, server } from '../e2e.setup'; +import 'dotenv/config'; +import { initApp } from '../shared'; + +// ── Shared user injector interceptor ──────────────────────────────────────── + +@Injectable() +class FakeUserInterceptor implements NestInterceptor { + intercept(ctx: ExecutionContext, next: CallHandler): Observable { + const http = ctx.switchToHttp(); + const req = http.getRequest<{ user: Record }>(); + req.user = { email: 'alice@example.com', tenantId: 'tenant-42' }; + return next.handle(); + } +} + +// ── Entity definitions ─────────────────────────────────────────────────────── + +@DynamicAPISchemaOptions({ + indexes: [{ fields: { firstName: 1 }, options: { unique: true } }], +}) +@Schema({ collection: 'derived-test-items' }) +class ItemEntity extends BaseEntity { + @Prop({ type: String, required: true }) + firstName: string; + + @Prop({ type: String, required: true }) + lastName: string; + + @Prop({ type: String }) + @DerivedField((e) => `${e.firstName ?? ''} ${e.lastName ?? ''}`.trim()) + fullName: string; + + @Prop({ type: String }) + @DerivedField((e) => `Hi, my name is ${e.firstName}`, { on: 'read' }) + greeting: string; + + @Prop({ type: String }) + @ProtectedField() + internalCode: string; + + @Prop({ type: String }) + createdBy: string; +} + +// ── Test suite ─────────────────────────────────────────────────────────────── + +describe('DynamicApiModule forFeature — @DerivedField + @ProtectedField + fromUser (e2e)', () => { + + beforeEach(() => { + DynamicApiModule.state['resetState'](); + }); + + afterEach(async () => { + await closeTestingApp(mongoose.connections); + }); + + // ── @DerivedField (on:'save') ────────────────────────────────────────────── + describe('@DerivedField on save', () => { + beforeEach(async () => { + await initApp({ + entity: ItemEntity, + controllerOptions: { path: 'items', isPublic: true }, + routes: [ + { type: 'CreateOne' }, + { type: 'UpdateOne' }, + { type: 'GetMany' }, + { type: 'GetOne' }, + ], + }); + }); + + it('POST /items — should compute fullName on save', async () => { + const { body, status } = await server.post('/items', { + firstName: 'John', + lastName: 'Doe', + }); + + expect(status).toBe(201); + expect(body.fullName).toBe('John Doe'); + expect(body.firstName).toBe('John'); + expect(body.lastName).toBe('Doe'); + }); + + it('PATCH /items/:id — should recompute fullName on update', async () => { + const { body: created } = await server.post('/items', { + firstName: 'Jane', + lastName: 'Doe', + }); + + const { body, status } = await server.patch(`/items/${created.id}`, { + lastName: 'Smith', + }); + + expect(status).toBe(200); + expect(body.fullName).toBe('Jane Smith'); + }); + }); + + // ── @DerivedField (on:'read') ────────────────────────────────────────────── + describe('@DerivedField on read', () => { + beforeEach(async () => { + await initApp({ + entity: ItemEntity, + controllerOptions: { path: 'items', isPublic: true }, + routes: [ + { type: 'CreateOne' }, + { type: 'GetOne' }, + ], + }); + }); + + it('GET /items/:id — should compute greeting on read but not persist it', async () => { + const { body: created } = await server.post('/items', { + firstName: 'Bob', + lastName: 'Martin', + }); + + const { body, status } = await server.get(`/items/${created.id}`); + + expect(status).toBe(200); + expect(body.greeting).toBe('Hi, my name is Bob'); + // fullName is on:'save' so it should also be present + expect(body.fullName).toBe('Bob Martin'); + }); + }); + + // ── @ProtectedField ──────────────────────────────────────────────────────── + describe('@ProtectedField', () => { + beforeEach(async () => { + await initApp({ + entity: ItemEntity, + controllerOptions: { path: 'items', isPublic: true }, + routes: [ + { type: 'CreateOne' }, + { type: 'UpdateOne' }, + { type: 'GetOne' }, + ], + }); + }); + + it('POST /items — internalCode sent in body should be ignored', async () => { + const { body, status } = await server.post('/items', { + firstName: 'Protected', + lastName: 'User', + internalCode: 'secret-001', + }); + + expect(status).toBe(201); + // internalCode was excluded from the DTO so the value should not appear + expect(body.internalCode).toBeUndefined(); + }); + + it('PATCH /items/:id — internalCode in patch body should be ignored', async () => { + const { body: created } = await server.post('/items', { + firstName: 'Protected2', + lastName: 'User', + }); + + const { body, status } = await server.patch(`/items/${created.id}`, { + lastName: 'Updated', + internalCode: 'hacked-code', + }); + + expect(status).toBe(200); + expect(body.internalCode).toBeUndefined(); + }); + }); + + // ── fromUser field injection ─────────────────────────────────────────────── + describe('fromUser injection', () => { + beforeEach(async () => { + await initApp({ + entity: ItemEntity, + controllerOptions: { path: 'items', isPublic: true }, + routes: [ + { + type: 'CreateOne', + useInterceptors: [FakeUserInterceptor], + fromUser: { + createdBy: 'email', + }, + }, + { type: 'GetOne' }, + ], + }); + }); + + it('POST /items — should inject createdBy from req.user.email', async () => { + const { body, status } = await server.post('/items', { + firstName: 'FromUser', + lastName: 'Test', + }); + + expect(status).toBe(201); + expect(body.createdBy).toBe('alice@example.com'); + }); + + it('POST /items — client cannot override fromUser-injected field', async () => { + const { body, status } = await server.post('/items', { + firstName: 'Override2', + lastName: 'Attempt', + createdBy: 'evil@attacker.com', + }); + + expect(status).toBe(201); + // fromUser always wins over body value (applied after toEntity) + expect(body.createdBy).toBe('alice@example.com'); + }); + }); +}); + + + + + + From 4eaf187404cd60953a9cc5895bb3063eec308679 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 20:10:25 +0200 Subject: [PATCH 11/14] fix(decorators): fix @DerivedField on update and @ProtectedField runtime strip Fix @DerivedField recomputation on UpdateOne/UpdateMany: applyDerivedFields now accepts an optional existingDoc parameter so computeFn receives the full merged snapshot (existingDoc + partial) instead of the partial alone. This ensures fields absent from the PATCH body (e.g. firstName in a lastName-only update) are still available for derivation. Fix @ProtectedField not enforced at runtime: OmitType only removes properties from TypeScript types and Swagger docs, not from the parsed JSON body. Add stripProtectedFields() helper that deletes @ProtectedField keys from the partial at controller layer, after toEntity/fromUser injection. Applied in all 7 mutating controller mixins (CreateOne, CreateMany, UpdateOne, UpdateMany, ReplaceOne, DuplicateOne, DuplicateMany). All 7 e2e tests pass. 1042/1042 unit tests green. --- .../src/helpers/from-user.helper.ts | 2 +- .../src/mixins/entity-body.mixin.spec.ts | 33 ++++++++++++++++++- .../src/mixins/entity-body.mixin.ts | 24 ++++++++++++-- .../create-many-controller.mixin.ts | 4 +-- .../create-one/create-one-controller.mixin.ts | 4 +-- .../duplicate-many-controller.mixin.ts | 4 +-- .../duplicate-one-controller.mixin.ts | 4 +-- .../replace-one-controller.mixin.ts | 4 +-- .../update-many/base-update-many.service.ts | 4 ++- .../update-many-controller.mixin.ts | 4 +-- .../update-one/base-update-one.service.ts | 2 +- .../update-one/update-one-controller.mixin.ts | 4 +-- .../src/services/base/base.service.ts | 16 +++++++-- 13 files changed, 86 insertions(+), 23 deletions(-) diff --git a/libs/dynamic-api/src/helpers/from-user.helper.ts b/libs/dynamic-api/src/helpers/from-user.helper.ts index 747d115..06e6f1e 100644 --- a/libs/dynamic-api/src/helpers/from-user.helper.ts +++ b/libs/dynamic-api/src/helpers/from-user.helper.ts @@ -23,7 +23,7 @@ function applyFromUser( if (typeof source === 'function') { result[field] = source(user) as Entity[typeof field]; - } else if (typeof source === 'string' && typeof user === 'object' && user !== null) { + } else if (typeof source === 'string' && typeof user === 'object') { result[field] = (user as Record)[source] as Entity[typeof field]; } } diff --git a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts index a536a16..796acee 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts @@ -1,7 +1,7 @@ import { Type } from '@nestjs/common'; import { ProtectedField } from '../decorators/protected-field.decorator'; import { BaseEntity, SoftDeletableEntity } from '../models'; -import { EntityBodyMixin } from './entity-body.mixin'; +import { EntityBodyMixin, stripProtectedFields } from './entity-body.mixin'; class Entity extends BaseEntity { additionalKey = 'fake-key'; @@ -66,3 +66,34 @@ describe('EntityBodyMixin', () => { }); }); +describe('stripProtectedFields', () => { + class StripEntity extends BaseEntity { + name: string; + email: string; + internalCode: string; + passwordHash: string; + } + ProtectedField()(StripEntity.prototype, 'internalCode'); + ProtectedField()(StripEntity.prototype, 'passwordHash'); + + it('should remove @ProtectedField keys from partial at runtime', () => { + const partial: Partial = { name: 'Alice', internalCode: 'hack', passwordHash: 'bcrypt' }; + const result = stripProtectedFields(partial, StripEntity); + expect(result).toHaveProperty('name', 'Alice'); + expect(result).not.toHaveProperty('internalCode'); + expect(result).not.toHaveProperty('passwordHash'); + }); + + it('should return partial unchanged for entity with no @ProtectedField', () => { + class CleanEntity extends BaseEntity { val: string; } + const partial: Partial = { val: 'ok' }; + expect(stripProtectedFields(partial, CleanEntity)).toEqual(partial); + }); + + it('should not mutate the original partial', () => { + const original: Partial = { name: 'Bob', internalCode: 'leak' }; + stripProtectedFields(original, StripEntity); + expect(original).toHaveProperty('internalCode', 'leak'); + }); +}); + diff --git a/libs/dynamic-api/src/mixins/entity-body.mixin.ts b/libs/dynamic-api/src/mixins/entity-body.mixin.ts index 3728358..cc68337 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.ts @@ -1,6 +1,6 @@ import { Type } from '@nestjs/common'; import { OmitType, PartialType } from '@nestjs/swagger'; -import { PROTECTED_FIELD_METADATA } from '../decorators/protected-field.decorator'; +import { PROTECTED_FIELD_METADATA } from '../decorators'; import { BaseEntity } from '../models'; const baseEntityKeysToExclude = () => @@ -20,6 +20,26 @@ function getProtectedFieldKeys(entity: Type): ); } +/** + * Strips @ProtectedField keys from a partial entity at **runtime**. + * OmitType only removes keys from TypeScript types and Swagger docs — it does NOT + * prevent the JSON body from carrying those keys at runtime. This function must be + * called in controller mixins after body → partial conversion to enforce the protection. + */ +function stripProtectedFields( + partial: Partial, + entity: Type, +): Partial { + const keys = getProtectedFieldKeys(entity); + if (!keys.length) return partial; + + const result = { ...partial }; + for (const key of keys) { + delete result[key]; + } + return result; +} + function EntityBodyMixin( entity: Type, optional = false, @@ -39,4 +59,4 @@ function EntityBodyMixin( return optional ? PartialType(EntityBody) : EntityBody; } -export { baseEntityKeysToExclude, EntityBodyMixin, getProtectedFieldKeys }; +export { baseEntityKeysToExclude, EntityBodyMixin, getProtectedFieldKeys, stripProtectedFields }; diff --git a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts index 2437918..c23594e 100644 --- a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts @@ -2,7 +2,7 @@ import { BadRequestException, Body, Optional, Request, Type, UseGuards, UseInter import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateManyBodyMixin } from './create-many-body.mixin'; @@ -95,7 +95,7 @@ function CreateManyControllerMixin( const rawList = toEntities ? toEntities(body) : toCreateList; const list = await this.service.createMany( - rawList.map((p) => applyFromUser(p, fromUser, req?.user)), + rawList.map((p) => applyFromUser(stripProtectedFields(p, this.entity), fromUser, req?.user)), req?.user, ); diff --git a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts index 936d8a3..9eb99ce 100644 --- a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nest import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateOneController, CreateOneControllerConstructor } from './create-one-controller.interface'; @@ -81,7 +81,7 @@ function CreateOneControllerMixin( ).toEntity; const rawPartial = toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const entity = await this.service.createOne(partial, req?.user); diff --git a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts index e9e5986..5300e28 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateManyController, DuplicateManyControllerConstructor } from './duplicate-many-controller.interface'; @@ -89,7 +89,7 @@ function DuplicateManyControllerMixin( ).toEntity; const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const list = await this.service.duplicateMany( ids, diff --git a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts index ce7d997..4adbb7c 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts @@ -3,7 +3,7 @@ import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateOneController, DuplicateOneControllerConstructor } from './duplicate-one-controller.interface'; @@ -87,7 +87,7 @@ function DuplicateOneControllerMixin( ).toEntity; const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const entity = await this.service.duplicateOne( id, diff --git a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts index 7d027d8..b9280fe 100644 --- a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts @@ -3,7 +3,7 @@ import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { ReplaceOneController, ReplaceOneControllerConstructor } from './replace-one-controller.interface'; @@ -87,7 +87,7 @@ function ReplaceOneControllerMixin( ).toEntity; const rawPartial = toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const entity = await this.service.replaceOne(id, partial, req?.user); diff --git a/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts b/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts index dde40c3..98086d0 100644 --- a/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts +++ b/libs/dynamic-api/src/routes/update-many/base-update-many.service.ts @@ -37,7 +37,9 @@ export abstract class BaseUpdateManyService user, ); - const updatesWithDerived = updates.map((u) => this.applyDerivedFields(u, 'save')); + const updatesWithDerived = updates.map((u, index) => + this.applyDerivedFields(u, 'save', this.addDocumentId(toUpdateList[index]) as Partial), + ); await Promise.all( updatesWithDerived.map((update, index) => diff --git a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts index c8fdf46..10ef3fa 100644 --- a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateManyController, UpdateManyControllerConstructor } from './update-many-controller.interface'; @@ -93,7 +93,7 @@ function UpdateManyControllerMixin( ).toEntity; const rawPartial = toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const list = await this.service.updateMany(ids, partial, req?.user); diff --git a/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts b/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts index 7b3a32d..9b679ae 100644 --- a/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts +++ b/libs/dynamic-api/src/routes/update-one/base-update-one.service.ts @@ -46,7 +46,7 @@ export abstract class BaseUpdateOneService ) : cloneDeep(partial); - const update = this.applyDerivedFields(afterCallback, 'save'); + const update = this.applyDerivedFields(afterCallback, 'save', this.addDocumentId(document) as Partial); const updatedDocument = await this.model .findOneAndUpdate( diff --git a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts index 73ce1ac..9a29d7a 100644 --- a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts @@ -3,7 +3,7 @@ import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateOneController, UpdateOneControllerConstructor } from './update-one-controller.interface'; @@ -91,7 +91,7 @@ function UpdateOneControllerMixin( ).toEntity; const rawPartial = toEntity ? toEntity(body) : body as Partial; - const partial = applyFromUser(rawPartial, fromUser, req?.user); + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), fromUser, req?.user); const entity = await this.service.updateOne(id, partial, req?.user); diff --git a/libs/dynamic-api/src/services/base/base.service.ts b/libs/dynamic-api/src/services/base/base.service.ts index 921193a..6955ccd 100644 --- a/libs/dynamic-api/src/services/base/base.service.ts +++ b/libs/dynamic-api/src/services/base/base.service.ts @@ -221,9 +221,19 @@ export abstract class BaseService { /** * Applies `@DerivedField` computed values to a partial entity snapshot. * Only fields whose `on` option matches `trigger` (or is `'both'`) are computed. - * The `computeFn` receives the snapshot **before** any mutation to avoid circular deps. + * + * When `existingDoc` is supplied (update/replace scenarios), `computeFn` receives + * the full merged document `{ ...existingDoc, ...partial }` as snapshot so that + * fields not present in the partial (e.g. firstName during a lastName-only PATCH) + * are still available for derivation. + * The computed values are written back into the **returned partial only** (not the + * full doc), preserving the semantics of a partial $set update. */ - protected applyDerivedFields(partial: Partial, trigger: 'save' | 'read'): Partial { + protected applyDerivedFields( + partial: Partial, + trigger: 'save' | 'read', + existingDoc?: Partial, + ): Partial { if (!this.entity?.prototype) { return partial; } @@ -235,7 +245,7 @@ export abstract class BaseService { return partial; } - const snapshot = { ...partial }; + const snapshot = existingDoc ? { ...existingDoc, ...partial } : { ...partial }; const result = { ...partial }; for (const key of keys) { From a7e65582f5f83fd1bf499a94941340111a097470 Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 20:25:09 +0200 Subject: [PATCH 12/14] feat(interfaces): add DynamicApiRequest typed interface Replace the implicit any-typed request parameter used across all controller mixins with a dedicated DynamicApiRequest interface. Exposes user?: unknown, satisfying the no-any rule while keeping full runtime compatibility with Express/Fastify request objects. --- .../src/interfaces/dynamic-api-request.interface.ts | 12 ++++++++++++ libs/dynamic-api/src/interfaces/index.ts | 1 + .../dynamic-api/src/mixins/entity-body.mixin.spec.ts | 2 +- .../aggregate/aggregate-controller.interface.ts | 5 +++-- .../routes/aggregate/aggregate-controller.mixin.ts | 4 ++-- .../create-many/create-many-controller.interface.ts | 7 ++++--- .../create-many/create-many-controller.mixin.ts | 4 ++-- .../create-one/create-one-controller.interface.ts | 5 +++-- .../routes/create-one/create-one-controller.mixin.ts | 4 ++-- .../delete-many/delete-many-controller.interface.ts | 6 +++--- .../delete-many/delete-many-controller.mixin.ts | 4 ++-- .../delete-one/delete-one-controller.interface.ts | 6 +++--- .../routes/delete-one/delete-one-controller.mixin.ts | 4 ++-- .../duplicate-many-controller.interface.ts | 5 +++-- .../duplicate-many-controller.mixin.ts | 4 ++-- .../duplicate-one-controller.interface.ts | 5 +++-- .../duplicate-one/duplicate-one-controller.mixin.ts | 4 ++-- .../routes/get-many/get-many-controller.interface.ts | 5 +++-- .../src/routes/get-many/get-many-controller.mixin.ts | 4 ++-- .../routes/get-one/get-one-controller.interface.ts | 5 +++-- .../src/routes/get-one/get-one-controller.mixin.ts | 4 ++-- .../replace-one/replace-one-controller.interface.ts | 5 +++-- .../replace-one/replace-one-controller.mixin.ts | 4 ++-- .../update-many/update-many-controller.interface.ts | 5 +++-- .../update-many/update-many-controller.mixin.ts | 4 ++-- .../update-one/update-one-controller.interface.ts | 5 +++-- .../routes/update-one/update-one-controller.mixin.ts | 4 ++-- 27 files changed, 75 insertions(+), 52 deletions(-) create mode 100644 libs/dynamic-api/src/interfaces/dynamic-api-request.interface.ts diff --git a/libs/dynamic-api/src/interfaces/dynamic-api-request.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-request.interface.ts new file mode 100644 index 0000000..a2b51c4 --- /dev/null +++ b/libs/dynamic-api/src/interfaces/dynamic-api-request.interface.ts @@ -0,0 +1,12 @@ +/** + * Minimal typed HTTP request surface used in controller mixins. + * Only `user` is required — the authenticated principal from the JWT guard. + * All other request properties remain accessible via the concrete NestJS + * `Request` object; this interface only types what the library itself reads. + */ +interface DynamicApiRequest { + user?: unknown; +} + +export type { DynamicApiRequest }; + diff --git a/libs/dynamic-api/src/interfaces/index.ts b/libs/dynamic-api/src/interfaces/index.ts index 54a0787..1c912d7 100644 --- a/libs/dynamic-api/src/interfaces/index.ts +++ b/libs/dynamic-api/src/interfaces/index.ts @@ -1,4 +1,5 @@ export * from './dynamic-api-controller-options.interface'; +export * from './dynamic-api-request.interface'; export * from './dynamic-api-decorator-builder.interface'; export * from './dynamic-api-cache-options.interface'; export * from './dynamic-api-ability.interface'; diff --git a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts index 796acee..ae4f7e5 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts @@ -1,5 +1,5 @@ import { Type } from '@nestjs/common'; -import { ProtectedField } from '../decorators/protected-field.decorator'; +import { ProtectedField } from '../decorators'; import { BaseEntity, SoftDeletableEntity } from '../models'; import { EntityBodyMixin, stripProtectedFields } from './entity-body.mixin'; diff --git a/libs/dynamic-api/src/routes/aggregate/aggregate-controller.interface.ts b/libs/dynamic-api/src/routes/aggregate/aggregate-controller.interface.ts index d889c1d..996c34d 100644 --- a/libs/dynamic-api/src/routes/aggregate/aggregate-controller.interface.ts +++ b/libs/dynamic-api/src/routes/aggregate/aggregate-controller.interface.ts @@ -1,8 +1,9 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { AggregateService } from './aggregate-service.interface'; -interface AggregateController { - aggregate(query: Query, req?: any): Promise; +interface AggregateController { + aggregate(query: Query, req?: DynamicApiRequest): Promise; } type AggregateControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/aggregate/aggregate-controller.mixin.ts b/libs/dynamic-api/src/routes/aggregate/aggregate-controller.mixin.ts index e8bcb02..2720139 100644 --- a/libs/dynamic-api/src/routes/aggregate/aggregate-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/aggregate/aggregate-controller.mixin.ts @@ -3,7 +3,7 @@ import { plainToInstance } from 'class-transformer'; import { RouteDecoratorsBuilder } from '../../builders'; import { DISABLE_CACHE_KEY } from '../../decorators'; import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { Aggregatable, DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { Aggregatable, DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin } from '../../mixins'; import { BaseEntity } from '../../models'; import { AggregateController, AggregateControllerConstructor } from './aggregate-controller.interface'; @@ -86,7 +86,7 @@ function AggregateControllerMixin( @UseGuards(AggregatePoliciesGuard) @UseInterceptors(...useInterceptors) @SetMetadata(DISABLE_CACHE_KEY, disableCache) - async aggregate(@Query() query: AggregateQuery, @Request() req?: any) { + async aggregate(@Query() query: AggregateQuery, @Request() req?: DynamicApiRequest) { const toPipeline = ( AggregateQuery as Aggregatable ).toPipeline; diff --git a/libs/dynamic-api/src/routes/create-many/create-many-controller.interface.ts b/libs/dynamic-api/src/routes/create-many/create-many-controller.interface.ts index 8748c35..5712efc 100644 --- a/libs/dynamic-api/src/routes/create-many/create-many-controller.interface.ts +++ b/libs/dynamic-api/src/routes/create-many/create-many-controller.interface.ts @@ -1,13 +1,14 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateManyService } from './create-many-service.interface'; -type CreateManyBody = { +type CreateManyBody = { list: Partial[]; }; -interface CreateManyController { - createMany(body: CreateManyBody, req?: any): Promise<(Entity | Response)[]>; +interface CreateManyController { + createMany(body: CreateManyBody, req?: DynamicApiRequest): Promise<(Entity | Response)[]>; } type CreateManyControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts index c23594e..4b9f305 100644 --- a/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-many/create-many-controller.mixin.ts @@ -1,7 +1,7 @@ import { BadRequestException, Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -77,7 +77,7 @@ function CreateManyControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(CreateManyPoliciesGuard) @UseInterceptors(...useInterceptors) - async createMany(@Body() body: CreateManyBody, @Request() req?: any) { + async createMany(@Body() body: CreateManyBody, @Request() req?: DynamicApiRequest) { if (!( 'list' in body && Array.isArray(body.list) && diff --git a/libs/dynamic-api/src/routes/create-one/create-one-controller.interface.ts b/libs/dynamic-api/src/routes/create-one/create-one-controller.interface.ts index 5e08d61..f2c3016 100644 --- a/libs/dynamic-api/src/routes/create-one/create-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/create-one/create-one-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateOneService } from './create-one-service.interface'; -interface CreateOneController { - createOne(body: Body, req?: any): Promise; +interface CreateOneController { + createOne(body: Body, req?: DynamicApiRequest): Promise; } type CreateOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts index 9eb99ce..2b8366b 100644 --- a/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/create-one/create-one-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -75,7 +75,7 @@ function CreateOneControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(CreateOnePoliciesGuard) @UseInterceptors(...useInterceptors) - async createOne(@Body() body: CreateOneBody, @Request() req?: any) { + async createOne(@Body() body: CreateOneBody, @Request() req?: DynamicApiRequest) { const toEntity = ( CreateOneBody as Mappable ).toEntity; diff --git a/libs/dynamic-api/src/routes/delete-many/delete-many-controller.interface.ts b/libs/dynamic-api/src/routes/delete-many/delete-many-controller.interface.ts index b622480..54f9c4d 100644 --- a/libs/dynamic-api/src/routes/delete-many/delete-many-controller.interface.ts +++ b/libs/dynamic-api/src/routes/delete-many/delete-many-controller.interface.ts @@ -1,11 +1,11 @@ import { ManyEntityQuery } from '../../dtos'; -import { DeleteResult } from '../../interfaces'; +import { DeleteResult, DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DeleteManyService } from './delete-many-service.interface'; -interface DeleteManyController<_Entity extends BaseEntity, Response = any> { - deleteMany(query: ManyEntityQuery, req?: any): Promise; +interface DeleteManyController<_Entity extends BaseEntity, Response = unknown> { + deleteMany(query: ManyEntityQuery, req?: DynamicApiRequest): Promise; } type DeleteManyControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/delete-many/delete-many-controller.mixin.ts b/libs/dynamic-api/src/routes/delete-many/delete-many-controller.mixin.ts index b5e3abf..e3fd308 100644 --- a/libs/dynamic-api/src/routes/delete-many/delete-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/delete-many/delete-many-controller.mixin.ts @@ -2,7 +2,7 @@ import { Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nes import { RouteDecoratorsBuilder } from '../../builders'; import { ManyEntityQuery, DeletePresenter } from '../../dtos'; import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -68,7 +68,7 @@ function DeleteManyControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(DeleteManyPoliciesGuard) @UseInterceptors(...useInterceptors) - async deleteMany(@Query() { ids }: ManyEntityQuery, @Request() req?: any) { + async deleteMany(@Query() { ids }: ManyEntityQuery, @Request() req?: DynamicApiRequest) { if (!ids?.length) { throw new Error('Invalid query'); } diff --git a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.interface.ts b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.interface.ts index 5b0d293..b3da92b 100644 --- a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.interface.ts @@ -1,10 +1,10 @@ -import { DeleteResult } from '../../interfaces'; +import { DeleteResult, DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DeleteOneService } from './delete-one-service.interface'; -interface DeleteOneController<_Entity extends BaseEntity, Response = any> { - deleteOne(id: string, req?: any): Promise; +interface DeleteOneController<_Entity extends BaseEntity, Response = unknown> { + deleteOne(id: string, req?: DynamicApiRequest): Promise; } type DeleteOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts index c86cdf9..d37daee 100644 --- a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts @@ -2,7 +2,7 @@ import { Optional, Param, Request, Type, UseGuards, UseInterceptors } from '@nes import { RouteDecoratorsBuilder } from '../../builders'; import { DeletePresenter, EntityParam } from '../../dtos'; import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -71,7 +71,7 @@ function DeleteOneControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(DeleteOnePoliciesGuard) @UseInterceptors(...useInterceptors) - async deleteOne(@Param('id') id: string, @Request() req?: any) { + async deleteOne(@Param('id') id: string, @Request() req?: DynamicApiRequest) { const deleteResult = await this.service.deleteOne(id, req?.user); const fromDeleteResult = ( diff --git a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.interface.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.interface.ts index 795f1b9..36d3dd0 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.interface.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateManyService } from './duplicate-many-service.interface'; -interface DuplicateManyController { - duplicateMany(ids: string[], body?: Body, req?: any): Promise<(Entity | Response)[]>; +interface DuplicateManyController { + duplicateMany(ids: string[], body?: Body, req?: DynamicApiRequest): Promise<(Entity | Response)[]>; } type DuplicateManyControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts index 5300e28..3545c62 100644 --- a/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -79,7 +79,7 @@ function DuplicateManyControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(DuplicateManyPoliciesGuard) @UseInterceptors(...useInterceptors) - async duplicateMany(@Query('ids') ids: string[], @Body() body?: DuplicateManyBody, @Request() req?: any) { + async duplicateMany(@Query('ids') ids: string[], @Body() body?: DuplicateManyBody, @Request() req?: DynamicApiRequest) { if (!ids?.length) { throw new Error('Invalid query'); } diff --git a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.interface.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.interface.ts index 898a5ca..87298db 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateOneService } from './duplicate-one-service.interface'; -interface DuplicateOneController { - duplicateOne(id: string, body?: Body, req?: any): Promise; +interface DuplicateOneController { + duplicateOne(id: string, body?: Body, req?: DynamicApiRequest): Promise; } type DuplicateOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts index 4adbb7c..dc89baf 100644 --- a/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -81,7 +81,7 @@ function DuplicateOneControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(DuplicateOnePoliciesGuard) @UseInterceptors(...useInterceptors) - async duplicateOne(@Param('id') id: string, @Body() body?: DuplicateOneBody, @Request() req?: any) { + async duplicateOne(@Param('id') id: string, @Body() body?: DuplicateOneBody, @Request() req?: DynamicApiRequest) { const toEntity = ( DuplicateOneBody as Mappable ).toEntity; diff --git a/libs/dynamic-api/src/routes/get-many/get-many-controller.interface.ts b/libs/dynamic-api/src/routes/get-many/get-many-controller.interface.ts index e574c42..1fdaf43 100644 --- a/libs/dynamic-api/src/routes/get-many/get-many-controller.interface.ts +++ b/libs/dynamic-api/src/routes/get-many/get-many-controller.interface.ts @@ -1,8 +1,9 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { GetManyService } from './get-many-service.interface'; -interface GetManyController { - getMany(query?: Query, req?: any): Promise<(Entity | Response)[]>; +interface GetManyController { + getMany(query?: Query, req?: DynamicApiRequest): Promise<(Entity | Response)[]>; } type GetManyControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/get-many/get-many-controller.mixin.ts b/libs/dynamic-api/src/routes/get-many/get-many-controller.mixin.ts index 2f775f9..714195b 100644 --- a/libs/dynamic-api/src/routes/get-many/get-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/get-many/get-many-controller.mixin.ts @@ -3,7 +3,7 @@ import { RouteDecoratorsBuilder } from '../../builders'; import { DISABLE_CACHE_KEY } from '../../decorators'; import { EntityQuery } from '../../dtos'; import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; import { GetManyController, GetManyControllerConstructor } from './get-many-controller.interface'; @@ -81,7 +81,7 @@ function GetManyControllerMixin( @UseGuards(GetManyPoliciesGuard) @UseInterceptors(...useInterceptors) @SetMetadata(DISABLE_CACHE_KEY, disableCache) - async getMany(@Query() query: GetManyQuery, @Request() req?: any) { + async getMany(@Query() query: GetManyQuery, @Request() req?: DynamicApiRequest) { const list = await this.service.getMany(query ?? {}, req?.user); const fromEntities = ( diff --git a/libs/dynamic-api/src/routes/get-one/get-one-controller.interface.ts b/libs/dynamic-api/src/routes/get-one/get-one-controller.interface.ts index 6025f0d..a3150ee 100644 --- a/libs/dynamic-api/src/routes/get-one/get-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/get-one/get-one-controller.interface.ts @@ -1,8 +1,9 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { GetOneService } from './get-one-service.interface'; -interface GetOneController { - getOne(id: string, req?: any): Promise; +interface GetOneController { + getOne(id: string, req?: DynamicApiRequest): Promise; } type GetOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/get-one/get-one-controller.mixin.ts b/libs/dynamic-api/src/routes/get-one/get-one-controller.mixin.ts index 749d340..5663cdd 100644 --- a/libs/dynamic-api/src/routes/get-one/get-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/get-one/get-one-controller.mixin.ts @@ -3,7 +3,7 @@ import { RouteDecoratorsBuilder } from '../../builders'; import { DISABLE_CACHE_KEY } from '../../decorators'; import { EntityParam } from '../../dtos'; import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityPresenterMixin } from '../../mixins'; import { BaseEntity } from '../../models'; import { GetOneController, GetOneControllerConstructor } from './get-one-controller.interface'; @@ -70,7 +70,7 @@ function GetOneControllerMixin( @UseGuards(GetOnePoliciesGuard) @UseInterceptors(...useInterceptors) @SetMetadata(DISABLE_CACHE_KEY, disableCache) - async getOne(@Param('id') id: string, @Request() req?: any) { + async getOne(@Param('id') id: string, @Request() req?: DynamicApiRequest) { const entity = await this.service.getOne(id, req?.user); const fromEntity = ( diff --git a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.interface.ts b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.interface.ts index 24a5a1a..5233e81 100644 --- a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { ReplaceOneService } from './replace-one-service.interface'; -interface ReplaceOneController { - replaceOne(id: string, body: Body, req?: any): Promise; +interface ReplaceOneController { + replaceOne(id: string, body: Body, req?: DynamicApiRequest): Promise; } type ReplaceOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts index b9280fe..5397b4b 100644 --- a/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -81,7 +81,7 @@ function ReplaceOneControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(ReplaceOnePoliciesGuard) @UseInterceptors(...useInterceptors) - async replaceOne(@Param('id') id: string, @Body() body: ReplaceOneBody, @Request() req?: any) { + async replaceOne(@Param('id') id: string, @Body() body: ReplaceOneBody, @Request() req?: DynamicApiRequest) { const toEntity = ( ReplaceOneBody as Mappable ).toEntity; diff --git a/libs/dynamic-api/src/routes/update-many/update-many-controller.interface.ts b/libs/dynamic-api/src/routes/update-many/update-many-controller.interface.ts index 3e0f72c..2754321 100644 --- a/libs/dynamic-api/src/routes/update-many/update-many-controller.interface.ts +++ b/libs/dynamic-api/src/routes/update-many/update-many-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateManyService } from './update-many-service.interface'; -interface UpdateManyController { - updateMany(ids: string[], partial: Body, req?: any): Promise<(Entity | Response)[]>; +interface UpdateManyController { + updateMany(ids: string[], partial: Body, req?: DynamicApiRequest): Promise<(Entity | Response)[]>; } type UpdateManyControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts index 10ef3fa..9134e59 100644 --- a/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-many/update-many-controller.mixin.ts @@ -1,7 +1,7 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -79,7 +79,7 @@ function UpdateManyControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(UpdateManyPoliciesGuard) @UseInterceptors(...useInterceptors) - async updateMany(@Query('ids') ids: string[], @Body() body: UpdateManyBody, @Request() req?: any) { + async updateMany(@Query('ids') ids: string[], @Body() body: UpdateManyBody, @Request() req?: DynamicApiRequest) { if (!ids?.length) { throw new Error('Invalid query'); } diff --git a/libs/dynamic-api/src/routes/update-one/update-one-controller.interface.ts b/libs/dynamic-api/src/routes/update-one/update-one-controller.interface.ts index 3672349..b99c3b8 100644 --- a/libs/dynamic-api/src/routes/update-one/update-one-controller.interface.ts +++ b/libs/dynamic-api/src/routes/update-one/update-one-controller.interface.ts @@ -1,9 +1,10 @@ +import { DynamicApiRequest } from '../../interfaces'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateOneService } from './update-one-service.interface'; -interface UpdateOneController { - updateOne(id: string, partial: Body, req?: any): Promise; +interface UpdateOneController { + updateOne(id: string, partial: Body, req?: DynamicApiRequest): Promise; } type UpdateOneControllerConstructor = new ( diff --git a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts index 9a29d7a..acbb0cd 100644 --- a/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/update-one/update-one-controller.mixin.ts @@ -2,7 +2,7 @@ import { Body, Optional, Param, Request, Type, UseGuards, UseInterceptors } from import { RouteDecoratorsBuilder } from '../../builders'; import { EntityParam } from '../../dtos'; import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; @@ -81,7 +81,7 @@ function UpdateOneControllerMixin( @RouteDecoratorsHelper(routeDecoratorsBuilder) @UseGuards(UpdateOnePoliciesGuard) @UseInterceptors(...useInterceptors) - async updateOne(@Param('id') id: string, @Body() body: UpdateOneBody, @Request() req?: any) { + async updateOne(@Param('id') id: string, @Body() body: UpdateOneBody, @Request() req?: DynamicApiRequest) { if (isEmpty(body)) { throw new Error('Invalid request body'); } From 664a323aff6bb0e15bc387c5f399dd020586f77a Mon Sep 17 00:00:00 2001 From: "Mickael N." Date: Sun, 24 May 2026 20:48:22 +0200 Subject: [PATCH 13/14] refactor: remove unused import and simplify redundant type casts - auth-controller.mixin: drop unused LoginResponse import - delete-one-controller.mixin: cast broadcast payload to Entity instead of object - base.service: remove redundant intermediate casts flagged by SonarQube --- .../src/modules/auth/mixins/auth-controller.mixin.ts | 2 +- .../src/routes/delete-one/delete-one-controller.mixin.ts | 2 +- libs/dynamic-api/src/services/base/base.service.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts index 8886c09..40d1f14 100644 --- a/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts +++ b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts @@ -19,7 +19,7 @@ import { import { ChangePasswordDto } from '../dtos/change-password.dto'; import { ResetPasswordDto } from '../dtos/reset-password.dto'; import { JwtAuthGuard, JwtRefreshGuard, LocalAuthGuard, ResetPasswordGuard } from '../guards'; -import { AuthController, AuthControllerConstructor, AuthService, DynamicApiGetAccountOptions, DynamicApiLoginOptions, DynamicApiRefreshTokenOptions, DynamicApiRegisterOptions, DynamicApiResetPasswordOptions, DynamicApiUpdateAccountOptions, LoginResponse } from '../interfaces'; +import { AuthController, AuthControllerConstructor, AuthService, DynamicApiGetAccountOptions, DynamicApiLoginOptions, DynamicApiRefreshTokenOptions, DynamicApiRegisterOptions, DynamicApiResetPasswordOptions, DynamicApiUpdateAccountOptions } from '../interfaces'; import { AuthPoliciesGuardMixin } from './auth-policies-guard.mixin'; const REFRESH_TOKEN_COOKIE = 'refreshToken'; diff --git a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts index d37daee..a9d422e 100644 --- a/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts +++ b/libs/dynamic-api/src/routes/delete-one/delete-one-controller.mixin.ts @@ -80,7 +80,7 @@ function DeleteOneControllerMixin( const responseData = fromDeleteResult ? fromDeleteResult(deleteResult) : deleteResult; - this.broadcastService?.broadcastFromHttp(event, [{ id } as object], broadcastConfig); + this.broadcastService?.broadcastFromHttp(event, [{ id } as Entity], broadcastConfig); return responseData; } diff --git a/libs/dynamic-api/src/services/base/base.service.ts b/libs/dynamic-api/src/services/base/base.service.ts index 6955ccd..a4b823c 100644 --- a/libs/dynamic-api/src/services/base/base.service.ts +++ b/libs/dynamic-api/src/services/base/base.service.ts @@ -215,7 +215,7 @@ export abstract class BaseService { ), }); - return this.applyDerivedFields(instance as unknown as Partial, 'read') as Entity; + return this.applyDerivedFields(instance, 'read') as Entity; } /** @@ -316,7 +316,7 @@ export abstract class BaseService { } protected addDocumentId(document: T): T { - return { ...document, id: document._id.toString() } as T; + return { ...document, id: document._id.toString() }; } private isModelSoftDeletable(model: Model): boolean { From cc7ac8735aa810e52e77e9f1902b6f334d63bc0c Mon Sep 17 00:00:00 2001 From: Mickael Date: Sun, 24 May 2026 18:54:28 +0000 Subject: [PATCH 14/14] chore(release): 4.7.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ libs/dynamic-api/src/version.json | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be0443d..3693cae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ Changelog +## [4.7.0](https://github.com/MikeDev75015/mongodb-dynamic-api/compare/v4.6.1...v4.7.0) (2026-05-24) + +### interfaces + +* **interfaces:** add DynamicApiRequest typed interface ([a7e6558](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/a7e65582f5f83fd1bf499a94941340111a097470)) + +### decorators + +* **decorators:** add @DerivedField and @ProtectedField decorators ([d01b52a](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/d01b52a588ba138b38b708c52e7c660b8c53d897)) +* **decorators:** fix @DerivedField on update and @ProtectedField runtime strip ([4eaf187](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/4eaf187404cd60953a9cc5895bb3063eec308679)) + +### auth + +* **auth:** implement refreshTokenOnUpdate and operation context ([3044a50](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/3044a50fdbbeb018d7469ebf5221f5eae3637191)) +* **auth:** remove unnecessary type assertions flagged by SonarQube ([dad0a7a](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/dad0a7ae70ecd8384a900a61fb8cc59530b2855b)) + +### dynamic-api-config + +* **dynamic-api-config:** add missing refreshTokenOnUpdate field in spec config ([0e3c41b](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/0e3c41bbf2a3f05b9c8e6bf761c88ee22d7869e9)) + +### agent + +* **agent:** install caveman skills and project documentation ([533db4d](https://github.com/MikeDev75015/mongodb-dynamic-api/commit/533db4dfe902334abf12d1e8590ff6a2978a32d9)) + ## [4.6.1](https://github.com/MikeDev75015/mongodb-dynamic-api/compare/v4.6.0...v4.6.1) (2026-03-27) ## [4.6.0](https://github.com/MikeDev75015/mongodb-dynamic-api/compare/v4.5.0...v4.6.0) (2026-03-26) diff --git a/libs/dynamic-api/src/version.json b/libs/dynamic-api/src/version.json index 5941789..4ab67fb 100644 --- a/libs/dynamic-api/src/version.json +++ b/libs/dynamic-api/src/version.json @@ -1,3 +1,3 @@ { - "version": "4.6.1" + "version": "4.7.0" } diff --git a/package-lock.json b/package-lock.json index 22b516a..ea7b8e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mongodb-dynamic-api", - "version": "4.6.1", + "version": "4.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mongodb-dynamic-api", - "version": "4.6.1", + "version": "4.7.0", "license": "MIT", "dependencies": { "@nestjs/cache-manager": "^3.0.1", diff --git a/package.json b/package.json index cc06327..9443448 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mongodb-dynamic-api", - "version": "4.6.1", + "version": "4.7.0", "description": "Auto generated CRUD API for MongoDB using NestJS", "readmeFilename": "README.md", "main": "index.js",