diff --git a/.agents/skills/cavecrew/SKILL.md b/.agents/skills/cavecrew/SKILL.md new file mode 100644 index 00000000..656f69ea --- /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 00000000..62563d61 --- /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 00000000..c196616b --- /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 00000000..9be288c0 --- /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 00000000..b8399600 --- /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 00000000..74c51bc6 --- /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 00000000..4a0e303e --- /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 00000000..61956afb --- /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 + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index be0443d3..3693cae4 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/README/authentication.md b/README/authentication.md index 8b81076d..51acdc67 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`: diff --git a/README/callbacks.md b/README/callbacks.md index 5346ddf7..14753894 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 9a599733..827f7242 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 30ebe225..69effc7f 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 00000000..7d36c7bc --- /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 00000000..b7b610de --- /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 a1e15b03..79a728e2 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 00000000..33205051 --- /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 00000000..e1aaee10 --- /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/dynamic-api.module.ts b/libs/dynamic-api/src/dynamic-api.module.ts index c54c5e71..e716d0e2 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/helpers/from-user.helper.spec.ts b/libs/dynamic-api/src/helpers/from-user.helper.spec.ts new file mode 100644 index 00000000..cb08bd3f --- /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 00000000..06e6f1ed --- /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') { + 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 b11aade7..de44245c 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/index.ts b/libs/dynamic-api/src/index.ts index 443011d4..ef7868ef 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/src/interfaces/dynamic-api-global-state.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-global-state.interface.ts index 089de2ea..276a0ebe 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/interfaces/dynamic-api-request.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-request.interface.ts new file mode 100644 index 00000000..a2b51c4a --- /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/dynamic-api-route-config.interface.ts b/libs/dynamic-api/src/interfaces/dynamic-api-route-config.interface.ts index bf4332e2..2c8a569d 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/interfaces/index.ts b/libs/dynamic-api/src/interfaces/index.ts index 54a0787b..1c912d75 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 acc0e229..ae4f7e5d 100644 --- a/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts +++ b/libs/dynamic-api/src/mixins/entity-body.mixin.spec.ts @@ -1,6 +1,7 @@ import { Type } from '@nestjs/common'; +import { ProtectedField } from '../decorators'; 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'; @@ -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,59 @@ 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'); + }); +}); + +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 92a9b64a..cc68337a 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'; import { BaseEntity } from '../models'; const baseEntityKeysToExclude = () => @@ -13,13 +14,42 @@ const baseEntityKeysToExclude = () => '__v', ] as (keyof Entity)[]; +function getProtectedFieldKeys(entity: Type): (keyof Entity)[] { + return ( + (Reflect.getMetadata(PROTECTED_FIELD_METADATA, entity.prototype) as (keyof Entity)[] | undefined) ?? [] + ); +} + +/** + * 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, additionalKeysToExclude?: (keyof Entity)[], ) { + const protectedKeys = getProtectedFieldKeys(entity); + const keysToExclude = [ ...baseEntityKeysToExclude(), + ...protectedKeys, ...(additionalKeysToExclude ?? []), ]; @@ -29,4 +59,4 @@ function EntityBodyMixin( return optional ? PartialType(EntityBody) : EntityBody; } -export { baseEntityKeysToExclude, EntityBodyMixin }; +export { baseEntityKeysToExclude, EntityBodyMixin, getProtectedFieldKeys, stripProtectedFields }; 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 00000000..2c066de2 --- /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/auth-operation-context.ts b/libs/dynamic-api/src/modules/auth/auth-operation-context.ts new file mode 100644 index 00000000..76353e0a --- /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 b385bdf1..f9a1aa21 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 97e4930a..4383458f 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 c559a39b..62253f52 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 3d1a4550..719f2bef 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 af9becfb..1800988c 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 a9475a86..ffd62c7a 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.spec.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.spec.ts index d370a9bd..7f79bb73 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/mixins/auth-controller.mixin.ts b/libs/dynamic-api/src/modules/auth/mixins/auth-controller.mixin.ts index 803dee77..40d1f14f 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, @@ -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; + + 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 6a7cd208..42152325 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.spec.ts b/libs/dynamic-api/src/modules/auth/services/base-auth.service.spec.ts index 4dfe9f76..88943915 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', () => { 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 b1451387..2ee2e92d 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'); @@ -49,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) { @@ -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() }, true); + } + return this.getAccount({ id } as Entity); } 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 6b7a0f83..4569be0d 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, 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 d889c1d0..996c34d2 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 e8bcb02f..27201397 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/base-create-many.service.ts b/libs/dynamic-api/src/routes/create-many/base-create-many.service.ts index 12ff37ef..d692e869 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.interface.ts b/libs/dynamic-api/src/routes/create-many/create-many-controller.interface.ts index 8748c357..5712efc2 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 f4804586..4b9f3055 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,8 +1,8 @@ import { BadRequestException, Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateManyBodyMixin } from './create-many-body.mixin'; @@ -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 { @@ -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) && @@ -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(stripProtectedFields(p, this.entity), 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 2c32ed40..d58dcd43 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 b18e7b91..375f6286 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.interface.ts b/libs/dynamic-api/src/routes/create-one/create-one-controller.interface.ts index 5e08d61f..f2c30160 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 3eee030b..2b8366b5 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,8 +1,8 @@ import { Body, Optional, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { CreateOneController, CreateOneControllerConstructor } from './create-one-controller.interface'; @@ -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 { @@ -75,12 +75,15 @@ 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; - 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(stripProtectedFields(rawPartial, this.entity), 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/delete-many/delete-many-controller.interface.ts b/libs/dynamic-api/src/routes/delete-many/delete-many-controller.interface.ts index b622480b..54f9c4d8 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 b5e3abf7..e3fd3088 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 5b0d293e..b3da92b0 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 c86cdf94..a9d422e0 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 = ( @@ -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/routes/duplicate-many/base-duplicate-many.service.ts b/libs/dynamic-api/src/routes/duplicate-many/base-duplicate-many.service.ts index f193b551..60ffccf4 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.interface.ts b/libs/dynamic-api/src/routes/duplicate-many/duplicate-many-controller.interface.ts index 795f1b90..36d3dd07 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 40de32dd..3545c623 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,8 +1,8 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateManyController, DuplicateManyControllerConstructor } from './duplicate-many-controller.interface'; @@ -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 { @@ -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'); } @@ -88,9 +88,12 @@ function DuplicateManyControllerMixin( DuplicateManyBody as Mappable ).toEntity; + const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), 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 9df3c435..3951bd58 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.interface.ts b/libs/dynamic-api/src/routes/duplicate-one/duplicate-one-controller.interface.ts index 898a5ca8..87298dbd 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 b45f9d6e..dc89baff 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,9 +1,9 @@ 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 { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { DuplicateOneController, DuplicateOneControllerConstructor } from './duplicate-one-controller.interface'; @@ -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 { @@ -81,14 +81,17 @@ 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; + const rawPartial = !isEmpty(body) && toEntity ? toEntity(body) : body as Partial; + const partial = applyFromUser(stripProtectedFields(rawPartial, this.entity), 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/get-many/get-many-controller.interface.ts b/libs/dynamic-api/src/routes/get-many/get-many-controller.interface.ts index e574c42c..1fdaf43c 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 2f775f90..714195b3 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 6025f0d8..a3150ee3 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 749d340f..5663cdd0 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/base-replace-one.service.ts b/libs/dynamic-api/src/routes/replace-one/base-replace-one.service.ts index c6140747..12a3ba5d 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.interface.ts b/libs/dynamic-api/src/routes/replace-one/replace-one-controller.interface.ts index 24a5a1a4..5233e81b 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 d753015f..5397b4ba 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,9 +1,9 @@ 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 { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { ReplaceOneController, ReplaceOneControllerConstructor } from './replace-one-controller.interface'; @@ -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 { @@ -81,12 +81,15 @@ 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; - 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(stripProtectedFields(rawPartial, this.entity), 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 85819af3..98086d03 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,12 @@ export abstract class BaseUpdateManyService user, ); + const updatesWithDerived = updates.map((u, index) => + this.applyDerivedFields(u, 'save', this.addDocumentId(toUpdateList[index]) as Partial), + ); + await Promise.all( - updates.map((update, index) => + updatesWithDerived.map((update, index) => this.model .findByIdAndUpdate( toUpdateList[index]._id, @@ -50,6 +54,8 @@ export abstract class BaseUpdateManyService ), ); } else { + const partialWithDerived = this.applyDerivedFields(partial, 'save'); + await this.model .updateMany( { @@ -58,7 +64,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.interface.ts b/libs/dynamic-api/src/routes/update-many/update-many-controller.interface.ts index 3e0f72cb..27543210 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 7f0e9a7e..9134e59c 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,8 +1,8 @@ import { Body, Optional, Query, Request, Type, UseGuards, UseInterceptors } from '@nestjs/common'; import { RouteDecoratorsBuilder } from '../../builders'; -import { addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; -import { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateManyController, UpdateManyControllerConstructor } from './update-many-controller.interface'; @@ -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 { @@ -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'); } @@ -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(stripProtectedFields(rawPartial, this.entity), 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 9cdca967..9b679aef 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', this.addDocumentId(document) as Partial); + const updatedDocument = await this.model .findOneAndUpdate( { _id: id }, 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 36723497..b99c3b82 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 d10c98d8..acbb0cd5 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,9 +1,9 @@ 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 { DynamicApiControllerOptions, DynamicAPIRouteConfig, Mappable } from '../../interfaces'; -import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin } from '../../mixins'; +import { applyFromUser, addVersionSuffix, getMixinData, isEmpty, provideName, RouteDecoratorsHelper } from '../../helpers'; +import { DynamicApiControllerOptions, DynamicAPIRouteConfig, DynamicApiRequest, Mappable } from '../../interfaces'; +import { RoutePoliciesGuardMixin, EntityBodyMixin, EntityPresenterMixin, stripProtectedFields } from '../../mixins'; import { BaseEntity } from '../../models'; import { DynamicApiBroadcastService } from '../../services'; import { UpdateOneController, UpdateOneControllerConstructor } from './update-one-controller.interface'; @@ -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 { @@ -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'); } @@ -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(stripProtectedFields(rawPartial, this.entity), 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 c6a327c2..127876f4 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 e741faab..a4b823cf 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,53 @@ export abstract class BaseService { isDeleted ? { deletedAt } : {} ), }); + + return this.applyDerivedFields(instance, 'read') as Entity; + } + + /** + * Applies `@DerivedField` computed values to a partial entity snapshot. + * Only fields whose `on` option matches `trigger` (or is `'both'`) are computed. + * + * 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', + existingDoc?: Partial, + ): 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 = existingDoc ? { ...existingDoc, ...partial } : { ...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) { @@ -268,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 { diff --git a/libs/dynamic-api/src/version.json b/libs/dynamic-api/src/version.json index 59417897..4ab67fbc 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/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 00000000..3a0c4d51 --- /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'); + }); + }); +}); + + + + + + 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 90e299cf..40615f4b 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 00000000..cf15c885 --- /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 818f0825..d0ceb0ec 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: [ diff --git a/package-lock.json b/package-lock.json index 102ff5c1..ea7b8e73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "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", "@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", @@ -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", @@ -2638,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" }, @@ -2678,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", @@ -3025,249 +3046,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 +3295,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 +3350,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 +3410,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 +3794,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 +4093,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 +4579,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 +4897,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 +4976,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 +5130,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 +5174,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 +5408,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 +5436,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 +5621,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": "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": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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", "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" + "fill-range": "^7.1.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "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" + "node": ">=8" } }, "node_modules/browserslist": { @@ -5852,6 +5754,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 +5785,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 +6010,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 +6093,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 +6124,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 +6337,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 +6372,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 +6415,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": ">=18" } }, - "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==", + "node_modules/conventional-changelog-conventionalcommits": { + "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-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==", + "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, + "license": "MIT", "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==", + "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": { - "compare-func": "^2.0.0" + "@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-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==", - "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" - }, - "engines": { - "node": ">=16" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "node_modules/conventional-changelog-eslint": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-5.0.0.tgz", - "integrity": "sha512-6JtLWqAQIeJLn/OzUlYmzd9fKeNSWmQVim9kql+v4GrZwLx807kAJl3IJVc3jTYfVKWLxhC3BGUxYiuVEcVjgA==", - "dev": true, - "engines": { - "node": ">=16" - } - }, - "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==", + "node_modules/conventional-commits-filter": { + "version": "5.0.0", + "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-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==", + "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 +6809,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 +6906,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 +6922,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 +6939,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 +6977,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 +6985,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 +7005,9 @@ }, "engines": { "node": ">= 14" + }, + "peerDependencies": { + "quickjs-wasi": "^0.0.1" } }, "node_modules/delayed-stream": { @@ -7119,12 +7033,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 +7085,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 +7230,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 +7373,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 +7441,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 +7776,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 +7978,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 +8010,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 +8041,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 +8074,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 +8108,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 +8136,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 +8224,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 +8637,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 +8705,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==", + "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": { - "dargs": "^8.0.0", - "meow": "^12.0.1", - "split2": "^4.0.0" + "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-raw-commits": "cli.mjs" - }, - "engines": { - "node": ">=16" + "giget": "dist/cli.mjs" } }, - "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/git-up": { + "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": { - "meow": "^12.0.1", - "semver": "^7.5.2" - }, - "bin": { - "git-semver-tags": "cli.mjs" - }, - "engines": { - "node": ">=16" - } - }, - "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==", - "dev": true, - "dependencies": { - "is-ssh": "^1.4.0", - "parse-url": "^8.1.0" + "is-ssh": "^1.4.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,69 +8793,6 @@ "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, - "license": "MIT", - "dependencies": { - "ini": "4.1.1" - }, - "engines": { - "node": ">=18" - }, - "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", @@ -8877,10 +8837,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 +8906,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 +8931,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 +8991,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 +9018,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 +9041,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 +9140,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 +9191,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 +9240,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 +9258,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 +9272,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 +9281,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 +9296,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 +9317,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 +9335,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 +9348,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 +11176,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 +11243,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 +11281,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 +11351,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 +11606,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 +11730,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 +11821,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 +11870,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 +12200,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 +12293,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 +12359,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,20 +12426,58 @@ "set-blocking": "^2.0.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.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/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" + "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", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" } }, "node_modules/object-inspect": { @@ -12570,6 +12492,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 +12535,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 +12684,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 +12759,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 +12826,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 +13034,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 +13059,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 +13177,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 +13199,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 +13288,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 +13309,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 +13369,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 +13432,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 +13479,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 +13496,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, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "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": ">=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": ">=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/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==", + "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": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, "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/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": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "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": { - "@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" + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" }, "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/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": { + "@inquirer/core": "^11.1.10", + "@inquirer/type": "^4.0.5" + }, "engines": { - "node": ">=14.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/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/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": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@inquirer/ansi": "^2.0.5", + "@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/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": { - "readable-stream": "^3.6.0" + "@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": ">=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/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": { - "resolve": "^1.1.6" + "@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/search": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.9.tgz", + "integrity": "sha512-ZlbM28Q9lmLkFPNAIv+ZuY530n5Km8U1WW48oYEvDhe9yc2uL3m3t+JSdRUkQlk5fuIuskgiIVjcb7czFzQpuA==", "dev": true, "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^3.0.2" + "@inquirer/core": "^11.1.10", + "@inquirer/figures": "^2.0.5", + "@inquirer/type": "^4.0.5" }, "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/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": { - "rc": "1.2.8" + "@inquirer/ansi": "^2.0.5", + "@inquirer/core": "^11.1.10", + "@inquirer/figures": "^2.0.5", + "@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/type": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", + "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/webpro" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/webpro" - } - ], "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" + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, - "bin": { - "release-it": "bin/release-it.js" + "peerDependencies": { + "@types/node": ">=18" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || ^22.0.0" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/release-it/node_modules/ansi-regex": { @@ -13816,16 +13936,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 +13981,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" - }, - "engines": { - "node": ">=16.17" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, - "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 +14024,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 +14037,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 +14054,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" + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.1", + "string-width": "^8.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" - }, - "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 +14147,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 +14378,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 +14390,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 +14465,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 +14628,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 +14895,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 +14966,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 +14976,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 +15034,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 +15172,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 +15514,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 +15554,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 +15574,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 +15993,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 +16052,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 +16088,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 +16159,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 +16215,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 +16238,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 +16456,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 +16493,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 +16513,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 +16641,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 +16744,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 ea36ec04..9443448e 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", @@ -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", @@ -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 00000000..0ec18f09 --- /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); +} +