Compare commits

...

92 Commits

Author SHA1 Message Date
github-actions[bot]
5d89682a3f chore(main): release 4.18.2 (#1643)
🤖 I have created a release *beep* *boop*
---


## [4.18.2](https://github.com/unraid/api/compare/v4.18.1...v4.18.2)
(2025-09-03)


### Bug Fixes

* add missing CPU guest metrics to CPU responses
([#1644](https://github.com/unraid/api/issues/1644))
([99dbad5](99dbad57d5))
* **plugin:** raise minimum unraid os version to 6.12.15
([#1649](https://github.com/unraid/api/issues/1649))
([bc15bd3](bc15bd3d70))
* update GitHub Actions token for workflow trigger
([4d8588b](4d8588b173))
* update OIDC URL validation and add tests
([#1646](https://github.com/unraid/api/issues/1646))
([c7c3bb5](c7c3bb57ea))
* use shared bg & border color for styled toasts
([#1647](https://github.com/unraid/api/issues/1647))
([7c3aee8](7c3aee8f3f))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-03 15:23:02 -04:00
Pujit Mehrotra
bc15bd3d70 fix(plugin): raise minimum unraid os version to 6.12.15 (#1649) 2025-09-03 15:20:24 -04:00
Pujit Mehrotra
7c3aee8f3f fix: use shared bg & border color for styled toasts (#1647)
Addresses user complaints about light colored notifications in dark
themes.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Introduced type-specific toast color tokens (success, info, warning,
error) for richer, clearer toast styling.
- Applied consistent theming across light, inverted, and dark modes to
improve readability and contrast.
  - Enabled compatibility with rich color settings for toasts.
- Bug Fixes
- Corrected a CSS comment block to ensure styles compile and apply
reliably.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-03 12:36:18 -04:00
Eli Bosley
c7c3bb57ea fix: update OIDC URL validation and add tests (#1646)
- Updated the OIDC issuer URL validation to prevent trailing slashes and
whitespace.
- Introduced a utility class `OidcUrlPatterns` for managing URL patterns
and validation logic.
- Added comprehensive tests for the new URL validation logic and
examples to ensure correctness.
- Bumped version to 4.18.1 in the configuration file.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Added strict validation for OIDC issuer URLs in the SSO configuration
form, with clearer guidance to avoid trailing slashes.
- Bug Fixes
- Prevented misconfiguration by rejecting issuer URLs with trailing
slashes (e.g., Google issuer), avoiding double slashes in discovery
URLs.
- Tests
- Introduced comprehensive unit tests covering issuer URL validation,
patterns, and real-world scenarios to ensure reliability.
- Chores
  - Bumped version to 4.18.1.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-03 11:56:30 -04:00
Eli Bosley
99dbad57d5 fix: add missing CPU guest metrics to CPU responses (#1644)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- CPU load metrics now include guest runtime and hypervisor steal time
percentages, exposed as additional fields in CPU load responses
(per‑CPU).
- Tests
- Added comprehensive unit tests for CPU info and load generation,
including edge cases and validation of the new guest and steal metrics.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-03 11:06:51 -04:00
Eli Bosley
c42f79d406 chore: add code coverage monitoring (#1645) 2025-09-03 11:06:05 -04:00
Eli Bosley
4d8588b173 fix: update GitHub Actions token for workflow trigger
Replaced the token used for triggering workflows in the build-plugin.yml file from WORKFLOW_TRIGGER_PAT to UNRAID_BOT_GITHUB_ADMIN_TOKEN for improved security and access control.
2025-09-03 10:04:54 -04:00
github-actions[bot]
0d1d27064e chore(main): release 4.18.1 (#1641)
🤖 I have created a release *beep* *boop*
---


## [4.18.1](https://github.com/unraid/api/compare/v4.18.0...v4.18.1)
(2025-09-03)


### Bug Fixes

* OIDC and API Key management issues
([#1642](https://github.com/unraid/api/issues/1642))
([0fe2c2c](0fe2c2c1c8))
* rm redundant emission to `$HOME/.pm2/logs`
([#1640](https://github.com/unraid/api/issues/1640))
([a8e4119](a8e4119270))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-03 09:47:55 -04:00
Eli Bosley
0fe2c2c1c8 fix: OIDC and API Key management issues (#1642) 2025-09-03 09:47:11 -04:00
Pujit Mehrotra
a8e4119270 fix: rm redundant emission to $HOME/.pm2/logs (#1640)
Override the pm2 daemon's emission of `logs/unraid-api-{out,error}.log`,
which is a duplicate of the stdout appended to
`/var/log/graphql-api.log`.

Correctly implements the sane interpretation of what the `log_file`
configuration does.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved process logging by separating standard output and error
streams while supporting merged logs when multiple sources are present.
* Enhances log clarity and troubleshooting without changing application
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-02 15:30:13 -04:00
github-actions[bot]
372a4ebb42 chore(main): release 4.18.0 (#1636)
🤖 I have created a release *beep* *boop*
---


## [4.18.0](https://github.com/unraid/api/compare/v4.17.0...v4.18.0)
(2025-09-02)


### Features

* **api:** enhance OIDC redirect URI handling in service and tests
([#1618](https://github.com/unraid/api/issues/1618))
([4e945f5](4e945f5f56))


### Bug Fixes

* api key creation cli
([#1637](https://github.com/unraid/api/issues/1637))
([c147a6b](c147a6b507))
* **cli:** support `--log-level` for `start` and `restart` cmds
([#1623](https://github.com/unraid/api/issues/1623))
([a1ee915](a1ee915ca5))
* confusing server -&gt; status query
([#1635](https://github.com/unraid/api/issues/1635))
([9d42b36](9d42b36f74))
* use unraid css variables in sonner
([#1634](https://github.com/unraid/api/issues/1634))
([26a95af](26a95af953))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-02 13:03:04 -04:00
Eli Bosley
4e945f5f56 feat(api): enhance OIDC redirect URI handling in service and tests (#1618)
- Updated `getRedirectUri` method in `OidcAuthService` to handle various
edge cases for redirect URIs, including full URIs, malformed URLs, and
default ports.
- Added comprehensive tests for `OidcAuthService` to validate redirect
URI construction and error handling.
- Modified `RestController` to utilize `redirect_uri` query parameter
for authorization requests.
- Updated frontend components to include `redirect_uri` in authorization
URLs, ensuring correct handling of different protocols and ports.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Stronger OIDC redirect_uri validation and an admin GraphQL endpoint to
view full OIDC configuration.
* OIDC Debug Logs UI (panel, button, modal), enhanced log viewer with
presets/filters, ANSI-colored rendering, and a File Viewer component.
* New GraphQL queries to list and fetch config files; API Config
Download page.

* **Refactor**
* Centralized, modular OIDC flows and safer redirect handling;
topic-based log subscriptions with a watcher manager for scalable live
logs.

* **Documentation**
  * Cache TTL guidance clarified to use milliseconds.

* **Chores**
* Added ansi_up and escape-html deps; improved log formatting; added
root codegen script.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-09-02 10:40:20 -04:00
Eli Bosley
6356f9c41d chore: lint unraid ui (#1638)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Fullscreen dialogs now use dedicated open/close animations for
smoother transitions.

- UX
- Added a “Loading Notifications...” message while notifications are
being fetched.

- Style
- Standardized Tailwind utility class ordering across numerous
components for consistent styling; no functional or visual changes
expected.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 14:48:14 -04:00
Pujit Mehrotra
a1ee915ca5 fix(cli): support --log-level for start and restart cmds (#1623)
Resolve #1614 
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Start and Restart commands accept a validated --log-level option (now
includes "fatal"); chosen level is applied to the running/restarted
service and defaults to the LOG_LEVEL environment value when set.

* **Documentation**
* CLI docs updated to list the --log-level option and allowed levels
(including fatal), show LOG_LEVEL as an environment-variable
alternative, and include usage examples.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 10:56:20 -04:00
Eli Bosley
c147a6b507 fix: api key creation cli (#1637)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
  - CLI now prompts for roles/permissions only when not provided.
- Bug Fixes
  - Existing API keys are detected by name during overwrite checks.
  - Invalid role inputs are filtered out with clear warnings.
- Refactor
  - Centralized role parsing/validation with improved error messages.
- CLI create flow prompts only when minimum info is missing and uses a
sensible default description.
- Tests
- Added comprehensive unit tests for role parsing and CLI flows (create,
retrieve, overwrite).
- Chores
  - Updated API configuration version to 4.17.0.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 10:49:31 -04:00
Pujit Mehrotra
9d42b36f74 fix: confusing server -> status query (#1635)
represent the target server's status instead of whether it's connected to Mothership.

Resolves #1627

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Bug Fixes
- Ensures the local server is handled consistently as a single server
across views; list views show it as a single-item list when applicable.
  - Server status now reliably displays as Online for the local server.

- Documentation
- Added a clearer description for the server status field in the API
schema to improve tooltips and autogenerated docs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-28 16:31:47 -04:00
Pujit Mehrotra
26a95af953 fix: use unraid css variables in sonner (#1634)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Style
- Unified toast colors with theme variables for consistent light/dark
theming.
- Refined close button colors, borders, and hover state for improved
contrast and clarity.
  - Updated loading bar color to better align with muted text tones.
  - Visual-only updates; no behavioral changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-28 16:26:32 -04:00
github-actions[bot]
0ead267838 chore(main): release 4.17.0 (#1631)
🤖 I have created a release *beep* *boop*
---


## [4.17.0](https://github.com/unraid/api/compare/v4.16.0...v4.17.0)
(2025-08-27)


### Features

* add tailwind class sort plugin
([#1562](https://github.com/unraid/api/issues/1562))
([ab11e7f](ab11e7ff7f))


### Bug Fixes

* cleanup obsoleted legacy api keys on api startup (cli / connect)
([#1630](https://github.com/unraid/api/issues/1630))
([6469d00](6469d002b7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-27 16:38:35 -04:00
renovate[bot]
163763f9e5 chore(deps): pin dependency prettier-plugin-tailwindcss to 0.6.14 (#1632)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[prettier-plugin-tailwindcss](https://redirect.github.com/tailwindlabs/prettier-plugin-tailwindcss)
| devDependencies | pin | [`^0.6.14` ->
`0.6.14`](https://renovatebot.com/diffs/npm/prettier-plugin-tailwindcss/0.6.14/0.6.14)
|

Add the preset `:preserveSemverRanges` to your config if you don't want
to pin your dependencies.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS44Mi43IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 16:37:27 -04:00
Pujit Mehrotra
6469d002b7 fix: cleanup obsoleted legacy api keys on api startup (cli / connect) (#1630)
- these keys have been replaced with a session based authorization system
2025-08-27 16:37:13 -04:00
Michael Datelle
ab11e7ff7f feat: add tailwind class sort plugin (#1562)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Updated code formatting tools to include support for Tailwind
CSS-specific formatting.
* Adjusted code formatting issue reporting from errors to warnings for a
smoother development experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
Co-authored-by: Eli Bosley <ekbosley@gmail.com>
2025-08-27 16:26:09 -04:00
renovate[bot]
7316dc753f fix(deps): update all non-major dependencies (#1580)
This PR contains the following updates:

| Package | Change | Age | Confidence | Type | Update |
|---|---|---|---|---|---|
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.9` ->
`3.14.0`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.9/3.14.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.9/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.9` ->
`3.14.0`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.9/3.14.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.9/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | minor |
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.9` ->
`3.14.0`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.9/3.14.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.9/3.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@eslint/js](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint/tree/HEAD/packages/js))
| [`9.33.0` ->
`9.34.0`](https://renovatebot.com/diffs/npm/@eslint%2fjs/9.33.0/9.34.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@eslint%2fjs/9.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@eslint%2fjs/9.33.0/9.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@floating-ui/dom](https://floating-ui.com)
([source](https://redirect.github.com/floating-ui/floating-ui/tree/HEAD/packages/dom))
| [`1.7.3` ->
`1.7.4`](https://renovatebot.com/diffs/npm/@floating-ui%2fdom/1.7.3/1.7.4)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@floating-ui%2fdom/1.7.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@floating-ui%2fdom/1.7.3/1.7.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@floating-ui/vue](https://floating-ui.com/docs/vue)
([source](https://redirect.github.com/floating-ui/floating-ui/tree/HEAD/packages/vue))
| [`1.1.8` ->
`1.1.9`](https://renovatebot.com/diffs/npm/@floating-ui%2fvue/1.1.8/1.1.9)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@floating-ui%2fvue/1.1.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@floating-ui%2fvue/1.1.8/1.1.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[@ianvs/prettier-plugin-sort-imports](https://redirect.github.com/ianvs/prettier-plugin-sort-imports)
| [`4.6.1` ->
`4.6.3`](https://renovatebot.com/diffs/npm/@ianvs%2fprettier-plugin-sort-imports/4.6.1/4.6.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@ianvs%2fprettier-plugin-sort-imports/4.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@ianvs%2fprettier-plugin-sort-imports/4.6.1/4.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@internationalized/number](https://redirect.github.com/adobe/react-spectrum)
| [`3.6.4` ->
`3.6.5`](https://renovatebot.com/diffs/npm/@internationalized%2fnumber/3.6.4/3.6.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@internationalized%2fnumber/3.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@internationalized%2fnumber/3.6.4/3.6.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/testing](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/testing))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2ftesting/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2ftesting/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2ftesting/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nuxt/devtools](https://devtools.nuxt.com)
([source](https://redirect.github.com/nuxt/devtools/tree/HEAD/packages/devtools))
| [`2.6.2` ->
`2.6.3`](https://renovatebot.com/diffs/npm/@nuxt%2fdevtools/2.6.2/2.6.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2fdevtools/2.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2fdevtools/2.6.2/2.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nuxt/eslint](https://redirect.github.com/nuxt/eslint)
([source](https://redirect.github.com/nuxt/eslint/tree/HEAD/packages/module))
| [`1.8.0` ->
`1.9.0`](https://renovatebot.com/diffs/npm/@nuxt%2feslint/1.8.0/1.9.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2feslint/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2feslint/1.8.0/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@nuxt/ui](https://ui.nuxt.com)
([source](https://redirect.github.com/nuxt/ui)) | [`3.3.0` ->
`3.3.2`](https://renovatebot.com/diffs/npm/@nuxt%2fui/3.3.0/3.3.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2fui/3.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2fui/3.3.0/3.3.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@rollup/rollup-linux-x64-gnu](https://rollupjs.org/)
([source](https://redirect.github.com/rollup/rollup)) | [`4.46.2` ->
`4.49.0`](https://renovatebot.com/diffs/npm/@rollup%2frollup-linux-x64-gnu/4.46.2/4.49.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@rollup%2frollup-linux-x64-gnu/4.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@rollup%2frollup-linux-x64-gnu/4.46.2/4.49.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| optionalDependencies | minor |
|
[@storybook/addon-docs](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/docs)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/docs))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/@storybook%2faddon-docs/9.1.2/9.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-docs/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-docs/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/addon-links](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/links)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/links))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/@storybook%2faddon-links/9.1.2/9.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-links/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-links/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/builder-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/builders/builder-vite))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/@storybook%2fbuilder-vite/9.1.2/9.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fbuilder-vite/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fbuilder-vite/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/vue3-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/frameworks/vue3-vite)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/frameworks/vue3-vite))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/@storybook%2fvue3-vite/9.1.2/9.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fvue3-vite/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fvue3-vite/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@swc/core](https://swc.rs)
([source](https://redirect.github.com/swc-project/swc)) | [`1.13.3` ->
`1.13.5`](https://renovatebot.com/diffs/npm/@swc%2fcore/1.13.3/1.13.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@swc%2fcore/1.13.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@swc%2fcore/1.13.3/1.13.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@tailwindcss/cli](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-cli))
| [`4.1.11` ->
`4.1.12`](https://renovatebot.com/diffs/npm/@tailwindcss%2fcli/4.1.11/4.1.12)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@tailwindcss%2fcli/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@tailwindcss%2fcli/4.1.11/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@tailwindcss/vite](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite))
| [`4.1.11` ->
`4.1.12`](https://renovatebot.com/diffs/npm/@tailwindcss%2fvite/4.1.11/4.1.12)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@tailwindcss%2fvite/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@tailwindcss%2fvite/4.1.11/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/bun](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/bun)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bun))
| [`1.2.20` ->
`1.2.21`](https://renovatebot.com/diffs/npm/@types%2fbun/1.2.20/1.2.21)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fbun/1.2.21?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fbun/1.2.20/1.2.21?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/dockerode](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/dockerode)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/dockerode))
| [`3.3.42` ->
`3.3.43`](https://renovatebot.com/diffs/npm/@types%2fdockerode/3.3.42/3.3.43)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fdockerode/3.3.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fdockerode/3.3.42/3.3.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`22.17.1` ->
`22.18.0`](https://renovatebot.com/diffs/npm/@types%2fnode/22.17.1/22.18.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.17.1/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@types/wtfnode](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/wtfnode)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/wtfnode))
| [`0.7.3` ->
`0.10.0`](https://renovatebot.com/diffs/npm/@types%2fwtfnode/0.7.3/0.10.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fwtfnode/0.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fwtfnode/0.7.3/0.10.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@typescript-eslint/eslint-plugin](https://typescript-eslint.io/packages/eslint-plugin)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin))
| [`8.39.1` ->
`8.41.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/8.39.1/8.41.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2feslint-plugin/8.41.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2feslint-plugin/8.39.1/8.41.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@vue/tsconfig](https://redirect.github.com/vuejs/tsconfig) | [`0.7.0`
->
`0.8.1`](https://renovatebot.com/diffs/npm/@vue%2ftsconfig/0.7.0/0.8.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vue%2ftsconfig/0.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vue%2ftsconfig/0.7.0/0.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@vueuse/components](https://redirect.github.com/vueuse/vueuse/tree/main/packages/components#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/components))
| [`13.6.0` ->
`13.8.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcomponents/13.6.0/13.8.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcomponents/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcomponents/13.6.0/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [@vueuse/core](https://redirect.github.com/vueuse/vueuse)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/core))
| [`13.6.0` ->
`13.8.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/13.6.0/13.8.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcore/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcore/13.6.0/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@vueuse/core](https://redirect.github.com/vueuse/vueuse)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/core))
| [`13.6.0` ->
`13.8.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/13.6.0/13.8.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcore/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcore/13.6.0/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@vueuse/integrations](https://redirect.github.com/vueuse/vueuse/tree/main/packages/integrations#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/integrations))
| [`13.6.0` ->
`13.8.0`](https://renovatebot.com/diffs/npm/@vueuse%2fintegrations/13.6.0/13.8.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fintegrations/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fintegrations/13.6.0/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@vueuse/nuxt](https://redirect.github.com/vueuse/vueuse/tree/main/packages/nuxt#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/nuxt))
| [`13.6.0` ->
`13.8.0`](https://renovatebot.com/diffs/npm/@vueuse%2fnuxt/13.6.0/13.8.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fnuxt/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fnuxt/13.6.0/13.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [cache-manager](https://redirect.github.com/jaredwray/cacheable)
([source](https://redirect.github.com/jaredwray/cacheable/tree/HEAD/packages/cache-manager))
| [`7.1.1` ->
`7.2.0`](https://renovatebot.com/diffs/npm/cache-manager/7.1.1/7.2.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/cache-manager/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/cache-manager/7.1.1/7.2.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [chalk](https://redirect.github.com/chalk/chalk) | [`5.5.0` ->
`5.6.0`](https://renovatebot.com/diffs/npm/chalk/5.5.0/5.6.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/chalk/5.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/chalk/5.5.0/5.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[concurrently](https://redirect.github.com/open-cli-tools/concurrently)
| [`9.2.0` ->
`9.2.1`](https://renovatebot.com/diffs/npm/concurrently/9.2.0/9.2.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/concurrently/9.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/concurrently/9.2.0/9.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [dayjs](https://day.js.org)
([source](https://redirect.github.com/iamkun/dayjs)) | [`1.11.13` ->
`1.11.14`](https://renovatebot.com/diffs/npm/dayjs/1.11.13/1.11.14) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/dayjs/1.11.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/dayjs/1.11.13/1.11.14?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [eslint](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint)) | [`9.33.0` ->
`9.34.0`](https://renovatebot.com/diffs/npm/eslint/9.33.0/9.34.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint/9.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint/9.33.0/9.34.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[eslint-plugin-storybook](https://redirect.github.com/storybookjs/storybook/code/lib/eslint-plugin#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/lib/eslint-plugin))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/eslint-plugin-storybook/9.1.2/9.1.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-storybook/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-storybook/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[inquirer](https://redirect.github.com/SBoudrias/Inquirer.js/blob/main/packages/inquirer/README.md)
([source](https://redirect.github.com/SBoudrias/Inquirer.js)) |
[`12.9.1` ->
`12.9.4`](https://renovatebot.com/diffs/npm/inquirer/12.9.1/12.9.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/inquirer/12.9.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/inquirer/12.9.1/12.9.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.12` ->
`6.0.13`](https://renovatebot.com/diffs/npm/jose/6.0.12/6.0.13) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.12/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.12` ->
`6.0.13`](https://renovatebot.com/diffs/npm/jose/6.0.12/6.0.13) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.12/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.12` ->
`6.0.13`](https://renovatebot.com/diffs/npm/jose/6.0.12/6.0.13) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.12/6.0.13?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [lucide-vue-next](https://lucide.dev)
([source](https://redirect.github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next))
| [`0.539.0` ->
`0.542.0`](https://renovatebot.com/diffs/npm/lucide-vue-next/0.539.0/0.542.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/lucide-vue-next/0.542.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/lucide-vue-next/0.539.0/0.542.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [marked](https://marked.js.org)
([source](https://redirect.github.com/markedjs/marked)) | [`16.1.2` ->
`16.2.1`](https://renovatebot.com/diffs/npm/marked/16.1.2/16.2.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/marked/16.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/marked/16.1.2/16.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [nest-commander](https://nest-commander.jaymcdoniel.dev)
([source](https://redirect.github.com/jmcdo29/nest-commander/tree/HEAD/pacakges/nest-commander))
| [`3.18.0` ->
`3.19.0`](https://renovatebot.com/diffs/npm/nest-commander/3.18.0/3.19.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/nest-commander/3.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nest-commander/3.18.0/3.19.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [openid-client](https://redirect.github.com/panva/openid-client) |
[`6.6.2` ->
`6.6.4`](https://renovatebot.com/diffs/npm/openid-client/6.6.2/6.6.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/openid-client/6.6.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/openid-client/6.6.2/6.6.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [pino](https://getpino.io)
([source](https://redirect.github.com/pinojs/pino)) | [`9.8.0` ->
`9.9.0`](https://renovatebot.com/diffs/npm/pino/9.8.0/9.9.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pino/9.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pino/9.8.0/9.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
[`10.14.0` ->
`10.15.0`](https://renovatebot.com/diffs/npm/pnpm/10.14.0/10.15.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.14.0/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| packageManager | minor |
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
[`10.14.0` ->
`10.15.0`](https://renovatebot.com/diffs/npm/pnpm/10.14.0/10.15.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.14.0/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| engines | minor |
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
`10.14.0` -> `10.15.0` |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.14.0/10.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| uses-with | minor |
| [python](https://redirect.github.com/actions/python-versions) |
`3.13.6` -> `3.13.7` |
[![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fpython-versions/3.13.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fpython-versions/3.13.6/3.13.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| uses-with | patch |
| [reka-ui](https://redirect.github.com/unovue/reka-ui) | [`2.4.1` ->
`2.5.0`](https://renovatebot.com/diffs/npm/reka-ui/2.4.1/2.5.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/reka-ui/2.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/reka-ui/2.4.1/2.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[rollup-plugin-node-externals](https://redirect.github.com/Septh/rollup-plugin-node-externals)
| [`8.0.1` ->
`8.1.0`](https://renovatebot.com/diffs/npm/rollup-plugin-node-externals/8.0.1/8.1.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/rollup-plugin-node-externals/8.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/rollup-plugin-node-externals/8.0.1/8.1.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [storybook](https://storybook.js.org)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/core))
| [`9.1.2` ->
`9.1.3`](https://renovatebot.com/diffs/npm/storybook/9.1.2/9.1.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/storybook/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/storybook/9.1.2/9.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [systeminformation](https://systeminformation.io)
([source](https://redirect.github.com/sebhildebrandt/systeminformation))
| [`5.27.7` ->
`5.27.8`](https://renovatebot.com/diffs/npm/systeminformation/5.27.7/5.27.8)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/systeminformation/5.27.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/systeminformation/5.27.7/5.27.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [tailwindcss](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss))
| [`4.1.11` ->
`4.1.12`](https://renovatebot.com/diffs/npm/tailwindcss/4.1.11/4.1.12) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tailwindcss/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tailwindcss/4.1.11/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [tailwindcss](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss))
| [`4.1.11` ->
`4.1.12`](https://renovatebot.com/diffs/npm/tailwindcss/4.1.11/4.1.12) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tailwindcss/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tailwindcss/4.1.11/4.1.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [tsx](https://tsx.is)
([source](https://redirect.github.com/privatenumber/tsx)) | [`4.20.3` ->
`4.20.5`](https://renovatebot.com/diffs/npm/tsx/4.19.3/4.20.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tsx/4.20.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tsx/4.19.3/4.20.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [tsx](https://tsx.is)
([source](https://redirect.github.com/privatenumber/tsx)) | [`4.20.3` ->
`4.20.5`](https://renovatebot.com/diffs/npm/tsx/4.20.3/4.20.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tsx/4.20.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tsx/4.20.3/4.20.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[tw-animate-css](https://redirect.github.com/Wombosvideo/tw-animate-css)
| [`1.3.6` ->
`1.3.7`](https://renovatebot.com/diffs/npm/tw-animate-css/1.3.6/1.3.7) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tw-animate-css/1.3.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tw-animate-css/1.3.6/1.3.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[tw-animate-css](https://redirect.github.com/Wombosvideo/tw-animate-css)
| [`1.3.6` ->
`1.3.7`](https://renovatebot.com/diffs/npm/tw-animate-css/1.3.6/1.3.7) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tw-animate-css/1.3.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tw-animate-css/1.3.6/1.3.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint))
| [`8.39.1` ->
`8.41.0`](https://renovatebot.com/diffs/npm/typescript-eslint/8.39.1/8.41.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.41.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.39.1/8.41.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [undici](https://undici.nodejs.org)
([source](https://redirect.github.com/nodejs/undici)) | [`7.13.0` ->
`7.15.0`](https://renovatebot.com/diffs/npm/undici/7.13.0/7.15.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/undici/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/undici/7.13.0/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | minor |
| [undici](https://undici.nodejs.org)
([source](https://redirect.github.com/nodejs/undici)) | [`7.13.0` ->
`7.15.0`](https://renovatebot.com/diffs/npm/undici/7.13.0/7.15.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/undici/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/undici/7.13.0/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [undici](https://undici.nodejs.org)
([source](https://redirect.github.com/nodejs/undici)) | [`7.13.0` ->
`7.15.0`](https://renovatebot.com/diffs/npm/undici/7.13.0/7.15.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/undici/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/undici/7.13.0/7.15.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[unplugin-swc](https://redirect.github.com/unplugin/unplugin-swc/tree/main/#readme)
([source](https://redirect.github.com/unplugin/unplugin-swc)) | [`1.5.5`
-> `1.5.7`](https://renovatebot.com/diffs/npm/unplugin-swc/1.5.5/1.5.7)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/unplugin-swc/1.5.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/unplugin-swc/1.5.5/1.5.7?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [vite](https://vite.dev)
([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite))
| [`7.1.1` ->
`7.1.3`](https://renovatebot.com/diffs/npm/vite/7.1.1/7.1.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite/7.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/7.1.1/7.1.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [vite-plugin-vue-devtools](https://redirect.github.com/vuejs/devtools)
([source](https://redirect.github.com/vuejs/devtools/tree/HEAD/packages/vite))
| [`8.0.0` ->
`8.0.1`](https://renovatebot.com/diffs/npm/vite-plugin-vue-devtools/8.0.0/8.0.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite-plugin-vue-devtools/8.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite-plugin-vue-devtools/8.0.0/8.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[vue](https://redirect.github.com/vuejs/core/tree/main/packages/vue#readme)
([source](https://redirect.github.com/vuejs/core)) | [`3.5.18` ->
`3.5.20`](https://renovatebot.com/diffs/npm/vue/3.5.18/3.5.20) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue/3.5.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue/3.5.18/3.5.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[vue](https://redirect.github.com/vuejs/core/tree/main/packages/vue#readme)
([source](https://redirect.github.com/vuejs/core)) | [`3.5.18` ->
`3.5.20`](https://renovatebot.com/diffs/npm/vue/3.5.18/3.5.20) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue/3.5.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue/3.5.18/3.5.20?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [vue-tsc](https://redirect.github.com/vuejs/language-tools)
([source](https://redirect.github.com/vuejs/language-tools/tree/HEAD/packages/tsc))
| [`3.0.5` ->
`3.0.6`](https://renovatebot.com/diffs/npm/vue-tsc/3.0.5/3.0.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue-tsc/3.0.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-tsc/3.0.5/3.0.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [vuetify](https://vuetifyjs.com)
([source](https://redirect.github.com/vuetifyjs/vuetify/tree/HEAD/packages/vuetify))
| [`3.9.4` ->
`3.9.6`](https://renovatebot.com/diffs/npm/vuetify/3.9.4/3.9.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vuetify/3.9.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vuetify/3.9.4/3.9.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [wrangler](https://redirect.github.com/cloudflare/workers-sdk)
([source](https://redirect.github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler))
| [`4.28.1` ->
`4.33.0`](https://renovatebot.com/diffs/npm/wrangler/4.28.1/4.33.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/wrangler/4.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/wrangler/4.28.1/4.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [zx](https://google.github.io/zx/)
([source](https://redirect.github.com/google/zx)) | [`8.8.0` ->
`8.8.1`](https://renovatebot.com/diffs/npm/zx/8.3.2/8.8.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/zx/8.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/zx/8.3.2/8.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [zx](https://google.github.io/zx/)
([source](https://redirect.github.com/google/zx)) | [`8.8.0` ->
`8.8.1`](https://renovatebot.com/diffs/npm/zx/8.8.0/8.8.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/zx/8.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/zx/8.8.0/8.8.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |

---

### Release Notes

<details>
<summary>apollographql/apollo-client (@&#8203;apollo/client)</summary>

###
[`v3.14.0`](https://redirect.github.com/apollographql/apollo-client/blob/HEAD/CHANGELOG.md#3140)

[Compare
Source](5c202cf3b2...v3.14.0)

##### Minor Changes

-
[#&#8203;12752](https://redirect.github.com/apollographql/apollo-client/pull/12752)
[`8b779b4`](8b779b428b)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add deprecations and warnings to remaining APIs changed in Apollo Client
4.0.

-
[#&#8203;12746](https://redirect.github.com/apollographql/apollo-client/pull/12746)
[`0bcd2f4`](0bcd2f4ead)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add warnings and deprecations for options and methods for all React
APIs.

-
[#&#8203;12751](https://redirect.github.com/apollographql/apollo-client/pull/12751)
[`567cad8`](567cad8fcc)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add `@deprecated` tags to all properties returned from any query API
(e.g. `client.query`, `observableQuery.refetch`, etc.), `client.mutate`,
and `client.subscribe` that are no longer available in Apollo Client
4.0.

-
[#&#8203;12746](https://redirect.github.com/apollographql/apollo-client/pull/12746)
[`0bcd2f4`](0bcd2f4ead)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add `preloadQuery.toPromise(queryRef)` as a replacement for
`queryRef.toPromise()`. `queryRef.toPromise()` has been removed in
Apollo Client 4.0 in favor of `preloadQuery.toPromise` and is now
considered deprecated.

-
[#&#8203;12736](https://redirect.github.com/apollographql/apollo-client/pull/12736)
[`ea89440`](ea89440132)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add deprecations and deprecation warnings for `ApolloClient` options and
methods.

-
[#&#8203;12763](https://redirect.github.com/apollographql/apollo-client/pull/12763)
[`5de6a3d`](5de6a3d318)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Version bump only to release latest as `rc`.

-
[#&#8203;12459](https://redirect.github.com/apollographql/apollo-client/pull/12459)
[`1c5a031`](1c5a0313d3)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Reset `addTypenameTransform` and `fragments` caches when calling
`cache.gc()` only when `resetResultCache` is `true`.

-
[#&#8203;12743](https://redirect.github.com/apollographql/apollo-client/pull/12743)
[`92ad409`](92ad4097e5)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add deprecations and warnings for `addTypename` in `InMemoryCache` and
`MockedProvider`.

-
[#&#8203;12743](https://redirect.github.com/apollographql/apollo-client/pull/12743)
[`92ad409`](92ad4097e5)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Add deprecations and warnings for `canonizeResults`.

-
[#&#8203;12751](https://redirect.github.com/apollographql/apollo-client/pull/12751)
[`567cad8`](567cad8fcc)
Thanks [@&#8203;jerelmiller](https://redirect.github.com/jerelmiller)! -
Warn when using a `standby` fetch policy with `client.query`.

##### Patch Changes

-
[#&#8203;12750](https://redirect.github.com/apollographql/apollo-client/pull/12750)
[`ecf3de1`](ecf3de1cc9)
Thanks [@&#8203;phryneas](https://redirect.github.com/phryneas)! -
Prevent field policies from overwriting/merging into supertype field
policies.

</details>

<details>
<summary>eslint/eslint (@&#8203;eslint/js)</summary>

###
[`v9.34.0`](https://redirect.github.com/eslint/eslint/compare/v9.33.0...b48fa20034e53bc65d1a58f3d834705e3087b00c)

[Compare
Source](https://redirect.github.com/eslint/eslint/compare/v9.33.0...v9.34.0)

</details>

<details>
<summary>floating-ui/floating-ui (@&#8203;floating-ui/dom)</summary>

###
[`v1.7.4`](https://redirect.github.com/floating-ui/floating-ui/blob/HEAD/packages/dom/CHANGELOG.md#174)

[Compare
Source](https://redirect.github.com/floating-ui/floating-ui/compare/@floating-ui/dom@1.7.3...@floating-ui/dom@1.7.4)

##### Patch Changes

- fix(getViewportRect): account for space left by `scrollbar-gutter:
stable`

</details>

<details>
<summary>floating-ui/floating-ui (@&#8203;floating-ui/vue)</summary>

###
[`v1.1.9`](https://redirect.github.com/floating-ui/floating-ui/blob/HEAD/packages/vue/CHANGELOG.md#119)

[Compare
Source](https://redirect.github.com/floating-ui/floating-ui/compare/@floating-ui/vue@1.1.8...@floating-ui/vue@1.1.9)

##### Patch Changes

- Update dependencies: `@floating-ui/dom@1.7.4`

</details>

<details>
<summary>ianvs/prettier-plugin-sort-imports
(@&#8203;ianvs/prettier-plugin-sort-imports)</summary>

###
[`v4.6.3`](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/releases/tag/v4.6.3)

[Compare
Source](https://redirect.github.com/ianvs/prettier-plugin-sort-imports/compare/4.6.2...v4.6.3)

#### What's Changed

- Revert "fix: conditionally register ember and oxc parsers when depend…
by [@&#8203;IanVS](https://redirect.github.com/IanVS) in
[IanVS#237](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/pull/237)

**Full Changelog**:
<https://github.com/IanVS/prettier-plugin-sort-imports/compare/4.6.2...v4.6.3>

###
[`v4.6.2`](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/releases/tag/4.6.2)

[Compare
Source](https://redirect.github.com/ianvs/prettier-plugin-sort-imports/compare/v4.6.1...4.6.2)

#### What's Changed

- fix: conditionally register ember and oxc parsers when dependencies
available by [@&#8203;jahands](https://redirect.github.com/jahands) in
[IanVS#234](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/pull/234)

#### New Contributors

- [@&#8203;jahands](https://redirect.github.com/jahands) made their
first contribution in
[IanVS#234](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/pull/234)

**Full Changelog**:
<https://github.com/IanVS/prettier-plugin-sort-imports/compare/v4.6.1...4.6.2>

</details>

<details>
<summary>adobe/react-spectrum
(@&#8203;internationalized/number)</summary>

###
[`v3.6.5`](https://redirect.github.com/adobe/react-spectrum/compare/@internationalized/number@3.6.4...@internationalized/number@3.6.5)

[Compare
Source](https://redirect.github.com/adobe/react-spectrum/compare/@internationalized/number@3.6.4...@internationalized/number@3.6.5)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/testing)</summary>

###
[`v11.1.6`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.6)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.5...v11.1.6)

#### v11.1.6 (2025-08-07)

##### Bug fixes

- `core`
- [#&#8203;15504](https://redirect.github.com/nestjs/nest/pull/15504)
fix(core): fix race condition in class dependency resolution from
imported modules
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- [#&#8203;15469](https://redirect.github.com/nestjs/nest/pull/15469)
fix(core): attach root inquirer for nested transient providers
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- `microservices`
- [#&#8203;15508](https://redirect.github.com/nestjs/nest/pull/15508)
fix(microservices): report correct buffer length in exception
([@&#8203;kim-sung-jee](https://redirect.github.com/kim-sung-jee))
- [#&#8203;15492](https://redirect.github.com/nestjs/nest/pull/15492)
fix(microservices): fix kafka serilization of class instances
([@&#8203;LeonBiersch](https://redirect.github.com/LeonBiersch))

##### Dependencies

- `platform-fastify`
- [#&#8203;15493](https://redirect.github.com/nestjs/nest/pull/15493)
chore(deps): bump
[@&#8203;fastify/cors](https://redirect.github.com/fastify/cors) from
11.0.1 to 11.1.0
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

##### Committers: 6

- Jiri Hajek
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- Kamil Mysliwiec
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- Leon Biersch
([@&#8203;LeonBiersch](https://redirect.github.com/LeonBiersch))
- Seongjee Kim
([@&#8203;kim-sung-jee](https://redirect.github.com/kim-sung-jee))
- [@&#8203;premierbell](https://redirect.github.com/premierbell)
- pTr ([@&#8203;ptrgits](https://redirect.github.com/ptrgits))

</details>

<details>
<summary>nuxt/devtools (@&#8203;nuxt/devtools)</summary>

###
[`v2.6.3`](https://redirect.github.com/nuxt/devtools/blob/HEAD/CHANGELOG.md#263-2025-08-22)

[Compare
Source](https://redirect.github.com/nuxt/devtools/compare/v2.6.2...v2.6.3)

</details>

<details>
<summary>nuxt/eslint (@&#8203;nuxt/eslint)</summary>

###
[`v1.9.0`](https://redirect.github.com/nuxt/eslint/releases/tag/v1.9.0)

[Compare
Source](https://redirect.github.com/nuxt/eslint/compare/v1.8.0...v1.9.0)

#####    🚀 Features

- Update plugins  -  by
[@&#8203;antfu](https://redirect.github.com/antfu)
[<samp>(b80cb)</samp>](https://redirect.github.com/nuxt/eslint/commit/b80cbeb)

#####    🐞 Bug Fixes

- Add `defineNuxtConfig` as ESLint's globals, close
[#&#8203;603](https://redirect.github.com/nuxt/eslint/issues/603)  -  by
[@&#8203;antfu](https://redirect.github.com/antfu) in
[#&#8203;603](https://redirect.github.com/nuxt/eslint/issues/603)
[<samp>(2e67f)</samp>](https://redirect.github.com/nuxt/eslint/commit/2e67f94)

#####     [View changes on
GitHub](https://redirect.github.com/nuxt/eslint/compare/v1.8.0...v1.9.0)

</details>

<details>
<summary>nuxt/ui (@&#8203;nuxt/ui)</summary>

###
[`v3.3.2`](https://redirect.github.com/nuxt/ui/blob/HEAD/CHANGELOG.md#332-2025-08-14)

[Compare
Source](https://redirect.github.com/nuxt/ui/compare/v3.3.1...v3.3.2)

###
[`v3.3.1`](https://redirect.github.com/nuxt/ui/blob/HEAD/CHANGELOG.md#331-2025-08-14)

[Compare
Source](https://redirect.github.com/nuxt/ui/compare/v3.3.0...v3.3.1)

##### Features

- **Form:** support error RegExp in exposed methods
([#&#8203;4608](https://redirect.github.com/nuxt/ui/issues/4608))
([b8b74a0](b8b74a0c33))
- **Tree:** add `item-wrapper` slot
([#&#8203;4521](https://redirect.github.com/nuxt/ui/issues/4521))
([411d937](411d93710a))
- **useOverlay:** return promise on `open` method
([#&#8203;4592](https://redirect.github.com/nuxt/ui/issues/4592))
([58aac86](58aac862dd))

##### Bug Fixes

- **Drawer:** improve closing animation with `inset` prop
([#&#8203;4676](https://redirect.github.com/nuxt/ui/issues/4676))
([9da1527](9da1527f62))
- **FileUpload:** handle wildcard in dropzone `dataTypes`
([#&#8203;4671](https://redirect.github.com/nuxt/ui/issues/4671))
([729bed4](729bed47f5))
- **FileUpload:** improve file removal a11y
([#&#8203;4607](https://redirect.github.com/nuxt/ui/issues/4607))
([f90bba0](f90bba00c1))
- **FileUpload:** open dialog on keyup
([#&#8203;4629](https://redirect.github.com/nuxt/ui/issues/4629))
([8e9265e](8e9265e91f))
- **FileUpload:** prevent default on keydown
([#&#8203;4633](https://redirect.github.com/nuxt/ui/issues/4633))
([68d8a98](68d8a983ed))
- **Input:** incorrect rendering of type `date` / `time` on iOS
([#&#8203;4715](https://redirect.github.com/nuxt/ui/issues/4715))
([93cc83c](93cc83cbc7))
- **InputMenu/Select/SelectMenu:** add display value fallback when no
items found
([#&#8203;4689](https://redirect.github.com/nuxt/ui/issues/4689))
([34ca811](34ca811ff0))
- **Select/InputMenu:** handle focus via label inside a FormField
([#&#8203;4696](https://redirect.github.com/nuxt/ui/issues/4696))
([55dbcd2](55dbcd20a8))
- **Tabs:** add missing Badge import
([#&#8203;4594](https://redirect.github.com/nuxt/ui/issues/4594))
([fbec29c](fbec29c1b7))
- **Toast:** add type for progress `ui` prop
([#&#8203;4677](https://redirect.github.com/nuxt/ui/issues/4677))
([a8af85c](a8af85c14b))
- **Tooltip:** render only if `text` or `kbds` are present
([#&#8203;4568](https://redirect.github.com/nuxt/ui/issues/4568))
([5e39cbb](5e39cbb3b2))

</details>

<details>
<summary>rollup/rollup (@&#8203;rollup/rollup-linux-x64-gnu)</summary>

###
[`v4.49.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4490)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.48.1...v4.49.0)

*2025-08-27*

##### Features

- Allow config plugins to resolve imports first before deciding whether
to treat them as external
([#&#8203;6038](https://redirect.github.com/rollup/rollup/issues/6038))

##### Pull Requests

- [#&#8203;6038](https://redirect.github.com/rollup/rollup/pull/6038):
feat: Run external check in `cli/run/loadConfigFile.ts` as last in order
to allow handling of e.g. workspace package imports in TS monorepos
correctly ([@&#8203;stazz](https://redirect.github.com/stazz),
[@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;6082](https://redirect.github.com/rollup/rollup/pull/6082):
Improve build pipeline performance
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

###
[`v4.48.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4481)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.48.0...v4.48.1)

*2025-08-25*

##### Bug Fixes

- Correctly ignore white-space in JSX strings around line-breaks
([#&#8203;6051](https://redirect.github.com/rollup/rollup/issues/6051))

##### Pull Requests

- [#&#8203;6051](https://redirect.github.com/rollup/rollup/pull/6051):
fix: handle whitespace according to JSX common practice
([@&#8203;cyyynthia](https://redirect.github.com/cyyynthia))
- [#&#8203;6078](https://redirect.github.com/rollup/rollup/pull/6078):
build: optimize pipeline take two
([@&#8203;cyyynthia](https://redirect.github.com/cyyynthia))

###
[`v4.48.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4480)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.47.1...v4.48.0)

*2025-08-23*

##### Features

- If configured, also keep unparseable import attributes of external
dynamic imports in the
output([#&#8203;6071](https://redirect.github.com/rollup/rollup/issues/6071))

##### Bug Fixes

- Ensure variables referenced in non-removed import attributes are
included
([#&#8203;6071](https://redirect.github.com/rollup/rollup/issues/6071))

##### Pull Requests

- [#&#8203;6071](https://redirect.github.com/rollup/rollup/pull/6071):
Keep attributes for external dynamic imports
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi))
- [#&#8203;6079](https://redirect.github.com/rollup/rollup/pull/6079):
fix(deps): update swc monorepo (major)
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])
- [#&#8203;6080](https://redirect.github.com/rollup/rollup/pull/6080):
fix(deps): lock file maintenance minor/patch updates
([@&#8203;renovate](https://redirect.github.com/renovate)\[bot])

###
[`v4.47.1`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4471)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.47.0...v4.47.1)

*2025-08-21*

##### Bug Fixes

- Revert build process changes to investigate issues
([#&#8203;6077](https://redirect.github.com/rollup/rollup/issues/6077))

##### Pull Requests

- [#&#8203;6077](https://redirect.github.com/rollup/rollup/pull/6077):
Revert "build: aggressively optimize wasm build, improve pipeline
([#&#8203;6053](https://redirect.github.com/rollup/rollup/issues/6053))"
([@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

### [`v4.47.0`](https://redirect.github.com/rollup/rollup/blob/HEAD/C

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS42MC40IiwidXBkYXRlZEluVmVyIjoiNDEuODIuNyIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-27 16:14:56 -04:00
github-actions[bot]
1bf74e9d6c chore(main): release 4.16.0 (#1613)
🤖 I have created a release *beep* *boop*
---


## [4.16.0](https://github.com/unraid/api/compare/v4.15.1...v4.16.0)
(2025-08-27)


### Features

* add `parityCheckStatus` field to `array` query
([#1611](https://github.com/unraid/api/issues/1611))
([c508366](c508366702))
* generated UI API key management + OAuth-like API Key Flows
([#1609](https://github.com/unraid/api/issues/1609))
([674323f](674323fd87))


### Bug Fixes

* **connect:** clear `wanport` upon disabling remote access
([#1624](https://github.com/unraid/api/issues/1624))
([9df6a3f](9df6a3f5eb))
* **connect:** valid LAN FQDN while remote access is enabled
([#1625](https://github.com/unraid/api/issues/1625))
([aa58888](aa588883cc))
* correctly parse periods in share names from ini file
([#1629](https://github.com/unraid/api/issues/1629))
([7d67a40](7d67a40433))
* **rc.unraid-api:** remove profile sourcing
([#1622](https://github.com/unraid/api/issues/1622))
([6947b5d](6947b5d4af))
* remove unused api key calls
([#1628](https://github.com/unraid/api/issues/1628))
([9cd0d6a](9cd0d6ac65))
* retry VMs init for up to 2 min
([#1612](https://github.com/unraid/api/issues/1612))
([b2e7801](b2e7801238))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-27 16:06:03 -04:00
Eli Bosley
9cd0d6ac65 fix: remove unused api key calls (#1628)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
  - None.
- Bug Fixes
- Improved reliability of API key updates and clearer errors when keys
are missing or data is invalid.
- More robust initialization to prevent intermittent failures during API
key operations.
- Refactor
- Simplified API key management by unifying lookup flows and adopting
consistent async handling.
- Tests
- Expanded coverage for error scenarios and strengthened test setup for
initialization and file-read issues.
- Chores
  - Minor formatting cleanups.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-27 15:50:15 -04:00
Eli Bosley
f0348aa038 refactor: simplify DockerService by removing OnModuleInit implementation
Removed the OnModuleInit interface and its associated initialization logic from DockerService. This change streamlines the service by eliminating unnecessary complexity related to module initialization, while maintaining core functionality.
2025-08-27 15:34:20 -04:00
Eli Bosley
c1ab3a4746 refactor: implement local-session for internal client auth (#1606)
Remove the confusing API keys that were auto-generated for the CLI &
Connect. Instead, support authentication via a custom `local-session`
header.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Local-session authentication for internal/CLI requests
(x-local-session) with generation, validation, on-disk persistence, and
lifecycle init.
* Internal client gains multi-strategy auth (local session, cookie, or
API key), supports subscriptions/origin, and can be cleared/recreated.

* **Security**
  * Embedded development API keys removed from the repository.

* **Refactor**
* Canonical internal client introduced; consumers migrated from legacy
CLI key services.

* **Tests / Chores**
* Tests, env, and gitignore updated for local-session and
canonical-client changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
2025-08-27 15:28:25 -04:00
Pujit Mehrotra
7d67a40433 fix: correctly parse periods in share names from ini file (#1629)
Share names live as section headers in `emhttp/state/shares.ini`.
However, periods in ini section headers typically denote nested
hierarchy. This behavior is disabled in unraid's php setup, but cannot
be disabled/configured in the api's current ini parser.

So, we perform post-processing to reconcile nested objects into dot
notation.

Known issue: trailing and leading periods will not be treated as
distinct from shares without them.

i.e. `.share.name.` will conflict with `share.name`, `.share.name`, or
`share.name.`. The last of the conflicting names will be used/exposed.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- New Features
- Support shares with periods and emoji in their names across parsing
and listings.

- Bug Fixes
- Fixed configuration parsing for section names containing periods to
ensure affected shares load and display correctly.
  - Standardized reporting of encryption status for all shares.

- Tests
- Expanded coverage to validate parsing and retrieval of shares with
special characters (periods and emoji), ensuring consistent behavior
across modules.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-27 15:14:07 -04:00
Eli Bosley
674323fd87 feat: generated UI API key management + OAuth-like API Key Flows (#1609)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* API Key Authorization flow with consent screen, callback support, and
a Tools page.
* Schema-driven API Key creation UI with permission presets, templates,
and Developer Authorization Link.
* Effective Permissions preview and a new multi-select permission
control.

* **UI Improvements**
* Mask/toggle API keys, copy-to-clipboard with toasts, improved select
labels, new label styles, tab wrapping, and accordionized color
controls.

* **Documentation**
  * Public guide for the API Key authorization flow and scopes added.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-27 12:37:39 -04:00
google-labs-jules[bot]
6947b5d4af fix(rc.unraid-api): remove profile sourcing (#1622)
This change removes the line that sources `/etc/profile` from the
`rc.unraid-api` script. This is done to prevent unexpected side effects
and improve the script's isolation.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-26 13:11:16 -04:00
Pujit Mehrotra
c4cc54923c test: add integration tests for deleting invalid notifications (#1626) 2025-08-25 13:58:02 -04:00
Pujit Mehrotra
c508366702 feat: add parityCheckStatus field to array query (#1611)
Responds with a ParityCheck:
```ts
type ParityCheck {
  """Date of the parity check"""
  date: DateTime

  """Duration of the parity check in seconds"""
  duration: Int

  """Speed of the parity check, in MB/s"""
  speed: String

  """Status of the parity check"""
  status: ParityCheckStatus!

  """Number of errors during the parity check"""
  errors: Int

  """Progress percentage of the parity check"""
  progress: Int

  """Whether corrections are being written to parity"""
  correcting: Boolean

  """Whether the parity check is paused"""
  paused: Boolean

  """Whether the parity check is running"""
  running: Boolean
}

enum ParityCheckStatus {
    NEVER_RUN = 'never_run',
    RUNNING = 'running',
    PAUSED = 'paused',
    COMPLETED = 'completed',
    CANCELLED = 'cancelled',
    FAILED = 'failed',
}
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Exposes a structured parity-check status for arrays with detailed
fields (status enum: NEVER_RUN, RUNNING, PAUSED, COMPLETED, CANCELLED,
FAILED), date, duration, speed, progress, errors, and running/paused
flags.

- Tests
- Adds comprehensive unit tests covering all parity-check states,
numeric edge cases, speed/date/duration/progress calculations, and
real-world scenarios.

- Refactor
- Safer numeric/date parsing and a new numeric-conversion helper; minor
formatting/cleanup in shared utilities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-25 13:22:43 -04:00
Pujit Mehrotra
9df6a3f5eb fix(connect): clear wanport upon disabling remote access (#1624)
Resolve #1615 -- lingering wanport caused issue with LAN Access via
Connect, because those URL's are constructed using `wanport`, but since
WAN access would be disabled, NGINX would not listen on the port.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* WAN access settings now correctly gate related options; UPnP only
enables when WAN access is Always.
* Static WAN port is applied only when eligible and is cleared when WAN
access is disabled to avoid unintended overrides.
* Dynamic remote access migration uses sanitized URLs to prevent
propagation of user-provided addressing.

* **Chores**
* Minor ordering and formatting adjustments to reflect updated
precedence and clarify behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-25 13:21:48 -04:00
Pujit Mehrotra
aa588883cc fix(connect): valid LAN FQDN while remote access is enabled (#1625)
Stop appending `wanport` to LAN FQDN when remote access is enabled.
2025-08-25 13:15:07 -04:00
Pujit Mehrotra
b2e7801238 fix: retry VMs init for up to 2 min (#1612) 2025-08-22 15:29:44 -04:00
Pujit Mehrotra
fd895cacf0 refactor: reuse ChangelogModal in HeaderOsVersion component (#1607)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- View OS release notes directly for the currently displayed version,
with a link to open in a new tab.
- Changelog modal supports viewing a specific release outside the update
flow.
- Improvements
- Changelog modal prioritizes a prettier docs view when available, with
fallback to raw notes.
- Enhanced theming with better dark mode support, including Azure theme
alignment.
- Clearer modal behavior: consistent open/close handling and titles
based on the selected release.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-21 12:11:04 -04:00
github-actions[bot]
6edd3a3d16 chore(main): release 4.15.1 (#1604)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-20 17:23:07 -04:00
Eli Bosley
ac198d5d1a fix: minor duplicate click handler and version resolver nullability issue 2025-08-20 17:21:18 -04:00
github-actions[bot]
f1c043fe5f chore(main): release 4.15.0 (#1603)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-20 17:13:08 -04:00
Eli Bosley
d0c66020e1 feat(api): restructure versioning information in GraphQL schema (#1600) 2025-08-20 17:03:53 -04:00
Eli Bosley
335f949b53 docs: update API documentation for Unraid OS v7.2 integration
- Revised language to clarify that the API is built into Unraid OS v7.2 and does not require plugin installation.
- Updated sections for earlier versions to reflect the Unraid Connect plugin's role and access to newer API features.
- Enhanced clarity in the "Get Started" section with step-by-step instructions for both v7.2 and pre-7.2 users.
2025-08-20 15:13:06 -04:00
github-actions[bot]
26aeca3624 chore(main): release 4.14.0 (#1589)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-19 14:45:26 -04:00
Eli Bosley
2b4c2a264b feat(api): add cpu utilization query and subscription (#1590)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-19 14:44:16 -04:00
Eli Bosley
b7798b82f4 feat: enhance OIDC claim evaluation with array handling (#1596) 2025-08-19 12:21:17 -04:00
Eli Bosley
426283011a fix: remove unraid-api sso users & always apply sso modification on < 7.2 (#1595) 2025-08-19 12:00:00 -04:00
smdion
effdbcf0f5 Authentik SSO Instruction Clarity (#1591) 2025-08-18 17:20:52 -04:00
renovate[bot]
541b0edd35 chore(deps): update actions/checkout action to v5 (#1581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 13:30:54 -04:00
Eli Bosley
ce63d5dca2 docs: enhance API documentation with structured sections and tips
- Added front matter to CLI and API usage documentation for better organization.
- Introduced tips and info boxes to highlight important information and best practices.
- Updated sections with icons for improved visual clarity and navigation.
- Enhanced the OIDC provider setup guide with quick start instructions and detailed configuration tips.
2025-08-15 13:22:50 -04:00
Eli Bosley
bcaacca061 docs: enhance API documentation with web GUI management options
- Added tips for managing developer options and API keys through the web interface.
- Updated the GraphQL sandbox enabling instructions to include a web GUI method.
- Clarified API key management and authentication methods, including SSO/OIDC.
- Revised the availability section to reflect native integration in Unraid v7.2 and provide guidance for earlier versions.
2025-08-15 13:19:23 -04:00
Eli Bosley
0afc4e8e9a docs: enhance OIDC provider setup and roadmap documentation
- Updated OIDC provider setup instructions to clarify redirect URI protocol requirements.
- Revised security best practices to emphasize the importance of using HTTPS.
- Expanded the upcoming features section with a detailed roadmap, including completed and planned features for the Unraid API.
2025-08-15 13:14:34 -04:00
Eli Bosley
1a01696dc7 fix: update OIDC provider setup documentation for navigation clarity
- Revised navigation instructions to specify the path for accessing OIDC settings.
- Enhanced clarity in the documentation to improve user experience.
2025-08-15 13:07:39 -04:00
Eli Bosley
1bc5251310 fix: update OIDC provider setup documentation for redirect URI and screenshots
- Changed the format of screenshot descriptions to italic for consistency.
- Updated redirect URI examples to remove the port number for standard configurations.
- Clarified instructions regarding the use of correct ports in non-standard setups.
2025-08-15 12:56:04 -04:00
Eli Bosley
3a10871918 fix: update Docusaurus PR workflow to process and copy API docs
- Renamed the step to "Copy and process docs" for clarity.
- Updated the workflow to copy images to the Docusaurus static directory.
- Added functionality to update image paths in markdown files to use absolute paths pointing to /img/api/.
2025-08-15 12:49:29 -04:00
github-actions[bot]
58b5544bea chore(main): release 4.13.1 (#1588)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-15 12:44:16 -04:00
Eli Bosley
a4ff3c4092 fix: insecure routes not working for SSO (#1587) 2025-08-15 12:43:22 -04:00
github-actions[bot]
1e0a54d9ef chore(main): release 4.13.0 (#1557)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-15 12:09:52 -04:00
Eli Bosley
096fe98710 chore: update reviewers in Docusaurus PR workflow
- Removed 'pujitm' and 'mdatelle' from the list of reviewers in the create-docusaurus-pr.yml workflow file to streamline the review process.
2025-08-15 12:04:02 -04:00
renovate[bot]
57217852a3 fix(deps): pin dependencies (#1586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 12:01:23 -04:00
Eli Bosley
979a267bc5 feat: implement OIDC provider management in GraphQL API (#1563)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-15 11:59:21 -04:00
Zack Spear
96c120f9b2 feat: connect settings page updated for responsive webgui (#1585) 2025-08-15 09:44:06 -04:00
Eli Bosley
a2c5d2495f fix: refactor API client to support Unix socket connections (#1575) 2025-08-13 16:15:15 -04:00
Eli Bosley
b3216874fa fix(theme): API key white text on white background (#1584)
When generating an API key with the black theme active, the key was
displayed with white text on a white background. This was caused by
hardcoded light-theme classes (`bg-gray-50` and `border-gray-200`) on
the `Input` component that displays the key.

This change removes the hardcoded background and border color classes,
allowing the themed styles to be applied correctly.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- Refactor
- Consolidated common UI and form components behind single import entry
points, simplifying usage and making components easier to discover. No
functional changes.
- Style
- Refined API key input appearance for a cleaner, less intrusive look
while preserving readability and existing controls (copy, visibility
toggle, read-only).
- Chores
- Streamlined component import paths to improve developer experience and
consistency across the app. No user-facing behavior changes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-13 14:43:42 -04:00
renovate[bot]
27dbfde845 chore(deps): update dependency vite-plugin-vue-devtools to v8 (#1553) 2025-08-11 22:24:31 -04:00
renovate[bot]
1a25fedd23 chore(deps): pin dependency pnpm to 10.14.0 (#1578) 2025-08-11 22:23:44 -04:00
renovate[bot]
ad6aa3b674 fix(deps): update all non-major dependencies (#1579) 2025-08-11 22:23:18 -04:00
Pujit Mehrotra
9c4e764c95 chore: add check-node-version pre-install check to monorepo (#1570)
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210813683607812

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
  * Enforced use of Node.js version 22 during installation.
* Added a new tool to verify Node.js version before installing
dependencies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 11:06:54 -04:00
Pujit Mehrotra
20c2d5b445 feat: add moveDockerEntriesToFolder mutation (#1569)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added the ability to move multiple Docker entries into a specified
folder using a new mutation in the API.

* **Tests**
* Introduced comprehensive tests to validate moving entries between
folders and collecting ancestor entries, ensuring correct behavior and
error handling in various scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-11 11:03:10 -04:00
renovate[bot]
85a441b51d chore(deps): update actions/download-artifact action to v5 (#1573)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[actions/download-artifact](https://redirect.github.com/actions/download-artifact)
| action | major | `v4` -> `v5` |

---

### Release Notes

<details>
<summary>actions/download-artifact (actions/download-artifact)</summary>

###
[`v5`](https://redirect.github.com/actions/download-artifact/compare/v4...v5)

[Compare
Source](https://redirect.github.com/actions/download-artifact/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 20:53:47 -04:00
renovate[bot]
c9577e9bf2 chore(deps): update dependency tailwindcss to v4 (#1281)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [tailwindcss](https://tailwindcss.com)
([source](https://redirect.github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss))
| [`3.4.17` ->
`4.1.11`](https://renovatebot.com/diffs/npm/tailwindcss/3.4.17/4.1.11) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tailwindcss/4.1.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tailwindcss/3.4.17/4.1.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>tailwindlabs/tailwindcss (tailwindcss)</summary>

###
[`v4.1.11`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4111---2025-06-26)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.10...v4.1.11)

##### Fixed

- Add heuristic to skip candidate migrations inside `emit(…)`
([#&#8203;18330](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18330))
- Extract candidates with variants in Clojure/ClojureScript keywords
([#&#8203;18338](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18338))
- Document `--watch=always` in the CLI's usage
([#&#8203;18337](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18337))
- Add support for Vite 7 to `@tailwindcss/vite`
([#&#8203;18384](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18384))

###
[`v4.1.10`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4110---2025-06-11)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.9...v4.1.10)

##### Fixed

- Fix incorrectly generated CSS when using percentages in arbitrary
values with calc, e.g.: `w-[calc(100%-var(--offset))]`
([#&#8203;18289](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18289))

###
[`v4.1.9`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#419---2025-06-11)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.8...v4.1.9)

##### Fixed

- Correctly parse custom properties with strings containing semicolons
([#&#8203;18251](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18251))
- Upgrade: Migrate arbitrary modifiers without percentage signs to bare
values (e.g. `/[0.16]` → `/16`)
([#&#8203;18184](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18184))
- Upgrade: Migrate CSS variable shorthands where fallback value contains
function call
([#&#8203;18184](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18184))
- Upgrade: Migrate negative arbitrary values to negative bare values
(e.g. `mb-[-32rem]` → `-mb-128`)
([#&#8203;18212](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18212))
- Upgrade: Do not migrate `blur` in `wire:model.blur`
([#&#8203;18216](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18216))
- Don't add spaces around CSS dashed idents when formatting math
expressions
([#&#8203;18220](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18220))

###
[`v4.1.8`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#418---2025-05-27)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.7...v4.1.8)

##### Added

- Improve error messages when `@apply` fails
([#&#8203;18059](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18059))

##### Fixed

- Upgrade: Do not migrate declarations that look like candidates in
`<style>` blocks
([#&#8203;18057](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18057),
[18068](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18068))
- Upgrade: Don't error when looking for `tailwindcss` in pnpm monorepos
([#&#8203;18065](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18065))
- Upgrade: Don't error when updating dependencies in pnpm monorepos
([#&#8203;18065](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18065))
- Upgrade: Migrate deprecated `order-none` to `order-0`
([#&#8203;18126](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18126))
- Support Leptos `class:` attributes when extracting classes
([#&#8203;18093](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18093))
- Fix "Cannot read properties of undefined" crash on malformed arbitrary
value
([#&#8203;18133](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18133))
- Upgrade: Migrate `-mt-[0px]` to `mt-[0px]` instead of the other way
around
([#&#8203;18154](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18154))
- Fix Haml pre-processing crash when there is no `\n` at the end of the
file
([#&#8203;18155](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18155))
- Ignore `.pnpm-store` folders by default (can be overridden by `@source
…` rules)
([#&#8203;18163](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18163))
- Fix PostCSS crash when calling `toJSON()`
([#&#8203;18083](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18083))

###
[`v4.1.7`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#417---2025-05-15)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.6...v4.1.7)

##### Added

- Upgrade: Migrate bare values to named values
([#&#8203;18000](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18000))
- Upgrade: Added cache to improve template migration performance
([#&#8203;18025](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18025))

##### Fixed

- Allow `_` before numbers during candidate extraction
([#&#8203;17961](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17961))
- Prevent duplicate suggestions when using `@theme` and `@utility`
together
([#&#8203;17675](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17675))
- Ensure that media queries within `::before` and `::after` pseudo
selectors create valid CSS rules in production builds
([#&#8203;17979](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17979))
- Ensure that the standalone CLI does not leave temporary files behind
([#&#8203;17981](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17981))
- Ensure `-rotate-*` utilities properly negate arbitrary values
([#&#8203;18014](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18014))
- Ignore custom variants using `:merge(…)` selectors in legacy JS
plugins
([#&#8203;18020](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18020))
- Ensure classes containing `.` are properly extracted from Clojure
files
([#&#8203;18038](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18038))
- Upgrade: Fix error when using `@import … source(…)`
([#&#8203;17963](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17963))
- Upgrade: Change casing of utilities with named values to kebab-case to
match updated theme variables
([#&#8203;18017](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18017))
- Upgrade: Don't migrate strings that match utility names in Vue
attribute bindings other than `class`
([#&#8203;18025](https://redirect.github.com/tailwindlabs/tailwindcss/pull/18025))

###
[`v4.1.6`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#416---2025-05-09)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.5...v4.1.6)

##### Added

- Upgrade: Automatically convert arbitrary values to named values when
possible (e.g. `h-[1lh]` to `h-lh`)
([#&#8203;17831](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17831),
[#&#8203;17854](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17854))
- Upgrade: Update dependencies in parallel for improved performance
([#&#8203;17898](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17898))
- Add detailed logging about `@source` directives, discovered files and
scanned files when using `DEBUG=*`
([#&#8203;17906](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17906),
[#&#8203;17952](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17952))
- Add support for generating source maps in development
([#&#8203;17775](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17775))

##### Fixed

- Ensure negative arbitrary `scale` values generate negative values
([#&#8203;17831](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17831))
- Fix HAML extraction with embedded Ruby
([#&#8203;17846](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17846))
- Don't scan files for utilities when using `@reference`
([#&#8203;17836](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17836))
- Fix incorrectly replacing `_` with ` ` in arbitrary modifier shorthand
`bg-red-500/(--my_opacity)`
([#&#8203;17889](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17889))
- Don't scan `.log` files for classes by default
([#&#8203;17906](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17906))
- Ensure that custom utilities applying other custom utilities don't
swallow nested `@apply` rules
([#&#8203;17925](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17925))
- Download platform specific package if `optionalDependencies` are
skipped
([#&#8203;17929](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17929))

###
[`v4.1.5`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#415---2025-04-30)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.4...v4.1.5)

##### Added

- Support using `@tailwindcss/upgrade` to upgrade between versions of
v4.\*
([#&#8203;17717](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17717))
- Add `h-lh` / `min-h-lh` / `max-h-lh` utilities
([#&#8203;17790](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17790))
- Transition `display`, `visibility`, `content-visibility`, `overlay`,
and `pointer-events` when using `transition` to simplify
`@starting-style` usage
([#&#8203;17812](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17812))

##### Fixed

- Don't scan `.geojson` or `.db` files for classes by default
([#&#8203;17700](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17700),
[#&#8203;17711](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17711))
- Hide default shadow suggestions when missing default shadow theme keys
([#&#8203;17743](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17743))
- Replace `_` with `.` in theme suggestions for `@utility` if surrounded
by digits
([#&#8203;17733](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17733))
- Skip `color-mix(…)` when opacity is `100%`
([#&#8203;17815](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17815))
- PostCSS: Ensure that errors in imported stylesheets are recoverable
([#&#8203;17754](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17754))
- Upgrade: Bump all Tailwind CSS related dependencies during upgrade
([#&#8203;17763](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17763))
- Upgrade: Don't add `-` to variants starting with `@`
([#&#8203;17814](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17814))
- Upgrade: Don't format stylesheets that didn't change when upgrading
([#&#8203;17824](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17824))

###
[`v4.1.4`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#414---2025-04-14)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.3...v4.1.4)

##### Added

- Add experimental `@tailwindcss/oxide-wasm32-wasi` target for running
Tailwind in browser environments like StackBlitz
([#&#8203;17558](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17558))

##### Fixed

- Ensure `color-mix(…)` polyfills do not cause used CSS variables to be
removed
([#&#8203;17555](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17555))
- Ensure `color-mix(…)` polyfills create fallbacks for theme variables
that reference other theme variables
([#&#8203;17562](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17562))
- Fix brace expansion in declining ranges like `{10..0..5}` and
`{0..10..-5}`
([#&#8203;17591](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17591))
- Work around a Chrome rendering bug when using the `skew-*` utilities
([#&#8203;17627](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17627))
- Ensure container query variant names can contain hyphens
([#&#8203;17628](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17628))
- Ensure `shadow-inherit`, `inset-shadow-inherit`,
`drop-shadow-inherit`, and `text-shadow-inherit` inherit the shadow
color
([#&#8203;17647](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17647))
- Ensure compatibility with array tuples used in `fontSize` JS theme
keys
([#&#8203;17630](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17630))
- Ensure folders with binary file extensions in their names are scanned
for utilities
([#&#8203;17595](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17595))
- Upgrade: Convert `fontSize` array tuple syntax to CSS theme variables
([#&#8203;17630](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17630))

###
[`v4.1.3`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#413---2025-04-04)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.2...v4.1.3)

##### Fixed

- Show warning when using unsupported bare value data type in
`--value(…)`
([#&#8203;17464](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17464))
- PostCSS: Ensure changes to the input CSS file don't generate stale
output when using Turbopack
([#&#8203;17554](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17554))
- Ensure classes are detected in Ruby's `%w` syntax in Slim templates
([#&#8203;17557](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17557))

###
[`v4.1.2`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#412---2025-04-03)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.1...v4.1.2)

##### Fixed

- Don't rely on the presence of `@layer base` to polyfill `@property`
([#&#8203;17506](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17506))
- Support setting multiple inset shadows as arbitrary values
([#&#8203;17523](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17523))
- Fix `drop-shadow-*` utilities that are defined with multiple shadows
([#&#8203;17515](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17515))
- PostCSS: Fix race condition when two changes are queued concurrently
([#&#8203;17514](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17514))
- PostCSS: Ensure files containing `@tailwind utilities` are processed
([#&#8203;17514](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17514))
- Ensure the `color-mix(…)` polyfill creates fallbacks even when using
colors that cannot be statically analyzed
([#&#8203;17513](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17513))
- Fix slow incremental builds with `@tailwindcss/vite` and
`@tailwindcss/postscss` (especially on Windows)
([#&#8203;17511](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17511))
- Vite: Fix missing CSS file in Qwik setups
([#&#8203;17533](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17533))

###
[`v4.1.1`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#411---2025-04-02)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.1.0...v4.1.1)

##### Fixed

- Disable padding in `@source inline(…)` brace expansion
([#&#8203;17491](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17491))
- Inject polyfills after `@import` and body-less `@layer`
([#&#8203;17493](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17493))
- Ensure `@tailwindcss/cli` does not contain an import for `jiti`
([#&#8203;17502](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17502))

###
[`v4.1.0`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#410---2025-04-01)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.17...v4.1.0)

##### Added

- Add `details-content` variant
([#&#8203;15319](https://redirect.github.com/tailwindlabs/tailwindcss/pull/15319))
- Add `inverted-colors` variant
([#&#8203;11693](https://redirect.github.com/tailwindlabs/tailwindcss/pull/11693))
- Add `noscript` variant
([#&#8203;11929](https://redirect.github.com/tailwindlabs/tailwindcss/pull/11929),
[#&#8203;17431](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17431))
- Add `items-baseline-last` and `self-baseline-last` utilities
([#&#8203;13888](https://redirect.github.com/tailwindlabs/tailwindcss/pull/13888),
[#&#8203;17476](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17476))
- Add `pointer-none`, `pointer-coarse`, and `pointer-fine` variants
([#&#8203;16946](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16946))
- Add `any-pointer-none`, `any-pointer-coarse`, and `any-pointer-fine`
variants
([#&#8203;16941](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16941))
- Add safe alignment utilities
([#&#8203;14607](https://redirect.github.com/tailwindlabs/tailwindcss/pull/14607))
- Add `user-valid` and `user-invalid` variants
([#&#8203;12370](https://redirect.github.com/tailwindlabs/tailwindcss/pull/12370))
- Add `wrap-anywhere`, `wrap-break-word`, and `wrap-normal` utilities
([#&#8203;12128](https://redirect.github.com/tailwindlabs/tailwindcss/pull/12128))
- Add `@source inline(…)` and `@source not inline(…)`
([#&#8203;17147](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17147))
- Add `@source not "…"`
([#&#8203;17255](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17255))
- Add `text-shadow-*` utilities
([#&#8203;17389](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17389))
- Add `mask-*` utilities
([#&#8203;17134](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17134))
- Add `bg-{position,size}-*` utilities for arbitrary values
([#&#8203;17432](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17432))
- Add `shadow-*/<alpha>`, `inset-shadow-*/<alpha>`,
`drop-shadow-*/<alpha>`, and `text-shadow-*/<alpha>` utilities to
control shadow opacity
([#&#8203;17398](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17398),
[#&#8203;17434](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17434))
- Add `drop-shadow-<color>` utilities
([#&#8203;17434](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17434))
- Improve compatibility with older versions of Safari and Firefox
([#&#8203;17435](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17435))

##### Fixed

- Follow symlinks when resolving `@source` directives
([#&#8203;17391](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17391))
- Don't scan ignored files for classes when changing an ignored file
triggers a rebuild using `@tailwindcss/cli`
([#&#8203;17255](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17255))
- Support negated `content` rules in legacy JavaScript configuration
([#&#8203;17255](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17255))
- Interpret syntax like `@("@&#8203;")md:…` as `@md:…` in Razor files
([#&#8203;17427](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17427))
- Disallow top-level braces, top-level semicolons, and unbalanced
parentheses and brackets in arbitrary values
([#&#8203;17361](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17361))
- Ensure the `--theme(…)` function still resolves to the CSS variables
when using legacy JS plugins
([#&#8203;17458](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17458))
- Detect used theme variables in CSS module files
([#&#8203;17433](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17433),
[#&#8203;17467](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17467))

##### Changed

- Ignore `node_modules` by default (can be overridden by `@source …`
rules)
([#&#8203;17255](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17255))
- `@source` rules that include file extensions or point inside
`node_modules/` folders no longer consider your `.gitignore` rules
([#&#8203;17255](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17255))
- Deprecate `bg-{left,right}-{top,bottom}` in favor of
`bg-{top,bottom}-{left,right}` utilities
([#&#8203;17378](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17378))
- Deprecate `object-{left,right}-{top,bottom}` in favor of
`object-{top,bottom}-{left,right}` utilities
([#&#8203;17437](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17437))

###
[`v4.0.17`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4017---2025-03-26)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.16...v4.0.17)

##### Fixed

- Fix an issue causing the CLI to hang when processing Ruby files
([#&#8203;17383](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17383))

###
[`v4.0.16`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4016---2025-03-25)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.15...v4.0.16)

##### Added

- Add support for literal values in `--value('…')` and `--modifier('…')`
([#&#8203;17304](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17304))

##### Fixed

- Fix class extraction followed by `(` in Pug
([#&#8203;17320](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17320))
- Ensure `@keyframes` for theme animations are emitted if they are
referenced following a comma
([#&#8203;17352](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17352))
- Vite: Ensure that updates to an imported CSS file are properly
propagated after updating source files
([#&#8203;17347](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17347))
- Pre process `Slim` templates embedded in Ruby files
([#&#8203;17336](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17336))
- Error when input and output files resolve to the same file when using
the CLI
([#&#8203;17311](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17311))
- Add missing suggestions when `--spacing(--value(integer, number))` is
used
([#&#8203;17308](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17308))
- Add `::-webkit-details-marker` pseudo to `marker` variant
([#&#8203;17362](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17362))

###
[`v4.0.15`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4015---2025-03-20)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.14...v4.0.15)

##### Fixed

- Fix incorrect angle in `-bg-conic-*` utilities
([#&#8203;17174](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17174))
- Fix `border-[12px_4px]` being interpreted as a `border-color` instead
of a `border-width`
([#&#8203;17248](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17248))
- Work around a crash in Safari 16.4 and 16.5 when using the default
Preflight styles
([#&#8203;17306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17306))
- Pre-process `<template lang="…">` in Vue files
([#&#8203;17252](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17252))
- Ensure that all CSS variables used by Preflight are prefixed
([#&#8203;17036](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17036))
- Prevent segfault when loaded in a worker thread on Linux
([#&#8203;17276](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17276))
- Ensure multiple `--value(…)` or `--modifier(…)` calls don't delete
subsequent declarations
([#&#8203;17273](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17273))
- Fix class extraction followed by `(` in Slim
([#&#8203;17278](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17278))
- Export `PluginUtils` from `tailwindcss/plugin` for compatibility with
v3
([#&#8203;17299](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17299))
- Remove redundant `line-height: initial` from Preflight
([#&#8203;15212](https://redirect.github.com/tailwindlabs/tailwindcss/pull/15212))
- Increase Standalone hardware compatibility on macOS x64 builds
([#&#8203;17267](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17267))
- Ensure that the CSS file rebuilds if a new CSS variable is used from
templates
([#&#8203;17301](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17301))

##### Changed

- The `--theme(…)` function now returns CSS variables from your theme
variables unless used inside positions where CSS variables are invalid
(e.g. inside `@media` queries)
([#&#8203;17036](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17036))

###
[`v4.0.14`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4014---2025-03-13)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.13...v4.0.14)

##### Fixed

- Do not extract candidates with JS string interpolation `${`
([#&#8203;17142](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17142))
- Fix extraction of variants containing `.` character
([#&#8203;17153](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17153))
- Fix extracting candidates in Clojure/ClojureScript
([#&#8203;17087](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17087))

###
[`v4.0.13`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4013---2025-03-11)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.12...v4.0.13)

##### Fixed

- Fix Haml pre-processing
([#&#8203;17051](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17051))
- Ensure `.node` and `.wasm` files are not scanned for utilities
([#&#8203;17123](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17123))
- Improve performance when scanning JSON files
([#&#8203;17125](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17125))
- Fix extracting candidates containing dots in Haml, Pug, and Slim pre
processors
([#&#8203;17094](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17094),
[#&#8203;17085](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17085),
[#&#8203;17113](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17113))
- Don't create invalid CSS when encountering a link wrapped in square
brackets
([#&#8203;17129](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17129))

###
[`v4.0.12`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4012---2025-03-07)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.11...v4.0.12)

##### Fixed

- Vite: Fix `url(…)` rebasing in transitively imported CSS files
([#&#8203;16965](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16965))
- PostCSS: Rebase `url(…)`s in imported CSS files
([#&#8203;16965](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16965))
- Ensure utilities are sorted based on their actual property order
([#&#8203;16995](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16995))
- Ensure strings in Pug and Slim templates are handled correctly
([#&#8203;17000](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17000))
- Ensure classes between `}` and `{` are properly extracted
([#&#8203;17001](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17001))
- Fix `razor`/`cshtml` pre-processing
([#&#8203;17027](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17027))
- Ensure extracting candidates from JS embedded in a PHP string works as
expected
([#&#8203;17031](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17031))

###
[`v4.0.11`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4011---2025-03-06)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.10...v4.0.11)

##### Fixed

- Ensure classes containing `--` are extracted correctly
([#&#8203;16972](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16972))
- Ensure classes containing numbers followed by dash or underscore are
extracted correctly
([#&#8203;16980](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16980))
- Ensure arbitrary container queries are extracted correctly
([#&#8203;16984](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16984))
- Ensure classes ending in `[` are extracted in Slim templating language
([#&#8203;16985](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16985))
- Ensure arbitrary variables with data types are extracted correctly
([#&#8203;16986](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16986))

###
[`v4.0.10`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4010---2025-03-05)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.9...v4.0.10)

##### Added

- Add `col-<number>` and `row-<number>` utilities for `grid-column` and
`grid-row`
([#&#8203;15183](https://redirect.github.com/tailwindlabs/tailwindcss/pull/15183))

##### Fixed

- Ensure `not-*` does not remove `:is(…)` from variants
([#&#8203;16825](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16825))
- Ensure `@keyframes` are correctly emitted when using a prefix
([#&#8203;16850](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16850))
- Don't swallow `@utility` declarations when `@apply` is used in nested
rules
([#&#8203;16940](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16940))
- Ensure `outline-hidden` behaves like `outline-none` outside of forced
colors mode
([#&#8203;16943](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16943))
- Allow `!important` on CSS variables again
([#&#8203;16873](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16873))
- Vite: Do not crash when encountering an `.svg` file with `#` or `?` in
the filename
([#&#8203;16957](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16957))
- Ensure utilities are properly detected within square brackets
([#&#8203;16306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16306))
- Ensure utilities are properly detected using Angular's conditional
class binding syntax
([#&#8203;16306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16306))
- Ensure utilities starting with numbers are properly extracted from
Slim templates
([#&#8203;16306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16306))
- Discard arbitrary property candidates that have guaranteed-invalid
property names
([#&#8203;16306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16306))

##### Changed

- Removed `max-w-auto` and `max-h-auto` utilities as they generate
invalid CSS
([#&#8203;16917](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16917))
- Replaced the existing candidate extractor with a brand new extractor
to improve maintainability, correctness, and performance
([#&#8203;16306](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16306))

###
[`v4.0.9`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#409---2025-02-25)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.8...v4.0.9)

##### Fixed

- Make JS APIs available to plugins and configs in the Standalone CLI
([#&#8203;15934](https://redirect.github.com/tailwindlabs/tailwindcss/pull/15934))
- Vite: Don't crash when importing a virtual module from JavaScript that
ends in `.css`
([#&#8203;16780](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16780))
- Fix an issue where `@reference "…"` would sometimes omit keyframe
animations
([#&#8203;16774](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16774))
- Ensure `z-*!` utilities are properly marked as `!important`
([#&#8203;16795](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16795))
- Read UTF-8 CSS files that start with a byte-order mark (BOM)
([#&#8203;16796](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16796))
- Ensure nested functions in selectors used with JavaScript plugins are
not truncated
([#&#8203;16802](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16802))

##### Changed

- Emit variable fallbacks when using `@reference "…"` instead of
duplicate CSS variable declarations
([#&#8203;16774](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16774))

###
[`v4.0.8`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#408---2025-02-21)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.7...v4.0.8)

##### Added

- Allow `@import` with `theme(…)` options for stylesheets that contain
more than just `@theme` rules
([#&#8203;16514](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16514))

##### Fixed

- Don't add `!important` to CSS variable declarations when using the
important modifier
([#&#8203;16668](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16668))
- Vite: Ignore files and directories specified in your `.gitignore` file
when using automatic source
detection([#&#8203;16631](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16631))
- Vite: Don't rely on the module graph for detecting candidates to
ensure setups with multiple Vite builds work as expected
([#&#8203;16631](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16631))
- Vite: Ensure Astro production builds always contain classes used in
client-only components
([#&#8203;16631](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16631))
- Vite: Always scan raw file contents for utility classes before any
other transforms have been applied to ensure utility classes are scanned
without any additional escaping
([#&#8203;16631](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16631))
- Ensure utilities with more declarations are always sorted before
utilities with fewer declarations when utilities only define CSS
variables
([#&#8203;16715](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16715))
- Only include `translate-z-px` utilities once in compiled CSS
([#&#8203;16718](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16718))

##### Changed

- Don't include theme variables that aren't used in compiled CSS
([#&#8203;16211](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16211),
[#&#8203;16676](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16676))

###
[`v4.0.7`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#407---2025-02-18)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.6...v4.0.7)

##### Fixed

- Export `tailwindcss/lib/util/flattenColorPalette.js` for backward
compatibility
([#&#8203;16411](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16411))
- Fix sorting of numeric utility suggestions when they have different
magnitudes
([#&#8203;16414](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16414))
- Show suggestions for fractions in IntelliSense
([#&#8203;16353](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16353))
- Don’t replace `_` in suggested theme keys
([#&#8203;16433](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16433))
- Ensure `--default-outline-width` can be used to change the
`outline-width` value of the `outline` utility
([#&#8203;16469](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16469))
- Ensure drop shadow utilities don't inherit unexpectedly
([#&#8203;16471](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16471))
- Export config and plugin types from `tailwindcss/plugin` for backward
compatibility
([#&#8203;16505](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16505))
- Ensure JavaScript plugins that emit nested rules referencing the
utility name work as expected
([#&#8203;16539](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16539))
- Statically link Visual Studio redistributables in `@tailwindcss/oxide`
Windows builds
([#&#8203;16602](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16602))
- Ensure that Next.js splat routes are scanned for classes
([#&#8203;16457](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16457))
- Pin exact version of `tailwindcss` in `@tailwindcss/*` packages
([#&#8203;16623](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16623))
- Upgrade: Report errors when updating dependencies
([#&#8203;16504](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16504))
- Upgrade: Ensure a `darkMode` JS config setting with block syntax
converts to use `@slot`
([#&#8203;16507](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16507))
- Upgrade: Ensure the latest version of `tailwindcss` and
`@tailwindcss/postcss` are installed when upgrading
([#&#8203;16620](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16620))

###
[`v4.0.6`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#406---2025-02-10)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.5...v4.0.6)

##### Fixed

- Revert change to no longer include theme variables that aren't used in
compiled CSS
([#&#8203;16403](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16403))
- Upgrade: Don't migrate `blur` to `blur-sm` when used with Next.js
`<Image placeholder="blur" />`
([#&#8203;16405](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16405))

###
[`v4.0.5`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#405---2025-02-08)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.4...v4.0.5)

##### Added

- Add `@theme static` option for always including theme variables in
compiled CSS
([#&#8203;16211](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16211))

##### Fixed

- Remove rogue `console.log` from `@tailwindcss/vite`
([#&#8203;16307](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16307))

##### Changed

- Don't include theme variables that aren't used in compiled CSS
([#&#8203;16211](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16211))

###
[`v4.0.4`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#404---2025-02-06)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.3...v4.0.4)

##### Fixed

- Fix a crash when setting JS theme values to `null`
([#&#8203;16210](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16210))
- Ensure escaped underscores in CSS variables in arbitrary values are
properly unescaped
([#&#8203;16206](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16206))
- Ensure that the `containers` JS theme key is added to the
`--container-*` namespace
([#&#8203;16169](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16169))
- Ensure theme `@keyframes` are generated even if an `--animation-*`
variable spans multiple lines
([#&#8203;16237](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16237))
- Vite: Skip parsing stylesheets with the `?commonjs-proxy` flag
([#&#8203;16238](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16238))
- Fix `order-first` and `order-last` for Firefox
([#&#8203;16266](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16266))
- Fix support for older instruction sets on Linux x64 builds of the
standalone CLI
([#&#8203;16244](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16244))
- Ensure `NODE_PATH` is respected when resolving JavaScript and CSS
files
([#&#8203;16274](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16274))
- Ensure Node addons are packaged correctly with FreeBSD builds
([#&#8203;16277](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16277))
- Fix an issue where `@variant` inside a referenced stylesheet could
cause a stack overflow
([#&#8203;16300](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16300))

###
[`v4.0.3`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#403---2025-02-01)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.2...v4.0.3)

##### Fixed

- Fix incorrect removal of `@import url();`
([#&#8203;16144](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16144))

###
[`v4.0.2`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#402---2025-01-31)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.1...v4.0.2)

##### Fixed

- Only generate positive `grid-cols-*` and `grid-rows-*` utilities
([#&#8203;16020](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16020))
- Ensure escaped theme variables are handled correctly
([#&#8203;16064](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16064))
- Ensure we process Tailwind CSS features when only using `@reference`
or `@variant`
([#&#8203;16057](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16057))
- Refactor gradient implementation to work around
[prettier/prettier#17058](https://redirect.github.com/prettier/prettier/issues/17058)
([#&#8203;16072](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16072))
- Vite: Ensure hot-reloading works with SolidStart setups
([#&#8203;16052](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Fix a crash when starting the development server in SolidStart
setups
([#&#8203;16052](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16052))
- Vite: Don't rebase URLs that appear to be aliases
([#&#8203;16078](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16078))
- Vite: Transform `<style>` blocks in HTML files
([#&#8203;16069](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16069))
- Prevent camel-casing CSS custom properties added by JavaScript plugins
([#&#8203;16103](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16103))
- Do not emit `@keyframes` in `@theme reference`
([#&#8203;16120](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16120))
- Discard invalid declarations when parsing CSS
([#&#8203;16093](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16093))
- Do not emit empty CSS rules and at-rules
([#&#8203;16121](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16121))
- Handle `@variant` when at the top-level of a stylesheet
([#&#8203;16129](https://redirect.github.com/tailwindlabs/tailwindcss/pull/16129))

###
[`v4.0.1`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#4017---2025-03-26)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v4.0.0...v4.0.1)

##### Fixed

- Fix an issue causing the CLI to hang when processing Ruby files
([#&#8203;17383](https://redirect.github.com/tailwindlabs/tailwindcss/pull/17383))

###
[`v4.0.0`](https://redirect.github.com/tailwindlabs/tailwindcss/blob/HEAD/CHANGELOG.md#400---2025-01-21)

[Compare
Source](https://redirect.github.com/tailwindlabs/tailwindcss/compare/v3.4.17...v4.0.0)

##### Added

- [New high-performance
engine](https://tailwindcss.com/blog/tailwindcss-v4#new-high-performance-engine)
— where full builds are up to 5x faster, and incremental builds are over
100x faster — and measured in microseconds.
- [Designed for the modern
web](https://tailwindcss.com/blog/tailwindcss-v4#designed-for-the-modern-web)
— built on cutting-edge CSS features like cascade layers, registered
custom properties with `@property`, and `color-mix()`.
- [Simplified
installation](https://tailwindcss.com/blog/tailwindcss-v4#simplified-installation)
— fewer dependencies, zero configuration, and just a single line of code
in your CSS file.
- [First-party Vite
plugin](https://tailwindcss.com/blog/tailwindcss-v4#first-party-vite-plugin)
— tight integration for maximum performance and minimum configuration.
- [Automatic content
detection](https://tailwindcss.com/blog/tailwindcss-v4#automatic-content-detection)
— all of your template files are discovered automatically, with no
configuration required.
- [Built-in import
support](https://tailwindcss.com/blog/tailwindcss-v4#built-in-import-support)
— no additional tooling necessary to bundle multiple CSS files.
- [CSS-first
configuration](https://tailwindcss.com/blog/tailwindcss-v4#css-first-configuration)
— a reimagined developer experience where you customize and extend the
framework directly in CSS instead of a JavaScript configuration file.
- [CSS theme
variables](https://tailwindcss.com/blog/tailwindcss-v4#css-theme-variables)
— all of your design tokens exposed as native CSS variables so you can
access them anywhere.
- [Dynamic utility values and
variants](https://tailwindcss.com/blog/tailwindcss-v4#dynamic-utility-values-and-variants)
— stop guessing what values exist in your spacing scale, or extending
your configuration for things like basic data attributes.
- [Modernized P3 color
palette](https://tailwindcss.com/blog/tailwindcss-v4#modernized-p3-color-palette)
— a redesigned, more vivid color palette that takes full advantage of
modern display technology.
- [Container
queries](https://tailwindcss.com/blog/tailwindcss-v4#container-queries)
— first-class APIs for styling elements based on their container size,
no plugins required.
- [New 3D transform
utilities](https://tailwindcss.com/blog/tailwindcss-v4#new-3d-transform-utilities)
— transform elements in 3D space directly in your HTML.
- [Expanded gradient
APIs](https://tailwindcss.com/blog/tailwindcss-v4#expanded-gradient-apis)
— radial and conic gradients, interpolation modes, and more.
- [@&#8203;starting-style
support](https://tailwindcss.com/blog/tailwindcss-v4#starting-style-support)
— a new variant you can use to create enter and exit transitions,
without the need for JavaScript.
- [not-\*
variant](https://tailwindcss.com/blog/tailwindcss-v4#not-variant) —
style an element only when it doesn't match another variant, custom
selector, or media or feature query.
- [Even more new utilities and
variants](https://tailwindcss.com/blog/tailwindcss-v4#even-more-new-utilities-and-variants)
— including support for `color-scheme`, `field-sizing`, complex shadows,
`inert`, and more.

Start using Tailwind CSS v4.0 today by [installing it in a new
project](https://tailwindcss.com/docs/installation/), or playing with it
directly in the browser on [Tailwind
Play](https://play.tailwindcss.com/).

For existing projects, we've published a comprehensive [upgrade
guide](https://tailwindcss.com/docs/upgrade-guide) and built an
[automated upgrade
tool](https://tailwindcss.com/docs/upgrade-guide#using-the-upgrade-tool)
to get you on the latest version as quickly and painlessly as possible.

For a deep-dive into everything that's new, [check out the announcement
post](https://tailwindcss.com/blog/tailwindcss-v4).

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4yMDcuMSIsInVwZGF0ZWRJblZlciI6IjQxLjQwLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 20:52:43 -04:00
renovate[bot]
18b5209087 fix(deps): update all non-major dependencies (#1543)
This PR contains the following updates:

| Package | Change | Age | Confidence | Type | Update |
|---|---|---|---|---|---|
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.8` ->
`3.13.9`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.8/3.13.9)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.8/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.8` ->
`3.13.9`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.8/3.13.9)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.8/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [@apollo/client](https://www.apollographql.com/docs/react/)
([source](https://redirect.github.com/apollographql/apollo-client)) |
[`3.13.8` ->
`3.13.9`](https://renovatebot.com/diffs/npm/@apollo%2fclient/3.13.8/3.13.9)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@apollo%2fclient/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@apollo%2fclient/3.13.8/3.13.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@eslint/js](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint/tree/HEAD/packages/js))
| [`9.32.0` ->
`9.33.0`](https://renovatebot.com/diffs/npm/@eslint%2fjs/9.32.0/9.33.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@eslint%2fjs/9.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@eslint%2fjs/9.32.0/9.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@floating-ui/dom](https://floating-ui.com)
([source](https://redirect.github.com/floating-ui/floating-ui/tree/HEAD/packages/dom))
| [`1.7.2` ->
`1.7.3`](https://renovatebot.com/diffs/npm/@floating-ui%2fdom/1.7.2/1.7.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@floating-ui%2fdom/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@floating-ui%2fdom/1.7.2/1.7.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@floating-ui/vue](https://floating-ui.com/docs/vue)
([source](https://redirect.github.com/floating-ui/floating-ui/tree/HEAD/packages/vue))
| [`1.1.7` ->
`1.1.8`](https://renovatebot.com/diffs/npm/@floating-ui%2fvue/1.1.7/1.1.8)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@floating-ui%2fvue/1.1.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@floating-ui%2fvue/1.1.7/1.1.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[@ianvs/prettier-plugin-sort-imports](https://redirect.github.com/ianvs/prettier-plugin-sort-imports)
| [`4.5.1` ->
`4.6.1`](https://renovatebot.com/diffs/npm/@ianvs%2fprettier-plugin-sort-imports/4.5.1/4.6.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@ianvs%2fprettier-plugin-sort-imports/4.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@ianvs%2fprettier-plugin-sort-imports/4.5.1/4.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/platform-fastify](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/platform-fastify))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-fastify/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-fastify/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-fastify/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/testing](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/testing))
| [`11.1.5` ->
`11.1.6`](https://renovatebot.com/diffs/npm/@nestjs%2ftesting/11.1.5/11.1.6)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2ftesting/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2ftesting/11.1.5/11.1.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nuxt/eslint](https://redirect.github.com/nuxt/eslint)
([source](https://redirect.github.com/nuxt/eslint/tree/HEAD/packages/module))
| [`1.7.1` ->
`1.8.0`](https://renovatebot.com/diffs/npm/@nuxt%2feslint/1.7.1/1.8.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2feslint/1.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2feslint/1.7.1/1.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@rollup/rollup-linux-x64-gnu](https://rollupjs.org/)
([source](https://redirect.github.com/rollup/rollup)) | [`4.46.1` ->
`4.46.2`](https://renovatebot.com/diffs/npm/@rollup%2frollup-linux-x64-gnu/4.46.1/4.46.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@rollup%2frollup-linux-x64-gnu/4.46.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@rollup%2frollup-linux-x64-gnu/4.46.1/4.46.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| optionalDependencies | patch |
|
[@storybook/addon-docs](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/docs)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/docs))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/@storybook%2faddon-docs/9.0.18/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-docs/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-docs/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@storybook/addon-links](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/links)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/links))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/@storybook%2faddon-links/9.0.18/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-links/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-links/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@storybook/builder-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/builders/builder-vite))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/@storybook%2fbuilder-vite/9.0.18/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fbuilder-vite/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fbuilder-vite/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@storybook/vue3-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/frameworks/vue3-vite)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/frameworks/vue3-vite))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/@storybook%2fvue3-vite/9.0.18/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fvue3-vite/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fvue3-vite/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@swc/core](https://swc.rs)
([source](https://redirect.github.com/swc-project/swc)) | [`1.13.2` ->
`1.13.3`](https://renovatebot.com/diffs/npm/@swc%2fcore/1.13.2/1.13.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@swc%2fcore/1.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@swc%2fcore/1.13.2/1.13.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/inquirer](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/inquirer)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/inquirer))
| [`9.0.8` ->
`9.0.9`](https://renovatebot.com/diffs/npm/@types%2finquirer/9.0.8/9.0.9)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2finquirer/9.0.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2finquirer/9.0.8/9.0.9?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`22.16.5` ->
`22.17.1`](https://renovatebot.com/diffs/npm/@types%2fnode/22.16.5/22.17.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.16.5/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@typescript-eslint/eslint-plugin](https://typescript-eslint.io/packages/eslint-plugin)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin))
| [`8.38.0` ->
`8.39.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/8.38.0/8.39.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2feslint-plugin/8.39.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2feslint-plugin/8.38.0/8.39.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [cache-manager](https://redirect.github.com/jaredwray/cacheable)
([source](https://redirect.github.com/jaredwray/cacheable/tree/HEAD/packages/cache-manager))
| [`7.0.1` ->
`7.1.1`](https://renovatebot.com/diffs/npm/cache-manager/7.0.1/7.1.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/cache-manager/7.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/cache-manager/7.0.1/7.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [chalk](https://redirect.github.com/chalk/chalk) | [`5.4.1` ->
`5.5.0`](https://renovatebot.com/diffs/npm/chalk/5.4.1/5.5.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/chalk/5.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/chalk/5.4.1/5.5.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [cron](https://redirect.github.com/kelektiv/node-cron) | [`4.3.2` ->
`4.3.3`](https://renovatebot.com/diffs/npm/cron/4.3.2/4.3.3) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/cron/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/cron/4.3.2/4.3.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [eslint](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint)) | [`9.32.0` ->
`9.33.0`](https://renovatebot.com/diffs/npm/eslint/9.32.0/9.33.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint/9.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint/9.32.0/9.33.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[eslint-plugin-prettier](https://redirect.github.com/prettier/eslint-plugin-prettier)
| [`5.5.3` ->
`5.5.4`](https://renovatebot.com/diffs/npm/eslint-plugin-prettier/5.5.3/5.5.4)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-prettier/5.5.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-prettier/5.5.3/5.5.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[eslint-plugin-storybook](https://redirect.github.com/storybookjs/storybook/code/lib/eslint-plugin#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/lib/eslint-plugin))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/eslint-plugin-storybook/9.0.18/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-storybook/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-storybook/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [eslint-plugin-vue](https://eslint.vuejs.org)
([source](https://redirect.github.com/vuejs/eslint-plugin-vue)) |
[`10.3.0` ->
`10.4.0`](https://renovatebot.com/diffs/npm/eslint-plugin-vue/10.3.0/10.4.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-vue/10.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-vue/10.3.0/10.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [fs-extra](https://redirect.github.com/jprichardson/node-fs-extra) |
[`11.3.0` ->
`11.3.1`](https://renovatebot.com/diffs/npm/fs-extra/11.3.0/11.3.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/fs-extra/11.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/fs-extra/11.3.0/11.3.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [inquirer](https://redirect.github.com/SBoudrias/Inquirer.js) |
[`12.8.2` ->
`12.9.1`](https://renovatebot.com/diffs/npm/inquirer/12.8.2/12.9.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/inquirer/12.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/inquirer/12.8.2/12.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [lint-staged](https://redirect.github.com/lint-staged/lint-staged) |
[`16.1.2` ->
`16.1.5`](https://renovatebot.com/diffs/npm/lint-staged/16.1.2/16.1.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/lint-staged/16.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/lint-staged/16.1.2/16.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [lucide-vue-next](https://lucide.dev)
([source](https://redirect.github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next))
| [`0.528.0` ->
`0.539.0`](https://renovatebot.com/diffs/npm/lucide-vue-next/0.528.0/0.539.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/lucide-vue-next/0.539.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/lucide-vue-next/0.528.0/0.539.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [marked](https://marked.js.org)
([source](https://redirect.github.com/markedjs/marked)) | [`16.1.1` ->
`16.1.2`](https://renovatebot.com/diffs/npm/marked/16.1.1/16.1.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/marked/16.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/marked/16.1.1/16.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [node](https://nodejs.org)
([source](https://redirect.github.com/nodejs/node)) | `22.17.1` ->
`22.18.0` |
[![age](https://developer.mend.io/api/mc/badges/age/node-version/node/v22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/node-version/node/v22.17.1/v22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| | minor |
| [node](https://redirect.github.com/actions/node-versions) | `22.17.1`
-> `22.18.0` |
[![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fnode-versions/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fnode-versions/22.17.1/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| uses-with | minor |
| [node](https://redirect.github.com/nodejs/node) |
`22.17.1-bookworm-slim` -> `22.18.0-bookworm-slim` |
[![age](https://developer.mend.io/api/mc/badges/age/docker/node/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/docker/node/22.17.1/22.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| final | minor |
| [nuxt](https://nuxt.com)
([source](https://redirect.github.com/nuxt/nuxt/tree/HEAD/packages/nuxt))
| [`3.17.7` ->
`3.18.1`](https://renovatebot.com/diffs/npm/nuxt/3.17.7/3.18.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/nuxt/3.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nuxt/3.17.7/3.18.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [pino](https://getpino.io)
([source](https://redirect.github.com/pinojs/pino)) | [`9.7.0` ->
`9.8.0`](https://renovatebot.com/diffs/npm/pino/9.7.0/9.8.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pino/9.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pino/9.7.0/9.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [pino-pretty](https://redirect.github.com/pinojs/pino-pretty) |
[`13.0.0` ->
`13.1.1`](https://renovatebot.com/diffs/npm/pino-pretty/13.0.0/13.1.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pino-pretty/13.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pino-pretty/13.0.0/13.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
[`10.13.1` ->
`10.14.0`](https://renovatebot.com/diffs/npm/pnpm/10.13.1/10.14.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.13.1/10.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| packageManager | minor |
| [pnpm](https://pnpm.io)
([source](https://redirect.github.com/pnpm/pnpm/tree/HEAD/pnpm)) |
[`10.13.1` ->
`10.14.0`](https://renovatebot.com/diffs/npm/pnpm/10.13.1/10.14.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/pnpm/10.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/pnpm/10.13.1/10.14.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| engines | minor |
| [python](https://redirect.github.com/actions/python-versions) |
`3.13.5` -> `3.13.6` |
[![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fpython-versions/3.13.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fpython-versions/3.13.5/3.13.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| uses-with | patch |
| [reka-ui](https://redirect.github.com/unovue/reka-ui) | [`2.4.0` ->
`2.4.1`](https://renovatebot.com/diffs/npm/reka-ui/2.4.0/2.4.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/reka-ui/2.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/reka-ui/2.4.0/2.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[simple-git-hooks](https://redirect.github.com/toplenboren/simple-git-hooks)
| [`2.13.0` ->
`2.13.1`](https://renovatebot.com/diffs/npm/simple-git-hooks/2.13.0/2.13.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/simple-git-hooks/2.13.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/simple-git-hooks/2.13.0/2.13.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [storybook](https://storybook.js.org)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/core))
| [`9.0.18` ->
`9.1.1`](https://renovatebot.com/diffs/npm/storybook/9.0.18/9.1.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/storybook/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/storybook/9.0.18/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[tw-animate-css](https://redirect.github.com/Wombosvideo/tw-animate-css)
| [`1.3.5` ->
`1.3.6`](https://renovatebot.com/diffs/npm/tw-animate-css/1.3.5/1.3.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tw-animate-css/1.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tw-animate-css/1.3.5/1.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [typescript](https://www.typescriptlang.org/)
([source](https://redirect.github.com/microsoft/TypeScript)) | [`5.8.3`
-> `5.9.2`](https://renovatebot.com/diffs/npm/typescript/5.8.3/5.9.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/typescript/5.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript/5.8.3/5.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint))
| [`8.38.0` ->
`8.39.0`](https://renovatebot.com/diffs/npm/typescript-eslint/8.38.0/8.39.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.39.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.38.0/8.39.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [vite](https://vite.dev)
([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite))
| [`7.0.6` ->
`7.1.1`](https://renovatebot.com/diffs/npm/vite/7.0.6/7.1.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite/7.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/7.0.6/7.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [vue-tsc](https://redirect.github.com/vuejs/language-tools)
([source](https://redirect.github.com/vuejs/language-tools/tree/HEAD/packages/tsc))
| [`3.0.4` ->
`3.0.5`](https://renovatebot.com/diffs/npm/vue-tsc/3.0.4/3.0.5) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue-tsc/3.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-tsc/3.0.4/3.0.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [vuetify](https://vuetifyjs.com)
([source](https://redirect.github.com/vuetifyjs/vuetify/tree/HEAD/packages/vuetify))
| [`3.9.2` ->
`3.9.4`](https://renovatebot.com/diffs/npm/vuetify/3.9.2/3.9.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vuetify/3.9.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vuetify/3.9.2/3.9.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [wrangler](https://redirect.github.com/cloudflare/workers-sdk)
([source](https://redirect.github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler))
| [`4.26.0` ->
`4.28.1`](https://renovatebot.com/diffs/npm/wrangler/4.26.0/4.28.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/wrangler/4.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/wrangler/4.26.0/4.28.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [zx](https://google.github.io/zx/)
([source](https://redirect.github.com/google/zx)) | [`8.7.1` ->
`8.8.0`](https://renovatebot.com/diffs/npm/zx/8.3.2/8.8.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/zx/8.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/zx/8.3.2/8.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [zx](https://google.github.io/zx/)
([source](https://redirect.github.com/google/zx)) | [`8.7.1` ->
`8.8.0`](https://renovatebot.com/diffs/npm/zx/8.7.1/8.8.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/zx/8.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/zx/8.7.1/8.8.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |

---

### Release Notes

<details>
<summary>apollographql/apollo-client (@&#8203;apollo/client)</summary>

###
[`v3.13.9`](https://redirect.github.com/apollographql/apollo-client/blob/HEAD/CHANGELOG.md#3139)

[Compare
Source](https://redirect.github.com/apollographql/apollo-client/compare/v3.13.8...5c202cf3b26dbcffe8314fd6af917dec1b77c65c)

##### Patch Changes

-
[#&#8203;12804](https://redirect.github.com/apollographql/apollo-client/pull/12804)
[`32c9aa9`](32c9aa9215)
Thanks [@&#8203;phryneas](https://redirect.github.com/phryneas)! - Fix a
possible race condition on queries that were reobserved before they were
subscribed to the first time.

</details>

<details>
<summary>eslint/eslint (@&#8203;eslint/js)</summary>

###
[`v9.33.0`](https://redirect.github.com/eslint/eslint/compare/v9.32.0...ad283717ed4764a171120ca7c6cba82a78fa024c)

[Compare
Source](https://redirect.github.com/eslint/eslint/compare/v9.32.0...v9.33.0)

</details>

<details>
<summary>floating-ui/floating-ui (@&#8203;floating-ui/dom)</summary>

###
[`v1.7.3`](https://redirect.github.com/floating-ui/floating-ui/blob/HEAD/packages/dom/CHANGELOG.md#173)

[Compare
Source](https://redirect.github.com/floating-ui/floating-ui/compare/@floating-ui/dom@1.7.2...@floating-ui/dom@1.7.3)

##### Patch Changes

- Update dependencies: `@floating-ui/core@1.7.3`

</details>

<details>
<summary>floating-ui/floating-ui (@&#8203;floating-ui/vue)</summary>

###
[`v1.1.8`](https://redirect.github.com/floating-ui/floating-ui/blob/HEAD/packages/vue/CHANGELOG.md#118)

[Compare
Source](https://redirect.github.com/floating-ui/floating-ui/compare/@floating-ui/vue@1.1.7...@floating-ui/vue@1.1.8)

##### Patch Changes

- Update dependencies: `@floating-ui/dom@1.7.3`

</details>

<details>
<summary>ianvs/prettier-plugin-sort-imports
(@&#8203;ianvs/prettier-plugin-sort-imports)</summary>

###
[`v4.6.1`](https://redirect.github.com/ianvs/prettier-plugin-sort-imports/compare/v4.6.0...ec4ea16eb0cddec2b80bf75625530e5b495b08f5)

[Compare
Source](https://redirect.github.com/ianvs/prettier-plugin-sort-imports/compare/v4.6.0...v4.6.1)

###
[`v4.6.0`](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/releases/tag/v4.6.0)

[Compare
Source](https://redirect.github.com/ianvs/prettier-plugin-sort-imports/compare/v4.5.1...v4.6.0)

#### What's Changed

- Be more tolerant of babel parse errors by
[@&#8203;IanVS](https://redirect.github.com/IanVS) in
[https://github.com/IanVS/prettier-plugin-sort-imports/pull/230](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/pull/230)
- Add support for ember template tags by
[@&#8203;IanVS](https://redirect.github.com/IanVS) in
[https://github.com/IanVS/prettier-plugin-sort-imports/pull/231](https://redirect.github.com/IanVS/prettier-plugin-sort-imports/pull/231)

**Full Changelog**:
https://github.com/IanVS/prettier-plugin-sort-imports/compare/v4.5.1...v4.6.0

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/common)</summary>

###
[`v11.1.6`](https://redirect.github.com/nestjs/nest/compare/v11.1.5...35c3ded6dbf3f23f917ae88d0ed966932788cae6)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.5...v11.1.6)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/core)</summary>

###
[`v11.1.6`](https://redirect.github.com/nestjs/nest/compare/v11.1.5...35c3ded6dbf3f23f917ae88d0ed966932788cae6)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.5...v11.1.6)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/platform-fastify)</summary>

###
[`v11.1.6`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.6)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.5...v11.1.6)

#### v11.1.6 (2025-08-07)

##### Bug fixes

- `core`
- [#&#8203;15504](https://redirect.github.com/nestjs/nest/pull/15504)
fix(core): fix race condition in class dependency resolution from
imported modules
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- [#&#8203;15469](https://redirect.github.com/nestjs/nest/pull/15469)
fix(core): attach root inquirer for nested transient providers
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- `microservices`
- [#&#8203;15508](https://redirect.github.com/nestjs/nest/pull/15508)
fix(microservices): report correct buffer length in exception
([@&#8203;kim-sung-jee](https://redirect.github.com/kim-sung-jee))
- [#&#8203;15492](https://redirect.github.com/nestjs/nest/pull/15492)
fix(microservices): fix kafka serilization of class instances
([@&#8203;LeonBiersch](https://redirect.github.com/LeonBiersch))

##### Dependencies

- `platform-fastify`
- [#&#8203;15493](https://redirect.github.com/nestjs/nest/pull/15493)
chore(deps): bump
[@&#8203;fastify/cors](https://redirect.github.com/fastify/cors) from
11.0.1 to 11.1.0
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

##### Committers: 6

- Jiri Hajek
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- Kamil Mysliwiec
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- Leon Biersch
([@&#8203;LeonBiersch](https://redirect.github.com/LeonBiersch))
- Seongjee Kim
([@&#8203;kim-sung-jee](https://redirect.github.com/kim-sung-jee))
- [@&#8203;premierbell](https://redirect.github.com/premierbell)
- pTr ([@&#8203;ptrgits](https://redirect.github.com/ptrgits))

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/testing)</summary>

###
[`v11.1.6`](https://redirect.github.com/nestjs/nest/compare/v11.1.5...35c3ded6dbf3f23f917ae88d0ed966932788cae6)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.5...v11.1.6)

</details>

<details>
<summary>nuxt/eslint (@&#8203;nuxt/eslint)</summary>

###
[`v1.8.0`](https://redirect.github.com/nuxt/eslint/releases/tag/v1.8.0)

[Compare
Source](https://redirect.github.com/nuxt/eslint/compare/v1.7.1...v1.8.0)

#####    🚀 Features

- Update plugins  -  by
[@&#8203;antfu](https://redirect.github.com/antfu)
[<samp>(932a7)</samp>](https://redirect.github.com/nuxt/eslint/commit/932a760)

#####     [View changes on
GitHub](https://redirect.github.com/nuxt/eslint/compare/v1.7.1...v1.8.0)

</details>

<details>
<summary>rollup/rollup (@&#8203;rollup/rollup-linux-x64-gnu)</summary>

###
[`v4.46.2`](https://redirect.github.com/rollup/rollup/blob/HEAD/CHANGELOG.md#4462)

[Compare
Source](https://redirect.github.com/rollup/rollup/compare/v4.46.1...v4.46.2)

*2025-07-29*

##### Bug Fixes

- Fix in-operator handling for external namespace and when the left side
cannot be analyzed
([#&#8203;6041](https://redirect.github.com/rollup/rollup/issues/6041))

##### Pull Requests

- [#&#8203;6041](https://redirect.github.com/rollup/rollup/pull/6041):
Correct the logic of include in BinaryExpression and don't optimize
external references away
([@&#8203;TrickyPi](https://redirect.github.com/TrickyPi),
[@&#8203;cyyynthia](https://redirect.github.com/cyyynthia),
[@&#8203;lukastaegert](https://redirect.github.com/lukastaegert))

</details>

<details>
<summary>storybookjs/storybook (@&#8203;storybook/addon-docs)</summary>

###
[`v9.1.1`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#911)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v9.1.0...v9.1.1)

- CLI: Fix throwing in readonly environments -
[#&#8203;31785](https://redirect.github.com/storybookjs/storybook/pull/31785),
thanks [@&#8203;JReinhold](https://redirect.github.com/JReinhold)!
- Onboarding: Tweak referral wording in survey -
[#&#8203;32185](https://redirect.github.com/storybookjs/storybook/pull/32185),
thanks [@&#8203;shilman](https://redirect.github.com/shilman)!
- Telemetry: Send index stats on dev exit -
[#&#8203;32168](https://redirect.github.com/storybookjs/storybook/pull/32168),
thanks [@&#8203;shilman](https://redirect.github.com/shilman)!

###
[`v9.1.0`](https://redirect.github.com/storybookjs/storybook/blob/HEAD/CHANGELOG.md#910)

[Compare
Source](https://redirect.github.com/storybookjs/storybook/compare/v9.0.18...v9.1.0)

Storybook 9.1 is packed with new features and improvements to enhance
accessibility, streamline testing, and make your development workflow
even smoother!

🚀 Improved upgrade command with monorepo support for seamless upgrades
🅰 Angular fixes for Tailwind 4, cache busting, and zoneless
compatibility
🧪 `sb.mock` API and Automocking: one-line module mocking to simplify
your testing workflow
🧪 Favicon shows test run status for quick visual feedback
⚛️ Easier configuration for React Native projects
🔥 Auto-abort play functions on HMR to avoid unwanted side effects
🏗️ Improved CSF factories API for type safe story definitions
️ A11y improvements across Storybook’s UI — addon panel, toolbar,
sidebar, mobile & more
💯 Dozens more fixes and improvements based on community feedback!

<details>
<summary>List of all updates</summary>

- A11y: Improved toolbar a11y by fixing semantics -
[#&#8203;28672](https://redirect.github.com/storybookjs/storybook/pull/28672),
thanks [@&#8203;mehm8128](https://redirect.github.com/mehm8128)!
- Addon Vitest: Remove Optimize deps candidates due to Vitest warnings -
[#&#8203;31809](https://redirect.github.com/storybookjs/storybook/pull/31809),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Angular: Bundle using TSup -
[#&#8203;31690](https://redirect.github.com/storybookjs/storybook/pull/31690),
thanks [@&#8203;ndelangen](https://redirect.github.com/ndelangen)!
- Angular: Prevent directory import in Angular builders -
[#&#8203;32012](https://redirect.github.com/storybookjs/storybook/pull/32012),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Automigration: Await updateMainConfig in removeEssentials -
[#&#8203;32140](https://redirect.github.com/storybookjs/storybook/pull/32140),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Builder-Vite: Fix logic related to setting allowedHosts when IP
address used -
[#&#8203;31472](https://redirect.github.com/storybookjs/storybook/pull/31472),
thanks [@&#8203;JSMike](https://redirect.github.com/JSMike)!
- Controls: Improve the accessibility of the object control -
[#&#8203;31581](https://redirect.github.com/storybookjs/storybook/pull/31581),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!
- Core: Abort play function on HMR -
[#&#8203;31542](https://redirect.github.com/storybookjs/storybook/pull/31542),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Core: Avoid pausing animations in non-Vitest Playwright environments -
[#&#8203;32123](https://redirect.github.com/storybookjs/storybook/pull/32123),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Core: Cleanup of type following up v9 and small verbatimModuleSyntax
type fix -
[#&#8203;31823](https://redirect.github.com/storybookjs/storybook/pull/31823),
thanks [@&#8203;alcpereira](https://redirect.github.com/alcpereira)!
- Core: Fix aria-controls attribute on sidebar nodes to include all
children -
[#&#8203;31491](https://redirect.github.com/storybookjs/storybook/pull/31491),
thanks [@&#8203;candrepa1](https://redirect.github.com/candrepa1)!
- Core: Fix horizontal scrollbar covering part of the toolbar -
[#&#8203;31704](https://redirect.github.com/storybookjs/storybook/pull/31704),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!
- Core: Fix moving log file across drives and projectRoot detection on
Windows -
[#&#8203;32020](https://redirect.github.com/storybookjs/storybook/pull/32020),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Core: Prevent interactions panel from flickering and showing incorrect
state -
[#&#8203;32150](https://redirect.github.com/storybookjs/storybook/pull/32150),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Core: Serve dynamic favicon based on testing module status -
[#&#8203;31763](https://redirect.github.com/storybookjs/storybook/pull/31763),
thanks [@&#8203;ghengeveld](https://redirect.github.com/ghengeveld)!
- Core: Support container queries in addon panels -
[#&#8203;23261](https://redirect.github.com/storybookjs/storybook/pull/23261),
thanks
[@&#8203;neil-morrison44](https://redirect.github.com/neil-morrison44)!
- CSF Factories: Add parameters/globals types, `extend` API, portable
stories -
[#&#8203;30601](https://redirect.github.com/storybookjs/storybook/pull/30601),
thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
- CSF: Improve controls parameters -
[#&#8203;31745](https://redirect.github.com/storybookjs/storybook/pull/31745),
thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
- CSF: Improve docs parameter types -
[#&#8203;31736](https://redirect.github.com/storybookjs/storybook/pull/31736),
thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
- CSF: Only add preview annotations to definePreview in csf-factories
automigration -
[#&#8203;31727](https://redirect.github.com/storybookjs/storybook/pull/31727),
thanks [@&#8203;kasperpeulen](https://redirect.github.com/kasperpeulen)!
- Docs: Update
[@&#8203;storybook/icons](https://redirect.github.com/storybook/icons) -
[#&#8203;32144](https://redirect.github.com/storybookjs/storybook/pull/32144),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Docs: Update `react-element-to-jsx-string` -
[#&#8203;31170](https://redirect.github.com/storybookjs/storybook/pull/31170),
thanks [@&#8203;7rulnik](https://redirect.github.com/7rulnik)!
- Init: Exclude mdx stories when docs feature isn't selected during init
-
[#&#8203;32142](https://redirect.github.com/storybookjs/storybook/pull/32142),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Maintenance: Add flag to toggle default automigrations -
[#&#8203;32113](https://redirect.github.com/storybookjs/storybook/pull/32113),
thanks [@&#8203;yannbf](https://redirect.github.com/yannbf)!
- React Native Web: Simplify config by using vite-plugin-rnw -
[#&#8203;32051](https://redirect.github.com/storybookjs/storybook/pull/32051),
thanks [@&#8203;dannyhw](https://redirect.github.com/dannyhw)!
- Telemetry: Add automigration errors -
[#&#8203;32103](https://redirect.github.com/storybookjs/storybook/pull/32103),
thanks [@&#8203;yannbf](https://redirect.github.com/yannbf)!
- Telemetry: Fix `project.json` for getAbsolutePath -
[#&#8203;31510](https://redirect.github.com/storybookjs/storybook/pull/31510),
thanks [@&#8203;ndelangen](https://redirect.github.com/ndelangen)!
- Test: Add mock capabilities -
[#&#8203;31987](https://redirect.github.com/storybookjs/storybook/pull/31987),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Test: Consider exports map -
[#&#8203;32157](https://redirect.github.com/storybookjs/storybook/pull/32157),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Test: Fix missing source map for Webpack/Vite mock loaders and plugins
-
[#&#8203;32111](https://redirect.github.com/storybookjs/storybook/pull/32111),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Test: Invalidate vite cache for manual mocks -
[#&#8203;32152](https://redirect.github.com/storybookjs/storybook/pull/32152),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- Test: Remove source map generation from webpack automock-loader -
[#&#8203;32115](https://redirect.github.com/storybookjs/storybook/pull/32115),
thanks
[@&#8203;valentinpalkovic](https://redirect.github.com/valentinpalkovic)!
- UI: Apply user updates for mobile navigation accessibility -
[#&#8203;31401](https://redirect.github.com/storybookjs/storybook/pull/31401),
thanks [@&#8203;yatishgoel](https://redirect.github.com/yatishgoel)!
- UI: Fix interaction step collapse icon -
[#&#8203;31853](https://redirect.github.com/storybookjs/storybook/pull/31853),
thanks [@&#8203;AvitalHass](https://redirect.github.com/AvitalHass)!
- UI: Visual focus indicators (VFIs) aren't visible in high contrast
mode (rebase) -
[#&#8203;31848](https://redirect.github.com/storybookjs/storybook/pull/31848),
thanks [@&#8203;Sidnioulz](https://redirect.github.com/Sidnioulz)!

</details>

</details>

<details>
<summary>swc-project/swc (@&#8203;swc/core)</summary>

###
[`v1.13.3`](https://redirect.github.com/swc-project/swc/blob/HEAD/CHANGELOG.md#1133---2025-07-29)

[Compare
Source](https://redirect.github.com/swc-project/swc/compare/v1.13.2...v1.13.3)

##### Bug Fixes

- **(es/minifier)** Check exported when optimize last expr
([#&#8203;10939](https://redirect.github.com/swc-project/swc/issues/10939))
([f6f15f3](f6f15f38d3))

- **(es/minifier)** Disallow types
([#&#8203;10945](https://redirect.github.com/swc-project/swc/issues/10945))
([63172ef](63172ef3b0))

- **(es/minifier)** Don't optimize Number properties when Number is
shadowed
([#&#8203;10947](https://redirect.github.com/swc-project/swc/issues/10947))
([40a1e2e](40a1e2e6b8))

- **(es/minifier)** Fix cycle detection
([#&#8203;10950](https://redirect.github.com/swc-project/swc/issues/10950))
([212d8bc](212d8bcff1))

- **(es/parser)** Correctly handle EOF position
([#&#8203;10934](https://redirect.github.com/swc-project/swc/issues/10934))
([dd70fbd](dd70fbd0dd))

##### Performance

- **(es/minifier)** Remove visitor of CharFreqAnalyzer
([#&#8203;10928](https://redirect.github.com/swc-project/swc/issues/10928))
([65534ff](65534ff998))

- **(es/minifier)** Remove pre-compress pass
([#&#8203;10932](https://redirect.github.com/swc-project/swc/issues/10932))
([c7f0e4d](c7f0e4d5e6))

- **(es/minifier)** Use bitflag for var kind
([#&#8203;10940](https://redirect.github.com/swc-project/swc/issues/10940))
([4317988](4317988952))

- **(es/minifier)** Remove needless clone
([#&#8203;10949](https://redirect.github.com/swc-project/swc/issues/10949))
([b5e5e8c](b5e5e8c35a))

- **(es/parser)** Reduce comparison
([#&#8203;10933](https://redirect.github.com/swc-project/swc/issues/10933))
([e44fbd4](e44fbd44f3))

- **(es/parser)** Reduce comparison
([#&#8203;10941](https://redirect.github.com/swc-project/swc/issues/10941))
([a262eeb](a262eeb053))

</details>

<details>
<summary>typescript-eslint/typescript-eslint
(@&#8203;typescript-eslint/eslint-plugin)</summary>

###
[`v8.39.0`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#8390-2025-08-04)

[Compare
Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.38.0...v8.39.0)

##### 🚀 Features

- **eslint-plugin:** \[only-throw-error] support yield/await expressions
([#&#8203;11417](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11417))
- **eslint-plugin:** add no-unnecessary-type-conversion to
strict-type-checked ruleset
([#&#8203;11427](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11427))
- update to TypeScript 5.9.2
([#&#8203;11445](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11445))
- **eslint-plugin:** \[naming-convention] add enumMember PascalCase
default option
([#&#8203;11127](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11127))

##### 🩹 Fixes

- **eslint-plugin:** \[no-unsafe-assignment] add an
`unsafeObjectPattern` message
([#&#8203;11403](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11403))
- **eslint-plugin:** \[prefer-optional-chain] ignore `check` option for
most RHS of a chain
([#&#8203;11272](https://redirect.github.com/typescript-eslint/typescript-eslint/pull/11272))

##### ❤️ Thank You

- Brad Zacher
[@&#8203;bradzacher](https://redirect.github.com/bradzacher)
- James Garbutt [@&#8203;43081j](https://redirect.github.com/43081j)
- Kim Sang Du
[@&#8203;developer-bandi](https://redirect.github.com/developer-bandi)
- Sasha Kondrashov
- tao
- Younsang Na
[@&#8203;nayounsang](https://redirect.github.com/nayounsang)

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

</details>

<details>
<summary>chalk/chalk (chalk)</summary>

###
[`v5.5.0`](https://redirect.github.com/chalk/chalk/releases/tag/v5.5.0)

[Compare
Source](https://redirect.github.com/chalk/chalk/compare/v5.4.1...v5.5.0)

- Make Ghostty terminal use true color
([#&#8203;653](https://redirect.github.com/chalk/chalk/issues/653))
[`79ee2d3`](https://redirect.github.com/chalk/chalk/commit/79ee2d3)

***

</details>

<details>
<summary>kelektiv/node-cron (cron)</summary>

###
[`v4.3.3`](https://redirect.github.com/kelektiv/node-cron/blob/HEAD/CHANGELOG.md#433-2025-08-01)

[Compare
Source](https://redirect.github.com/kelektiv/node-cron/compare/v4.3.2...v4.3.3)

##### 🛠 Builds

- **deps:** update dependency
[@&#8203;types](https://redirect.github.com/types)/luxon to ~3.7.0
([9bd0c4e](9bd0c4e1c0))

##### ♻️ Chores

- **action:** update github/codeql-action action to v3.29.4
([f28ea6a](f28ea6a660))
- **action:** update marocchino/sticky-pull-request-comment action to
v2.9.4
([ceb7a0c](ceb7a0c1b3))
- **action:** update step-security/harden-runner action to v2.13.0
([91e2402](91e2402038))
- **deps:** lock file maintenance
([34130fc](34130fc0d7))
- **deps:** lock file maintenance
([b79e0c2](b79e0c27eb))
- **deps:** lock file maintenance
([281e1aa](281e1aa587))
- **deps:** update dependency
[@&#8203;types](https://redirect.github.com/types)/node to v22.16.5
([16cdbab](16cdbab130))
- **deps:** update dependency chai to v5.2.1
([08b58ce](08b58ceb38))
- **deps:** update dependency semantic-release to v24.2.7
([bc3fab6](bc3fab6bb8))
- **deps:** update linters
([b692865](b692865878))
- **deps:** update swc monorepo
([4f3d063](4f3d063bd3))

</details>

<details>
<summary>eslint/eslint (eslint)</summary>

###
[`v9.33.0`](https://redirect.github.com/eslint/eslint/compare/v9.32.0...a90d7c4fe5ef83054e29d21d7ffb442103429d03)

[Compare
Source](https://redirect.github.com/eslint/eslint/compare/v9.32.0...v9.33.0)

</details>

<details>
<summary>prettier/eslint-plugin-prettier
(eslint-plugin-prettier)</summary>

###
[`v5.5.4`](https://redirect.github.com/prettier/eslint-plugin-prettier/blob/HEAD/CHANGELOG.md#554)

[Compare
Source](https://redirect.github.com/prettier/eslint-plugin-prettier/compare/v5.5.3...v5.5.4)

##### Patch Changes

-
[#&#8203;755](https://redirect.github.com/prettier/eslint-plugin-prettier/pull/755)
[`723f7a8`](723f7a803f)
Thanks [@&#8203;kbrilla](https://redirect.github.com/kbrilla)! - fix:
add 'oxc', 'oxc-ts' and 'hermes' parsers to `parserBlocklist`

-
[#&#8203;751](https://redirect.github.com/prettier/eslint-plugin-prettier/pull/751)
[`cf52b30`](cf52b306a5)
Thanks [@&#8203;andreww2012](https://redirect.github.com/andreww2012)! -
fix: disallow extra properties in rule options

</details>

<details>
<summary>vuejs/eslint-plugin-vue (eslint-plugin-vue)</summary>

###
[`v10.4.0`](https://redirect.github.com/vuejs/eslint-plugin-vue/blob/HEAD/CHANGELOG.md#1040)

[Compare
Source](https://redirect.github.com/vuejs/eslint-plugin-vue/compare/v10.3.0...v10.4.0)

##### Minor Changes

- Added `ignoreParents` option to
[`vue/no-deprecated-slot-attribute`](https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html)
([#&#8203;2784](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2784))

- Added new
[`vue/no-negated-v-if-condition`](https://eslint.vuejs.org/rules/no-negated-v-if-condition.html)
rule
([#&#8203;2794](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2794))

- Added new
[`vue/no-negated-condition`](https://eslint.vuejs.org/rules/no-negated-condition.html)
rule
([#&#8203;2795](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2795))

##### Patch Changes

- Resolved TypeScript compatibility issues introduced by
[eslint-typegen](https://redirect.github.com/antfu/eslint-typegen)
([#&#8203;2790](https://redirect.github.com/vuejs/eslint-plugin-vue/pull/2790))

- Fixed inconsistent quotes in
[`vue/block-lang`](https://eslint.vuejs.org/rules/block-lang.html) error
messages ([#&#8203;2805](https

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40My41IiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Eli Bosley <ekbosley@gmail.com>
2025-08-08 21:12:46 -04:00
Eli Bosley
ec8f4f38c8 chore: less claude more code 2025-08-08 20:08:18 -04:00
Eli Bosley
db0e725107 chore(api): remove unused dependencies (#1554)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
  * Updated the API version to 4.12.0.
  * Added configuration for improved TypeScript dependency checking.
  * Removed unused dependencies and development tools from the project.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-08 15:35:00 -04:00
Pujit Mehrotra
5afca5ecba chore: reduce logging verbosity in restore_dependencies script (#1568)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Adjusted the messages displayed during the restore process for
improved clarity and specificity.
* Reduced unnecessary informational messages, focusing on more relevant
feedback during restoration.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 12:54:25 -04:00
Pujit Mehrotra
beab83b56e refactor: mv 7.2.0 version check to file modifier super class (#1567)
## Summary by CodeRabbit

* **Bug Fixes**
* `nginx:reload` effect is no longer triggered via the nginx.conf
modification on 7.2.0.
* Improved consistency in determining when patches and modifications
should be applied for Unraid versions 7.2.0 and above.
* Removed redundant version checks from several modification modules to
streamline patch application logic.
* Adjusted logging for skipped modifications to reduce output verbosity.

* **Refactor**
* Centralized version-based logic for patch application, reducing
duplication and improving maintainability.
2025-08-07 10:03:53 -04:00
Pujit Mehrotra
78997a02c6 feat: deleteDockerEntries mutation (#1564)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added the ability to delete multiple Docker entries (including folders
and their descendants) via a new mutation in the interface.
* **Bug Fixes**
* Ensured that deleting entries handles complex folder hierarchies,
circular references, and missing references robustly.
* **Tests**
* Introduced comprehensive tests for deleting entries and handling
organizer structures, ensuring correct behavior in various scenarios and
edge cases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-07 09:28:09 -04:00
Pujit Mehrotra
3534d6fdd7 fix: change config file loading error log to debug (#1565)
To reduce noise and a false-negative syslog upon a user's first boot.
this way, we can opt into the trace if there's an issue.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210958709343109

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Adjusted logging for configuration file loading errors to reduce log
verbosity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-05 11:29:44 -04:00
Pujit Mehrotra
557b03f882 feat: createDockerFolder & setDockerFolderChildren mutations (#1558)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added the ability to create Docker folders and set folder children via
new GraphQL mutations in the Docker organizer.
* Enhanced organizer management with pure functions for folder creation
and child assignment, ensuring immutability.

* **Bug Fixes**
* Improved validation to prevent empty or invalid folder entries in the
organizer structure.

* **Tests**
* Added comprehensive tests for folder creation, child assignment, and
organizer resolution, including edge cases and immutability checks.
* Updated test guidelines to focus on observable behavior and error
handling best practices.

* **Documentation**
  * Expanded testing best practices in project documentation.

* **Chores**
  * Updated ignore rules for local configuration files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-04 12:22:51 -04:00
Eli Bosley
514a0ef560 fix(connect): remove unraid-api folder before creating symlink (#1556)
During plugin installation, if `/usr/local/bin/unraid-api` exists as a
directory, the installation fails because `rm -f` cannot remove a
directory. This change replaces `rm -f` with `rm -rf` to ensure that the
path is removed regardless of whether it is a file or a directory,
allowing the symlink to be created successfully.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-01 11:01:13 -04:00
Pujit Mehrotra
dfe352dfa1 feat: add docker -> organizer query (#1555)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a structured organizer system for Docker containers,
enabling hierarchical organization and retrieval of container data.
* Added a comprehensive UPS (Uninterruptible Power Supply) management
API, including device monitoring, configuration, and real-time updates.
* Exposed new GraphQL fields and types for organizer structures and UPS
management.

* **Bug Fixes**
  * None.

* **Tests**
* Added extensive tests for organizer resource transformation, view
resolution, and UPS API functionality.

* **Chores**
* Updated configuration to enable sandbox mode and register a new
plugin.
  * Updated .gitignore to exclude local status files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-01 10:53:11 -04:00
renovate[bot]
8005b8c3b6 chore(deps): pin dependencies (#1542)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[@types/supertest](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/supertest)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/supertest))
| devDependencies | pin | [`^6.0.3` ->
`6.0.3`](https://renovatebot.com/diffs/npm/@types%2fsupertest/6.0.3/6.0.3)
|
| [supertest](https://redirect.github.com/ladjs/supertest) |
devDependencies | pin | [`^7.1.4` ->
`7.1.4`](https://renovatebot.com/diffs/npm/supertest/7.1.4/7.1.4) |
|
[tw-animate-css](https://redirect.github.com/Wombosvideo/tw-animate-css)
| devDependencies | pin | [`^1.3.5` ->
`1.3.5`](https://renovatebot.com/diffs/npm/tw-animate-css/1.3.5/1.3.5) |
|
[vue-eslint-parser](https://redirect.github.com/vuejs/vue-eslint-parser)
| devDependencies | pin | [`^10.2.0` ->
`10.2.0`](https://renovatebot.com/diffs/npm/vue-eslint-parser/10.2.0/10.2.0)
|

Add the preset `:preserveSemverRanges` to your config if you don't want
to pin your dependencies.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDMuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-30 21:55:26 -04:00
google-labs-jules[bot]
d6fa102d06 refactor: use go links for docs (#1552)
Update hardcoded doc links to use go-links and move them to a constants
file.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-07-30 21:37:28 -04:00
github-actions[bot]
52f22678e3 chore(main): release 4.12.0 (#1547)
🤖 I have created a release *beep* *boop*
---


## [4.12.0](https://github.com/unraid/api/compare/v4.11.0...v4.12.0)
(2025-07-30)


### Features

* add ups monitoring to graphql api
([#1526](https://github.com/unraid/api/issues/1526))
([6ea94f0](6ea94f061d))


### Bug Fixes

* enhance plugin management with interactive removal prompts
([#1549](https://github.com/unraid/api/issues/1549))
([23ef760](23ef760d76))
* remove connect api plugin upon removal of Connect Unraid plugin
([#1548](https://github.com/unraid/api/issues/1548))
([782d5eb](782d5ebadc))
* SSO not being detected
([#1546](https://github.com/unraid/api/issues/1546))
([6b3b951](6b3b951d82))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-30 16:42:01 -04:00
Eli Bosley
23ef760d76 fix: enhance plugin management with interactive removal prompts (#1549)
- Add RemovePluginQuestionSet for interactive plugin removal
- Update plugin commands to use PluginManagementService
- Improve plugin installation error handling and warnings
- Clean up test fixtures and update plugin command tests
- Reset dev config to clean state (v4.11.0, no plugins)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved plugin management in the CLI with interactive prompts for
plugin removal and enhanced error handling.
* CLI plugin commands now provide clearer user feedback and warnings for
missing plugins.
* Added log suppression capability and dedicated plugin log file
support.

* **Refactor**
* Plugin CLI commands now use dedicated management services and
interactive prompts instead of direct GraphQL operations, streamlining
workflows and improving reliability.
* Simplified CLI imports and logging for more straightforward error
handling.
* Deferred plugin module logging to application bootstrap for improved
lifecycle management.
* Updated logging service to respect global log suppression and added
unconditional logging method.

* **Tests**
* Refactored plugin CLI command tests for better isolation and coverage,
using service mocks and enhanced prompt simulations.
  * Updated report command tests to reflect new logging behavior.

* **Chores**
* Updated API configuration settings and removed outdated test fixture
files and timestamp data.
* Simplified test file logic by removing remote file download and cache
functionality.
* Adjusted build configuration to conditionally set CLI shebang based on
environment.
* Enhanced configuration file handler to optionally accept external
logging.
  * Updated CLI command script to set environment variable for testing.
  * Added environment variables for log file paths and log suppression.
* Updated logging setup to conditionally suppress logs and write plugin
logs to file.
  * Improved error and output logging consistency across CLI commands.
* Added placeholder file to ensure log directory version control
tracking.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Pujit Mehrotra <pujit@lime-technology.com>
2025-07-30 16:38:08 -04:00
Eli Bosley
6ea94f061d feat: add ups monitoring to graphql api (#1526)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced UPS management capabilities, including queries and
mutations for UPS device status, configuration, and live updates via
GraphQL.
* Added support for configuring UPS parameters such as service state,
cable type, communication protocol, shutdown thresholds, and power
control options.
* Provided detailed UPS device information including battery, power, and
operational status.

* **Tests**
* Added comprehensive tests for UPS resolver and service logic, covering
configuration, event publishing, killpower functionality, and error
handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-07-30 14:52:32 -04:00
Pujit Mehrotra
782d5ebadc fix: remove connect api plugin upon removal of Connect Unraid plugin (#1548)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved plugin removal process on Unraid 7.2 and above by ensuring
the associated API plugin component is actively uninstalled during
plugin removal.
* **Enhancements**
* API version is now consistently set during application startup and
configuration migration.
* Configuration file writing logs now include detailed file paths for
better traceability.
  * File operations now use atomic writes for increased reliability.
* **Chores**
  * Updated dependencies to include atomic file writing support.
* Removed redundant configuration persistence calls after plugin
changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-30 08:04:54 -04:00
Pujit Mehrotra
dfe363bc37 chore: add organizer data structure for docker folders (#1540)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a comprehensive validation system for organizer data,
including structural and referential integrity checks for views and
organizers.
* Added new data models for resources, folders, references, and views,
with strong typing and validation.
* Implemented a sequential validation processor with configurable
fail-fast behavior and detailed error reporting.
* Added a dedicated service for managing and validating Docker organizer
configuration files.

* **Bug Fixes**
* Corrected spelling of error-related properties from "errorOccured" to
"errorOccurred" in multiple services to ensure consistent error
handling.

* **Tests**
* Added extensive unit tests for organizer validation logic, view
structure validation, and the validation processor to ensure correctness
across various edge cases and scenarios.
* Added comprehensive tests verifying validation processor behavior
under diverse conditions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-29 14:41:49 -04:00
Eli Bosley
6b3b951d82 fix: SSO not being detected (#1546)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved reliability of Single Sign-On (SSO) status detection,
ensuring the SSO state is always correctly set regardless of plugin
status.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 17:04:23 -04:00
github-actions[bot]
5449e30eed chore(main): release 4.11.0 (#1519)
🤖 I have created a release *beep* *boop*
---


## [4.11.0](https://github.com/unraid/api/compare/v4.10.0...v4.11.0)
(2025-07-28)


### Features

* tailwind v4 ([#1522](https://github.com/unraid/api/issues/1522))
([2c62e0a](2c62e0ad09))
* **web:** install and configure nuxt ui
([#1524](https://github.com/unraid/api/issues/1524))
([407585c](407585cd40))


### Bug Fixes

* add missing breakpoints
([#1535](https://github.com/unraid/api/issues/1535))
([f5352e3](f5352e3a26))
* border color incorrect in tailwind
([#1544](https://github.com/unraid/api/issues/1544))
([f14b74a](f14b74af91))
* **connect:** omit extraneous fields during connect config validation
([#1538](https://github.com/unraid/api/issues/1538))
([45bd736](45bd73698b))
* **deps:** pin dependencies
([#1528](https://github.com/unraid/api/issues/1528))
([a74d935](a74d935b56))
* **deps:** pin dependency @nuxt/ui to 3.2.0
([#1532](https://github.com/unraid/api/issues/1532))
([8279531](8279531f2b))
* **deps:** update all non-major dependencies
([#1510](https://github.com/unraid/api/issues/1510))
([1a8da6d](1a8da6d92b))
* **deps:** update all non-major dependencies
([#1520](https://github.com/unraid/api/issues/1520))
([e2fa648](e2fa648d1c))
* inject Tailwind CSS into client entry point
([#1537](https://github.com/unraid/api/issues/1537))
([86b6c4f](86b6c4f85b))
* make settings grid responsive
([#1463](https://github.com/unraid/api/issues/1463))
([9dfdb8d](9dfdb8dce7))
* **notifications:** gracefully handle & mask invalid notifications
([#1529](https://github.com/unraid/api/issues/1529))
([05056e7](05056e7ca1))
* truncate log files when they take up more than 5mb of space
([#1530](https://github.com/unraid/api/issues/1530))
([0a18b38](0a18b38008))
* use async for primary file read/writes
([#1531](https://github.com/unraid/api/issues/1531))
([23b2b88](23b2b88461))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-28 11:55:29 -04:00
Eli Bosley
dc12656f81 chore: remove codeowners in favor of coderabbit suggested reviewers (#1545)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
  * Removed the code ownership assignments from the repository.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 11:50:22 -04:00
Eli Bosley
f14b74af91 fix: border color incorrect in tailwind (#1544)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Introduced new UI theme variables for border color, border radius, and
primary color states to enhance customization options.

* **Refactor**
* Removed redundant and unused CSS variables related to primary color
and border radius for improved consistency and maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-28 11:33:22 -04:00
renovate[bot]
e2fa648d1c fix(deps): update all non-major dependencies (#1520)
This PR contains the following updates:

| Package | Change | Age | Confidence | Type | Update |
|---|---|---|---|---|---|
| [@eslint/js](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint/tree/HEAD/packages/js))
| [`9.31.0` ->
`9.32.0`](https://renovatebot.com/diffs/npm/@eslint%2fjs/9.31.0/9.32.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@eslint%2fjs/9.32.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@eslint%2fjs/9.31.0/9.32.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@graphql-tools/merge](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/merge))
| [`9.0.24` ->
`9.1.1`](https://renovatebot.com/diffs/npm/@graphql-tools%2fmerge/9.0.24/9.1.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2fmerge/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2fmerge/9.0.24/9.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@graphql-tools/schema](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/schema))
| [`10.0.23` ->
`10.0.25`](https://renovatebot.com/diffs/npm/@graphql-tools%2fschema/10.0.23/10.0.25)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2fschema/10.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2fschema/10.0.23/10.0.25?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[@graphql-tools/utils](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/utils))
| [`10.8.6` ->
`10.9.1`](https://renovatebot.com/diffs/npm/@graphql-tools%2futils/10.8.6/10.9.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2futils/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2futils/10.8.6/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | minor |
|
[@graphql-tools/utils](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/utils))
| [`10.8.6` ->
`10.9.1`](https://renovatebot.com/diffs/npm/@graphql-tools%2futils/10.8.6/10.9.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2futils/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2futils/10.8.6/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@graphql-tools/utils](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/utils))
| [`10.8.6` ->
`10.9.1`](https://renovatebot.com/diffs/npm/@graphql-tools%2futils/10.8.6/10.9.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2futils/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2futils/10.8.6/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| pnpm.overrides | minor |
|
[@graphql-tools/utils](https://redirect.github.com/ardatan/graphql-tools)
([source](https://redirect.github.com/ardatan/graphql-tools/tree/HEAD/packages/utils))
| [`10.8.6` ->
`10.9.1`](https://renovatebot.com/diffs/npm/@graphql-tools%2futils/10.8.6/10.9.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@graphql-tools%2futils/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@graphql-tools%2futils/10.8.6/10.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@internationalized/number](https://redirect.github.com/adobe/react-spectrum)
| [`3.6.3` ->
`3.6.4`](https://renovatebot.com/diffs/npm/@internationalized%2fnumber/3.6.3/3.6.4)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@internationalized%2fnumber/3.6.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@internationalized%2fnumber/3.6.3/3.6.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@manypkg/cli](https://redirect.github.com/Thinkmill/manypkg)
([source](https://redirect.github.com/Thinkmill/manypkg/tree/HEAD/packages/cli))
| [`0.24.0` ->
`0.25.0`](https://renovatebot.com/diffs/npm/@manypkg%2fcli/0.24.0/0.25.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@manypkg%2fcli/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@manypkg%2fcli/0.24.0/0.25.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nestjs/common](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/common))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcommon/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcommon/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcommon/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nestjs/core](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/core))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fcore/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fcore/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fcore/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/platform-fastify](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/platform-fastify))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2fplatform-fastify/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2fplatform-fastify/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2fplatform-fastify/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@nestjs/testing](https://nestjs.com)
([source](https://redirect.github.com/nestjs/nest/tree/HEAD/packages/testing))
| [`11.1.3` ->
`11.1.5`](https://renovatebot.com/diffs/npm/@nestjs%2ftesting/11.1.3/11.1.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nestjs%2ftesting/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nestjs%2ftesting/11.1.3/11.1.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@nuxt/eslint](https://redirect.github.com/nuxt/eslint)
([source](https://redirect.github.com/nuxt/eslint/tree/HEAD/packages/module))
| [`1.5.2` ->
`1.7.1`](https://renovatebot.com/diffs/npm/@nuxt%2feslint/1.5.2/1.7.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2feslint/1.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2feslint/1.5.2/1.7.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@nuxt/ui](https://ui.nuxt.com)
([source](https://redirect.github.com/nuxt/ui)) | [`3.2.0` ->
`3.3.0`](https://renovatebot.com/diffs/npm/@nuxt%2fui/3.2.0/3.3.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/@nuxt%2fui/3.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@nuxt%2fui/3.2.0/3.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [@pinia/nuxt](https://pinia.vuejs.org/ssr/nuxt.html)
([source](https://redirect.github.com/vuejs/pinia)) | [`0.11.1` ->
`0.11.2`](https://renovatebot.com/diffs/npm/@pinia%2fnuxt/0.11.1/0.11.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@pinia%2fnuxt/0.11.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@pinia%2fnuxt/0.11.1/0.11.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [@rollup/rollup-linux-x64-gnu](https://rollupjs.org/)
([source](https://redirect.github.com/rollup/rollup)) | [`4.45.1` ->
`4.46.1`](https://renovatebot.com/diffs/npm/@rollup%2frollup-linux-x64-gnu/4.45.1/4.46.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@rollup%2frollup-linux-x64-gnu/4.46.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@rollup%2frollup-linux-x64-gnu/4.45.1/4.46.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| optionalDependencies | minor |
|
[@storybook/addon-docs](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/docs)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/docs))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/@storybook%2faddon-docs/9.0.17/9.0.18)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-docs/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-docs/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/addon-links](https://redirect.github.com/storybookjs/storybook/tree/next/code/addons/links)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/addons/links))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/@storybook%2faddon-links/9.0.17/9.0.18)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2faddon-links/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2faddon-links/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/builder-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/builders/builder-vite/#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/builders/builder-vite))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/@storybook%2fbuilder-vite/9.0.17/9.0.18)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fbuilder-vite/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fbuilder-vite/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@storybook/vue3-vite](https://redirect.github.com/storybookjs/storybook/tree/next/code/frameworks/vue3-vite)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/frameworks/vue3-vite))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/@storybook%2fvue3-vite/9.0.17/9.0.18)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@storybook%2fvue3-vite/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@storybook%2fvue3-vite/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [@swc/core](https://swc.rs)
([source](https://redirect.github.com/swc-project/swc)) | [`1.12.14` ->
`1.13.2`](https://renovatebot.com/diffs/npm/@swc%2fcore/1.12.14/1.13.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@swc%2fcore/1.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@swc%2fcore/1.12.14/1.13.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@types/bun](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/bun)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bun))
| [`1.2.18` ->
`1.2.19`](https://renovatebot.com/diffs/npm/@types%2fbun/1.2.18/1.2.19)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fbun/1.2.19?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fbun/1.2.18/1.2.19?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@types/node](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node))
| [`22.16.4` ->
`22.16.5`](https://renovatebot.com/diffs/npm/@types%2fnode/22.16.4/22.16.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2fnode/22.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2fnode/22.16.4/22.16.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@typescript-eslint/eslint-plugin](https://typescript-eslint.io/packages/eslint-plugin)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin))
| [`8.37.0` ->
`8.38.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/8.37.0/8.38.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2feslint-plugin/8.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2feslint-plugin/8.37.0/8.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[@vitejs/plugin-vue](https://redirect.github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue#readme)
([source](https://redirect.github.com/vitejs/vite-plugin-vue/tree/HEAD/packages/plugin-vue))
| [`6.0.0` ->
`6.0.1`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-vue/6.0.0/6.0.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vitejs%2fplugin-vue/6.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitejs%2fplugin-vue/6.0.0/6.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[@vueuse/components](https://redirect.github.com/vueuse/vueuse/tree/main/packages/components#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/components))
| [`13.5.0` ->
`13.6.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcomponents/13.5.0/13.6.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcomponents/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcomponents/13.5.0/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [@vueuse/core](https://redirect.github.com/vueuse/vueuse)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/core))
| [`13.5.0` ->
`13.6.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/13.5.0/13.6.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcore/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcore/13.5.0/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [@vueuse/core](https://redirect.github.com/vueuse/vueuse)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/core))
| [`13.5.0` ->
`13.6.0`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/13.5.0/13.6.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fcore/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fcore/13.5.0/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@vueuse/integrations](https://redirect.github.com/vueuse/vueuse/tree/main/packages/integrations#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/integrations))
| [`13.5.0` ->
`13.6.0`](https://renovatebot.com/diffs/npm/@vueuse%2fintegrations/13.5.0/13.6.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fintegrations/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fintegrations/13.5.0/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
|
[@vueuse/nuxt](https://redirect.github.com/vueuse/vueuse/tree/main/packages/nuxt#readme)
([source](https://redirect.github.com/vueuse/vueuse/tree/HEAD/packages/nuxt))
| [`13.5.0` ->
`13.6.0`](https://renovatebot.com/diffs/npm/@vueuse%2fnuxt/13.5.0/13.6.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@vueuse%2fnuxt/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vueuse%2fnuxt/13.5.0/13.6.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [dotenv](https://redirect.github.com/motdotla/dotenv) | [`17.2.0` ->
`17.2.1`](https://renovatebot.com/diffs/npm/dotenv/17.2.0/17.2.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/dotenv/17.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/dotenv/17.2.0/17.2.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [eslint](https://eslint.org)
([source](https://redirect.github.com/eslint/eslint)) | [`9.31.0` ->
`9.32.0`](https://renovatebot.com/diffs/npm/eslint/9.31.0/9.32.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint/9.32.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint/9.31.0/9.32.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[eslint-config-prettier](https://redirect.github.com/prettier/eslint-config-prettier)
| [`10.1.5` ->
`10.1.8`](https://renovatebot.com/diffs/npm/eslint-config-prettier/10.1.5/10.1.8)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-config-prettier/10.1.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-config-prettier/10.1.5/10.1.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[eslint-plugin-n](https://redirect.github.com/eslint-community/eslint-plugin-n)
| [`17.21.0` ->
`17.21.2`](https://renovatebot.com/diffs/npm/eslint-plugin-n/17.21.0/17.21.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-n/17.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-n/17.21.0/17.21.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[eslint-plugin-prettier](https://redirect.github.com/prettier/eslint-plugin-prettier)
| [`5.5.1` ->
`5.5.3`](https://renovatebot.com/diffs/npm/eslint-plugin-prettier/5.5.1/5.5.3)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-prettier/5.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-prettier/5.5.1/5.5.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[eslint-plugin-storybook](https://redirect.github.com/storybookjs/storybook/code/lib/eslint-plugin#readme)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/lib/eslint-plugin))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/eslint-plugin-storybook/9.0.17/9.0.18)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/eslint-plugin-storybook/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/eslint-plugin-storybook/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[inquirer](https://redirect.github.com/SBoudrias/Inquirer.js/blob/main/packages/inquirer/README.md)
([source](https://redirect.github.com/SBoudrias/Inquirer.js)) |
[`12.7.0` ->
`12.8.2`](https://renovatebot.com/diffs/npm/inquirer/12.7.0/12.8.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/inquirer/12.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/inquirer/12.7.0/12.8.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [jiti](https://redirect.github.com/unjs/jiti) | [`2.4.2` ->
`2.5.1`](https://renovatebot.com/diffs/npm/jiti/2.4.2/2.5.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jiti/2.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jiti/2.4.2/2.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
| [jiti](https://redirect.github.com/unjs/jiti) | [`2.4.2` ->
`2.5.1`](https://renovatebot.com/diffs/npm/jiti/2.4.2/2.5.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jiti/2.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jiti/2.4.2/2.5.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| overrides | minor |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.11` ->
`6.0.12`](https://renovatebot.com/diffs/npm/jose/6.0.11/6.0.12) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.11/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.11` ->
`6.0.12`](https://renovatebot.com/diffs/npm/jose/6.0.11/6.0.12) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.11/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [jose](https://redirect.github.com/panva/jose) | [`6.0.11` ->
`6.0.12`](https://renovatebot.com/diffs/npm/jose/6.0.11/6.0.12) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/jose/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/jose/6.0.11/6.0.12?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [lucide-vue-next](https://lucide.dev)
([source](https://redirect.github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-vue-next))
| [`0.525.0` ->
`0.528.0`](https://renovatebot.com/diffs/npm/lucide-vue-next/0.525.0/0.528.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/lucide-vue-next/0.528.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/lucide-vue-next/0.525.0/0.528.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [marked](https://marked.js.org)
([source](https://redirect.github.com/markedjs/marked)) | [`16.0.0` ->
`16.1.1`](https://renovatebot.com/diffs/npm/marked/16.0.0/16.1.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/marked/16.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/marked/16.0.0/16.1.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [nest-commander](https://nest-commander.jaymcdoniel.dev)
([source](https://redirect.github.com/jmcdo29/nest-commander/tree/HEAD/pacakges/nest-commander))
| [`3.17.0` ->
`3.18.0`](https://renovatebot.com/diffs/npm/nest-commander/3.17.0/3.18.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/nest-commander/3.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/nest-commander/3.17.0/3.18.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [node](https://nodejs.org)
([source](https://redirect.github.com/nodejs/node)) | `22.17.0` ->
`22.17.1` |
[![age](https://developer.mend.io/api/mc/badges/age/node-version/node/v22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/node-version/node/v22.17.0/v22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| | patch |
| [node](https://redirect.github.com/actions/node-versions) | `22.17.0`
-> `22.17.1` |
[![age](https://developer.mend.io/api/mc/badges/age/github-releases/actions%2fnode-versions/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/github-releases/actions%2fnode-versions/22.17.0/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| uses-with | patch |
| [node](https://redirect.github.com/nodejs/node) |
`22.17.0-bookworm-slim` -> `22.17.1-bookworm-slim` |
[![age](https://developer.mend.io/api/mc/badges/age/docker/node/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/docker/node/22.17.0/22.17.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| final | patch |
| [reka-ui](https://redirect.github.com/unovue/reka-ui) | [`2.3.2` ->
`2.4.0`](https://renovatebot.com/diffs/npm/reka-ui/2.3.2/2.4.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/reka-ui/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/reka-ui/2.3.2/2.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | minor |
| [storybook](https://storybook.js.org)
([source](https://redirect.github.com/storybookjs/storybook/tree/HEAD/code/core))
| [`9.0.17` ->
`9.0.18`](https://renovatebot.com/diffs/npm/storybook/9.0.17/9.0.18) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/storybook/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/storybook/9.0.17/9.0.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[tw-animate-css](https://redirect.github.com/Wombosvideo/tw-animate-css)
| [`1.3.5` ->
`1.3.6`](https://renovatebot.com/diffs/npm/tw-animate-css/1.3.5/1.3.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/tw-animate-css/1.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/tw-animate-css/1.3.5/1.3.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
|
[typescript-eslint](https://typescript-eslint.io/packages/typescript-eslint)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint))
| [`8.37.0` ->
`8.38.0`](https://renovatebot.com/diffs/npm/typescript-eslint/8.37.0/8.38.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/typescript-eslint/8.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/typescript-eslint/8.37.0/8.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |
|
[validate-npm-package-name](https://redirect.github.com/npm/validate-npm-package-name)
| [`6.0.1` ->
`6.0.2`](https://renovatebot.com/diffs/npm/validate-npm-package-name/6.0.1/6.0.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/validate-npm-package-name/6.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/validate-npm-package-name/6.0.1/6.0.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [vite](https://vite.dev)
([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite))
| [`7.0.4` ->
`7.0.6`](https://renovatebot.com/diffs/npm/vite/7.0.4/7.0.6) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite/7.0.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/7.0.4/7.0.6?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[vue](https://redirect.github.com/vuejs/core/tree/main/packages/vue#readme)
([source](https://redirect.github.com/vuejs/core)) | [`3.5.17` ->
`3.5.18`](https://renovatebot.com/diffs/npm/vue/3.5.17/3.5.18) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue/3.5.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue/3.5.17/3.5.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
|
[vue](https://redirect.github.com/vuejs/core/tree/main/packages/vue#readme)
([source](https://redirect.github.com/vuejs/core)) | [`3.5.17` ->
`3.5.18`](https://renovatebot.com/diffs/npm/vue/3.5.17/3.5.18) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue/3.5.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue/3.5.17/3.5.18?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| peerDependencies | patch |
|
[vue-i18n](https://redirect.github.com/intlify/vue-i18n/tree/master/packages/vue-i18n#readme)
([source](https://redirect.github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n))
| [`11.1.9` ->
`11.1.11`](https://renovatebot.com/diffs/npm/vue-i18n/11.1.9/11.1.11) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue-i18n/11.1.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-i18n/11.1.9/11.1.11?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [vue-tsc](https://redirect.github.com/vuejs/language-tools)
([source](https://redirect.github.com/vuejs/language-tools/tree/HEAD/packages/tsc))
| [`3.0.1` ->
`3.0.4`](https://renovatebot.com/diffs/npm/vue-tsc/3.0.1/3.0.4) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vue-tsc/3.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vue-tsc/3.0.1/3.0.4?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | patch |
| [vuetify](https://vuetifyjs.com)
([source](https://redirect.github.com/vuetifyjs/vuetify/tree/HEAD/packages/vuetify))
| [`3.9.0` ->
`3.9.2`](https://renovatebot.com/diffs/npm/vuetify/3.9.0/3.9.2) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vuetify/3.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vuetify/3.9.0/3.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| dependencies | patch |
| [wrangler](https://redirect.github.com/cloudflare/workers-sdk)
([source](https://redirect.github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler))
| [`4.24.3` ->
`4.26.0`](https://renovatebot.com/diffs/npm/wrangler/4.24.3/4.26.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/wrangler/4.26.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/wrangler/4.24.3/4.26.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
| devDependencies | minor |

---

### Release Notes

<details>
<summary>eslint/eslint (@&#8203;eslint/js)</summary>

###
[`v9.32.0`](https://redirect.github.com/eslint/eslint/compare/v9.31.0...50de1ced9df2b1ee48ee6843c8cfe0f5d8edbc27)

[Compare
Source](https://redirect.github.com/eslint/eslint/compare/v9.31.0...v9.32.0)

</details>

<details>
<summary>ardatan/graphql-tools (@&#8203;graphql-tools/merge)</summary>

###
[`v9.1.1`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/merge/CHANGELOG.md#911)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/merge@9.1.0...@graphql-tools/merge@9.1.1)

##### Patch Changes

-
[#&#8203;7298](https://redirect.github.com/ardatan/graphql-tools/pull/7298)

[`984d542`](984d542b95)
Thanks [@&#8203;jdolle](https://redirect.github.com/jdolle)! -
dependencies updates:
  - Removed dependency
[`@theguild/federation-composition@^0.19.0`
↗︎](https://www.npmjs.com/package/@&#8203;theguild/federation-composition/v/0.19.0)
    (from `dependencies`)

-
[#&#8203;7298](https://redirect.github.com/ardatan/graphql-tools/pull/7298)

[`984d542`](984d542b95)
Thanks [@&#8203;jdolle](https://redirect.github.com/jdolle)! - Fix
"Named export 'OperationTypeNode' not found"

- Updated dependencies

\[[`32d0457`](32d0457f3f)]:
-
[@&#8203;graphql-tools/utils](https://redirect.github.com/graphql-tools/utils)@&#8203;10.9.1

###
[`v9.1.0`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/merge/CHANGELOG.md#910)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/merge@9.0.24...@graphql-tools/merge@9.1.0)

##### Minor Changes

-
[#&#8203;7249](https://redirect.github.com/ardatan/graphql-tools/pull/7249)

[`e5f98c2`](e5f98c231b)
Thanks [@&#8203;jdolle](https://redirect.github.com/jdolle)! - Support
repeatable [@&#8203;link-ed](https://redirect.github.com/link-ed)
federation directives;
  fix merging non-identical, repeatable directives

##### Patch Changes

-
[#&#8203;7249](https://redirect.github.com/ardatan/graphql-tools/pull/7249)

[`e5f98c2`](e5f98c231b)
Thanks [@&#8203;jdolle](https://redirect.github.com/jdolle)! -
dependencies updates:
  - Added dependency
[`@theguild/federation-composition@^0.16.0`
↗︎](https://www.npmjs.com/package/@&#8203;theguild/federation-composition/v/0.16.0)
    (to `dependencies`)

-
[#&#8203;7276](https://redirect.github.com/ardatan/graphql-tools/pull/7276)

[`3c21496`](3c21496330)
Thanks [@&#8203;renovate](https://redirect.github.com/apps/renovate)! -
dependencies updates:
  - Updated dependency
[`@theguild/federation-composition@^0.19.0`
↗︎](https://www.npmjs.com/package/@&#8203;theguild/federation-composition/v/0.19.0)
    (from `^0.16.0`, in `dependencies`)

- Updated dependencies

\[[`22af985`](22af98581e),

[`53db005`](53db00540c)]:
-
[@&#8203;graphql-tools/utils](https://redirect.github.com/graphql-tools/utils)@&#8203;10.9.0

</details>

<details>
<summary>ardatan/graphql-tools (@&#8203;graphql-tools/schema)</summary>

###
[`v10.0.25`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/schema/CHANGELOG.md#10025)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/schema@10.0.24...@graphql-tools/schema@10.0.25)

##### Patch Changes

- Updated dependencies

\[[`984d542`](984d542b95),

[`984d542`](984d542b95),

[`32d0457`](32d0457f3f)]:
-
[@&#8203;graphql-tools/merge](https://redirect.github.com/graphql-tools/merge)@&#8203;9.1.1
-
[@&#8203;graphql-tools/utils](https://redirect.github.com/graphql-tools/utils)@&#8203;10.9.1

###
[`v10.0.24`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/schema/CHANGELOG.md#10024)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/schema@10.0.23...@graphql-tools/schema@10.0.24)

##### Patch Changes

- Updated dependencies

\[[`e5f98c2`](e5f98c231b),

[`3c21496`](3c21496330),

[`e5f98c2`](e5f98c231b),

[`22af985`](22af98581e),

[`53db005`](53db00540c)]:
-
[@&#8203;graphql-tools/merge](https://redirect.github.com/graphql-tools/merge)@&#8203;9.1.0
-
[@&#8203;graphql-tools/utils](https://redirect.github.com/graphql-tools/utils)@&#8203;10.9.0

</details>

<details>
<summary>ardatan/graphql-tools (@&#8203;graphql-tools/utils)</summary>

###
[`v10.9.1`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/utils/CHANGELOG.md#1091)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/utils@10.9.0...@graphql-tools/utils@10.9.1)

##### Patch Changes

-
[`32d0457`](32d0457f3f)
Thanks [@&#8203;ardatan](https://redirect.github.com/ardatan)! - Fix
oneOf handling

###
[`v10.9.0`](https://redirect.github.com/ardatan/graphql-tools/blob/HEAD/packages/utils/CHANGELOG.md#1090)

[Compare
Source](https://redirect.github.com/ardatan/graphql-tools/compare/@graphql-tools/utils@10.8.6...@graphql-tools/utils@10.9.0)

##### Minor Changes

-
[#&#8203;7281](https://redirect.github.com/ardatan/graphql-tools/pull/7281)

[`53db005`](53db00540c)
Thanks [@&#8203;EmrysMyrddin](https://redirect.github.com/EmrysMyrddin)!
- Add optional `subgraphName` preoperty
to the `ExecutionRequest` interface for usage in Gateways like Hive
Gateway.

##### Patch Changes

-
[#&#8203;7282](https://redirect.github.com/ardatan/graphql-tools/pull/7282)

[`22af985`](22af98581e)
Thanks [@&#8203;renovate](https://redirect.github.com/apps/renovate)! -
Support `@oneOf` directive

</details>

<details>
<summary>adobe/react-spectrum
(@&#8203;internationalized/number)</summary>

###
[`v3.6.4`](https://redirect.github.com/adobe/react-spectrum/compare/@internationalized/number@3.6.3...@internationalized/number@3.6.4)

[Compare
Source](https://redirect.github.com/adobe/react-spectrum/compare/@internationalized/number@3.6.3...@internationalized/number@3.6.4)

</details>

<details>
<summary>Thinkmill/manypkg (@&#8203;manypkg/cli)</summary>

###
[`v0.25.0`](https://redirect.github.com/Thinkmill/manypkg/blob/HEAD/packages/cli/CHANGELOG.md#0250)

[Compare
Source](https://redirect.github.com/Thinkmill/manypkg/compare/@manypkg/cli@0.24.0...@manypkg/cli@0.25.0)

##### Minor Changes

- [#&#8203;254](https://redirect.github.com/Thinkmill/manypkg/pull/254)
[`2c06ac0`](2c06ac0939)
Thanks [@&#8203;cjkihl](https://redirect.github.com/cjkihl)! - Add Bun
support

##### Patch Changes

- Updated dependencies
\[[`2c06ac0`](2c06ac0939)]:
-
[@&#8203;manypkg/get-packages](https://redirect.github.com/manypkg/get-packages)@&#8203;3.1.0

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/common)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/compare/v11.1.3...1f101ac8b0a5bb5b97a7caf6634fcea8d65196e0)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/core)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.4)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

##### v11.1.4 (2025-07-16)

##### Bug fixes

- `platform-fastify`
- [#&#8203;15385](https://redirect.github.com/nestjs/nest/pull/15385)
fix(testing): auto-init fastify adapter for middleware registration
([@&#8203;mag123c](https://redirect.github.com/mag123c))
- `core`, `testing`
- [#&#8203;15405](https://redirect.github.com/nestjs/nest/pull/15405)
fix(core): fix race condition in class dependency resolution
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- `core`
- [#&#8203;15333](https://redirect.github.com/nestjs/nest/pull/15333)
fix(core): Make flattenRoutePath return a valid module
([@&#8203;gentunian](https://redirect.github.com/gentunian))
- `microservices`
- [#&#8203;15305](https://redirect.github.com/nestjs/nest/pull/15305)
fix(microservices): Revisit RMQ pattern matching with wildcards
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- [#&#8203;15250](https://redirect.github.com/nestjs/nest/pull/15250)
fix(constants): update RMQ\_DEFAULT\_QUEUE to an empty string
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))

##### Enhancements

- `platform-fastify`
- [#&#8203;14789](https://redirect.github.com/nestjs/nest/pull/14789)
feat(fastify): add decorator for custom schema
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- `common`, `core`, `microservices`, `platform-express`,
`platform-fastify`, `websockets`
- [#&#8203;15386](https://redirect.github.com/nestjs/nest/pull/15386)
feat: enhance introspection capabilities
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- `core`
- [#&#8203;15374](https://redirect.github.com/nestjs/nest/pull/15374)
feat: supporting fine async storage control
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))

##### Dependencies

- `platform-ws`
- [#&#8203;15350](https://redirect.github.com/nestjs/nest/pull/15350)
chore(deps): bump ws from 8.18.2 to 8.18.3
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))
- `platform-fastify`
- [#&#8203;15278](https://redirect.github.com/nestjs/nest/pull/15278)
chore(deps): bump fastify from 5.3.3 to 5.4.0
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

##### Committers: 11

- Alexey Filippov
([@&#8203;SocketSomeone](https://redirect.github.com/SocketSomeone))
- EFIcats ([@&#8203;ext4cats](https://redirect.github.com/ext4cats))
- Edouard Maleix
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- JaeHo Jang ([@&#8203;mag123c](https://redirect.github.com/mag123c))
- Jiri Hajek
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- Kamil Mysliwiec
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- Khan / 이창민
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))
- Peter F.
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- Sebastian ([@&#8203;gentunian](https://redirect.github.com/gentunian))
- Thiago Oliveira Santos
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))
- jochong ([@&#8203;jochongs](https://redirect.github.com/jochongs))

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/platform-fastify)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.5)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

#### v11.1.5 (2025-07-18)

##### Dependencies

- `platform-express`
- [#&#8203;15425](https://redirect.github.com/nestjs/nest/pull/15425)
chore(deps): bump multer from 2.0.1 to 2.0.2 in
/packages/platform-express
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/releases/tag/v11.1.4)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

#### v11.1.4 (2025-07-16)

##### Bug fixes

- `platform-fastify`
- [#&#8203;15385](https://redirect.github.com/nestjs/nest/pull/15385)
fix(testing): auto-init fastify adapter for middleware registration
([@&#8203;mag123c](https://redirect.github.com/mag123c))
- `core`, `testing`
- [#&#8203;15405](https://redirect.github.com/nestjs/nest/pull/15405)
fix(core): fix race condition in class dependency resolution
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- `core`
- [#&#8203;15333](https://redirect.github.com/nestjs/nest/pull/15333)
fix(core): Make flattenRoutePath return a valid module
([@&#8203;gentunian](https://redirect.github.com/gentunian))
- `microservices`
- [#&#8203;15305](https://redirect.github.com/nestjs/nest/pull/15305)
fix(microservices): Revisit RMQ pattern matching with wildcards
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- [#&#8203;15250](https://redirect.github.com/nestjs/nest/pull/15250)
fix(constants): update RMQ\_DEFAULT\_QUEUE to an empty string
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))

##### Enhancements

- `platform-fastify`
- [#&#8203;14789](https://redirect.github.com/nestjs/nest/pull/14789)
feat(fastify): add decorator for custom schema
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- `common`, `core`, `microservices`, `platform-express`,
`platform-fastify`, `websockets`
- [#&#8203;15386](https://redirect.github.com/nestjs/nest/pull/15386)
feat: enhance introspection capabilities
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- `core`
- [#&#8203;15374](https://redirect.github.com/nestjs/nest/pull/15374)
feat: supporting fine async storage control
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))

##### Dependencies

- `platform-ws`
- [#&#8203;15350](https://redirect.github.com/nestjs/nest/pull/15350)
chore(deps): bump ws from 8.18.2 to 8.18.3
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))
- `platform-fastify`
- [#&#8203;15278](https://redirect.github.com/nestjs/nest/pull/15278)
chore(deps): bump fastify from 5.3.3 to 5.4.0
([@&#8203;dependabot\[bot\]](https://redirect.github.com/apps/dependabot))

##### Committers: 11

- Alexey Filippov
([@&#8203;SocketSomeone](https://redirect.github.com/SocketSomeone))
- EFIcats ([@&#8203;ext4cats](https://redirect.github.com/ext4cats))
- Edouard Maleix
([@&#8203;getlarge](https://redirect.github.com/getlarge))
- JaeHo Jang ([@&#8203;mag123c](https://redirect.github.com/mag123c))
- Jiri Hajek
([@&#8203;hajekjiri](https://redirect.github.com/hajekjiri))
- Kamil Mysliwiec
([@&#8203;kamilmysliwiec](https://redirect.github.com/kamilmysliwiec))
- Khan / 이창민
([@&#8203;EeeasyCode](https://redirect.github.com/EeeasyCode))
- Peter F.
([@&#8203;piotrfrankowski](https://redirect.github.com/piotrfrankowski))
- Sebastian ([@&#8203;gentunian](https://redirect.github.com/gentunian))
- Thiago Oliveira Santos
([@&#8203;Farenheith](https://redirect.github.com/Farenheith))
- jochong ([@&#8203;jochongs](https://redirect.github.com/jochongs))

</details>

<details>
<summary>nestjs/nest (@&#8203;nestjs/testing)</summary>

###
[`v11.1.5`](https://redirect.github.com/nestjs/nest/compare/v11.1.4...9bb0560e79743cc0bd2ce198c65e21332200c3ad)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.4...v11.1.5)

###
[`v11.1.4`](https://redirect.github.com/nestjs/nest/compare/v11.1.3...1f101ac8b0a5bb5b97a7caf6634fcea8d65196e0)

[Compare
Source](https://redirect.github.com/nestjs/nest/compare/v11.1.3...v11.1.4)

</details>

<details>
<summary>nuxt/eslint (@&#8203;nuxt/eslint)</summary>

###
[`v1.7.1`](https://redirect.github.com/nuxt/eslint/releases/tag/v1.7.1)

[Compare
Source](https://redirect.github.com/nuxt/eslint/compare/v1.7.0...v1.7.1)

#####    🐞 Bug Fixes

- Include `eslint-typegen.d.ts` in `nuxt.node.d.ts`, close
[#&#8203;596](https://redirect.github.com/nuxt/eslint/issues/596)  -  by
[@&#8203;antfu](https://redirect.github.com/antfu) in
[https://github.com/nuxt/eslint/issues/596](https://redirect.github.com/nuxt/eslint/issues/596)
[<samp>(ab74e)</samp>](https://redirect.github.com/nuxt/eslint/commit/ab74efd)

#####     [View changes on
GitHub](https://redirect.github.com/nuxt/eslint/compare/v1.7.0...v1.7.1)

###
[`v1.7.0`](https://redirect.github.com/nuxt/eslint/releases/tag/v1.7.0)

[Compare
Source](https://redirect.github.com/nuxt/eslint/compare/v1.6.0...v1.7.0)

#####    🚀 Features

- Upgrade eslint-plugin-unicorn  -  by
[@&#8203;antfu](https://redirect.github.com/antfu)
[<samp>(b3b7d)</samp>](https://redirect.github.com/nuxt/eslint/commit/b3b7d93)

#####     [View changes on
GitHub](https://redirect.github.com/nuxt/eslint/compare/v1.6.0...v1.7.0)

###
[`v1.6.0`](https://redirect.github.com/nuxt/eslint/releases/tag/v1.6.0)

[Compare
Source](https://redirect.github.com/nuxt/eslint/compare/v1.5.2...v1.6.0)

#####    🐞 Bug Fixes

- Bring back `eslint-plugin-import-x` as default, close
[#&#8203;590](https://redirect.github.com/nuxt/eslint/issues/590)  -  by
[@&#8203;antfu](https://redirect.github.com/antfu) in
[https://github.com/nuxt/eslint/issues/590](https://redirect.github.com/nuxt/eslint/issues/590)
[<samp>(e43d6)</samp>](https://redirect.github.com/nuxt/eslint/commit/e43d6de)

#####     [View changes on
GitHub](https://redirect.github.com/nuxt/eslint/compare/v1.5.2...v1.6.0)

</details>

<details>
<summary>nuxt/ui (@&#8203;nuxt/ui)</summary>

###
[`v3.3.0`](https://redirect.github.com/nuxt/ui/blob/HEAD/CHANGELOG.md#330-2025-07-24)

[Compare
Source](https://redirect.github.com/nuxt/ui/compare/v3.2.0...v3.3.0)

##### Features

- **CommandPalette:** add `footer` slot
([#&#8203;4457](https://redirect.github.com/nuxt/ui/issues/4457))
([63730d6](63730d684b))
- **Drawer:** add `nested` prop
([e2695ee](e2695ee7e4)),
closes [#&#8203;4320](https://redirect.github.com/nuxt/ui/issues/4320)
- **FileUpload:** new component
([#&#8203;4564](https://redirect.github.com/nuxt/ui/issues/4564))
([35dbe6c](35dbe6c2ab))
- **Input/Textarea:** add `default-value` prop
([#&#8203;4404](https://redirect.github.com/nuxt/ui/issues/4404))
([fb9e7bb](fb9e7bb856))
- **InputMenu:** emit `remove-tag` event
([#&#8203;4511](https://redirect.github.com/nuxt/ui/issues/4511))
([6ca7c8b](6ca7c8b7bf))
- **InputTags:** add `max-length` prop
([b96a1cc](b96a1ccbab)),
closes [#&#8203;4405](https://redirect.github.com/nuxt/ui/issues/4405)
- **Kbd:** add `color` prop & `soft` variant
([#&#8203;4549](https://redirect.github.com/nuxt/ui/issues/4549))
([f336600](f33660035f))
- **module:** add `theme.defaultVariants` option
([#&#8203;4400](https://redirect.github.com/nuxt/ui/issues/4400))
([35f90b9](35f90b9920))
- **Popover:** add `reference` prop
([b00e07f](b00e07f13d))
- **Table:** add `footer` support to display column summary
([#&#8203;4194](https://redirect.github.com/nuxt/ui/issues/4194))
([c355cac](c355cacd43))
- **Table:** add `style` to table and column `meta`
([#&#8203;4513](https://redirect.github.com/nuxt/ui/issues/4513))
([1db21d1](1db21d1b00))
- **Table:** add row `hover` event
([f903ec3](f903ec396f)),
closes [#&#8203;2435](https://redirect.github.com/nuxt/ui/issues/2435)
- **Table:** add support for `colspan` and `rowspan`
([#&#8203;4460](https://redirect.github.com/nuxt/ui/issues/4460))
([7ef1933](7ef19333f0))
- **Table:** add support for context menu
([f62c5ec](f62c5ec20c)),
closes [#&#8203;4259](https://redirect.github.com/nuxt/ui/issues/4259)
- **Tabs:** add badge on items
([#&#8203;4553](https://redirect.github.com/nuxt/ui/issues/4553))
([62ab016](62ab01655c))
- **Toast:** progress bar with Progress component
([ec569e4](ec569e427b))
- **Tooltip:** add `reference` prop
([69a7b95](69a7b957d5)),
closes [#&#8203;4430](https://redirect.github.com/nuxt/ui/issues/4430)

##### Bug Fixes

- **Button/Link:** merge `active-class` / `inactive-class` with app
config ([#&#8203;4446](https://redirect.github.com/nuxt/ui/issues/4446))
([9debce7](9debce737c))
- **Button:** add `active` styles to behave like `hover` on mobile
([df8f202](df8f20232f)),
closes [#&#8203;991](https://redirect.github.com/nuxt/ui/issues/991)
- **Carousel/Tree:** add type to button elements for accessibility
([#&#8203;4493](https://redirect.github.com/nuxt/ui/issues/4493))
([fc24e03](fc24e03cc4))
- **Carousel:** add `aria-current` attribute to active dot
([#&#8203;4447](https://redirect.github.com/nuxt/ui/issues/4447))
([1ba8a55](1ba8a55bcb))
- **Carousel:** improve accessibility
([55e06e9](55e06e97e7)),
closes [#&#8203;4494](https://redirect.github.com/nuxt/ui/issues/4494)
- **Carousel:** resolve plugins with page transitions
([#&#8203;4380](https://redirect.github.com/nuxt/ui/issues/4380))
([3b67d54](3b67d54833))
- **ColorPicker:** update color conversion logic
([#&#8203;4550](https://redirect.github.com/nuxt/ui/issues/4550))
([6b6ec8c](https://redirec

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/unraid/api).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4yMy4yIiwidXBkYXRlZEluVmVyIjoiNDEuNDMuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Eli Bosley <ekbosley@gmail.com>
2025-07-28 11:07:52 -04:00
Eli Bosley
3b00fec5fd chore: Remove legacy store modules and add new API key and reporting services (#1536)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added developer CLI tools for toggling GraphQL sandbox and modal
testing utilities.
* Introduced a "Show Activation Modal" developer component for UI
testing.
  * Added system initial setup detection and related GraphQL queries.
* Enhanced login and welcome pages with dynamic server info and initial
setup state.
  * Improved SSO button with internationalization and error handling.
* Added internal CLI admin API key management service and internal
GraphQL client service.
* Introduced comprehensive API report generation service for system and
service status.
* Added CLI commands and GraphQL mutations/queries for plugin and SSO
user management.
* Added new modal target components and improved teleport target
detection.

* **Enhancements**
* Refined modal dialog targeting and teleportation for flexible UI
placement.
* Updated modal components and stores for improved activation/welcome
modal control.
  * Improved plugin and SSO user management via CLI through GraphQL API.
* Refactored partner logo components to use props instead of store
dependencies.
  * Enhanced styling and accessibility for buttons and modals.
* Streamlined Tailwind CSS integration with shared styles and updated
theme variables.
* Improved GraphQL module configuration to avoid directive conflicts in
tests.
  * Adjusted Vite config for better dependency handling in test mode.
  * Improved error handling and logging in CLI commands and services.
* Reordered imports and refined component class bindings for UI
consistency.

* **Bug Fixes**
* Resolved issues with duplicate script tags and component registration
in the web UI.
* Fixed modal close button visibility and activation modal state
handling.
* Added error handling and logging improvements across CLI commands and
services.
  * Fixed newline issues in last-download-time fixture files.

* **Chores**
* Added and updated numerous tests for CLI commands, services, and UI
components.
* Updated translation files and localization resources for new UI
messages.
* Adjusted environment, configuration, and dependency files for improved
development and test workflows.
  * Cleaned up unused imports and mocks in tests.
  * Reorganized exports and barrel files in shared and UI modules.
  * Added integration and dependency resolution tests for core modules.

* **Removals & Refactoring**
* Removed legacy Redux state management, configuration, and UPnP logic
from the backend.
* Eliminated deprecated GraphQL subscriptions and client code related to
registration and mothership.
* Removed direct store manipulation and replaced with service-based
approaches in CLI commands.
  * Deleted unused or redundant test files and configuration listeners.
* Refactored SSO user service to consolidate add/remove operations into
a single update method.
* Simplified API key services with new methods for automatic key
management.
* Replaced direct plugin and SSO user service calls with GraphQL client
interactions in CLI commands.
* Removed complex theme fallback and dark mode CSS rules, replacing with
streamlined static theme variables.
* Cleaned up Tailwind CSS configuration and removed deprecated local
styles.
* Removed multiple internal utility files and replaced with simplified
or centralized implementations.
* Removed deprecated local configuration and synchronization files and
listeners.
  * Removed UPnP helper functions and job management classes.
* Refactored server resolver to dynamically construct local server data
internally.
* Removed CORS handler and replaced with simplified or externalized
logic.
* Removed store synchronization and registration event pubsub handling.
* Removed GraphQL client creation utilities for internal API
communication.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 15:07:37 -04:00
Eli Bosley
4ff6a1aaa0 Add Claude Code GitHub Workflow (#1541)
## 🤖 Installing Claude Code GitHub App

This PR adds a GitHub Actions workflow that enables Claude Code
integration in our repository.

### What is Claude Code?

[Claude Code](https://claude.ai/code) is an AI coding agent that can
help with:
- Bug fixes and improvements  
- Documentation updates
- Implementing new features
- Code reviews and suggestions
- Writing tests
- And more!

### How it works

Once this PR is merged, we'll be able to interact with Claude by
mentioning @claude in a pull request or issue comment.
Once the workflow is triggered, Claude will analyze the comment and
surrounding context, and execute on the request in a GitHub action.

### Important Notes

- **This workflow won't take effect until this PR is merged**
- **@claude mentions won't work until after the merge is complete**
- The workflow runs automatically whenever Claude is mentioned in PR or
issue comments
- Claude gets access to the entire PR or issue context including files,
diffs, and previous comments

### Security

- Our Anthropic API key is securely stored as a GitHub Actions secret
- Only users with write access to the repository can trigger the
workflow
- All Claude runs are stored in the GitHub Actions run history
- Claude's default tools are limited to reading/writing files and
interacting with our repo by creating comments, branches, and commits.
- We can add more allowed tools by adding them to the workflow file
like:

```
allowed_tools: Bash(npm install),Bash(npm run build),Bash(npm run lint),Bash(npm run test)
```

There's more information in the [Claude Code action
repo](https://github.com/anthropics/claude-code-action).

After merging this PR, let's try mentioning @claude in a comment on any
PR to get started!

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Introduced automated code review using Claude AI for pull requests.
* Added Claude AI code assistance, triggered by comments containing
"@claude" in issues and pull requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-25 15:00:03 -04:00
647 changed files with 53568 additions and 14977 deletions

123
.claude/settings.json Normal file
View File

@@ -0,0 +1,123 @@
{
"permissions": {
"allow": [
"# Development Commands",
"Bash(pnpm install)",
"Bash(pnpm dev)",
"Bash(pnpm build)",
"Bash(pnpm test)",
"Bash(pnpm test:*)",
"Bash(pnpm lint)",
"Bash(pnpm lint:fix)",
"Bash(pnpm type-check)",
"Bash(pnpm codegen)",
"Bash(pnpm storybook)",
"Bash(pnpm --filter * dev)",
"Bash(pnpm --filter * build)",
"Bash(pnpm --filter * test)",
"Bash(pnpm --filter * lint)",
"Bash(pnpm --filter * codegen)",
"# Git Commands (read-only)",
"Bash(git status)",
"Bash(git diff)",
"Bash(git log)",
"Bash(git branch)",
"Bash(git remote -v)",
"# Search Commands",
"Bash(rg *)",
"# File System (read-only)",
"Bash(ls)",
"Bash(ls -la)",
"Bash(pwd)",
"Bash(find . -name)",
"Bash(find . -type)",
"# Node/NPM Commands",
"Bash(node --version)",
"Bash(pnpm --version)",
"Bash(npx --version)",
"# Environment Commands",
"Bash(echo $*)",
"Bash(which *)",
"# Process Commands",
"Bash(ps aux | grep)",
"Bash(lsof -i)",
"# Documentation Domains",
"WebFetch(domain:tailwindcss.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:reka-ui.com)",
"WebFetch(domain:nodejs.org)",
"WebFetch(domain:pnpm.io)",
"WebFetch(domain:vitejs.dev)",
"WebFetch(domain:nuxt.com)",
"WebFetch(domain:nestjs.com)",
"# IDE Integration",
"mcp__ide__getDiagnostics",
"# Browser MCP (for testing)",
"mcp__browsermcp__browser_navigate",
"mcp__browsermcp__browser_click",
"mcp__browsermcp__browser_screenshot"
],
"deny": [
"# Dangerous Commands",
"Bash(rm -rf)",
"Bash(chmod 777)",
"Bash(curl)",
"Bash(wget)",
"Bash(ssh)",
"Bash(scp)",
"Bash(sudo)",
"Bash(su)",
"Bash(pkill)",
"Bash(kill)",
"Bash(killall)",
"Bash(python)",
"Bash(python3)",
"Bash(pip)",
"Bash(npm)",
"Bash(yarn)",
"Bash(apt)",
"Bash(brew)",
"Bash(systemctl)",
"Bash(service)",
"Bash(docker)",
"Bash(docker-compose)",
"# File Modification (use Edit/Write tools instead)",
"Bash(sed)",
"Bash(awk)",
"Bash(perl)",
"Bash(echo > *)",
"Bash(echo >> *)",
"Bash(cat > *)",
"Bash(cat >> *)",
"Bash(tee)",
"# Git Write Commands (require explicit user action)",
"Bash(git add)",
"Bash(git commit)",
"Bash(git push)",
"Bash(git pull)",
"Bash(git merge)",
"Bash(git rebase)",
"Bash(git checkout)",
"Bash(git reset)",
"Bash(git clean)",
"# Package Management Write Commands",
"Bash(pnpm add)",
"Bash(pnpm remove)",
"Bash(pnpm update)",
"Bash(pnpm upgrade)"
]
},
"enableAllProjectMcpServers": false
}

View File

@@ -1,45 +0,0 @@
{
"permissions": {
"allow": [
"Bash(rg:*)",
"Bash(find:*)",
"Bash(pnpm codegen:*)",
"Bash(pnpm dev:*)",
"Bash(pnpm build:*)",
"Bash(pnpm test:*)",
"Bash(grep:*)",
"Bash(pnpm type-check:*)",
"Bash(pnpm lint:*)",
"Bash(pnpm --filter ./api lint)",
"Bash(mv:*)",
"Bash(ls:*)",
"mcp__ide__getDiagnostics",
"Bash(pnpm --filter \"*connect*\" test connect-status-writer.service.spec)",
"Bash(pnpm storybook:*)",
"Bash(pnpm add:*)",
"Bash(pnpm install:*)",
"Bash(pkill:*)",
"Bash(true)",
"Bash(timeout 15 pnpm storybook)",
"WebFetch(domain:tailwindcss.com)",
"Bash(pnpm list:*)",
"Bash(pnpm remove:*)",
"WebFetch(domain:github.com)",
"mcp__browsermcp__browser_navigate",
"Bash(clear)",
"Bash(git log:*)",
"Bash(pnpm --filter ./unraid-ui build)",
"Bash(pnpm --filter @unraid/ui build)",
"Bash(pnpm --filter @unraid/web build)",
"Bash(python3:*)",
"Bash(pnpm tailwind:build:*)",
"WebFetch(domain:erangrin.github.io)",
"Bash(pnpm clean:*)",
"Bash(pnpm validate:css:*)",
"Bash(node:*)",
"Bash(rm:*)",
"Bash(pnpm run:*)"
]
},
"enableAllProjectMcpServers": false
}

View File

@@ -10,4 +10,5 @@ alwaysApply: false
* Test suite is VITEST, do not use jest
pnpm --filter ./api test
* Prefer to not mock simple dependencies
* For error testing, use `.rejects.toThrow()` without arguments - don't test exact error message strings unless the message format is specifically what you're testing

View File

@@ -4,6 +4,10 @@ globs: **/*.test.ts,**/__test__/components/**/*.ts,**/__test__/store/**/*.ts,**/
alwaysApply: false
---
## General Testing Best Practices
- **Error Testing:** Use `.rejects.toThrow()` without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
- **Focus on Behavior:** Test what the code does, not implementation details like exact error message wording
## Vue Component Testing Best Practices
- This is a Nuxt.js app but we are testing with vitest outside of the Nuxt environment
- Nuxt is currently set to auto import so some vue files may need compute or ref imported

20
.github/CODEOWNERS vendored
View File

@@ -1,20 +0,0 @@
# Default owners for everything in the repo
* @elibosley @pujitm @mdatelle @zackspear
# API specific files
/api/ @elibosley @pujitm @mdatelle
# Web frontend files
/web/ @elibosley @mdatelle @zackspear
# Plugin related files
/plugin/ @elibosley
# Unraid UI specific files
/unraid-ui/ @mdatelle @zackspear @pujitm
# GitHub workflows and configuration
/.github/ @elibosley
# Documentation
*.md @elibosley @pujitm @mdatelle @zackspear

View File

@@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -88,19 +88,19 @@ jobs:
pnpm install --frozen-lockfile --filter @unraid/connect-plugin
- name: Download Unraid UI Components
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: unraid-wc-ui
path: ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/uui
merge-multiple: true
- name: Download Unraid Web Components
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
pattern: unraid-wc-rich
path: ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/nuxt
merge-multiple: true
- name: Download Unraid API
uses: actions/download-artifact@v4
uses: actions/download-artifact@v5
with:
name: unraid-api
path: ${{ github.workspace }}/plugin/api/
@@ -152,7 +152,7 @@ jobs:
with:
workflow: release-production.yml
inputs: '{ "version": "${{ steps.vars.outputs.API_VERSION }}" }'
token: ${{ secrets.WORKFLOW_TRIGGER_PAT }}
token: ${{ secrets.UNRAID_BOT_GITHUB_ADMIN_TOKEN }}
- name: Upload to Cloudflare
if: inputs.RELEASE_CREATED == 'false'

103
.github/workflows/claude-code-review.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Skip reviews for non-code changes
paths-ignore:
- "**/*.md"
- "**/package-lock.json"
- "**/pnpm-lock.yaml"
- "**/.gitignore"
- "**/LICENSE"
- "**/*.config.js"
- "**/*.config.ts"
- "**/tsconfig.json"
- "**/.github/workflows/*.yml"
- "**/docs/**"
jobs:
claude-review:
# Skip review for bot PRs and WIP/skip-review PRs
# Only run if changes are significant (>10 lines)
if: |
(github.event.pull_request.additions > 10 || github.event.pull_request.deletions > 10) &&
!contains(github.event.pull_request.title, '[skip-review]') &&
!contains(github.event.pull_request.title, '[WIP]') &&
!endsWith(github.event.pull_request.user.login, '[bot]') &&
github.event.pull_request.user.login != 'dependabot' &&
github.event.pull_request.user.login != 'renovate'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: "claude-opus-4-20250514"
# Direct prompt for automated review (no @claude mention needed)
direct_prompt: |
IMPORTANT: Review ONLY the DIFF/CHANGESET - the actual lines that were added or modified in this PR.
DO NOT review the entire file context, only analyze the specific changes being made.
Look for HIGH-PRIORITY issues in the CHANGED LINES ONLY:
1. CRITICAL BUGS: Logic errors, null pointer issues, infinite loops, race conditions
2. SECURITY: SQL injection, XSS, authentication bypass, exposed secrets, unsafe operations
3. BREAKING CHANGES: API contract violations, removed exports, changed function signatures
4. DATA LOSS RISKS: Destructive operations without safeguards, missing data validation
DO NOT comment on:
- Code that wasn't changed in this PR
- Style, formatting, or documentation
- Test coverage (unless tests are broken by the changes)
- Minor optimizations or best practices
- Existing code issues that weren't introduced by this PR
If you find no critical issues in the DIFF, respond with: "✅ No critical issues found in changes"
Keep response under 10 lines. Reference specific line numbers from the diff when reporting issues.
# Optional: Use sticky comments to make Claude reuse the same comment on subsequent pushes to the same PR
use_sticky_comment: true
# Context-aware review based on PR characteristics
# Uncomment to enable different review strategies based on context
# direct_prompt: |
# ${{
# (github.event.pull_request.additions > 500) &&
# 'Large PR detected. Focus only on architectural issues and breaking changes. Skip minor issues.' ||
# contains(github.event.pull_request.title, 'fix') &&
# 'Bug fix PR: Verify the fix addresses the root cause and check for regression risks.' ||
# contains(github.event.pull_request.title, 'deps') &&
# 'Dependency update: Check for breaking changes and security advisories only.' ||
# contains(github.event.pull_request.title, 'refactor') &&
# 'Refactor PR: Verify no behavior changes and check for performance regressions.' ||
# contains(github.event.pull_request.title, 'feat') &&
# 'New feature: Check for security issues, edge cases, and integration problems only.' ||
# 'Standard review: Check for critical bugs, security issues, and breaking changes only.'
# }}
# Optional: Add specific tools for running tests or linting
# allowed_tools: "Bash(npm run test),Bash(npm run lint),Bash(npm run typecheck)"
# Optional: Skip review for certain conditions
# if: |
# !contains(github.event.pull_request.title, '[skip-review]') &&
# !contains(github.event.pull_request.title, '[WIP]')

64
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@beta
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: "claude-opus-4-20250514"
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
# Optional: Allow Claude to run specific commands
# allowed_tools: "Bash(npm install),Bash(npm run build),Bash(npm run test:*),Bash(npm run lint:*)"
# Optional: Add custom instructions for Claude to customize its behavior for your project
# custom_instructions: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Custom environment variables for Claude
# claude_env: |
# NODE_ENV: test

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Initialize CodeQL
uses: github/codeql-action/init@v3

View File

@@ -20,26 +20,49 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout source repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
path: source-repo
- name: Checkout docs repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
repository: unraid/docs
path: docs-repo
token: ${{ secrets.DOCS_PAT_UNRAID_BOT }}
- name: Copy updated docs
- name: Copy and process docs
run: |
if [ ! -d "source-repo/api/docs" ]; then
echo "Source directory does not exist!"
exit 1
fi
# Remove old API docs but preserve other folders
rm -rf docs-repo/docs/API/
mkdir -p docs-repo/docs/API
# Copy all markdown files and maintain directory structure
cp -r source-repo/api/docs/public/. docs-repo/docs/API/
# Copy images to Docusaurus static directory
mkdir -p docs-repo/static/img/api
# Copy images from public/images if they exist
if [ -d "source-repo/api/docs/public/images" ]; then
cp -r source-repo/api/docs/public/images/. docs-repo/static/img/api/
fi
# Also copy any images from the parent docs/images directory
if [ -d "source-repo/api/docs/images" ]; then
cp -r source-repo/api/docs/images/. docs-repo/static/img/api/
fi
# Update image paths in markdown files
# Replace relative image paths with absolute paths pointing to /img/api/
find docs-repo/docs/API -name "*.md" -type f -exec sed -i 's|!\[\([^]]*\)\](\./images/\([^)]*\))|![\1](/img/api/\2)|g' {} \;
find docs-repo/docs/API -name "*.md" -type f -exec sed -i 's|!\[\([^]]*\)\](images/\([^)]*\))|![\1](/img/api/\2)|g' {} \;
find docs-repo/docs/API -name "*.md" -type f -exec sed -i 's|!\[\([^]]*\)\](../images/\([^)]*\))|![\1](/img/api/\2)|g' {} \;
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
@@ -53,7 +76,7 @@ jobs:
Changes were automatically generated from api/docs/* directory.
@coderabbitai ignore
reviewers: ljm42, elibosley, pujitm, mdatelle
reviewers: ljm42, elibosley
branch: update-api-docs
base: main
delete-branch: true

View File

@@ -20,12 +20,12 @@ jobs:
name: Deploy Storybook
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.17.0'
node-version: '22.18.0'
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -33,7 +33,7 @@ jobs:
run_install: false
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system
version: 1.0

View File

@@ -19,7 +19,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
# Only run release-please on pushes to main
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4
@@ -45,7 +45,7 @@ jobs:
node-version-file: ".nvmrc"
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system
version: 1.0
@@ -117,42 +117,62 @@ jobs:
# Verify libvirt is running using sudo to bypass group membership delays
sudo virsh list --all || true
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Build UI Package First
run: |
echo "🔧 Building UI package for web tests dependency..."
cd ../unraid-ui && pnpm run build
- name: Run Tests Concurrently
run: |
set -e
# Run all tests in parallel with labeled output
# Run all tests in parallel with labeled output and coverage generation
echo "🚀 Starting API coverage tests..."
pnpm run coverage > api-test.log 2>&1 &
API_PID=$!
echo "🚀 Starting Connect plugin tests..."
(cd ../packages/unraid-api-plugin-connect && pnpm test) > connect-test.log 2>&1 &
(cd ../packages/unraid-api-plugin-connect && pnpm test --coverage 2>/dev/null || pnpm test) > connect-test.log 2>&1 &
CONNECT_PID=$!
echo "🚀 Starting Shared package tests..."
(cd ../packages/unraid-shared && pnpm test) > shared-test.log 2>&1 &
(cd ../packages/unraid-shared && pnpm test --coverage 2>/dev/null || pnpm test) > shared-test.log 2>&1 &
SHARED_PID=$!
echo "🚀 Starting Web package coverage tests..."
(cd ../web && (pnpm test --coverage || pnpm test)) > web-test.log 2>&1 &
WEB_PID=$!
echo "🚀 Starting UI package coverage tests..."
(cd ../unraid-ui && pnpm test --coverage 2>/dev/null || pnpm test) > ui-test.log 2>&1 &
UI_PID=$!
# Wait for all processes and capture exit codes
wait $API_PID && echo "✅ API tests completed" || { echo "❌ API tests failed"; API_EXIT=1; }
wait $CONNECT_PID && echo "✅ Connect tests completed" || { echo "❌ Connect tests failed"; CONNECT_EXIT=1; }
wait $SHARED_PID && echo "✅ Shared tests completed" || { echo "❌ Shared tests failed"; SHARED_EXIT=1; }
wait $WEB_PID && echo "✅ Web tests completed" || { echo "❌ Web tests failed"; WEB_EXIT=1; }
wait $UI_PID && echo "✅ UI tests completed" || { echo "❌ UI tests failed"; UI_EXIT=1; }
# Display all outputs
echo "📋 API Test Results:" && cat api-test.log
echo "📋 Connect Plugin Test Results:" && cat connect-test.log
echo "📋 Shared Package Test Results:" && cat shared-test.log
echo "📋 Web Package Test Results:" && cat web-test.log
echo "📋 UI Package Test Results:" && cat ui-test.log
# Exit with error if any test failed
if [[ ${API_EXIT:-0} -eq 1 || ${CONNECT_EXIT:-0} -eq 1 || ${SHARED_EXIT:-0} -eq 1 ]]; then
if [[ ${API_EXIT:-0} -eq 1 || ${CONNECT_EXIT:-0} -eq 1 || ${SHARED_EXIT:-0} -eq 1 || ${WEB_EXIT:-0} -eq 1 || ${UI_EXIT:-0} -eq 1 ]]; then
exit 1
fi
- name: Upload all coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/coverage-final.json,../web/coverage/coverage-final.json,../unraid-ui/coverage/coverage-final.json,../packages/unraid-api-plugin-connect/coverage/coverage-final.json,../packages/unraid-shared/coverage/coverage-final.json
fail_ci_if_error: false
build-api:
name: Build API
runs-on: ubuntu-latest
@@ -163,7 +183,7 @@ jobs:
working-directory: api
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4
@@ -190,7 +210,7 @@ jobs:
${{ runner.os }}-pnpm-store-
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: bash procps python3 libvirt-dev jq zstd git build-essential
version: 1.0
@@ -240,7 +260,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Node
uses: actions/setup-node@v4
@@ -267,7 +287,7 @@ jobs:
${{ runner.os }}-pnpm-store-
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: bash procps python3 libvirt-dev jq zstd git build-essential
version: 1.0
@@ -298,7 +318,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Create env file
run: |

View File

@@ -30,7 +30,7 @@ jobs:
prerelease: false
- uses: actions/setup-node@v4
with:
node-version: '22.17.0'
node-version: '22.18.0'
- run: |
cat << 'EOF' > release-notes.txt
${{ steps.release-info.outputs.body }}

View File

@@ -22,16 +22,16 @@ jobs:
working-directory: ./libvirt
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: "3.13.5"
python-version: "3.13.7"
- name: Cache APT Packages
uses: awalsh128/cache-apt-pkgs-action@v1.5.1
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
with:
packages: libvirt-dev
version: 1.0
@@ -44,7 +44,7 @@ jobs:
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
version: 10.15.0
run_install: false
- name: Get pnpm store directory

9
.gitignore vendored
View File

@@ -76,6 +76,9 @@ typescript
# Github actions
RELEASE_NOTES.md
# Test backups
api/dev/configs/api.json.backup
# Docker Deploy Folder
deploy/*
!deploy/.gitkeep
@@ -109,3 +112,9 @@ plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/dat
# Config file that changes between versions
api/dev/Unraid.net/myservers.cfg
# Claude local settings
.claude/settings.local.json
# local Mise settings
.mise.toml

2
.nvmrc
View File

@@ -1 +1 @@
22.17.0
22.18.0

View File

@@ -1 +1 @@
{".":"4.10.0"}
{".":"4.18.2"}

14
.vscode/settings.json vendored
View File

@@ -1,14 +0,0 @@
{
"files.associations": {
"*.page": "php"
},
"editor.codeActionsOnSave": {
"source.fixAll": "never",
"source.fixAll.eslint": "explicit"
},
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.keystyle": "flat",
"eslint.experimental.useFlatConfig": true,
"typescript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifier": "non-relative"
}

View File

@@ -1,22 +0,0 @@
{
"_comment": "rename this file to .vscode/sftp.json and replace name/host/privateKeyPath for your system",
"name": "Tower",
"host": "Tower.local",
"protocol": "sftp",
"port": 22,
"username": "root",
"privateKeyPath": "C:/Users/username/.ssh/tower",
"remotePath": "/",
"context": "plugin/source/dynamix.unraid.net/",
"uploadOnSave": true,
"useTempFile": false,
"openSsh": false,
"ignore": [
"// comment: ignore dot files/dirs in root of repo",
".github",
".vscode",
".git",
".DS_Store"
]
}

View File

@@ -0,0 +1,81 @@
@custom-variant dark (&:where(.dark, .dark *));
@layer utilities {
:host {
--tw-divide-y-reverse: 0;
--tw-border-style: solid;
--tw-font-weight: initial;
--tw-tracking: initial;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
--tw-rotate-x: rotateX(0);
--tw-rotate-y: rotateY(0);
--tw-rotate-z: rotateZ(0);
--tw-skew-x: skewX(0);
--tw-skew-y: skewY(0);
--tw-space-x-reverse: 0;
--tw-gradient-position: initial;
--tw-gradient-from: #0000;
--tw-gradient-via: #0000;
--tw-gradient-to: #0000;
--tw-gradient-stops: initial;
--tw-gradient-via-stops: initial;
--tw-gradient-from-position: 0%;
--tw-gradient-via-position: 50%;
--tw-gradient-to-position: 100%;
--tw-shadow: 0 0 #0000;
--tw-shadow-color: initial;
--tw-inset-shadow: 0 0 #0000;
--tw-inset-shadow-color: initial;
--tw-ring-color: initial;
--tw-ring-shadow: 0 0 #0000;
--tw-inset-ring-color: initial;
--tw-inset-ring-shadow: 0 0 #0000;
--tw-ring-inset: initial;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-offset-shadow: 0 0 #0000;
--tw-blur: initial;
--tw-brightness: initial;
--tw-contrast: initial;
--tw-grayscale: initial;
--tw-hue-rotate: initial;
--tw-invert: initial;
--tw-opacity: initial;
--tw-saturate: initial;
--tw-sepia: initial;
--tw-drop-shadow: initial;
--tw-duration: initial;
--tw-ease: initial;
}
}
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: hsl(var(--border));
}
body {
--color-alpha: #1c1b1b;
--color-beta: #f2f2f2;
--color-gamma: #999999;
--color-gamma-opaque: rgba(153, 153, 153, 0.5);
--color-customgradient-start: rgba(242, 242, 242, 0);
--color-customgradient-end: rgba(242, 242, 242, 0.85);
--shadow-beta: 0 25px 50px -12px rgba(242, 242, 242, 0.15);
--ring-offset-shadow: 0 0 var(--color-beta);
--ring-shadow: 0 0 var(--color-beta);
}
button:not(:disabled),
[role='button']:not(:disabled) {
cursor: pointer;
}
}

View File

@@ -0,0 +1,130 @@
/* Hybrid theme system: Native CSS + Theme Store fallback */
@layer base {
/* Light mode defaults */
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
/* Dark mode */
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
/* Alternative class-based dark mode support for specific Unraid themes */
.dark[data-theme='black'],
.dark[data-theme='gray'] {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--border: 0 0% 14.9%;
}
/* For web components: inherit CSS variables from the host */
:host {
--background: inherit;
--foreground: inherit;
--muted: inherit;
--muted-foreground: inherit;
--popover: inherit;
--popover-foreground: inherit;
--card: inherit;
--card-foreground: inherit;
--border: inherit;
--input: inherit;
--primary: inherit;
--primary-foreground: inherit;
--secondary: inherit;
--secondary-foreground: inherit;
--accent: inherit;
--accent-foreground: inherit;
--destructive: inherit;
--destructive-foreground: inherit;
--ring: inherit;
--chart-1: inherit;
--chart-2: inherit;
--chart-3: inherit;
--chart-4: inherit;
--chart-5: inherit;
}
/* Class-based dark mode support for web components using :host-context */
:host-context(.dark) {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
/* Alternative class-based dark mode support for specific Unraid themes */
:host-context(.dark[data-theme='black']),
:host-context(.dark[data-theme='gray']) {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--border: 0 0% 14.9%;
}
}

View File

@@ -0,0 +1,5 @@
/* Tailwind Shared Styles - Single entry point for all shared CSS */
@import './css-variables.css';
@import './unraid-theme.css';
@import './base-utilities.css';
@import './sonner.css';

View File

@@ -233,8 +233,8 @@
justify-content: center;
align-items: center;
padding: 0;
color: var(--gray12);
border: 1px solid var(--gray4);
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
transform: var(--toast-close-button-transform);
border-radius: 50%;
cursor: pointer;
@@ -243,7 +243,7 @@
}
[data-sonner-toast] [data-close-button] {
background: var(--gray1);
background: hsl(var(--background));
}
:where([data-sonner-toast]) :where([data-close-button]):focus-visible {
@@ -255,8 +255,8 @@
}
[data-sonner-toast]:hover [data-close-button]:hover {
background: var(--gray2);
border-color: var(--gray5);
background: hsl(var(--muted));
border-color: hsl(var(--border));
}
/* Leave a ghost div to avoid setting hover to false when swiping out */
@@ -414,10 +414,27 @@
}
[data-sonner-toaster][data-theme='light'] {
--normal-bg: #fff;
--normal-border: var(--gray4);
--normal-text: var(--gray12);
--normal-bg: hsl(var(--background));
--normal-border: hsl(var(--border));
--normal-text: hsl(var(--foreground));
--success-bg: hsl(var(--background));
--success-border: hsl(var(--border));
--success-text: hsl(140, 100%, 27%);
--info-bg: hsl(var(--background));
--info-border: hsl(var(--border));
--info-text: hsl(210, 92%, 45%);
--warning-bg: hsl(var(--background));
--warning-border: hsl(var(--border));
--warning-text: hsl(31, 92%, 45%);
--error-bg: hsl(var(--background));
--error-border: hsl(var(--border));
--error-text: hsl(360, 100%, 45%);
/* Old colors, preserved for reference
--success-bg: hsl(143, 85%, 96%);
--success-border: hsl(145, 92%, 91%);
--success-text: hsl(140, 100%, 27%);
@@ -432,26 +449,43 @@
--error-bg: hsl(359, 100%, 97%);
--error-border: hsl(359, 100%, 94%);
--error-text: hsl(360, 100%, 45%);
--error-text: hsl(360, 100%, 45%); */
}
[data-sonner-toaster][data-theme='light'] [data-sonner-toast][data-invert='true'] {
--normal-bg: #000;
--normal-border: hsl(0, 0%, 20%);
--normal-text: var(--gray1);
--normal-bg: hsl(0 0% 3.9%);
--normal-border: hsl(0 0% 14.9%);
--normal-text: hsl(0 0% 98%);
}
[data-sonner-toaster][data-theme='dark'] [data-sonner-toast][data-invert='true'] {
--normal-bg: #fff;
--normal-border: var(--gray3);
--normal-text: var(--gray12);
--normal-bg: hsl(0 0% 100%);
--normal-border: hsl(0 0% 89.8%);
--normal-text: hsl(0 0% 3.9%);
}
[data-sonner-toaster][data-theme='dark'] {
--normal-bg: #000;
--normal-border: hsl(0, 0%, 20%);
--normal-text: var(--gray1);
--normal-bg: hsl(var(--background));
--normal-border: hsl(var(--border));
--normal-text: hsl(var(--foreground));
--success-bg: hsl(var(--background));
--success-border: hsl(var(--border));
--success-text: hsl(150, 86%, 65%);
--info-bg: hsl(var(--background));
--info-border: hsl(var(--border));
--info-text: hsl(216, 87%, 65%);
--warning-bg: hsl(var(--background));
--warning-border: hsl(var(--border));
--warning-text: hsl(46, 87%, 65%);
--error-bg: hsl(var(--background));
--error-border: hsl(var(--border));
--error-text: hsl(358, 100%, 81%);
/* Old colors, preserved for reference
--success-bg: hsl(150, 100%, 6%);
--success-border: hsl(147, 100%, 12%);
--success-text: hsl(150, 86%, 65%);
@@ -466,7 +500,7 @@
--error-bg: hsl(358, 76%, 10%);
--error-border: hsl(357, 89%, 16%);
--error-text: hsl(358, 100%, 81%);
--error-text: hsl(358, 100%, 81%); */
}
[data-rich-colors='true'][data-sonner-toast][data-type='success'] {
@@ -541,7 +575,7 @@
.sonner-loading-bar {
animation: sonner-spin 1.2s linear infinite;
background: var(--gray11);
background: hsl(var(--muted-foreground));
border-radius: 6px;
height: 8%;
left: -10%;
@@ -662,4 +696,4 @@
.sonner-loader[data-visible='false'] {
opacity: 0;
transform: scale(0.8) translate(-50%, -50%);
}
}

View File

@@ -1,45 +1,8 @@
@import 'tailwindcss';
@import 'tw-animate-css';
/* Force generation of all color utilities for custom colors across packages */
@source inline("{bg,text,border,ring,fill,stroke}-{unraid-green,unraid-red}{,-50,-100,-200,-300,-400,-500,-600,-700,-800,-900,-950}");
@source inline("{bg,text,border,ring,fill,stroke}-{yellow-accent,orange-dark,orange}");
@source inline("{from,via,to}-{unraid-red,orange}");
@source inline("bg-linear-to-{r,l,t,b,tr,tl,br,bl}");
@source "../**/*.vue";
@source "../stories/**/*.ts";
@custom-variant dark (&:where(.dark, .dark *));
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-border);
}
/* Fix for Tailwind v4 border styles not working */
* {
--tw-border-style: solid;
}
}
@theme static {
/* Breakpoints */
--breakpoint-xs: 30rem;
--breakpoint-2xl: 100rem;
--breakpoint-3xl: 120rem;
/* Container settings */
--container-center: true;
--container-padding: 2rem;
@@ -55,6 +18,11 @@
/* Border color default */
--default-border-color: var(--color-border);
--ui-border-muted: hsl(var(--border));
--ui-radius: 0.5rem;
--ui-primary: var(--color-primary-500);
--ui-primary-hover: var(--color-primary-600);
--ui-primary-active: var(--color-primary-700);
/* Color palette */
--color-inherit: inherit;
@@ -103,11 +71,37 @@
--color-unraid-green-900: #284126;
--color-unraid-green-950: #122211;
/* Primary colors (orange) */
--color-primary-50: #fff7ed;
--color-primary-100: #ffedd5;
--color-primary-200: #fed7aa;
--color-primary-300: #fdba74;
--color-primary-400: #fb923c;
--color-primary-500: #ff6600;
--color-primary-600: #ea580c;
--color-primary-700: #c2410c;
--color-primary-800: #9a3412;
--color-primary-900: #7c2d12;
--color-primary-950: #431407;
/* Header colors */
--color-header-text-primary: var(--header-text-primary);
--color-header-text-secondary: var(--header-text-secondary);
--color-header-background-color: var(--header-background-color);
/* Legacy colors */
--color-alpha: var(--color-alpha);
--color-beta: var(--color-beta);
--color-gamma: var(--color-gamma);
--color-gamma-opaque: var(--color-gamma-opaque);
--color-customgradient-start: var(--color-customgradient-start);
--color-customgradient-end: var(--color-customgradient-end);
/* Gradients */
--color-header-gradient-start: var(--header-gradient-start);
--color-header-gradient-end: var(--header-gradient-end);
--color-banner-gradient: var(--banner-gradient);
/* Font sizes */
--font-10px: 10px;
--font-12px: 12px;
@@ -199,109 +193,6 @@
--spacing: 0.4rem; /* 4px at 10px base */
}
/* Hybrid theme system: Native CSS + Theme Store fallback */
@layer base {
/* Light mode defaults */
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
/* For web components: inherit CSS variables from the host */
:host {
--background: inherit;
--foreground: inherit;
--muted: inherit;
--muted-foreground: inherit;
--popover: inherit;
--popover-foreground: inherit;
--card: inherit;
--card-foreground: inherit;
--border: inherit;
--input: inherit;
--primary: inherit;
--primary-foreground: inherit;
--secondary: inherit;
--secondary-foreground: inherit;
--accent: inherit;
--accent-foreground: inherit;
--destructive: inherit;
--destructive-foreground: inherit;
--ring: inherit;
--chart-1: inherit;
--chart-2: inherit;
--chart-3: inherit;
--chart-4: inherit;
--chart-5: inherit;
}
/* Class-based dark mode support for web components using :host-context */
:host-context(.dark) {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
/* Alternative class-based dark mode support for specific Unraid themes */
:host-context(.dark[data-theme='black']),
:host-context(.dark[data-theme='gray']) {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--border: 0 0% 14.9%;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
/* Keyframes */
@keyframes mark-2 {
50% {
@@ -339,8 +230,7 @@
}
}
/* Define the theme colors that reference the existing CSS variables from web/themes */
/* Theme colors that reference CSS variables */
@theme inline {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
@@ -366,4 +256,4 @@
--color-chart-3: hsl(var(--chart-3, 197 37% 24%));
--color-chart-4: hsl(var(--chart-4, 43 74% 66%));
--color-chart-5: hsl(var(--chart-5, 27 87% 67%));
}
}

View File

@@ -46,6 +46,16 @@ cd api && pnpm codegen # Generate GraphQL types
pnpm unraid:deploy <SERVER_IP> # Deploy all to Unraid server
```
### Developer Tools
```bash
unraid-api developer # Interactive prompt for tools
unraid-api developer --sandbox true # Enable GraphQL sandbox
unraid-api developer --sandbox false # Disable GraphQL sandbox
unraid-api developer --enable-modal # Enable modal testing tool
unraid-api developer --disable-modal # Disable modal testing tool
```
## Architecture Notes
### API Structure (NestJS)
@@ -110,6 +120,13 @@ Enables GraphQL playground at `http://tower.local/graphql`
### Testing Guidelines
#### General Testing Best Practices
- **Error Testing:** Use `.rejects.toThrow()` without arguments to test that functions throw errors. Don't test exact error message strings unless the message format is specifically what you're testing
- **Focus on Behavior:** Test what the code does, not implementation details like exact error message wording
- **Avoid Brittleness:** Don't write tests that break when minor changes are made to error messages, log formats, or other non-essential details
- **Use Mocks Correctly**: Mocks should be used as nouns, not verbs.
#### Vue Component Testing
- This is a Nuxt.js app but we are testing with vitest outside of the Nuxt environment
@@ -139,4 +156,8 @@ Enables GraphQL playground at `http://tower.local/graphql`
## Development Memories
- We are using tailwind v4 we do not need a tailwind config anymore
- always search the internet for tailwind v4 documentation when making tailwind related style changes
- always search the internet for tailwind v4 documentation when making tailwind related style changes
- never run or restart the API server or web server. I will handle the lifecycle, simply wait and ask me to do this for you
- Never use the `any` type. Always prefer proper typing
- Avoid using casting whenever possible, prefer proper typing from the start
- **IMPORTANT:** cache-manager v7 expects TTL values in **milliseconds**, not seconds. Always use milliseconds when setting cache TTL (e.g., 600000 for 10 minutes, not 600)

10
api/.depcheckrc Normal file
View File

@@ -0,0 +1,10 @@
{
"parsers": {
"**/*.ts": [
"@depcheck/parser-typescript",
{
"project": "tsconfig.json"
}
]
}
}

View File

@@ -15,6 +15,10 @@ PATHS_ACTIVATION_BASE=./dev/activation
PATHS_PASSWD=./dev/passwd
PATHS_RCLONE_SOCKET=./dev/rclone-socket
PATHS_LOG_BASE=./dev/log # Where we store logs
PATHS_LOGS_FILE=./dev/log/graphql-api.log
PATHS_CONNECT_STATUS_FILE_PATH=./dev/connectStatus.json # Connect plugin status file
PATHS_OIDC_JSON=./dev/configs/oidc.local.json
PATHS_LOCAL_SESSION_FILE=./dev/local-session
ENVIRONMENT="development"
NODE_ENV="development"
PORT="3001"

View File

@@ -13,5 +13,7 @@ PATHS_PARITY_CHECKS=./dev/states/parity-checks.log
PATHS_CONFIG_MODULES=./dev/configs
PATHS_ACTIVATION_BASE=./dev/activation
PATHS_PASSWD=./dev/passwd
PATHS_LOGS_FILE=./dev/log/graphql-api.log
PATHS_LOCAL_SESSION_FILE=./dev/local-session
PORT=5000
NODE_ENV="test"

View File

@@ -4,54 +4,59 @@ import noRelativeImportPaths from 'eslint-plugin-no-relative-import-paths';
import prettier from 'eslint-plugin-prettier';
import tseslint from 'typescript-eslint';
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended, {
plugins: {
'no-relative-import-paths': noRelativeImportPaths,
prettier: prettier,
import: importPlugin,
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ['src/graphql/generated/client/**/*', 'src/**/**/dummy-process.js'],
},
rules: {
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'no-use-before-define': ['off'],
'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 1 }],
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'import/no-unresolved': 'off',
'import/no-absolute-path': 'off',
'import/prefer-default-export': 'off',
'no-relative-import-paths/no-relative-import-paths': [
'error',
{ allowSameFolder: false, rootDir: 'src', prefix: '@app' },
],
'prettier/prettier': 'error',
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always',
ts: 'always',
},
],
'no-restricted-globals': [
'error',
{
name: '__dirname',
message: 'Use import.meta.url instead of __dirname in ESM',
},
{
name: '__filename',
message: 'Use import.meta.url instead of __filename in ESM',
},
],
'eol-last': ['error', 'always'],
},
ignores: ['src/graphql/generated/client/**/*'],
});
{
plugins: {
'no-relative-import-paths': noRelativeImportPaths,
prettier: prettier,
import: importPlugin,
},
rules: {
'@typescript-eslint/no-redundant-type-constituents': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-object-type': 'off',
'no-use-before-define': ['off'],
'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 1 }],
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
'import/no-unresolved': 'off',
'import/no-absolute-path': 'off',
'import/prefer-default-export': 'off',
'no-relative-import-paths/no-relative-import-paths': [
'error',
{ allowSameFolder: false, rootDir: 'src', prefix: '@app' },
],
'prettier/prettier': 'error',
'import/extensions': [
'error',
'ignorePackages',
{
js: 'always',
ts: 'always',
},
],
'no-restricted-globals': [
'error',
{
name: '__dirname',
message: 'Use import.meta.url instead of __dirname in ESM',
},
{
name: '__filename',
message: 'Use import.meta.url instead of __filename in ESM',
},
],
'eol-last': ['error', 'always'],
},
}
);

11
api/.gitignore vendored
View File

@@ -82,3 +82,14 @@ deploy/*
.idea
!**/*.login.*
# local api configs - don't need project-wide tracking
dev/connectStatus.json
dev/configs/*
# local status - doesn't need to be tracked
dev/connectStatus.json
# mock local session file
dev/local-session
# local OIDC config for testing - contains secrets
dev/configs/oidc.local.json

View File

@@ -1,9 +0,0 @@
{
"eslint.lintTask.options": "--flag unstable_ts_config",
"eslint.options": {
"flags": ["unstable_ts_config"],
"overrideConfigFile": ".eslintrc.ts"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"javascript.preferences.importModuleSpecifier": "non-relative"
}

View File

@@ -1,5 +1,167 @@
# Changelog
## [4.18.2](https://github.com/unraid/api/compare/v4.18.1...v4.18.2) (2025-09-03)
### Bug Fixes
* add missing CPU guest metrics to CPU responses ([#1644](https://github.com/unraid/api/issues/1644)) ([99dbad5](https://github.com/unraid/api/commit/99dbad57d55a256f5f3f850f9a47a6eaa6348065))
* **plugin:** raise minimum unraid os version to 6.12.15 ([#1649](https://github.com/unraid/api/issues/1649)) ([bc15bd3](https://github.com/unraid/api/commit/bc15bd3d7008acb416ac3c6fb1f4724c685ec7e7))
* update GitHub Actions token for workflow trigger ([4d8588b](https://github.com/unraid/api/commit/4d8588b17331afa45ba8caf84fcec8c0ea03591f))
* update OIDC URL validation and add tests ([#1646](https://github.com/unraid/api/issues/1646)) ([c7c3bb5](https://github.com/unraid/api/commit/c7c3bb57ea482633a7acff064b39fbc8d4e07213))
* use shared bg & border color for styled toasts ([#1647](https://github.com/unraid/api/issues/1647)) ([7c3aee8](https://github.com/unraid/api/commit/7c3aee8f3f9ba82ae8c8ed3840c20ab47f3cb00f))
## [4.18.1](https://github.com/unraid/api/compare/v4.18.0...v4.18.1) (2025-09-03)
### Bug Fixes
* OIDC and API Key management issues ([#1642](https://github.com/unraid/api/issues/1642)) ([0fe2c2c](https://github.com/unraid/api/commit/0fe2c2c1c85dcc547e4b1217a3b5636d7dd6d4b4))
* rm redundant emission to `$HOME/.pm2/logs` ([#1640](https://github.com/unraid/api/issues/1640)) ([a8e4119](https://github.com/unraid/api/commit/a8e4119270868a1dabccd405853a7340f8dcd8a5))
## [4.18.0](https://github.com/unraid/api/compare/v4.17.0...v4.18.0) (2025-09-02)
### Features
* **api:** enhance OIDC redirect URI handling in service and tests ([#1618](https://github.com/unraid/api/issues/1618)) ([4e945f5](https://github.com/unraid/api/commit/4e945f5f56ce059eb275a9576caf3194a5df8a90))
### Bug Fixes
* api key creation cli ([#1637](https://github.com/unraid/api/issues/1637)) ([c147a6b](https://github.com/unraid/api/commit/c147a6b5075969e77798210c4a5cfd1fa5b96ae3))
* **cli:** support `--log-level` for `start` and `restart` cmds ([#1623](https://github.com/unraid/api/issues/1623)) ([a1ee915](https://github.com/unraid/api/commit/a1ee915ca52e5a063eccf8facbada911a63f37f6))
* confusing server -&gt; status query ([#1635](https://github.com/unraid/api/issues/1635)) ([9d42b36](https://github.com/unraid/api/commit/9d42b36f74274cad72490da5152fdb98fdc5b89b))
* use unraid css variables in sonner ([#1634](https://github.com/unraid/api/issues/1634)) ([26a95af](https://github.com/unraid/api/commit/26a95af9539d05a837112d62dc6b7dd46761c83f))
## [4.17.0](https://github.com/unraid/api/compare/v4.16.0...v4.17.0) (2025-08-27)
### Features
* add tailwind class sort plugin ([#1562](https://github.com/unraid/api/issues/1562)) ([ab11e7f](https://github.com/unraid/api/commit/ab11e7ff7ff74da1f1cd5e49938459d00bfc846b))
### Bug Fixes
* cleanup obsoleted legacy api keys on api startup (cli / connect) ([#1630](https://github.com/unraid/api/issues/1630)) ([6469d00](https://github.com/unraid/api/commit/6469d002b7b18e49c77ee650a4255974ab43e790))
## [4.16.0](https://github.com/unraid/api/compare/v4.15.1...v4.16.0) (2025-08-27)
### Features
* add `parityCheckStatus` field to `array` query ([#1611](https://github.com/unraid/api/issues/1611)) ([c508366](https://github.com/unraid/api/commit/c508366702b9fa20d9ed05559fe73da282116aa6))
* generated UI API key management + OAuth-like API Key Flows ([#1609](https://github.com/unraid/api/issues/1609)) ([674323f](https://github.com/unraid/api/commit/674323fd87bbcc55932e6b28f6433a2de79b7ab0))
### Bug Fixes
* **connect:** clear `wanport` upon disabling remote access ([#1624](https://github.com/unraid/api/issues/1624)) ([9df6a3f](https://github.com/unraid/api/commit/9df6a3f5ebb0319aa7e3fe3be6159d39ec6f587f))
* **connect:** valid LAN FQDN while remote access is enabled ([#1625](https://github.com/unraid/api/issues/1625)) ([aa58888](https://github.com/unraid/api/commit/aa588883cc2e2fe4aa4aea1d035236c888638f5b))
* correctly parse periods in share names from ini file ([#1629](https://github.com/unraid/api/issues/1629)) ([7d67a40](https://github.com/unraid/api/commit/7d67a404333a38d6e1ba5c3febf02be8b1b71901))
* **rc.unraid-api:** remove profile sourcing ([#1622](https://github.com/unraid/api/issues/1622)) ([6947b5d](https://github.com/unraid/api/commit/6947b5d4aff70319116eb65cf4c639444f3749e9))
* remove unused api key calls ([#1628](https://github.com/unraid/api/issues/1628)) ([9cd0d6a](https://github.com/unraid/api/commit/9cd0d6ac658475efa25683ef6e3f2e1d68f7e903))
* retry VMs init for up to 2 min ([#1612](https://github.com/unraid/api/issues/1612)) ([b2e7801](https://github.com/unraid/api/commit/b2e78012384e6b3f2630341281fc811026be23b9))
## [4.15.1](https://github.com/unraid/api/compare/v4.15.0...v4.15.1) (2025-08-20)
### Bug Fixes
* minor duplicate click handler and version resolver nullability issue ([ac198d5](https://github.com/unraid/api/commit/ac198d5d1a3073fdeb053c2ff8f704b0dba0d047))
## [4.15.0](https://github.com/unraid/api/compare/v4.14.0...v4.15.0) (2025-08-20)
### Features
* **api:** restructure versioning information in GraphQL schema ([#1600](https://github.com/unraid/api/issues/1600)) ([d0c6602](https://github.com/unraid/api/commit/d0c66020e1d1d5b6fcbc4ee8979bba4b3d34c7ad))
## [4.14.0](https://github.com/unraid/api/compare/v4.13.1...v4.14.0) (2025-08-19)
### Features
* **api:** add cpu utilization query and subscription ([#1590](https://github.com/unraid/api/issues/1590)) ([2b4c2a2](https://github.com/unraid/api/commit/2b4c2a264bb2769f88c3000d16447889cae57e98))
* enhance OIDC claim evaluation with array handling ([#1596](https://github.com/unraid/api/issues/1596)) ([b7798b8](https://github.com/unraid/api/commit/b7798b82f44aae9a428261270fd9dbde35ff7751))
### Bug Fixes
* remove unraid-api sso users & always apply sso modification on &lt; 7.2 ([#1595](https://github.com/unraid/api/issues/1595)) ([4262830](https://github.com/unraid/api/commit/426283011afd41e3af7e48cfbb2a2d351c014bd1))
* update Docusaurus PR workflow to process and copy API docs ([3a10871](https://github.com/unraid/api/commit/3a10871918fe392a1974b69d16a135546166e058))
* update OIDC provider setup documentation for navigation clarity ([1a01696](https://github.com/unraid/api/commit/1a01696dc7b947abf5f2f097de1b231d5593c2ff))
* update OIDC provider setup documentation for redirect URI and screenshots ([1bc5251](https://github.com/unraid/api/commit/1bc52513109436b3ce8237c3796af765e208f9fc))
## [4.13.1](https://github.com/unraid/api/compare/v4.13.0...v4.13.1) (2025-08-15)
### Bug Fixes
* insecure routes not working for SSO ([#1587](https://github.com/unraid/api/issues/1587)) ([a4ff3c4](https://github.com/unraid/api/commit/a4ff3c40926915f6989ed4af679b30cf295ea15d))
## [4.13.0](https://github.com/unraid/api/compare/v4.12.0...v4.13.0) (2025-08-15)
### Features
* `createDockerFolder` & `setDockerFolderChildren` mutations ([#1558](https://github.com/unraid/api/issues/1558)) ([557b03f](https://github.com/unraid/api/commit/557b03f8829d3f179b5e26162fa250121cb33420))
* `deleteDockerEntries` mutation ([#1564](https://github.com/unraid/api/issues/1564)) ([78997a0](https://github.com/unraid/api/commit/78997a02c6d96ec0ed75352dfc9849524147428c))
* add `moveDockerEntriesToFolder` mutation ([#1569](https://github.com/unraid/api/issues/1569)) ([20c2d5b](https://github.com/unraid/api/commit/20c2d5b4457ad50d1e287fb3141aa98e8e7de665))
* add docker -&gt; organizer query ([#1555](https://github.com/unraid/api/issues/1555)) ([dfe352d](https://github.com/unraid/api/commit/dfe352dfa1bd6aa059cab56357ba6bff5e8ed7cb))
* connect settings page updated for responsive webgui ([#1585](https://github.com/unraid/api/issues/1585)) ([96c120f](https://github.com/unraid/api/commit/96c120f9b24d3c91df5e9401917c8994eef36c46))
* implement OIDC provider management in GraphQL API ([#1563](https://github.com/unraid/api/issues/1563)) ([979a267](https://github.com/unraid/api/commit/979a267bc5e128a8b789f0123e23c61860ebb11b))
### Bug Fixes
* change config file loading error log to debug ([#1565](https://github.com/unraid/api/issues/1565)) ([3534d6f](https://github.com/unraid/api/commit/3534d6fdd7c59e65615167cfe306deebad9ca4d3))
* **connect:** remove unraid-api folder before creating symlink ([#1556](https://github.com/unraid/api/issues/1556)) ([514a0ef](https://github.com/unraid/api/commit/514a0ef560a90595f774b6c0db60f1d2b4cd853c))
* **deps:** pin dependencies ([#1586](https://github.com/unraid/api/issues/1586)) ([5721785](https://github.com/unraid/api/commit/57217852a337ead4c8c8e7596d1b7d590b64a26f))
* **deps:** update all non-major dependencies ([#1543](https://github.com/unraid/api/issues/1543)) ([18b5209](https://github.com/unraid/api/commit/18b52090874c0ba86878d0f7e31bf0dc42734d75))
* **deps:** update all non-major dependencies ([#1579](https://github.com/unraid/api/issues/1579)) ([ad6aa3b](https://github.com/unraid/api/commit/ad6aa3b6743aeeb42eff34d1c89ad874dfd0af09))
* refactor API client to support Unix socket connections ([#1575](https://github.com/unraid/api/issues/1575)) ([a2c5d24](https://github.com/unraid/api/commit/a2c5d2495ffc02efa1ec5c63f0a1c5d23c9ed7ff))
* **theme:** API key white text on white background ([#1584](https://github.com/unraid/api/issues/1584)) ([b321687](https://github.com/unraid/api/commit/b3216874faae208cdfc3edec719629fce428b6a3))
## [4.12.0](https://github.com/unraid/api/compare/v4.11.0...v4.12.0) (2025-07-30)
### Features
* add ups monitoring to graphql api ([#1526](https://github.com/unraid/api/issues/1526)) ([6ea94f0](https://github.com/unraid/api/commit/6ea94f061d5b2e6c6fbfa6949006960501e3f4e7))
### Bug Fixes
* enhance plugin management with interactive removal prompts ([#1549](https://github.com/unraid/api/issues/1549)) ([23ef760](https://github.com/unraid/api/commit/23ef760d763c525a38108048200fa73fc8531aed))
* remove connect api plugin upon removal of Connect Unraid plugin ([#1548](https://github.com/unraid/api/issues/1548)) ([782d5eb](https://github.com/unraid/api/commit/782d5ebadc67854298f3b2355255983024d2a225))
* SSO not being detected ([#1546](https://github.com/unraid/api/issues/1546)) ([6b3b951](https://github.com/unraid/api/commit/6b3b951d8288cd31d096252be544537dc2bfce50))
## [4.11.0](https://github.com/unraid/api/compare/v4.10.0...v4.11.0) (2025-07-28)
### Features
* tailwind v4 ([#1522](https://github.com/unraid/api/issues/1522)) ([2c62e0a](https://github.com/unraid/api/commit/2c62e0ad09c56d2293b76d07833dfb142c898937))
* **web:** install and configure nuxt ui ([#1524](https://github.com/unraid/api/issues/1524)) ([407585c](https://github.com/unraid/api/commit/407585cd40c409175d8e7b861f8d61d8cabc11c9))
### Bug Fixes
* add missing breakpoints ([#1535](https://github.com/unraid/api/issues/1535)) ([f5352e3](https://github.com/unraid/api/commit/f5352e3a26a2766e85d19ffb5f74960c536b91b3))
* border color incorrect in tailwind ([#1544](https://github.com/unraid/api/issues/1544)) ([f14b74a](https://github.com/unraid/api/commit/f14b74af91783b08640c0949c51ba7f18508f06f))
* **connect:** omit extraneous fields during connect config validation ([#1538](https://github.com/unraid/api/issues/1538)) ([45bd736](https://github.com/unraid/api/commit/45bd73698b2bd534a8aff2c6ac73403de6c58561))
* **deps:** pin dependencies ([#1528](https://github.com/unraid/api/issues/1528)) ([a74d935](https://github.com/unraid/api/commit/a74d935b566dd7af1a21824c9b7ab562232f9d8b))
* **deps:** pin dependency @nuxt/ui to 3.2.0 ([#1532](https://github.com/unraid/api/issues/1532)) ([8279531](https://github.com/unraid/api/commit/8279531f2b86a78e81a77e6c037a0fb752e98062))
* **deps:** update all non-major dependencies ([#1510](https://github.com/unraid/api/issues/1510)) ([1a8da6d](https://github.com/unraid/api/commit/1a8da6d92b96d3afa2a8b42446b36f1ee98b64a0))
* **deps:** update all non-major dependencies ([#1520](https://github.com/unraid/api/issues/1520)) ([e2fa648](https://github.com/unraid/api/commit/e2fa648d1cf5a6cbe3e55c3f52c203d26bb4d526))
* inject Tailwind CSS into client entry point ([#1537](https://github.com/unraid/api/issues/1537)) ([86b6c4f](https://github.com/unraid/api/commit/86b6c4f85b7b30bb4a13d57450a76bf4c28a3fff))
* make settings grid responsive ([#1463](https://github.com/unraid/api/issues/1463)) ([9dfdb8d](https://github.com/unraid/api/commit/9dfdb8dce781fa662d6434ee432e4521f905ffa5))
* **notifications:** gracefully handle & mask invalid notifications ([#1529](https://github.com/unraid/api/issues/1529)) ([05056e7](https://github.com/unraid/api/commit/05056e7ca1702eb7bf6c507950460b6b15bf7916))
* truncate log files when they take up more than 5mb of space ([#1530](https://github.com/unraid/api/issues/1530)) ([0a18b38](https://github.com/unraid/api/commit/0a18b38008dd86a125cde7f684636d5dbb36f082))
* use async for primary file read/writes ([#1531](https://github.com/unraid/api/issues/1531)) ([23b2b88](https://github.com/unraid/api/commit/23b2b8846158a27d1c9808bce0cc1506779c4dc3))
## [4.10.0](https://github.com/unraid/api/compare/v4.9.5...v4.10.0) (2025-07-15)

View File

@@ -1,7 +1,7 @@
###########################################################
# Development/Build Image
###########################################################
FROM node:22.17.0-bookworm-slim AS development
FROM node:22.18.0-bookworm-slim AS development
# Install build tools and dependencies
RUN apt-get update -y && apt-get install -y \

View File

@@ -27,19 +27,13 @@ const config: CodegenConfig = {
},
},
generates: {
// Generate Types for Mothership GraphQL Client
'src/graphql/generated/client/': {
documents: './src/graphql/mothership/*.ts',
schema: {
[process.env.MOTHERSHIP_GRAPHQL_LINK as string]: {
headers: {
origin: 'https://forums.unraid.net',
},
},
},
// Generate Types for CLI Internal GraphQL Queries
'src/unraid-api/cli/generated/': {
documents: ['src/unraid-api/cli/queries/**/*.ts', 'src/unraid-api/cli/mutations/**/*.ts'],
schema: './generated-schema.graphql',
preset: 'client',
presetConfig: {
gqlTagName: 'graphql',
gqlTagName: 'gql',
},
config: {
useTypeImports: true,
@@ -47,21 +41,6 @@ const config: CodegenConfig = {
},
plugins: [{ add: { content: '/* eslint-disable */' } }],
},
'src/graphql/generated/client/validators.ts': {
schema: {
[process.env.MOTHERSHIP_GRAPHQL_LINK as string]: {
headers: {
origin: 'https://forums.unraid.net',
},
},
},
plugins: ['typescript-validation-schema', { add: { content: '/* eslint-disable */' } }],
config: {
importFrom: '@app/graphql/generated/client/graphql.js',
strictScalars: false,
schema: 'zod',
},
},
},
};

34
api/dev/configs/README.md Normal file
View File

@@ -0,0 +1,34 @@
# Development Configuration Files
This directory contains configuration files for local development.
## OIDC Configuration
### oidc.json
The default OIDC configuration file. This file is committed to git and should only contain non-sensitive test configurations.
### Using a Local Configuration (gitignored)
For local testing with real OAuth providers:
1. Create an `oidc.local.json` file based on `oidc.json`
2. Set the environment variable: `PATHS_OIDC_JSON=./dev/configs/oidc.local.json`
3. The API will load your local configuration instead of the default
Example:
```bash
PATHS_OIDC_JSON=./dev/configs/oidc.local.json pnpm dev
```
### Setting up OAuth Apps
#### Google
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select existing
3. Enable Google+ API
4. Create OAuth 2.0 credentials
5. Add authorized redirect URI: `http://localhost:3000/graphql/api/auth/oidc/callback`
#### GitHub
1. Go to GitHub Settings > Developer settings > OAuth Apps
2. Create a new OAuth App
3. Set Authorization callback URL: `http://localhost:3000/graphql/api/auth/oidc/callback`

View File

@@ -1,9 +1,6 @@
{
"version": "4.10.0",
"extraOrigins": [
"https://google.com",
"https://test.com"
],
"version": "4.18.1",
"extraOrigins": [],
"sandbox": true,
"ssoSubIds": [],
"plugins": [

View File

@@ -2,11 +2,11 @@
"wanaccess": true,
"wanport": 8443,
"upnpEnabled": false,
"apikey": "_______________________BIG_API_KEY_HERE_________________________",
"apikey": "",
"localApiKey": "_______________________LOCAL_API_KEY_HERE_________________________",
"email": "test@example.com",
"username": "zspearmint",
"avatar": "https://via.placeholder.com/200",
"regWizTime": "1611175408732_0951-1653-3509-FBA155FA23C0",
"dynamicRemoteAccessType": "DISABLED"
"dynamicRemoteAccessType": "STATIC"
}

22
api/dev/configs/oidc.json Normal file
View File

@@ -0,0 +1,22 @@
{
"providers": [
{
"id": "unraid.net",
"name": "Unraid.net",
"clientId": "CONNECT_SERVER_SSO",
"issuer": "https://account.unraid.net",
"authorizationEndpoint": "https://account.unraid.net/sso/",
"tokenEndpoint": "https://account.unraid.net/api/oauth2/token",
"scopes": [
"openid",
"profile",
"email"
],
"authorizedSubIds": [
"297294e2-b31c-4bcc-a441-88aee0ad609f"
],
"buttonText": "Login With Unraid.net"
}
],
"defaultAllowedOrigins": []
}

View File

@@ -1,11 +0,0 @@
{
"createdAt": "2025-01-27T16:22:56.501Z",
"description": "API key for Connect user",
"id": "b5b4aa3d-8e40-4c92-bc40-d50182071886",
"key": "_______________________LOCAL_API_KEY_HERE_________________________",
"name": "Connect",
"permissions": [],
"roles": [
"CONNECT"
]
}

1
api/dev/log/.gitkeep Normal file
View File

@@ -0,0 +1 @@
# custom log directory for tests & development

View File

@@ -65,4 +65,38 @@ color="yellow-on"
size="0"
free="9091184"
used="32831348"
luksStatus="0"
["system.with.periods"]
name="system.with.periods"
nameOrig="system.with.periods"
comment="system data with periods"
allocator="highwater"
splitLevel="1"
floor="0"
include=""
exclude=""
useCache="prefer"
cachePool="cache"
cow="auto"
color="yellow-on"
size="0"
free="9091184"
used="32831348"
luksStatus="0"
["system.with.🚀"]
name="system.with.🚀"
nameOrig="system.with.🚀"
comment="system data with 🚀"
allocator="highwater"
splitLevel="1"
floor="0"
include=""
exclude=""
useCache="prefer"
cachePool="cache"
cow="auto"
color="yellow-on"
size="0"
free="9091184"
used="32831348"
luksStatus="0"

View File

@@ -0,0 +1,100 @@
# API Key Authorization Flow
This document describes the self-service API key creation flow for third-party applications.
## Overview
Applications can request API access to an Unraid server by redirecting users to a special authorization page where users can review requested permissions and create an API key with one click.
## Flow
1. **Application initiates request**: The app redirects the user to:
```
https://[unraid-server]/ApiKeyAuthorize?name=MyApp&scopes=docker:read,vm:*&redirect_uri=https://myapp.com/callback&state=abc123
```
2. **User authentication**: If not already logged in, the user is redirected to login first (standard Unraid auth)
3. **Consent screen**: User sees:
- Application name and description
- Requested permissions (with checkboxes to approve/deny specific scopes)
- API key name field (pre-filled)
- Authorize & Cancel buttons
4. **API key creation**: Upon authorization:
- API key is created with approved scopes
- Key is displayed to the user
- If `redirect_uri` is provided, user is redirected back with the key
5. **Callback**: App receives the API key:
```
https://myapp.com/callback?api_key=xxx&state=abc123
```
## Query Parameters
- `name` (required): Name of the requesting application
- `description` (optional): Description of the application
- `scopes` (required): Comma-separated list of requested scopes
- `redirect_uri` (optional): URL to redirect after authorization
- `state` (optional): Opaque value for maintaining state
## Scope Format
Scopes follow the pattern: `resource:action`
### Examples:
- `docker:read` - Read access to Docker
- `vm:*` - Full access to VMs
- `system:update` - Update access to system
- `role:viewer` - Viewer role access
- `role:admin` - Admin role access
### Available Resources:
- `docker`, `vm`, `system`, `share`, `user`, `network`, `disk`, etc.
### Available Actions:
- `create`, `read`, `update`, `delete` or `*` for all
## Security Considerations
1. **HTTPS required**: Redirect URIs must use HTTPS (except localhost for development)
2. **User consent**: Users explicitly approve each permission
3. **Session-based**: Uses existing Unraid authentication session
4. **One-time display**: API keys are shown once and must be saved securely
## Example Integration
```javascript
// JavaScript example
const unraidServer = 'tower.local';
const appName = 'My Docker Manager';
const scopes = 'docker:*,system:read';
const redirectUri = 'https://myapp.com/unraid/callback';
const state = generateRandomState();
// Store state for verification
sessionStorage.setItem('oauth_state', state);
// Redirect user to authorization page
window.location.href =
`https://${unraidServer}/ApiKeyAuthorize?` +
`name=${encodeURIComponent(appName)}&` +
`scopes=${encodeURIComponent(scopes)}&` +
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
`state=${encodeURIComponent(state)}`;
// Handle callback
const urlParams = new URLSearchParams(window.location.search);
const apiKey = urlParams.get('api_key');
const returnedState = urlParams.get('state');
if (returnedState === sessionStorage.getItem('oauth_state')) {
// Save API key securely
saveApiKey(apiKey);
}
```

View File

@@ -1,5 +1,17 @@
---
title: CLI Reference
description: Complete reference for all Unraid API CLI commands
sidebar_position: 4
---
# CLI Commands
:::info[Command Structure]
All commands follow the pattern: `unraid-api <command> [options]`
:::
## 🚀 Service Management
### Start
```bash
@@ -9,7 +21,14 @@ unraid-api start [--log-level <level>]
Starts the Unraid API service.
Options:
- `--log-level`: Set logging level (trace|debug|info|warn|error)
- `--log-level`: Set logging level (trace|debug|info|warn|error|fatal)
Alternative: You can also set the log level using the `LOG_LEVEL` environment variable:
```bash
LOG_LEVEL=trace unraid-api start
```
### Stop
@@ -24,11 +43,21 @@ Stops the Unraid API service.
### Restart
```bash
unraid-api restart
unraid-api restart [--log-level <level>]
```
Restarts the Unraid API service.
Options:
- `--log-level`: Set logging level (trace|debug|info|warn|error|fatal)
Alternative: You can also set the log level using the `LOG_LEVEL` environment variable:
```bash
LOG_LEVEL=trace unraid-api restart
```
### Logs
```bash
@@ -39,7 +68,7 @@ View the API logs.
- `-l, --lines`: Optional. Number of lines to tail (default: 100)
## Configuration Commands
## ⚙️ Configuration Commands
### Config
@@ -61,21 +90,36 @@ Switch between production and staging environments.
### Developer Mode
:::tip Web GUI Management
You can also manage developer options through the web interface at **Settings****Management Access****Developer Options**
:::
```bash
unraid-api developer
unraid-api developer # Interactive prompt for tools
unraid-api developer --sandbox true # Enable GraphQL sandbox
unraid-api developer --sandbox false # Disable GraphQL sandbox
unraid-api developer --enable-modal # Enable modal testing tool
unraid-api developer --disable-modal # Disable modal testing tool
```
Configure developer features for the API (e.g., GraphQL sandbox).
Configure developer features for the API:
- **GraphQL Sandbox**: Enable/disable Apollo GraphQL sandbox at `/graphql`
- **Modal Testing Tool**: Enable/disable UI modal testing in the Unraid menu
## API Key Management
:::tip Web GUI Management
You can also manage API keys through the web interface at **Settings****Management Access****API Keys**
:::
### API Key Commands
```bash
unraid-api apikey [options]
```
Create and manage API keys.
Create and manage API keys via CLI.
Options:
@@ -87,6 +131,10 @@ Options:
## SSO (Single Sign-On) Management
:::info OIDC Configuration
For OIDC/SSO provider configuration, see the web interface at **Settings****Management Access****API****OIDC** or refer to the [OIDC Provider Setup](./oidc-provider-setup.md) guide.
:::
### SSO Base Command
```bash

View File

@@ -1,33 +1,75 @@
---
title: Using the Unraid API
description: Learn how to interact with your Unraid server through the GraphQL API
sidebar_position: 2
---
# Using the Unraid API
:::tip[Quick Start]
The Unraid API provides a powerful GraphQL interface for managing your server. This guide covers authentication, common queries, and best practices.
:::
The Unraid API provides a GraphQL interface that allows you to interact with your Unraid server. This guide will help you get started with exploring and using the API.
## Enabling the GraphQL Sandbox
## 🎮 Enabling the GraphQL Sandbox
1. First, enable developer mode using the CLI:
### Web GUI Method (Recommended)
```bash
unraid-api developer
```
2. Follow the prompts to enable the sandbox. This will allow you to access the Apollo Sandbox interface.
:::info[Preferred Method]
Using the Web GUI is the easiest way to enable the GraphQL sandbox.
:::
1. Navigate to **Settings****Management Access****Developer Options**
2. Enable the **GraphQL Sandbox** toggle
3. Access the GraphQL playground by navigating to:
```txt
http://YOUR_SERVER_IP/graphql
```
## Authentication
### CLI Method
Most queries and mutations require authentication. You can authenticate using either:
Alternatively, you can enable developer mode using the CLI:
1. API Keys
2. Cookies (default method when signed into the WebGUI)
```bash
unraid-api developer --sandbox true
```
### Creating an API Key
Or use the interactive mode:
Use the CLI to create an API key:
```bash
unraid-api developer
```
## 🔑 Authentication
:::warning[Required for Most Operations]
Most queries and mutations require authentication. Always include appropriate credentials in your requests.
:::
You can authenticate using:
1. **API Keys** - For programmatic access
2. **Cookies** - Automatic when signed into the WebGUI
3. **SSO/OIDC** - When configured with external providers
### Managing API Keys
<tabs>
<tabItem value="gui" label="Web GUI (Recommended)" default>
Navigate to **Settings** → **Management Access** → **API Keys** in your Unraid web interface to:
- View existing API keys
- Create new API keys
- Manage permissions and roles
- Revoke or regenerate keys
</tabItem>
<tabItem value="cli" label="CLI Method">
You can also use the CLI to create an API key:
```bash
unraid-api apikey --create
@@ -40,6 +82,11 @@ Follow the prompts to set:
- Roles
- Permissions
</tabItem>
</tabs>
### Using API Keys
The generated API key should be included in your GraphQL requests as a header:
```json
@@ -48,7 +95,7 @@ The generated API key should be included in your GraphQL requests as a header:
}
```
## Available Schemas
## 📊 Available Schemas
The API provides access to various aspects of your Unraid server:
@@ -77,9 +124,9 @@ The API provides access to various aspects of your Unraid server:
- Handle SSO configuration
- Manage allowed origins
### Example Queries
### 💻 Example Queries
1. Check System Status:
#### Check System Status
```graphql
query {
@@ -100,7 +147,7 @@ query {
}
```
2. Monitor Array Status:
#### Monitor Array Status
```graphql
query {
@@ -123,7 +170,7 @@ query {
}
```
3. List Docker Containers:
#### List Docker Containers
```graphql
query {
@@ -137,7 +184,7 @@ query {
}
```
## Schema Types
## 🏗️ Schema Types
The API includes several core types:
@@ -164,19 +211,23 @@ Available roles:
- `connect`: Remote access features
- `guest`: Limited read access
## Best Practices
## Best Practices
:::tip[Pro Tips]
1. Use the Apollo Sandbox to explore the schema and test queries
2. Start with small queries and gradually add fields as needed
3. Monitor your query complexity to maintain performance
4. Use appropriate roles and permissions for your API keys
5. Keep your API keys secure and rotate them periodically
:::
## Rate Limiting
## ⏱️ Rate Limiting
:::caution[Rate Limits]
The API implements rate limiting to prevent abuse. Ensure your applications handle rate limit responses appropriately.
:::
## Error Handling
## 🚨 Error Handling
The API returns standard GraphQL errors in the following format:
@@ -192,11 +243,13 @@ The API returns standard GraphQL errors in the following format:
}
```
## Additional Resources
## 📚 Additional Resources
:::info[Learn More]
- Use the Apollo Sandbox's schema explorer to browse all available types and fields
- Check the documentation tab in Apollo Sandbox for detailed field descriptions
- Monitor the API's health using `unraid-api status`
- Generate reports using `unraid-api report` for troubleshooting
For more information about specific commands and configuration options, refer to the CLI documentation or run `unraid-api --help`.
For more information about specific commands and configuration options, refer to the [CLI documentation](/cli) or run `unraid-api --help`.
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -1,37 +1,94 @@
# Unraid API
---
title: Welcome to Unraid API
description: The official GraphQL API for Unraid Server management and automation
sidebar_position: 1
---
# Welcome to Unraid API
:::tip[What's New]
Starting with Unraid OS v7.2, the API comes built into the operating system - no plugin installation required!
:::
The Unraid API provides a GraphQL interface for programmatic interaction with your Unraid server. It enables automation, monitoring, and integration capabilities.
## Current Availability
## 📦 Availability
The API is available through the Unraid Connect Plugin:
### ✨ Native Integration (Unraid OS v7.2+)
1. Install Unraid Connect Plugin from Apps
Starting with Unraid OS v7.2, the API is integrated directly into the operating system:
- No plugin installation required
- Automatically available on system startup
- Deep system integration
- Access through **Settings****Management Access****API**
### 🔌 Plugin Installation (Pre-7.2 and Advanced Users)
For Unraid versions prior to v7.2 or to access newer API features:
1. Install the Unraid Connect Plugin from Community Apps
2. [Configure the plugin](./how-to-use-the-api.md#enabling-the-graphql-sandbox)
3. Access API functionality through the [GraphQL Sandbox](./how-to-use-the-api.md#accessing-the-graphql-sandbox)
3. Access API functionality through the [GraphQL Sandbox](./how-to-use-the-api.md)
## Future Availability
:::info Important Notes
- The Unraid Connect plugin provides the API for pre-7.2 versions
- You do NOT need to sign in to Unraid Connect to use the API locally
- Installing the plugin on 7.2+ gives you access to newer API features before they're included in OS releases
:::
The API will be integrated directly into the Unraid operating system in an upcoming OS release. This integration will:
## 📚 Documentation Sections
- Make the API a core part of the Unraid system
- Remove the need for separate plugin installation
- Enable deeper system integration capabilities
<cards>
<card title="CLI Commands" icon="terminal" href="./cli">
Complete reference for all CLI commands
</card>
<card title="Using the API" icon="code" href="./how-to-use-the-api">
Learn how to interact with the GraphQL API
</card>
<card title="OIDC Setup" icon="shield" href="./oidc-provider-setup">
Configure SSO authentication providers
</card>
<card title="Upcoming Features" icon="rocket" href="./upcoming-features">
See what's coming next
</card>
</cards>
## Documentation Sections
- [CLI Commands](./cli.md) - Reference for all available command-line interface commands
- [Using the Unraid API](./how-to-use-the-api.md) - Comprehensive guide on using the GraphQL API
- [Upcoming Features](./upcoming-features.md) - Roadmap of planned features and improvements
## Key Features
## 🌟 Key Features
:::info[Core Capabilities]
The API provides:
- GraphQL Interface: Modern, flexible API with strong typing
- Authentication: Secure access via API keys or session cookies
- Comprehensive Coverage: Access to system information, array management, and Docker operations
- Developer Tools: Built-in GraphQL sandbox for testing
- Role-Based Access: Granular permission control
- **GraphQL Interface**: Modern, flexible API with strong typing
- **Authentication**: Multiple methods including API keys, session cookies, and SSO/OIDC
- **Comprehensive Coverage**: Access to system information, array management, and Docker operations
- **Developer Tools**: Built-in GraphQL sandbox configurable via web interface or CLI
- **Role-Based Access**: Granular permission control
- **Web Management**: Manage API keys and settings through the web interface
:::
For detailed usage instructions, see [CLI Commands](./cli.md).
## 🚀 Get Started
<tabs>
<tabItem value="v72" label="Unraid OS v7.2+" default>
1. The API is already installed and running
2. Access settings at **Settings****Management Access****API**
3. Enable the GraphQL Sandbox for development
4. Create your first API key
5. Start making GraphQL queries!
</tabItem>
<tabItem value="older" label="Pre-7.2 Versions">
1. Install the Unraid Connect plugin from Community Apps
2. No Unraid Connect login required for local API access
3. Configure the plugin settings
4. Enable the GraphQL Sandbox
5. Start exploring the API!
</tabItem>
</tabs>
For detailed usage instructions, see the [CLI Commands](./cli) reference.

View File

@@ -0,0 +1,420 @@
---
title: OIDC Provider Setup
description: Configure OIDC (OpenID Connect) providers for SSO authentication in Unraid API
sidebar_position: 3
---
# OIDC Provider Setup
:::info[What is OIDC?]
OpenID Connect (OIDC) is an authentication protocol that allows users to sign in using their existing accounts from providers like Google, Microsoft, or your corporate identity provider. It enables Single Sign-On (SSO) for seamless and secure authentication.
:::
This guide walks you through configuring OIDC (OpenID Connect) providers for SSO authentication in the Unraid API using the web interface.
## 🚀 Quick Start
<details open>
<summary><strong>Getting to OIDC Settings</strong></summary>
1. Navigate to your Unraid server's web interface
2. Go to **Settings****Management Access****API****OIDC**
3. You'll see tabs for different providers - click the **+** button to add a new provider
</details>
### OIDC Providers Interface Overview
![Login Page with SSO Options](./images/sso-with-options.png)
*Login page showing traditional login form with SSO options - "Login With Unraid.net" and "Sign in with Google" buttons*
The interface includes:
- **Provider tabs**: Each configured provider (Unraid.net, Google, etc.) appears as a tab
- **Add Provider button**: Click the **+** button to add new providers
- **Authorization Mode dropdown**: Toggle between "simple" and "advanced" modes
- **Simple Authorization section**: Configure allowed email domains and specific addresses
- **Add Item buttons**: Click to add multiple authorization rules
## Understanding Authorization Modes
The interface provides two authorization modes:
### Simple Mode (Recommended)
Simple mode is the easiest way to configure authorization. You can:
- Allow specific email domains (e.g., @company.com)
- Allow specific email addresses
- Configure who can access your Unraid server with minimal setup
**When to use Simple Mode:**
- You want to allow all users from your company domain
- You have a small list of specific users
- You're new to OIDC configuration
<details>
<summary><strong>Advanced Mode</strong></summary>
Advanced mode provides granular control using claim-based rules. You can:
- Create complex authorization rules based on JWT claims
- Use operators like equals, contains, endsWith, startsWith
- Combine multiple conditions with OR/AND logic
- Choose whether ANY rule must pass (OR mode) or ALL rules must pass (AND mode)
**When to use Advanced Mode:**
- You need to check group memberships
- You want to verify multiple claims (e.g., email domain AND verified status)
- You have complex authorization requirements
- You need fine-grained control over how rules are evaluated
</details>
## Authorization Rules
![Authorization Rules Configuration](./images/advanced-rules.png)
*Advanced authorization rules showing JWT claim configuration with email endsWith operator for domain-based access control*
### Simple Mode Examples
#### Allow Company Domain
In Simple Authorization:
- **Allowed Email Domains**: Enter `company.com`
- This allows anyone with @company.com email
#### Allow Specific Users
- **Specific Email Addresses**: Add individual emails
- Click **Add Item** to add multiple addresses
<details>
<summary><strong>Advanced Mode Examples</strong></summary>
#### Authorization Rule Mode
When using multiple rules, you can choose how they're evaluated:
- **OR Mode** (default): User is authorized if ANY rule passes
- **AND Mode**: User is authorized only if ALL rules pass
#### Email Domain with Verification (AND Mode)
To require both email domain AND verification:
1. Set **Authorization Rule Mode** to `AND`
2. Add two rules:
- Rule 1:
- **Claim**: `email`
- **Operator**: `endsWith`
- **Value**: `@company.com`
- Rule 2:
- **Claim**: `email_verified`
- **Operator**: `equals`
- **Value**: `true`
This ensures users must have both a company email AND a verified email address.
#### Group-Based Access (OR Mode)
To allow access to multiple groups:
1. Set **Authorization Rule Mode** to `OR` (default)
2. Add rules for each group:
- **Claim**: `groups`
- **Operator**: `contains`
- **Value**: `admins`
Or add another rule:
- **Claim**: `groups`
- **Operator**: `contains`
- **Value**: `developers`
Users in either `admins` OR `developers` group will be authorized.
#### Multiple Domains
- **Claim**: `email`
- **Operator**: `endsWith`
- **Values**: Add multiple domains (e.g., `company.com`, `subsidiary.com`)
#### Complex Authorization (AND Mode)
For strict security requiring multiple conditions:
1. Set **Authorization Rule Mode** to `AND`
2. Add multiple rules that ALL must pass:
- Email must be from company domain
- Email must be verified
- User must be in specific group
- Account must have 2FA enabled (if claim available)
</details>
<details>
<summary><strong>Configuration Interface Details</strong></summary>
### Provider Tabs
- Each configured provider appears as a tab at the top
- Click a tab to switch between provider configurations
- The **+** button on the right adds a new provider
### Authorization Mode Dropdown
- **simple**: Best for email-based authorization (recommended for most users)
- **advanced**: For complex claim-based rules using JWT claims
### Simple Authorization Fields
When "simple" mode is selected, you'll see:
- **Allowed Email Domains**: Enter domains without @ (e.g., `company.com`)
- Helper text: "Users with emails ending in these domains can login"
- **Specific Email Addresses**: Add individual email addresses
- Helper text: "Only these exact email addresses can login"
- **Add Item** buttons to add multiple entries
### Advanced Authorization Fields
When "advanced" mode is selected, you'll see:
- **Authorization Rule Mode**: Choose `OR` (any rule passes) or `AND` (all rules must pass)
- **Authorization Rules**: Add multiple claim-based rules
- **For each rule**:
- **Claim**: The JWT claim to check
- **Operator**: How to compare (equals, contains, endsWith, startsWith)
- **Value**: What to match against
### Additional Interface Elements
- **Enable Developer Sandbox**: Toggle to enable GraphQL sandbox at `/graphql`
- The interface uses a dark theme for better visibility
- Field validation indicators help ensure correct configuration
</details>
### Required Redirect URI
:::caution[Important Configuration]
All providers must be configured with this exact redirect URI format:
:::
```bash
http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback
```
:::tip
Replace `YOUR_UNRAID_IP` with your actual server IP address (e.g., `192.168.1.100` or `tower.local`).
:::
### Issuer URL Format
The **Issuer URL** field accepts both formats, but **base URL is strongly recommended** for security:
- **Base URL** (recommended): `https://accounts.google.com`
- **Full discovery URL**: `https://accounts.google.com/.well-known/openid-configuration`
**⚠️ Security Note**: Always use the base URL format when possible. The system automatically appends `/.well-known/openid-configuration` for OIDC discovery. Using the full discovery URL directly disables important issuer validation checks and is not recommended by the OpenID Connect specification.
**Examples of correct base URLs:**
- Google: `https://accounts.google.com`
- Microsoft/Azure: `https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0`
- Keycloak: `https://keycloak.example.com/realms/YOUR_REALM`
- Authelia: `https://auth.yourdomain.com`
## ✅ Testing Your Configuration
![Login Page with SSO Buttons](./images/sso-with-options.png)
*Unraid login page displaying both traditional username/password authentication and SSO options with customized provider buttons*
1. Save your provider configuration
2. Log out (if logged in)
3. Navigate to the login page
4. Your configured provider button should appear
5. Click to test the login flow
## 🔧 Troubleshooting
### Common Issues
#### "Provider not found" error
- Ensure the Issuer URL is correct
- Check that the provider supports OIDC discovery (/.well-known/openid-configuration)
#### "Authorization failed"
- In Simple Mode: Check email domains are entered correctly (without @)
- In Advanced Mode:
- Verify claim names match exactly what your provider sends
- Check if Authorization Rule Mode is set correctly (OR vs AND)
- Ensure all required claims are present in the token
- Enable debug logging to see actual claims and rule evaluation
#### "Invalid redirect URI"
- Ensure the redirect URI in your provider matches exactly
- Include the correct port if using a non-standard configuration
- Verify the redirect URI protocol matches your server's configuration (HTTP or HTTPS)
#### Cannot see login button
- Check that at least one authorization rule is configured
- Verify the provider is enabled/saved
### Debug Mode
To troubleshoot issues:
1. Enable debug logging:
```bash
LOG_LEVEL=debug unraid-api start --debug
```
2. Check logs for:
- Received claims from provider
- Authorization rule evaluation
- Token validation errors
## 🔐 Security Best Practices
1. **Use Simple Mode for authorization** - Prevents overly accepting configurations and reduces misconfiguration risks
2. **Be specific with authorization** - Don't use overly broad rules
3. **Rotate secrets regularly** - Update client secrets periodically
4. **Test thoroughly** - Verify only intended users can access
## 💡 Need Help?
- Check provider's OIDC documentation
- Review Unraid API logs for detailed error messages
- Ensure your provider supports standard OIDC discovery
- Verify network connectivity between Unraid and provider
## 🏢 Provider-Specific Setup
### Unraid.net Provider
The Unraid.net provider is built-in and pre-configured. You only need to configure authorization rules in the interface.
**Configuration:**
- **Issuer URL**: Pre-configured (built-in provider)
- **Client ID/Secret**: Pre-configured (built-in provider)
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
:::tip[Redirect URI Protocol]
**Match the protocol to your server setup:** Use `http://` if accessing your Unraid server without SSL/TLS (typical for local network access). Use `https://` if you've configured SSL/TLS on your server. Some OIDC providers (like Google) require HTTPS and won't accept HTTP redirect URIs.
:::
Configure authorization rules using Simple Mode (allowed email domains/addresses) or Advanced Mode for complex requirements.
### Google
<details>
<summary><strong>📋 Setup Steps</strong></summary>
Set up OAuth 2.0 credentials in [Google Cloud Console](https://console.cloud.google.com/):
1. Go to **APIs & Services****Credentials**
2. Click **Create Credentials****OAuth client ID**
3. Choose **Web application** as the application type
4. Add your redirect URI to **Authorized redirect URIs**
5. Configure the OAuth consent screen if prompted
</details>
**Configuration:**
- **Issuer URL**: `https://accounts.google.com`
- **Client ID/Secret**: From your OAuth 2.0 client credentials
- **Required Scopes**: `openid`, `profile`, `email`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
:::warning[Google Domain Requirements]
**Google requires valid domain names for OAuth redirect URIs.** Local IP addresses and `.local` domains are not accepted. To use Google OAuth with your Unraid server, you'll need:
- **Option 1: Reverse Proxy** - Set up a reverse proxy (like NGINX Proxy Manager or Traefik) with a valid domain name pointing to your Unraid API
- **Option 2: Tailscale** - Use Tailscale to get a valid `*.ts.net` domain that Google will accept
- **Option 3: Dynamic DNS** - Use a DDNS service to get a public domain name for your server
Remember to update your redirect URI in both Google Cloud Console and your Unraid OIDC configuration to use the valid domain.
:::
For Google Workspace domains, use Advanced Mode with the `hd` claim to restrict access to your organization's domain.
### Authelia
Configure OIDC client in your Authelia `configuration.yml` with client ID `unraid-api` and generate a hashed secret using the Authelia hash-password command.
**Configuration:**
- **Issuer URL**: `https://auth.yourdomain.com`
- **Client ID**: `unraid-api` (or as configured in Authelia)
- **Client Secret**: Your unhashed secret
- **Required Scopes**: `openid`, `profile`, `email`, `groups`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
Use Advanced Mode with `groups` claim for group-based authorization.
### Microsoft/Azure AD
Register a new app in [Azure Portal](https://portal.azure.com/) under Azure Active Directory → App registrations. Note the Application ID, create a client secret, and note your tenant ID.
**Configuration:**
- **Issuer URL**: `https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0`
- **Client ID**: Your Application (client) ID
- **Client Secret**: Generated client secret
- **Required Scopes**: `openid`, `profile`, `email`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
Authorization rules can be configured in the interface using email domains or advanced claims.
### Keycloak
Create a new confidential client in Keycloak Admin Console with `openid-connect` protocol and copy the client secret from the Credentials tab.
**Configuration:**
- **Issuer URL**: `https://keycloak.example.com/realms/YOUR_REALM`
- **Client ID**: `unraid-api` (or as configured in Keycloak)
- **Client Secret**: From Keycloak Credentials tab
- **Required Scopes**: `openid`, `profile`, `email`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
For role-based authorization, use Advanced Mode with `realm_access.roles` or `resource_access` claims.
### Authentik
Create a new OAuth2/OpenID Provider in Authentik, then create an Application and link it to the provider.
**Configuration:**
- **Issuer URL**: `https://authentik.example.com/application/o/<application_slug>/`
- **Client ID**: From Authentik provider configuration
- **Client Secret**: From Authentik provider configuration
- **Required Scopes**: `openid`, `profile`, `email`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
Authorization rules can be configured in the interface.
### Okta
Create a new OIDC Web Application in Okta Admin Console and assign appropriate users or groups.
**Configuration:**
- **Issuer URL**: `https://YOUR_DOMAIN.okta.com`
- **Client ID**: From Okta application configuration
- **Client Secret**: From Okta application configuration
- **Required Scopes**: `openid`, `profile`, `email`
- **Redirect URI**: `http://YOUR_UNRAID_IP/graphql/api/auth/oidc/callback`
Authorization rules can be configured in the interface using email domains or advanced claims.

View File

@@ -0,0 +1,252 @@
---
title: Programmatic API Key Management
description: Create, use, and delete API keys programmatically for automated workflows
sidebar_position: 4
---
# Programmatic API Key Management
This guide explains how to create, use, and delete API keys programmatically using the Unraid API CLI, enabling automated workflows and scripts.
## Overview
The `unraid-api apikey` command supports both interactive and non-interactive modes, making it suitable for:
- Automated deployment scripts
- CI/CD pipelines
- Temporary access provisioning
- Infrastructure as code workflows
:::tip[Quick Start]
Jump to the [Complete Workflow Example](#complete-workflow-example) to see everything in action.
:::
## Creating API Keys Programmatically
### Basic Creation with JSON Output
Use the `--json` flag to get machine-readable output:
```bash
unraid-api apikey --create --name "workflow key" --roles ADMIN --json
```
**Output:**
```json
{
"key": "your-generated-api-key-here",
"name": "workflow key",
"id": "generated-uuid"
}
```
### Advanced Creation with Permissions
```bash
unraid-api apikey --create \
--name "limited access key" \
--permissions "DOCKER:READ_ANY,ARRAY:READ_ANY" \
--description "Read-only access for monitoring" \
--json
```
### Handling Existing Keys
If a key with the same name exists, use `--overwrite`:
```bash
unraid-api apikey --create --name "existing key" --roles ADMIN --overwrite --json
```
:::warning[Key Replacement]
The `--overwrite` flag will permanently replace the existing key. The old key will be immediately invalidated.
:::
## Deleting API Keys Programmatically
### Non-Interactive Deletion
Delete a key by name without prompts:
```bash
unraid-api apikey --delete --name "workflow key"
```
**Output:**
```
Successfully deleted 1 API key
```
### JSON Output for Deletion
Use `--json` flag for machine-readable delete confirmation:
```bash
unraid-api apikey --delete --name "workflow key" --json
```
**Success Output:**
```json
{
"deleted": 1,
"keys": [
{
"id": "generated-uuid",
"name": "workflow key"
}
]
}
```
**Error Output:**
```json
{
"deleted": 0,
"error": "No API key found with name: nonexistent key"
}
```
### Error Handling
When the specified key doesn't exist:
```bash
unraid-api apikey --delete --name "nonexistent key"
# Output: No API keys found to delete
```
**JSON Error Output:**
```json
{
"deleted": 0,
"message": "No API keys found to delete"
}
```
## Complete Workflow Example
Here's a complete example for temporary access provisioning:
```bash
#!/bin/bash
set -e
# 1. Create temporary API key
echo "Creating temporary API key..."
KEY_DATA=$(unraid-api apikey --create \
--name "temp deployment key" \
--roles ADMIN \
--description "Temporary key for deployment $(date)" \
--json)
# 2. Extract the API key
API_KEY=$(echo "$KEY_DATA" | jq -r '.key')
echo "API key created successfully"
# 3. Use the key for operations
echo "Configuring services..."
curl -H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider": "azure", "clientId": "your-client-id"}' \
http://localhost:3001/graphql
# 4. Clean up (always runs, even on error)
trap 'echo "Cleaning up..."; unraid-api apikey --delete --name "temp deployment key"' EXIT
echo "Deployment completed successfully"
```
## Command Reference
### Create Command Options
| Flag | Description | Example |
| ----------------------- | ----------------------- | --------------------------------- |
| `--name <name>` | Key name (required) | `--name "my key"` |
| `--roles <roles>` | Comma-separated roles | `--roles ADMIN,VIEWER` |
| `--permissions <perms>` | Resource:action pairs | `--permissions "DOCKER:READ_ANY"` |
| `--description <desc>` | Key description | `--description "CI/CD key"` |
| `--overwrite` | Replace existing key | `--overwrite` |
| `--json` | Machine-readable output | `--json` |
### Available Roles
- `ADMIN` - Full system access
- `CONNECT` - Unraid Connect features
- `VIEWER` - Read-only access
- `GUEST` - Limited access
### Available Resources and Actions
**Resources:** `ACTIVATION_CODE`, `API_KEY`, `ARRAY`, `CLOUD`, `CONFIG`, `CONNECT`, `CONNECT__REMOTE_ACCESS`, `CUSTOMIZATIONS`, `DASHBOARD`, `DISK`, `DISPLAY`, `DOCKER`, `FLASH`, `INFO`, `LOGS`, `ME`, `NETWORK`, `NOTIFICATIONS`, `ONLINE`, `OS`, `OWNER`, `PERMISSION`, `REGISTRATION`, `SERVERS`, `SERVICES`, `SHARE`, `VARS`, `VMS`, `WELCOME`
**Actions:** `CREATE_ANY`, `CREATE_OWN`, `READ_ANY`, `READ_OWN`, `UPDATE_ANY`, `UPDATE_OWN`, `DELETE_ANY`, `DELETE_OWN`
### Delete Command Options
| Flag | Description | Example |
| --------------- | ------------------------ | ----------------- |
| `--delete` | Enable delete mode | `--delete` |
| `--name <name>` | Key to delete (optional) | `--name "my key"` |
**Note:** If `--name` is omitted, the command runs interactively.
## Best Practices
:::info[Security Best Practices]
**Minimal Permissions**
- Use specific permissions instead of ADMIN role when possible
- Example: `--permissions "DOCKER:READ_ANY"` instead of `--roles ADMIN`
**Key Lifecycle Management**
- Always clean up temporary keys after use
- Store API keys securely (environment variables, secrets management)
- Use descriptive names and descriptions for audit trails
:::
### Error Handling
- Check exit codes (`$?`) after each command
- Use `set -e` in bash scripts to fail fast
- Implement proper cleanup with `trap`
### Key Naming
- Use descriptive names that include purpose and date
- Names must contain only letters, numbers, and spaces
- Unicode letters are supported
## Troubleshooting
### Common Issues
:::note[Common Error Messages]
**"API key name must contain only letters, numbers, and spaces"**
- **Solution:** Remove special characters like hyphens, underscores, or symbols
**"API key with name 'x' already exists"**
- **Solution:** Use `--overwrite` flag or choose a different name
**"Please add at least one role or permission to the key"**
- **Solution:** Specify either `--roles` or `--permissions` (or both)
:::
### Debug Mode
For troubleshooting, run with debug logging:
```bash
LOG_LEVEL=debug unraid-api apikey --create --name "debug key" --roles ADMIN
```

View File

@@ -1,71 +1,172 @@
# Upcoming Features
---
title: Roadmap & Features
description: Current status and upcoming features for the Unraid API
sidebar_position: 10
---
Note: This roadmap outlines planned features and improvements for the Unraid API. Features and timelines may change based on development priorities and community feedback.
# Roadmap & Features
:::info Development Status
This roadmap outlines completed and planned features for the Unraid API. Features and timelines may change based on development priorities and community feedback.
:::
## Feature Status Legend
| Status | Description |
|--------|-------------|
| ✅ **Done** | Feature is complete and available |
| 🚧 **In Progress** | Currently under active development |
| 📅 **Planned** | Scheduled for future development |
| 💡 **Under Consideration** | Being evaluated for future inclusion |
## Core Infrastructure
| Feature | Status | Tag |
|---------|--------|-----|
| API Development Environment Improvements | Done | v4.0.0 |
| Include API in Unraid OS | Planned (Q1 2025) | - |
| Make API Open Source | Planned (Q1 2025) | - |
| Separate API from Connect Plugin | Planned (Q2 2025) | - |
| Developer Tools for Plugins | Planned (Q2 2025) | - |
### Completed Features ✅
| Feature | Available Since |
|---------|-----------------|
| **API Development Environment Improvements** | v4.0.0 |
| **Include API in Unraid OS** | Unraid v7.2-beta.1 |
| **Separate API from Connect Plugin** | Unraid v7.2-beta.1 |
### Upcoming Features 📅
| Feature | Target Timeline |
|---------|-----------------|
| **Make API Open Source** | Q1 2025 |
| **Developer Tools for Plugins** | Q2 2025 |
## Security & Authentication
| Feature | Status | Tag |
|---------|--------|-----|
| Permissions System Rewrite | Done | v4.0.0 |
| User Interface Component Library | In Progress | - |
### Completed Features ✅
| Feature | Available Since |
|---------|-----------------|
| **Permissions System Rewrite** | v4.0.0 |
| **OIDC/SSO Support** | Unraid v7.2-beta.1 |
### In Development 🚧
- **User Interface Component Library** - Enhanced security components for the UI
## User Interface Improvements
| Feature | Status | Tag |
|---------|--------|-----|
| New Settings Pages | Planned (Q2 2025) | - |
| Custom Theme Creator | Planned (Q2-Q3 2025) | - |
| New Connect Settings Interface | Planned (Q1 2025) | - |
### Planned Features 📅
| Feature | Target Timeline | Description |
|---------|-----------------|-------------|
| **New Settings Pages** | Q2 2025 | Modernized settings interface with improved UX |
| **Custom Theme Creator** | Q2-Q3 2025 | Allow users to create and share custom themes |
| **New Connect Settings Interface** | Q1 2025 | Redesigned Unraid Connect configuration |
## Array Management
| Feature | Status | Tag |
|---------|--------|-----|
| Array Status Monitoring | Done | v4.0.0 |
| Storage Pool Creation Interface | Planned (Q2 2025) | - |
| Storage Pool Status Interface | Planned (Q2 2025) | - |
### Completed Features ✅
| Feature | Available Since |
|---------|-----------------|
| **Array Status Monitoring** | v4.0.0 |
### Planned Features 📅
| Feature | Target Timeline | Description |
|---------|-----------------|-------------|
| **Storage Pool Creation Interface** | Q2 2025 | Simplified pool creation workflow |
| **Storage Pool Status Interface** | Q2 2025 | Real-time pool health monitoring |
## Docker Integration
| Feature | Status | Tag |
|---------|--------|-----|
| Docker Container Status Monitoring | Done | v4.0.0 |
| New Docker Status Interface Design | Planned (Q3 2025) | - |
| New Docker Status Interface | Planned (Q3 2025) | - |
| Docker Container Setup Interface | Planned (Q3 2025) | - |
| Docker Compose Support | Planned | - |
### Completed Features ✅
| Feature | Available Since |
|---------|-----------------|
| **Docker Container Status Monitoring** | v4.0.0 |
### Planned Features 📅
| Feature | Target Timeline | Description |
|---------|-----------------|-------------|
| **New Docker Status Interface Design** | Q3 2025 | Modern container management UI |
| **New Docker Status Interface** | Q3 2025 | Implementation of new design |
| **Docker Container Setup Interface** | Q3 2025 | Streamlined container deployment |
| **Docker Compose Support** | TBD | Native docker-compose.yml support |
## Share Management
| Feature | Status | Tag |
|---------|--------|-----|
| Array/Cache Share Status Monitoring | Done | v4.0.0 |
| Storage Share Creation & Settings | Planned | - |
| Storage Share Management Interface | Planned | - |
### Completed Features ✅
| Feature | Available Since |
|---------|-----------------|
| **Array/Cache Share Status Monitoring** | v4.0.0 |
### Under Consideration 💡
- **Storage Share Creation & Settings** - Enhanced share configuration options
- **Storage Share Management Interface** - Unified share management dashboard
## Plugin System
| Feature | Status | Tag |
|---------|--------|-----|
| New Plugins Interface | Planned (Q3 2025) | - |
| Plugin Management Interface | Planned | - |
| Plugin Development Tools | Planned | - |
### Planned Features 📅
| Feature | Target Timeline | Description |
|---------|-----------------|-------------|
| **New Plugins Interface** | Q3 2025 | Redesigned plugin management UI |
| **Plugin Management Interface** | TBD | Advanced plugin configuration |
| **Plugin Development Tools** | TBD | SDK and tooling for developers |
## Notifications
| Feature | Status | Tag |
|---------|--------|-----|
| Notifications System | Done | v4.0.0 |
| Notifications Interface | Done | v4.0.0 |
### Completed Features ✅
Features marked as "Done" are available in current releases. The tag column shows the version where a feature was first introduced.
| Feature | Available Since |
|---------|-----------------|
| **Notifications System** | v4.0.0 |
| **Notifications Interface** | v4.0.0 |
---
## Recent Releases
:::info Full Release History
For a complete list of all releases, changelogs, and download links, visit the [Unraid API GitHub Releases](https://github.com/unraid/api/releases) page.
:::
### Unraid v7.2-beta.1 Highlights
- 🎉 **API included in Unraid OS** - Native integration
- 🔐 **OIDC/SSO Support** - Enterprise authentication
- 📦 **Standalone API** - Separated from Connect plugin
### v4.0.0 Highlights
- 🛡️ **Permissions System Rewrite** - Enhanced security
- 📊 **Comprehensive Monitoring** - Array, Docker, and Share status
- 🔔 **Notifications System** - Real-time alerts and notifications
- 🛠️ **Developer Environment** - Improved development tools
## Community Feedback
:::tip Have a Feature Request?
We value community input! Please submit feature requests and feedback through:
- [Unraid Forums](https://forums.unraid.net)
- [GitHub Issues](https://github.com/unraid/api/issues) - API is open source!
:::
## Version Support
| Unraid Version | API Version | Support Status |
|----------------|-------------|----------------|
| Unraid v7.2-beta.1+ | Latest | ✅ Active |
| 7.0 - 7.1.x | v4.x via Plugin | ⚠️ Limited |
| 6.12.x | v4.x via Plugin | ⚠️ Limited |
| < 6.12 | Not Supported | ❌ EOL |
:::warning Legacy Support
Versions prior to Unraid 7.2 require the API to be installed through the Unraid Connect plugin. Some features may not be available on older versions.
:::
:::tip Pre-release Versions
You can always install the Unraid Connect plugin to access pre-release versions of the API and get early access to new features before they're included in Unraid OS releases.
:::

View File

@@ -13,7 +13,9 @@
"watch": false,
"interpreter": "/usr/local/bin/node",
"ignore_watch": ["node_modules", "src", ".env.*", "myservers.cfg"],
"log_file": "/var/log/graphql-api.log",
"out_file": "/var/log/graphql-api.log",
"error_file": "/var/log/graphql-api.log",
"merge_logs": true,
"kill_timeout": 10000
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@unraid/api",
"version": "4.10.0",
"version": "4.18.2",
"main": "src/cli/index.ts",
"type": "module",
"corepack": {
@@ -10,14 +10,14 @@
"author": "Lime Technology, Inc. <unraid.net>",
"license": "GPL-2.0-or-later",
"engines": {
"pnpm": "10.13.1"
"pnpm": "10.15.0"
},
"scripts": {
"// Development": "",
"start": "node dist/main.js",
"dev": "vite",
"dev": "clear && vite",
"dev:debug": "NODE_OPTIONS='--inspect-brk=9229 --enable-source-maps' vite",
"command": "pnpm run build && clear && ./dist/cli.js",
"command": "COMMAND_TESTER=true pnpm run build > /dev/null 2>&1 && NODE_ENV=development ./dist/cli.js",
"command:raw": "./dist/cli.js",
"// Build and Deploy": "",
"build": "vite build --mode=production",
@@ -28,9 +28,8 @@
"preunraid:deploy": "pnpm build",
"unraid:deploy": "./scripts/deploy-dev.sh",
"// GraphQL Codegen": "",
"codegen": "MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config codegen.ts -r dotenv/config './.env.staging'",
"codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen --config codegen.ts --watch -r dotenv/config",
"codegen:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOTHERSHIP_GRAPHQL_LINK='https://mothership.localhost/ws' graphql-codegen --config codegen.ts --watch",
"codegen": "graphql-codegen --config codegen.ts",
"codegen:watch": "graphql-codegen --config codegen.ts --watch",
"// Code Quality": "",
"lint": "eslint --config .eslintrc.ts src/",
"lint:fix": "eslint --fix --config .eslintrc.ts src/",
@@ -52,26 +51,26 @@
"unraid-api": "dist/cli.js"
},
"dependencies": {
"@apollo/client": "3.13.8",
"@apollo/client": "3.14.0",
"@apollo/server": "4.12.2",
"@as-integrations/fastify": "2.1.1",
"@fastify/cookie": "11.0.2",
"@fastify/helmet": "13.0.1",
"@graphql-codegen/client-preset": "4.8.3",
"@graphql-tools/load-files": "7.0.1",
"@graphql-tools/merge": "9.0.24",
"@graphql-tools/schema": "10.0.23",
"@graphql-tools/utils": "10.8.6",
"@graphql-tools/merge": "9.1.1",
"@graphql-tools/schema": "10.0.25",
"@graphql-tools/utils": "10.9.1",
"@jsonforms/core": "3.6.0",
"@nestjs/apollo": "13.1.0",
"@nestjs/cache-manager": "3.0.1",
"@nestjs/common": "11.1.3",
"@nestjs/common": "11.1.6",
"@nestjs/config": "4.0.2",
"@nestjs/core": "11.1.3",
"@nestjs/core": "11.1.6",
"@nestjs/event-emitter": "3.0.1",
"@nestjs/graphql": "13.1.0",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-fastify": "11.1.3",
"@nestjs/platform-fastify": "11.1.6",
"@nestjs/schedule": "6.0.0",
"@nestjs/throttler": "6.4.0",
"@reduxjs/toolkit": "2.8.2",
@@ -80,9 +79,10 @@
"@unraid/libvirt": "2.1.0",
"@unraid/shared": "workspace:*",
"accesscontrol": "2.2.1",
"atomically": "2.0.3",
"bycontract": "2.0.11",
"bytes": "3.1.2",
"cache-manager": "7.0.1",
"cache-manager": "7.2.0",
"cacheable-lookup": "7.0.0",
"camelcase-keys": "9.1.3",
"casbin": "5.38.0",
@@ -94,16 +94,17 @@
"command-exists": "1.2.9",
"convert": "5.12.0",
"cookie": "1.0.2",
"cron": "4.3.2",
"cron": "4.3.3",
"cross-fetch": "4.1.0",
"diff": "8.0.2",
"dockerode": "4.0.7",
"dotenv": "17.2.0",
"dotenv": "17.2.1",
"escape-html": "1.0.3",
"execa": "9.6.0",
"exit-hook": "4.0.0",
"fastify": "5.4.0",
"fastify": "5.5.0",
"filenamify": "6.0.0",
"fs-extra": "11.3.0",
"fs-extra": "11.3.1",
"glob": "11.0.3",
"global-agent": "3.0.0",
"got": "14.4.7",
@@ -115,30 +116,31 @@
"graphql-ws": "6.0.6",
"ini": "5.0.0",
"ip": "2.0.1",
"jose": "6.0.11",
"jose": "6.0.13",
"json-bigint-patch": "0.0.8",
"lodash-es": "4.17.21",
"multi-ini": "2.3.2",
"mustache": "4.2.0",
"nest-authz": "2.17.0",
"nest-commander": "3.17.0",
"nest-commander": "3.19.0",
"nestjs-pino": "4.4.0",
"node-cache": "5.1.2",
"node-window-polyfill": "1.0.4",
"openid-client": "6.6.4",
"p-retry": "6.2.1",
"passport-custom": "1.1.1",
"passport-http-header-strategy": "1.1.0",
"path-type": "6.0.0",
"pino": "9.7.0",
"pino": "9.9.0",
"pino-http": "10.5.0",
"pino-pretty": "13.0.0",
"pino-pretty": "13.1.1",
"pm2": "6.0.8",
"reflect-metadata": "^0.1.14",
"request": "2.88.2",
"rxjs": "7.8.2",
"semver": "7.7.2",
"strftime": "0.10.3",
"systeminformation": "5.27.7",
"systeminformation": "5.27.8",
"undici": "7.15.0",
"uuid": "11.1.0",
"ws": "8.18.3",
"zen-observable-ts": "1.1.0",
@@ -153,7 +155,7 @@
}
},
"devDependencies": {
"@eslint/js": "9.31.0",
"@eslint/js": "9.34.0",
"@graphql-codegen/add": "5.0.3",
"@graphql-codegen/cli": "5.0.7",
"@graphql-codegen/fragment-matcher": "5.1.0",
@@ -163,17 +165,17 @@
"@graphql-codegen/typescript-operations": "4.6.1",
"@graphql-codegen/typescript-resolvers": "4.5.1",
"@graphql-typed-document-node/core": "3.2.0",
"@ianvs/prettier-plugin-sort-imports": "4.5.1",
"@nestjs/testing": "11.1.3",
"@ianvs/prettier-plugin-sort-imports": "4.6.3",
"@nestjs/testing": "11.1.6",
"@originjs/vite-plugin-commonjs": "1.0.3",
"@rollup/plugin-node-resolve": "16.0.1",
"@swc/core": "1.12.14",
"@swc/core": "1.13.5",
"@types/async-exit-hook": "2.0.2",
"@types/bytes": "3.1.5",
"@types/cli-table": "0.3.4",
"@types/command-exists": "1.2.3",
"@types/cors": "2.8.19",
"@types/dockerode": "3.3.42",
"@types/dockerode": "3.3.43",
"@types/graphql-fields": "1.3.9",
"@types/graphql-type-uuid": "0.2.6",
"@types/ini": "4.1.1",
@@ -181,43 +183,41 @@
"@types/lodash": "4.17.20",
"@types/lodash-es": "4.17.12",
"@types/mustache": "4.2.6",
"@types/node": "22.16.4",
"@types/node": "22.18.0",
"@types/pify": "6.1.0",
"@types/semver": "7.7.0",
"@types/sendmail": "1.4.7",
"@types/stoppable": "1.1.3",
"@types/strftime": "0.9.8",
"@types/supertest": "6.0.3",
"@types/uuid": "10.0.0",
"@types/ws": "8.18.1",
"@types/wtfnode": "0.7.3",
"@types/wtfnode": "0.10.0",
"@vitest/coverage-v8": "3.2.4",
"@vitest/ui": "3.2.4",
"cz-conventional-changelog": "3.3.0",
"eslint": "9.31.0",
"eslint": "9.34.0",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-n": "17.21.0",
"eslint-plugin-no-relative-import-paths": "1.6.1",
"eslint-plugin-prettier": "5.5.1",
"graphql-codegen-typescript-validation-schema": "0.17.1",
"jiti": "2.4.2",
"eslint-plugin-prettier": "5.5.4",
"jiti": "2.5.1",
"nodemon": "3.1.10",
"prettier": "3.6.2",
"rollup-plugin-node-externals": "8.0.1",
"commit-and-tag-version": "9.6.0",
"tsx": "4.20.3",
"rollup-plugin-node-externals": "8.1.0",
"supertest": "7.1.4",
"tsx": "4.20.5",
"type-fest": "4.41.0",
"typescript": "5.8.3",
"typescript-eslint": "8.37.0",
"unplugin-swc": "1.5.5",
"vite": "7.0.4",
"typescript": "5.9.2",
"typescript-eslint": "8.41.0",
"unplugin-swc": "1.5.7",
"vite": "7.1.3",
"vite-plugin-node": "7.0.0",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"zx": "8.7.1"
"zx": "8.8.1"
},
"overrides": {
"eslint": {
"jiti": "2.4.2"
"jiti": "2.5.1"
},
"@as-integrations/fastify": {
"fastify": "$fastify"
@@ -228,5 +228,5 @@
}
},
"private": true,
"packageManager": "pnpm@10.13.1"
"packageManager": "pnpm@10.15.0"
}

View File

@@ -1,45 +0,0 @@
import { getAllowedOrigins } from '@app/common/allowed-origins.js';
import { store } from '@app/store/index.js';
import { loadConfigFile } from '@app/store/modules/config.js';
import { loadStateFiles } from '@app/store/modules/emhttp.js';
import 'reflect-metadata';
import { expect, test } from 'vitest';
test('Returns allowed origins', async () => {
// Load state files into store
await store.dispatch(loadStateFiles()).unwrap();
await store.dispatch(loadConfigFile()).unwrap();
// Get allowed origins
const allowedOrigins = getAllowedOrigins();
// Test that the result is an array
expect(Array.isArray(allowedOrigins)).toBe(true);
// Test that it contains the expected socket paths
expect(allowedOrigins).toContain('/var/run/unraid-notifications.sock');
expect(allowedOrigins).toContain('/var/run/unraid-php.sock');
expect(allowedOrigins).toContain('/var/run/unraid-cli.sock');
// Test that it contains the expected local URLs
expect(allowedOrigins).toContain('http://localhost:8080');
expect(allowedOrigins).toContain('https://localhost:4443');
// Test that it contains the expected connect URLs
expect(allowedOrigins).toContain('https://connect.myunraid.net');
expect(allowedOrigins).toContain('https://connect-staging.myunraid.net');
expect(allowedOrigins).toContain('https://dev-my.myunraid.net:4000');
// Test that it contains the extra origins from config
expect(allowedOrigins).toContain('https://google.com');
expect(allowedOrigins).toContain('https://test.com');
// Test that it contains some of the remote URLs
expect(allowedOrigins).toContain('https://tower.local:4443');
expect(allowedOrigins).toContain('https://192.168.1.150:4443');
// Test that there are no duplicates
expect(allowedOrigins.length).toBe(new Set(allowedOrigins).size);
});

View File

@@ -1,158 +0,0 @@
import 'reflect-metadata';
import { cloneDeep } from 'lodash-es';
import { expect, test } from 'vitest';
import { getWriteableConfig } from '@app/core/utils/files/config-file-normalizer.js';
import { initialState } from '@app/store/modules/config.js';
test('it creates a FLASH config with NO OPTIONAL values', () => {
const basicConfig = initialState;
const config = getWriteableConfig(basicConfig, 'flash');
expect(config).toMatchInlineSnapshot(`
{
"api": {
"extraOrigins": "",
"version": "",
},
"local": {
"sandbox": "no",
},
"remote": {
"accesstoken": "",
"apikey": "",
"avatar": "",
"dynamicRemoteAccessType": "DISABLED",
"email": "",
"idtoken": "",
"localApiKey": "",
"refreshtoken": "",
"regWizTime": "",
"ssoSubIds": "",
"upnpEnabled": "",
"username": "",
"wanaccess": "",
"wanport": "",
},
}
`);
});
test('it creates a MEMORY config with NO OPTIONAL values', () => {
const basicConfig = initialState;
const config = getWriteableConfig(basicConfig, 'memory');
expect(config).toMatchInlineSnapshot(`
{
"api": {
"extraOrigins": "",
"version": "",
},
"connectionStatus": {
"minigraph": "PRE_INIT",
"upnpStatus": "",
},
"local": {
"sandbox": "no",
},
"remote": {
"accesstoken": "",
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000",
"apikey": "",
"avatar": "",
"dynamicRemoteAccessType": "DISABLED",
"email": "",
"idtoken": "",
"localApiKey": "",
"refreshtoken": "",
"regWizTime": "",
"ssoSubIds": "",
"upnpEnabled": "",
"username": "",
"wanaccess": "",
"wanport": "",
},
}
`);
});
test('it creates a FLASH config with OPTIONAL values', () => {
const basicConfig = cloneDeep(initialState);
// 2fa & t2fa should be ignored
basicConfig.remote['2Fa'] = 'yes';
basicConfig.local['2Fa'] = 'yes';
basicConfig.api.extraOrigins = 'myextra.origins';
basicConfig.remote.upnpEnabled = 'yes';
basicConfig.connectionStatus.upnpStatus = 'Turned On';
const config = getWriteableConfig(basicConfig, 'flash');
expect(config).toMatchInlineSnapshot(`
{
"api": {
"extraOrigins": "myextra.origins",
"version": "",
},
"local": {
"sandbox": "no",
},
"remote": {
"accesstoken": "",
"apikey": "",
"avatar": "",
"dynamicRemoteAccessType": "DISABLED",
"email": "",
"idtoken": "",
"localApiKey": "",
"refreshtoken": "",
"regWizTime": "",
"ssoSubIds": "",
"upnpEnabled": "yes",
"username": "",
"wanaccess": "",
"wanport": "",
},
}
`);
});
test('it creates a MEMORY config with OPTIONAL values', () => {
const basicConfig = cloneDeep(initialState);
// 2fa & t2fa should be ignored
basicConfig.remote['2Fa'] = 'yes';
basicConfig.local['2Fa'] = 'yes';
basicConfig.api.extraOrigins = 'myextra.origins';
basicConfig.remote.upnpEnabled = 'yes';
basicConfig.connectionStatus.upnpStatus = 'Turned On';
const config = getWriteableConfig(basicConfig, 'memory');
expect(config).toMatchInlineSnapshot(`
{
"api": {
"extraOrigins": "myextra.origins",
"version": "",
},
"connectionStatus": {
"minigraph": "PRE_INIT",
"upnpStatus": "Turned On",
},
"local": {
"sandbox": "no",
},
"remote": {
"accesstoken": "",
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000",
"apikey": "",
"avatar": "",
"dynamicRemoteAccessType": "DISABLED",
"email": "",
"idtoken": "",
"localApiKey": "",
"refreshtoken": "",
"regWizTime": "",
"ssoSubIds": "",
"upnpEnabled": "yes",
"username": "",
"wanaccess": "",
"wanport": "",
},
}
`);
});

View File

@@ -0,0 +1,5 @@
/* eslint-disable no-undef */
// Dummy process for PM2 testing
setInterval(() => {
// Keep process alive
}, 1000);

View File

@@ -0,0 +1,216 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { execa } from 'execa';
import pm2 from 'pm2';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { isUnraidApiRunning } from '@app/core/utils/pm2/unraid-api-running.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const PROJECT_ROOT = join(__dirname, '../../../../..');
const DUMMY_PROCESS_PATH = join(__dirname, 'dummy-process.js');
const CLI_PATH = join(PROJECT_ROOT, 'dist/cli.js');
const TEST_PROCESS_NAME = 'test-unraid-api';
// Shared PM2 connection state
let pm2Connected = false;
// Helper function to run CLI command (assumes CLI is built)
async function runCliCommand(command: string, options: any = {}) {
return await execa('node', [CLI_PATH, command], options);
}
// Helper to ensure PM2 connection is established
async function ensurePM2Connection() {
if (pm2Connected) return;
return new Promise<void>((resolve, reject) => {
pm2.connect((err) => {
if (err) {
reject(err);
return;
}
pm2Connected = true;
resolve();
});
});
}
// Helper to delete specific test processes (lightweight, reuses connection)
async function deleteTestProcesses() {
if (!pm2Connected) {
// No connection, nothing to clean up
return;
}
const deletePromise = new Promise<void>((resolve) => {
// Delete specific processes we might have created
const processNames = ['unraid-api', TEST_PROCESS_NAME];
let deletedCount = 0;
const deleteNext = () => {
if (deletedCount >= processNames.length) {
resolve();
return;
}
const processName = processNames[deletedCount];
pm2.delete(processName, (deleteErr) => {
// Ignore errors, process might not exist
deletedCount++;
deleteNext();
});
};
deleteNext();
});
const timeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 3000); // 3 second timeout
});
return Promise.race([deletePromise, timeoutPromise]);
}
// Helper to ensure PM2 is completely clean (heavy cleanup with daemon kill)
async function cleanupAllPM2Processes() {
// First delete test processes if we have a connection
if (pm2Connected) {
await deleteTestProcesses();
}
return new Promise<void>((resolve) => {
// Always connect fresh for daemon kill (in case we weren't connected)
pm2.connect((err) => {
if (err) {
// If we can't connect, assume PM2 is not running
pm2Connected = false;
resolve();
return;
}
// Kill the daemon to ensure fresh state
pm2.killDaemon((killErr) => {
pm2.disconnect();
pm2Connected = false;
// Small delay to let PM2 fully shutdown
setTimeout(resolve, 500);
});
});
});
}
describe.skipIf(!!process.env.CI)('PM2 integration tests', () => {
beforeAll(async () => {
// Build the CLI if it doesn't exist (only for CLI tests)
if (!existsSync(CLI_PATH)) {
console.log('Building CLI for integration tests...');
try {
await execa('pnpm', ['build'], {
cwd: PROJECT_ROOT,
stdio: 'inherit',
timeout: 120000, // 2 minute timeout for build
});
} catch (error) {
console.error('Failed to build CLI:', error);
throw new Error(
'Cannot run CLI integration tests without built CLI. Run `pnpm build` first.'
);
}
}
// Only do a full cleanup once at the beginning
await cleanupAllPM2Processes();
}, 150000); // 2.5 minute timeout for setup
afterAll(async () => {
// Only do a full cleanup once at the end
await cleanupAllPM2Processes();
});
afterEach(async () => {
// Lightweight cleanup after each test - just delete our test processes
await deleteTestProcesses();
}, 5000); // 5 second timeout for cleanup
describe('isUnraidApiRunning function', () => {
it('should return false when PM2 is not running the unraid-api process', async () => {
const result = await isUnraidApiRunning();
expect(result).toBe(false);
});
it('should return true when PM2 has unraid-api process running', async () => {
// Ensure PM2 connection
await ensurePM2Connection();
// Start a dummy process with the name 'unraid-api'
await new Promise<void>((resolve, reject) => {
pm2.start(
{
script: DUMMY_PROCESS_PATH,
name: 'unraid-api',
},
(startErr) => {
if (startErr) return reject(startErr);
resolve();
}
);
});
// Give PM2 time to start the process
await new Promise((resolve) => setTimeout(resolve, 2000));
const result = await isUnraidApiRunning();
expect(result).toBe(true);
}, 30000);
it('should return false when unraid-api process is stopped', async () => {
// Ensure PM2 connection
await ensurePM2Connection();
// Start and then stop the process
await new Promise<void>((resolve, reject) => {
pm2.start(
{
script: DUMMY_PROCESS_PATH,
name: 'unraid-api',
},
(startErr) => {
if (startErr) return reject(startErr);
// Stop the process after starting
setTimeout(() => {
pm2.stop('unraid-api', (stopErr) => {
if (stopErr) return reject(stopErr);
resolve();
});
}, 1000);
}
);
});
await new Promise((resolve) => setTimeout(resolve, 1000));
const result = await isUnraidApiRunning();
expect(result).toBe(false);
}, 30000);
it('should handle PM2 connection errors gracefully', async () => {
// Set an invalid PM2_HOME to force connection failure
const originalPM2Home = process.env.PM2_HOME;
process.env.PM2_HOME = '/invalid/path/that/does/not/exist';
const result = await isUnraidApiRunning();
expect(result).toBe(false);
// Restore original PM2_HOME
if (originalPM2Home) {
process.env.PM2_HOME = originalPM2Home;
} else {
delete process.env.PM2_HOME;
}
}, 15000); // 15 second timeout to allow for the Promise.race timeout
});
});

View File

@@ -95,6 +95,48 @@ test('Returns both disk and user shares', async () => {
"type": "user",
"used": 33619300,
},
{
"allocator": "highwater",
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with periods",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.periods",
"include": [],
"luksStatus": "0",
"name": "system.with.periods",
"nameOrig": "system.with.periods",
"nfs": {},
"size": 0,
"smb": {},
"splitLevel": "1",
"type": "user",
"used": 33619300,
},
{
"allocator": "highwater",
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with 🚀",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.🚀",
"include": [],
"luksStatus": "0",
"name": "system.with.🚀",
"nameOrig": "system.with.🚀",
"nfs": {},
"size": 0,
"smb": {},
"splitLevel": "1",
"type": "user",
"used": 33619300,
},
],
}
`);
@@ -211,6 +253,48 @@ test('Returns shares by type', async () => {
"type": "user",
"used": 33619300,
},
{
"allocator": "highwater",
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with periods",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.periods",
"include": [],
"luksStatus": "0",
"name": "system.with.periods",
"nameOrig": "system.with.periods",
"nfs": {},
"size": 0,
"smb": {},
"splitLevel": "1",
"type": "user",
"used": 33619300,
},
{
"allocator": "highwater",
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with 🚀",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.🚀",
"include": [],
"luksStatus": "0",
"name": "system.with.🚀",
"nameOrig": "system.with.🚀",
"nfs": {},
"size": 0,
"smb": {},
"splitLevel": "1",
"type": "user",
"used": 33619300,
},
]
`);
expect(getShares('disk')).toMatchInlineSnapshot('null');

View File

@@ -34,6 +34,15 @@ vi.mock('@app/store/index.js', () => ({
}),
},
}));
vi.mock('@app/environment.js', () => ({
ENVIRONMENT: 'development',
environment: {
IS_MAIN_PROCESS: true,
},
}));
vi.mock('@app/core/utils/files/file-exists.js', () => ({
fileExists: vi.fn().mockResolvedValue(true),
}));
// Mock NestJS Logger to suppress logs during tests
vi.mock('@nestjs/common', async (importOriginal) => {
@@ -63,13 +72,22 @@ describe('RCloneApiService', () => {
const { execa } = await import('execa');
const pRetry = await import('p-retry');
const { existsSync } = await import('node:fs');
const { fileExists } = await import('@app/core/utils/files/file-exists.js');
mockGot = vi.mocked(got);
mockExeca = vi.mocked(execa);
mockPRetry = vi.mocked(pRetry.default);
mockExistsSync = vi.mocked(existsSync);
mockGot.post = vi.fn().mockResolvedValue({ body: {} });
// Mock successful RClone API response for socket check
mockGot.post = vi.fn().mockResolvedValue({ body: { pid: 12345 } });
// Mock RClone binary exists check
vi.mocked(fileExists).mockResolvedValue(true);
// Mock socket exists
mockExistsSync.mockReturnValue(true);
mockExeca.mockReturnValue({
on: vi.fn(),
kill: vi.fn(),
@@ -77,10 +95,12 @@ describe('RCloneApiService', () => {
pid: 12345,
} as any);
mockPRetry.mockResolvedValue(undefined);
mockExistsSync.mockReturnValue(false);
service = new RCloneApiService();
await service.onModuleInit();
// Reset the mock after initialization to prepare for test-specific responses
mockGot.post.mockClear();
});
describe('getProviders', () => {
@@ -102,6 +122,9 @@ describe('RCloneApiService', () => {
json: {},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -129,6 +152,11 @@ describe('RCloneApiService', () => {
'http://unix:/tmp/rclone.sock:/config/listremotes',
expect.objectContaining({
json: {},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -155,6 +183,11 @@ describe('RCloneApiService', () => {
'http://unix:/tmp/rclone.sock:/config/get',
expect.objectContaining({
json: { name: 'test-remote' },
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -193,6 +226,11 @@ describe('RCloneApiService', () => {
type: 's3',
parameters: { access_key_id: 'AKIA...', secret_access_key: 'secret' },
},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -217,6 +255,11 @@ describe('RCloneApiService', () => {
name: 'existing-remote',
access_key_id: 'NEW_AKIA...',
},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -235,6 +278,11 @@ describe('RCloneApiService', () => {
'http://unix:/tmp/rclone.sock:/config/delete',
expect.objectContaining({
json: { name: 'remote-to-delete' },
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -261,6 +309,11 @@ describe('RCloneApiService', () => {
dstFs: 'remote:backup/path',
delete_on: 'dst',
},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -279,6 +332,11 @@ describe('RCloneApiService', () => {
'http://unix:/tmp/rclone.sock:/job/status',
expect.objectContaining({
json: { jobid: 'job-123' },
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});
@@ -299,6 +357,11 @@ describe('RCloneApiService', () => {
'http://unix:/tmp/rclone.sock:/job/list',
expect.objectContaining({
json: {},
responseType: 'json',
enableUnixSockets: true,
headers: expect.objectContaining({
Authorization: expect.stringMatching(/^Basic /),
}),
})
);
});

View File

@@ -3,6 +3,7 @@ import '@app/__test__/setup/env-setup.js';
import '@app/__test__/setup/keyserver-mock.js';
import '@app/__test__/setup/config-setup.js';
import '@app/__test__/setup/store-reset.js';
import '@app/__test__/setup/api-json-backup.js';
// This file is automatically loaded by Vitest before running tests
// It imports all the setup files that need to be run before tests

View File

@@ -0,0 +1,36 @@
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join, resolve } from 'path';
import { afterAll, beforeAll } from 'vitest';
// Get the project root directory
const projectRoot = resolve(process.cwd());
const apiJsonPath = join(projectRoot, 'dev/configs/api.json');
const apiJsonBackupPath = join(projectRoot, 'dev/configs/api.json.backup');
let originalContent: string | null = null;
/**
* Backs up api.json before tests run and restores it after tests complete.
* This prevents tests from permanently modifying the development configuration.
*/
export function setupApiJsonBackup() {
beforeAll(() => {
// Save the original content if the file exists
if (existsSync(apiJsonPath)) {
originalContent = readFileSync(apiJsonPath, 'utf-8');
// Create a backup file as well for safety
writeFileSync(apiJsonBackupPath, originalContent, 'utf-8');
}
});
afterAll(() => {
// Restore the original content if we saved it
if (originalContent !== null) {
writeFileSync(apiJsonPath, originalContent, 'utf-8');
}
});
}
// Auto-run for all tests that import this module
setupApiJsonBackup();

View File

@@ -1,303 +0,0 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js';
import { store } from '@app/store/index.js';
import { MyServersConfigMemory } from '@app/types/my-servers-config.js';
describe.skip('config tests', () => {
// Mock dependencies
vi.mock('@app/core/pubsub.js', () => {
const mockPublish = vi.fn();
return {
pubsub: {
publish: mockPublish,
},
PUBSUB_CHANNEL: {
OWNER: 'OWNER',
SERVERS: 'SERVERS',
},
__esModule: true,
default: {
pubsub: {
publish: mockPublish,
},
PUBSUB_CHANNEL: {
OWNER: 'OWNER',
SERVERS: 'SERVERS',
},
},
};
});
// Get the mock function for pubsub.publish
const mockPublish = vi.mocked(pubsub.publish);
// Clear mock before each test
beforeEach(() => {
mockPublish.mockClear();
});
vi.mock('@app/mothership/graphql-client.js', () => ({
GraphQLClient: {
clearInstance: vi.fn(),
},
}));
vi.mock('@app/mothership/jobs/ping-timeout-jobs.js', () => ({
stopPingTimeoutJobs: vi.fn(),
}));
const createConfigMatcher = (specificValues: Partial<MyServersConfigMemory> = {}) => {
const defaultMatcher = {
api: expect.objectContaining({
extraOrigins: expect.any(String),
version: expect.any(String),
}),
connectionStatus: expect.objectContaining({
minigraph: expect.any(String),
upnpStatus: expect.any(String),
}),
local: expect.objectContaining({
sandbox: expect.any(String),
}),
nodeEnv: expect.any(String),
remote: expect.objectContaining({
accesstoken: expect.any(String),
allowedOrigins: expect.any(String),
apikey: expect.any(String),
avatar: expect.any(String),
dynamicRemoteAccessType: expect.any(String),
email: expect.any(String),
idtoken: expect.any(String),
localApiKey: expect.any(String),
refreshtoken: expect.any(String),
regWizTime: expect.any(String),
ssoSubIds: expect.any(String),
upnpEnabled: expect.any(String),
username: expect.any(String),
wanaccess: expect.any(String),
wanport: expect.any(String),
}),
status: expect.any(String),
};
return expect.objectContaining({
...defaultMatcher,
...specificValues,
});
};
// test('Before init returns default values for all fields', async () => {
// const state = store.getState().config;
// expect(state).toMatchSnapshot();
// }, 10_000);
test('After init returns values from cfg file for all fields', async () => {
const { loadConfigFile } = await import('@app/store/modules/config.js');
// Load cfg into store
await store.dispatch(loadConfigFile());
// Check if store has cfg contents loaded
const state = store.getState().config;
expect(state).toMatchObject(createConfigMatcher());
});
test('updateUserConfig merges in changes to current state', async () => {
const { loadConfigFile, updateUserConfig } = await import('@app/store/modules/config.js');
// Load cfg into store
await store.dispatch(loadConfigFile());
// Update store
store.dispatch(
updateUserConfig({
remote: { avatar: 'https://via.placeholder.com/200' },
})
);
const state = store.getState().config;
expect(state).toMatchObject(
createConfigMatcher({
remote: expect.objectContaining({
avatar: 'https://via.placeholder.com/200',
}),
})
);
});
test('loginUser updates state and publishes to pubsub', async () => {
const { loginUser } = await import('@app/store/modules/config.js');
const userInfo = {
email: 'test@example.com',
avatar: 'https://via.placeholder.com/200',
username: 'testuser',
apikey: 'test-api-key',
localApiKey: 'test-local-api-key',
};
await store.dispatch(loginUser(userInfo));
expect(pubsub.publish).toHaveBeenCalledWith(PUBSUB_CHANNEL.OWNER, {
owner: {
username: userInfo.username,
url: '',
avatar: userInfo.avatar,
},
});
const state = store.getState().config;
expect(state).toMatchObject(
createConfigMatcher({
remote: expect.objectContaining(userInfo),
})
);
});
test('logoutUser clears state and publishes to pubsub', async () => {
const { logoutUser } = await import('@app/store/modules/config.js');
await store.dispatch(logoutUser({ reason: 'test logout' }));
expect(pubsub.publish).toHaveBeenCalledWith(PUBSUB_CHANNEL.SERVERS, { servers: [] });
expect(pubsub.publish).toHaveBeenCalledWith(PUBSUB_CHANNEL.OWNER, {
owner: {
username: 'root',
url: '',
avatar: '',
},
});
// expect(stopPingTimeoutJobs).toHaveBeenCalled();
// expect(GraphQLClient.clearInstance).toHaveBeenCalled();
});
test('updateAccessTokens updates token fields', async () => {
const { updateAccessTokens } = await import('@app/store/modules/config.js');
const tokens = {
accesstoken: 'new-access-token',
refreshtoken: 'new-refresh-token',
idtoken: 'new-id-token',
};
store.dispatch(updateAccessTokens(tokens));
const state = store.getState().config;
expect(state).toMatchObject(
createConfigMatcher({
remote: expect.objectContaining(tokens),
})
);
});
test('updateAllowedOrigins updates extraOrigins', async () => {
const { updateAllowedOrigins } = await import('@app/store/modules/config.js');
const origins = ['https://test1.com', 'https://test2.com'];
store.dispatch(updateAllowedOrigins(origins));
const state = store.getState().config;
expect(state.api.extraOrigins).toBe(origins.join(', '));
});
test('setUpnpState updates upnp settings', async () => {
const { setUpnpState } = await import('@app/store/modules/config.js');
store.dispatch(setUpnpState({ enabled: 'yes', status: 'active' }));
const state = store.getState().config;
expect(state.remote.upnpEnabled).toBe('yes');
expect(state.connectionStatus.upnpStatus).toBe('active');
});
test('setWanPortToValue updates wanport', async () => {
const { setWanPortToValue } = await import('@app/store/modules/config.js');
store.dispatch(setWanPortToValue(8443));
const state = store.getState().config;
expect(state.remote.wanport).toBe('8443');
});
test('setWanAccess updates wanaccess', async () => {
const { setWanAccess } = await import('@app/store/modules/config.js');
store.dispatch(setWanAccess('yes'));
const state = store.getState().config;
expect(state.remote.wanaccess).toBe('yes');
});
// test('addSsoUser adds user to ssoSubIds', async () => {
// const { addSsoUser } = await import('@app/store/modules/config.js');
// store.dispatch(addSsoUser('user1'));
// store.dispatch(addSsoUser('user2'));
// const state = store.getState().config;
// expect(state.remote.ssoSubIds).toBe('user1,user2');
// });
// test('removeSsoUser removes user from ssoSubIds', async () => {
// const { addSsoUser, removeSsoUser } = await import('@app/store/modules/config.js');
// store.dispatch(addSsoUser('user1'));
// store.dispatch(addSsoUser('user2'));
// store.dispatch(removeSsoUser('user1'));
// const state = store.getState().config;
// expect(state.remote.ssoSubIds).toBe('user2');
// });
// test('removeSsoUser with null clears all ssoSubIds', async () => {
// const { addSsoUser, removeSsoUser } = await import('@app/store/modules/config.js');
// store.dispatch(addSsoUser('user1'));
// store.dispatch(addSsoUser('user2'));
// store.dispatch(removeSsoUser(null));
// const state = store.getState().config;
// expect(state.remote.ssoSubIds).toBe('');
// });
test('setLocalApiKey updates localApiKey', async () => {
const { setLocalApiKey } = await import('@app/store/modules/config.js');
store.dispatch(setLocalApiKey('new-local-api-key'));
const state = store.getState().config;
expect(state.remote.localApiKey).toBe('new-local-api-key');
});
test('setLocalApiKey with null clears localApiKey', async () => {
const { setLocalApiKey } = await import('@app/store/modules/config.js');
store.dispatch(setLocalApiKey(null));
const state = store.getState().config;
expect(state.remote.localApiKey).toBe('');
});
// test('setGraphqlConnectionStatus updates minigraph status', async () => {
// store.dispatch(setGraphqlConnectionStatus({ status: MinigraphStatus.CONNECTED, error: null }));
// const state = store.getState().config;
// expect(state.connectionStatus.minigraph).toBe(MinigraphStatus.CONNECTED);
// });
// test('setupRemoteAccessThunk.fulfilled updates remote access settings', async () => {
// const remoteAccessSettings = {
// accessType: WAN_ACCESS_TYPE.DYNAMIC,
// forwardType: WAN_FORWARD_TYPE.UPNP,
// };
// await store.dispatch(setupRemoteAccessThunk(remoteAccessSettings));
// const state = store.getState().config;
// expect(state.remote).toMatchObject({
// wanaccess: 'no',
// dynamicRemoteAccessType: 'UPNP',
// wanport: '',
// upnpEnabled: 'yes',
// });
// });
});

View File

@@ -1,5 +1,6 @@
import { expect, test } from 'vitest';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { parseConfig } from '@app/core/utils/misc/parse-config.js';
import { store } from '@app/store/index.js';
import { FileLoadStatus } from '@app/store/types.js';
@@ -24,7 +25,7 @@ test('Before init returns default values for all fields', async () => {
`);
});
test('After init returns values from cfg file for all fields', async () => {
test('After init returns values from cfg file for all fields', { timeout: 30000 }, async () => {
const { loadStateFiles } = await import('@app/store/modules/emhttp.js');
// Load state files into store
@@ -446,6 +447,44 @@ test('After init returns values from cfg file for all fields', async () => {
"splitLevel": "1",
"used": 33619300,
},
{
"allocator": "highwater",
"cache": false,
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with periods",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.periods",
"include": [],
"luksStatus": "0",
"name": "system.with.periods",
"nameOrig": "system.with.periods",
"size": 0,
"splitLevel": "1",
"used": 33619300,
},
{
"allocator": "highwater",
"cache": false,
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with 🚀",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.🚀",
"include": [],
"luksStatus": "0",
"name": "system.with.🚀",
"nameOrig": "system.with.🚀",
"size": 0,
"splitLevel": "1",
"used": 33619300,
},
]
`);
expect(nfsShares).toMatchInlineSnapshot(`
@@ -1110,3 +1149,209 @@ test('After init returns values from cfg file for all fields', async () => {
}
`);
});
describe('Share parsing with periods in names', () => {
beforeEach(() => {
vi.clearAllMocks();
});
test('parseConfig handles periods in INI section names', () => {
const mockIniContent = `
["share.with.periods"]
name=share.with.periods
useCache=yes
include=
exclude=
[normal_share]
name=normal_share
useCache=no
include=
exclude=
`;
const result = parseConfig<any>({
file: mockIniContent,
type: 'ini',
});
// The result should now have properly flattened keys
expect(result).toHaveProperty('shareWithPeriods');
expect(result).toHaveProperty('normalShare');
expect(result.shareWithPeriods.name).toBe('share.with.periods');
expect(result.normalShare.name).toBe('normal_share');
});
test('shares parser handles periods in share names correctly', async () => {
const { parse } = await import('@app/store/state-parsers/shares.js');
// The parser expects an object where values are share configs
const mockSharesState = {
shareWithPeriods: {
name: 'share.with.periods',
free: '1000000',
used: '500000',
size: '1500000',
include: '',
exclude: '',
useCache: 'yes',
},
normalShare: {
name: 'normal_share',
free: '2000000',
used: '750000',
size: '2750000',
include: '',
exclude: '',
useCache: 'no',
},
} as any;
const result = parse(mockSharesState);
expect(result).toHaveLength(2);
const periodShare = result.find((s) => s.name === 'share.with.periods');
const normalShare = result.find((s) => s.name === 'normal_share');
expect(periodShare).toBeDefined();
expect(periodShare?.id).toBe('share.with.periods');
expect(periodShare?.name).toBe('share.with.periods');
expect(periodShare?.cache).toBe(true);
expect(normalShare).toBeDefined();
expect(normalShare?.id).toBe('normal_share');
expect(normalShare?.name).toBe('normal_share');
expect(normalShare?.cache).toBe(false);
});
test('SMB parser handles periods in share names', async () => {
const { parse } = await import('@app/store/state-parsers/smb.js');
const mockSmbState = {
'share.with.periods': {
export: 'e',
security: 'public',
writeList: '',
readList: '',
volsizelimit: '0',
},
normal_share: {
export: 'e',
security: 'private',
writeList: 'user1,user2',
readList: '',
volsizelimit: '1000',
},
} as any;
const result = parse(mockSmbState);
expect(result).toHaveLength(2);
const periodShare = result.find((s) => s.name === 'share.with.periods');
const normalShare = result.find((s) => s.name === 'normal_share');
expect(periodShare).toBeDefined();
expect(periodShare?.name).toBe('share.with.periods');
expect(periodShare?.enabled).toBe(true);
expect(normalShare).toBeDefined();
expect(normalShare?.name).toBe('normal_share');
expect(normalShare?.writeList).toEqual(['user1', 'user2']);
});
test('NFS parser handles periods in share names', async () => {
const { parse } = await import('@app/store/state-parsers/nfs.js');
const mockNfsState = {
'share.with.periods': {
export: 'e',
security: 'public',
writeList: '',
readList: 'user1',
hostList: '',
},
normal_share: {
export: 'd',
security: 'private',
writeList: 'user2',
readList: '',
hostList: '192.168.1.0/24',
},
} as any;
const result = parse(mockNfsState);
expect(result).toHaveLength(2);
const periodShare = result.find((s) => s.name === 'share.with.periods');
const normalShare = result.find((s) => s.name === 'normal_share');
expect(periodShare).toBeDefined();
expect(periodShare?.name).toBe('share.with.periods');
expect(periodShare?.enabled).toBe(true);
expect(periodShare?.readList).toEqual(['user1']);
expect(normalShare).toBeDefined();
expect(normalShare?.name).toBe('normal_share');
expect(normalShare?.enabled).toBe(false);
});
});
describe('Share lookup with periods in names', () => {
test('getShares finds user shares with periods in names', async () => {
// Mock the store state
const mockStore = await import('@app/store/index.js');
const mockEmhttpState = {
shares: [
{
id: 'share.with.periods',
name: 'share.with.periods',
cache: true,
free: 1000000,
used: 500000,
size: 1500000,
include: [],
exclude: [],
},
{
id: 'normal_share',
name: 'normal_share',
cache: false,
free: 2000000,
used: 750000,
size: 2750000,
include: [],
exclude: [],
},
],
smbShares: [
{ name: 'share.with.periods', enabled: true, security: 'public' },
{ name: 'normal_share', enabled: true, security: 'private' },
],
nfsShares: [
{ name: 'share.with.periods', enabled: false },
{ name: 'normal_share', enabled: true },
],
disks: [],
};
const gettersSpy = vi.spyOn(mockStore, 'getters', 'get').mockReturnValue({
emhttp: () => mockEmhttpState,
} as any);
const { getShares } = await import('@app/core/utils/shares/get-shares.js');
const periodShare = getShares('user', { name: 'share.with.periods' });
const normalShare = getShares('user', { name: 'normal_share' });
expect(periodShare).not.toBeNull();
expect(periodShare?.name).toBe('share.with.periods');
expect(periodShare?.type).toBe('user');
expect(normalShare).not.toBeNull();
expect(normalShare?.name).toBe('normal_share');
expect(normalShare?.type).toBe('user');
gettersSpy.mockRestore();
});
});

View File

@@ -92,6 +92,44 @@ test('Returns parsed state file', async () => {
"splitLevel": "1",
"used": 33619300,
},
{
"allocator": "highwater",
"cache": false,
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with periods",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.periods",
"include": [],
"luksStatus": "0",
"name": "system.with.periods",
"nameOrig": "system.with.periods",
"size": 0,
"splitLevel": "1",
"used": 33619300,
},
{
"allocator": "highwater",
"cache": false,
"cachePool": "cache",
"color": "yellow-on",
"comment": "system data with 🚀",
"cow": "auto",
"exclude": [],
"floor": "0",
"free": 9309372,
"id": "system.with.🚀",
"include": [],
"luksStatus": "0",
"name": "system.with.🚀",
"nameOrig": "system.with.🚀",
"size": 0,
"splitLevel": "1",
"used": 33619300,
},
]
`);
});

View File

@@ -1,34 +0,0 @@
import { expect, test, vi } from 'vitest';
import { store } from '@app/store/index.js';
import { loadStateFiles } from '@app/store/modules/emhttp.js';
import { loadRegistrationKey } from '@app/store/modules/registration.js';
import { createRegistrationEvent } from '@app/store/sync/registration-sync.js';
vi.mock('@app/core/pubsub', () => ({
pubsub: { publish: vi.fn() },
}));
test('Creates a registration event', async () => {
// Load state files into store
const config = await store.dispatch(loadStateFiles()).unwrap();
await store.dispatch(loadRegistrationKey());
expect(config.var.regFile).toBe('/app/dev/Unraid.net/Pro.key');
const state = store.getState();
const registrationEvent = createRegistrationEvent(state);
expect(registrationEvent).toMatchInlineSnapshot(`
{
"registration": {
"guid": "13FE-4200-C300-58C372A52B19",
"keyFile": {
"contents": "hVs1tLjvC9FiiQsIwIQ7G1KszAcexf0IneThhnmf22SB0dGs5WzRkqMiSMmt2DtR5HOXFUD32YyxuzGeUXmky3zKpSu6xhZNKVg5atGM1OfvkzHBMldI3SeBLuUFSgejLbpNUMdTrbk64JJdbzle4O8wiQgkIpAMIGxeYLwLBD4zHBcfyzq40QnxG--HcX6j25eE0xqa2zWj-j0b0rCAXahJV2a3ySCbPzr1MvfPRTVb0rr7KJ-25R592hYrz4H7Sc1B3p0lr6QUxHE6o7bcYrWKDRtIVoZ8SMPpd1_0gzYIcl5GsDFzFumTXUh8NEnl0Q8hwW1YE-tRc6Y_rrvd7w",
"location": "/app/dev/Unraid.net/Pro.key",
},
"state": "PRO",
"type": "PRO",
},
}
`);
});

View File

@@ -1,20 +0,0 @@
import { type Mapping } from '@runonflux/nat-upnp';
import { expect, test, vi } from 'vitest';
import { getWanPortForUpnp } from '@app/upnp/helpers.js';
test('it successfully gets a wan port given no exclusions', () => {
const port = getWanPortForUpnp(null, 36_000, 38_000);
expect(port).toBeGreaterThan(35_999);
expect(port).toBeLessThan(38_001);
});
test('it fails to get a wan port given exclusions', () => {
const port = getWanPortForUpnp([{ public: { port: 36_000 } }] as Mapping[], 36_000, 36_000);
expect(port).toBeNull();
});
test('it succeeds in getting a wan port given exclusions', () => {
const port = getWanPortForUpnp([{ public: { port: 36_000 } }] as Mapping[], 30_000, 36_000);
expect(port).not.toBeNull();
});

View File

@@ -1,29 +1,37 @@
import '@app/dotenv.js';
import { execa } from 'execa';
import { Logger } from '@nestjs/common';
import { CommandFactory } from 'nest-commander';
import { internalLogger, logger } from '@app/core/log.js';
import { LOG_LEVEL } from '@app/environment.js';
import { CliModule } from '@app/unraid-api/cli/cli.module.js';
import { LOG_LEVEL, SUPPRESS_LOGS } from '@app/environment.js';
import { LogService } from '@app/unraid-api/cli/log.service.js';
const getUnraidApiLocation = async () => {
const { execa } = await import('execa');
try {
const shellToUse = await execa('which unraid-api');
return shellToUse.stdout.trim();
} catch (err) {
logger.debug('Could not find unraid-api in PATH, using default location');
return '/usr/bin/unraid-api';
}
};
const getLogger = () => {
if (LOG_LEVEL === 'TRACE' && !SUPPRESS_LOGS) {
return new LogService();
}
return false;
};
const logger = getLogger();
try {
await import('json-bigint-patch');
const { CliModule } = await import('@app/unraid-api/cli/cli.module.js');
await CommandFactory.run(CliModule, {
cliName: 'unraid-api',
logger: LOG_LEVEL === 'TRACE' ? new LogService() : false, // - enable this to see nest initialization issues
logger: logger, // - enable this to see nest initialization issues
completion: {
fig: false,
cmd: 'completion-script',
@@ -32,10 +40,8 @@ try {
});
process.exit(0);
} catch (error) {
logger.error('ERROR:', error);
internalLogger.error({
message: 'Failed to start unraid-api',
error,
});
if (logger) {
logger.error('ERROR:', error);
}
process.exit(1);
}

View File

@@ -1,99 +0,0 @@
import { uniq } from 'lodash-es';
import type { RootState } from '@app/store/index.js';
import { logger } from '@app/core/log.js';
import { GRAPHQL_INTROSPECTION } from '@app/environment.js';
import { getServerIps, getUrlForField } from '@app/graphql/resolvers/subscription/network.js';
import { getters, store } from '@app/store/index.js';
import { FileLoadStatus } from '@app/store/types.js';
const getAllowedSocks = (): string[] => [
// Notifier bridge
'/var/run/unraid-notifications.sock',
// Unraid PHP scripts
'/var/run/unraid-php.sock',
// CLI
'/var/run/unraid-cli.sock',
];
const getLocalAccessUrlsForServer = (state: RootState = store.getState()): string[] => {
const { emhttp } = state;
if (emhttp.status !== FileLoadStatus.LOADED) {
return [];
}
const { nginx } = emhttp;
try {
return [
getUrlForField({
url: 'localhost',
port: nginx.httpPort,
}).toString(),
getUrlForField({
url: 'localhost',
portSsl: nginx.httpsPort,
}).toString(),
];
} catch (error: unknown) {
logger.debug('Caught error in getLocalAccessUrlsForServer: \n%o', error);
return [];
}
};
const getRemoteAccessUrlsForAllowedOrigins = (state: RootState = store.getState()): string[] => {
const { urls } = getServerIps(state);
if (urls) {
return urls.reduce<string[]>((acc, curr) => {
if ((curr.ipv4 && curr.ipv6) || curr.ipv4) {
acc.push(curr.ipv4.toString());
} else if (curr.ipv6) {
acc.push(curr.ipv6.toString());
}
return acc;
}, []);
}
return [];
};
export const getExtraOrigins = (): string[] => {
const { extraOrigins } = getters.config().api;
if (extraOrigins) {
return extraOrigins
.replaceAll(' ', '')
.split(',')
.filter((origin) => origin.startsWith('http://') || origin.startsWith('https://'));
}
return [];
};
const getConnectOrigins = (): string[] => {
const connectMain = 'https://connect.myunraid.net';
const connectStaging = 'https://connect-staging.myunraid.net';
const connectDev = 'https://dev-my.myunraid.net:4000';
return [connectMain, connectStaging, connectDev];
};
const getApolloSandbox = (): string[] => {
if (GRAPHQL_INTROSPECTION) {
return ['https://studio.apollographql.com'];
}
return [];
};
export const getAllowedOrigins = (state: RootState = store.getState()): string[] =>
uniq([
...getAllowedSocks(),
...getLocalAccessUrlsForServer(state),
...getRemoteAccessUrlsForAllowedOrigins(state),
...getExtraOrigins(),
...getConnectOrigins(),
...getApolloSandbox(),
]).map((url) => (url.endsWith('/') ? url.slice(0, -1) : url));

View File

@@ -1,7 +1,7 @@
import { pino } from 'pino';
import pino from 'pino';
import pretty from 'pino-pretty';
import { API_VERSION, LOG_LEVEL, LOG_TYPE } from '@app/environment.js';
import { API_VERSION, LOG_LEVEL, LOG_TYPE, PATHS_LOGS_FILE, SUPPRESS_LOGS } from '@app/environment.js';
export const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const;
@@ -9,18 +9,46 @@ export type LogLevel = (typeof levels)[number];
const level = levels[levels.indexOf(LOG_LEVEL.toLowerCase() as LogLevel)] ?? 'info';
export const logDestination = pino.destination();
const nullDestination = pino.destination({
write() {
// Suppress all logs
},
});
const stream =
LOG_TYPE === 'pretty'
? pretty({
singleLine: true,
hideObject: false,
colorize: true,
ignore: 'hostname,pid',
destination: logDestination,
})
: logDestination;
export const logDestination =
process.env.SUPPRESS_LOGS === 'true' ? nullDestination : pino.destination();
const localFileDestination = pino.destination({
dest: PATHS_LOGS_FILE,
sync: true,
});
const stream = SUPPRESS_LOGS
? nullDestination
: LOG_TYPE === 'pretty'
? pretty({
singleLine: true,
hideObject: false,
colorize: true,
colorizeObjects: true,
levelFirst: false,
ignore: 'hostname,pid',
destination: logDestination,
translateTime: 'HH:mm:ss',
customPrettifiers: {
time: (timestamp: string | object) => `[${timestamp}`,
level: (logLevel: string | object, key: string, log: any, extras: any) => {
// Use labelColorized which preserves the colors
const { labelColorized } = extras;
const context = log.context || log.logger || 'app';
return `${labelColorized} ${context}]`;
},
},
messageFormat: (log: any, messageKey: string) => {
const msg = log[messageKey] || log.msg || '';
return msg;
},
})
: logDestination;
export const logger = pino(
{
@@ -70,6 +98,7 @@ export const keyServerLogger = logger.child({ logger: 'key-server' });
export const remoteAccessLogger = logger.child({ logger: 'remote-access' });
export const remoteQueryLogger = logger.child({ logger: 'remote-query' });
export const apiLogger = logger.child({ logger: 'api' });
export const pluginLogger = logger.child({ logger: 'plugin', stream: localFileDestination });
export const loggers = [
internalLogger,

View File

@@ -1,6 +1,7 @@
import { GraphQLError } from 'graphql';
import { sum } from 'lodash-es';
import { getParityCheckStatus } from '@app/core/modules/array/parity-check-status.js';
import { store } from '@app/store/index.js';
import { FileLoadStatus } from '@app/store/types.js';
import {
@@ -61,5 +62,6 @@ export const getArrayData = (getState = store.getState): UnraidArray => {
parities,
disks,
caches,
parityCheckStatus: getParityCheckStatus(emhttp.var),
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
import { toNumberAlways } from '@unraid/shared/util/data.js';
import type { Var } from '@app/core/types/states/var.js';
import type { ParityCheck } from '@app/unraid-api/graph/resolvers/array/parity.model.js';
export enum ParityCheckStatus {
NEVER_RUN = 'never_run',
RUNNING = 'running',
PAUSED = 'paused',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
FAILED = 'failed',
}
function calculateParitySpeed(deltaTime: number, deltaBlocks: number) {
if (deltaTime === 0 || deltaBlocks === 0) return 0;
const deltaBytes = deltaBlocks * 1024;
const speedMBps = deltaBytes / deltaTime / 1024 / 1024;
return Math.round(speedMBps);
}
type RelevantVarData = Pick<
Var,
| 'mdResyncPos'
| 'mdResyncDt'
| 'sbSyncExit'
| 'sbSynced'
| 'sbSynced2'
| 'mdResyncDb'
| 'mdResyncSize'
>;
function getStatusFromVarData(varData: RelevantVarData): ParityCheckStatus {
const { mdResyncPos, mdResyncDt, sbSyncExit, sbSynced, sbSynced2 } = varData;
const mdResyncDtNumber = toNumberAlways(mdResyncDt, 0);
const sbSyncExitNumber = toNumberAlways(sbSyncExit, 0);
switch (true) {
case mdResyncPos > 0:
return mdResyncDtNumber > 0 ? ParityCheckStatus.RUNNING : ParityCheckStatus.PAUSED;
case sbSynced === 0:
return ParityCheckStatus.NEVER_RUN;
case sbSyncExitNumber === -4:
return ParityCheckStatus.CANCELLED;
case sbSyncExitNumber !== 0:
return ParityCheckStatus.FAILED;
case sbSynced2 > 0:
return ParityCheckStatus.COMPLETED;
default:
return ParityCheckStatus.NEVER_RUN;
}
}
export function getParityCheckStatus(varData: RelevantVarData): ParityCheck {
const { sbSynced, sbSynced2, mdResyncDt, mdResyncDb, mdResyncPos, mdResyncSize } = varData;
const deltaTime = toNumberAlways(mdResyncDt, 0);
const deltaBlocks = toNumberAlways(mdResyncDb, 0);
// seconds since epoch (unix timestamp)
const now = sbSynced2 > 0 ? sbSynced2 : Date.now() / 1000;
return {
status: getStatusFromVarData(varData),
speed: String(calculateParitySpeed(deltaTime, deltaBlocks)),
date: sbSynced > 0 ? new Date(sbSynced * 1000) : undefined,
duration: sbSynced > 0 ? Math.round(now - sbSynced) : undefined,
// percentage as integer, clamped to [0, 100]
progress:
mdResyncSize <= 0
? 0
: Math.round(Math.min(100, Math.max(0, (mdResyncPos / mdResyncSize) * 100))),
};
}

View File

@@ -8,7 +8,7 @@ export class NginxManager {
await execa('/etc/rc.d/rc.nginx', ['reload']);
return true;
} catch (err: unknown) {
logger.warn('Failed to restart Nginx with error: ', err);
logger.warn('Failed to restart Nginx with error: %o', err as object);
return false;
}
};

View File

@@ -8,7 +8,7 @@ export class UpdateDNSManager {
await execa('/usr/bin/php', ['/usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php']);
return true;
} catch (err: unknown) {
logger.warn('Failed to call Update DNS with error: ', err);
logger.warn('Failed to call Update DNS with error: %o', err as object);
return false;
}
};

View File

@@ -13,8 +13,11 @@ export const pubsub = new PubSub({ eventEmitter });
/**
* Create a pubsub subscription.
* @param channel The pubsub channel to subscribe to.
* @param channel The pubsub channel to subscribe to. Can be either a predefined GRAPHQL_PUBSUB_CHANNEL
* or a dynamic string for runtime-generated topics (e.g., log file paths like "LOG_FILE:/var/log/test.log")
*/
export const createSubscription = (channel: GRAPHQL_PUBSUB_CHANNEL) => {
return pubsub.asyncIterableIterator(channel);
export const createSubscription = <T = any>(
channel: GRAPHQL_PUBSUB_CHANNEL | string
): AsyncIterableIterator<T> => {
return pubsub.asyncIterableIterator<T>(channel);
};

View File

@@ -68,11 +68,24 @@ export type Var = {
mdNumStripes: number;
mdNumStripesDefault: number;
mdNumStripesStatus: string;
/**
* Serves a dual purpose depending on context:
* - Total size of the operation (in sectors/blocks)
* - Running state indicator (0 = paused, >0 = running)
*/
mdResync: number;
mdResyncAction: string;
mdResyncCorr: string;
mdResyncDb: string;
/** Average time interval (delta time) in seconds of current parity operations */
mdResyncDt: string;
/**
* Current position in the parity operation (in sectors/blocks).
* When mdResyncPos > 0, a parity operation is active.
* When mdResyncPos = 0, no parity operation is running.
*
* Used to calculate progress percentage.
*/
mdResyncPos: number;
mdResyncSize: number;
mdState: ArrayState;
@@ -136,9 +149,36 @@ export type Var = {
sbName: string;
sbNumDisks: number;
sbState: string;
/**
* Unix timestamp when parity operation started.
* When sbSynced = 0, indicates no parity check has ever been run.
*
* Used to calculate elapsed time during active operations.
*/
sbSynced: number;
sbSynced2: number;
/**
* Unix timestamp when parity operation completed (successfully or with errors).
* Used to display completion time in status messages.
*
* When sbSynced2 = 0, indicates operation started but not yet finished
*/
sbSyncErrs: number;
/**
* Exit status code that indicates how the last parity operation completed, following standard Unix conventions.
*
* sbSyncExit = 0 - Successful Completion
* - Parity operation completed normally without errors
* - Used to calculate speed and display success message
*
* sbSyncExit = -4 - Aborted/Cancelled
* - Operation was manually cancelled by user
* - Displays as "aborted" in the UI
*
* sbSyncExit ≠ 0 (other values) - Failed/Incomplete
* - Operation failed due to errors or other issues
* - Displays the numeric error code
*/
sbSyncExit: string;
sbUpdated: string;
sbVersion: string;

View File

@@ -1,40 +0,0 @@
import { isEqual, merge } from 'lodash-es';
import { getAllowedOrigins } from '@app/common/allowed-origins.js';
import { initialState } from '@app/store/modules/config.js';
import {
MyServersConfig,
MyServersConfigMemory,
MyServersConfigMemorySchema,
MyServersConfigSchema,
} from '@app/types/my-servers-config.js';
// Define ConfigType and ConfigObject
export type ConfigType = 'flash' | 'memory';
/**
* Get a writeable configuration based on the mode ('flash' or 'memory').
*/
export const getWriteableConfig = <T extends ConfigType>(
config: T extends 'memory' ? MyServersConfigMemory : MyServersConfig,
mode: T
): T extends 'memory' ? MyServersConfigMemory : MyServersConfig => {
const schema = mode === 'memory' ? MyServersConfigMemorySchema : MyServersConfigSchema;
const defaultConfig = schema.parse(initialState);
// Use a type assertion for the mergedConfig to include `connectionStatus` only if `mode === 'memory`
const mergedConfig = merge<
MyServersConfig,
T extends 'memory' ? MyServersConfigMemory : MyServersConfig
>(defaultConfig, config);
if (mode === 'memory') {
(mergedConfig as MyServersConfigMemory).remote.allowedOrigins = getAllowedOrigins().join(', ');
(mergedConfig as MyServersConfigMemory).connectionStatus = {
...(defaultConfig as MyServersConfigMemory).connectionStatus,
...(config as MyServersConfigMemory).connectionStatus,
};
}
return schema.parse(mergedConfig) as T extends 'memory' ? MyServersConfigMemory : MyServersConfig; // Narrowing ensures correct typing
};

View File

@@ -26,7 +26,7 @@ export const loadState = <T extends Record<string, unknown>>(filePath: string):
logger.trace(
'Failed loading state file "%s" with "%s"',
filePath,
error instanceof Error ? error.message : error
error instanceof Error ? error.message : String(error)
);
}

View File

@@ -23,6 +23,54 @@ type OptionsWithLoadedFile = {
type: ConfigType;
};
/**
* Flattens nested objects that were incorrectly created by periods in INI section names.
* For example: { system: { with: { periods: {...} } } } -> { "system.with.periods": {...} }
*/
const flattenPeriodSections = (obj: Record<string, any>, prefix = ''): Record<string, any> => {
const result: Record<string, any> = {};
const isNestedObject = (value: unknown) =>
Boolean(value && typeof value === 'object' && !Array.isArray(value));
// prevent prototype pollution/injection
const isUnsafeKey = (k: string) => k === '__proto__' || k === 'prototype' || k === 'constructor';
for (const [key, value] of Object.entries(obj)) {
if (isUnsafeKey(key)) continue;
const fullKey = prefix ? `${prefix}.${key}` : key;
if (!isNestedObject(value)) {
result[fullKey] = value;
continue;
}
const section = {};
const nestedObjs = {};
let hasSectionProps = false;
for (const [propKey, propValue] of Object.entries(value)) {
if (isUnsafeKey(propKey)) continue;
if (isNestedObject(propValue)) {
nestedObjs[propKey] = propValue;
} else {
section[propKey] = propValue;
hasSectionProps = true;
}
}
// Process direct properties first to maintain order
if (hasSectionProps) {
result[fullKey] = section;
}
// Then process nested objects
if (Object.keys(nestedObjs).length > 0) {
Object.assign(result, flattenPeriodSections(nestedObjs, fullKey));
}
}
return result;
};
/**
* Converts the following
* ```
@@ -127,6 +175,8 @@ export const parseConfig = <T extends Record<string, any>>(
let data: Record<string, any>;
try {
data = parseIni(fileContents);
// Fix nested objects created by periods in section names
data = flattenPeriodSections(data);
} catch (error) {
throw new AppError(
`Failed to parse config file: ${error instanceof Error ? error.message : String(error)}`

View File

@@ -1,25 +1,40 @@
export const isUnraidApiRunning = async (): Promise<boolean | undefined> => {
const { connect, describe, disconnect } = await import('pm2');
return new Promise((resolve, reject) => {
connect(function (err) {
const { PM2_HOME } = await import('@app/environment.js');
// Set PM2_HOME if not already set
if (!process.env.PM2_HOME) {
process.env.PM2_HOME = PM2_HOME;
}
const pm2Module = await import('pm2');
const pm2 = pm2Module.default || pm2Module;
const pm2Promise = new Promise<boolean>((resolve) => {
pm2.connect(function (err) {
if (err) {
console.error(err);
reject('Could not connect to pm2');
// Don't reject here, resolve with false since we can't connect to PM2
resolve(false);
return;
}
describe('unraid-api', function (err, processDescription) {
console.log(err);
// Now try to describe unraid-api specifically
pm2.describe('unraid-api', function (err, processDescription) {
if (err || processDescription.length === 0) {
console.log(false); // Service not found or error occurred
// Service not found or error occurred
resolve(false);
} else {
const isOnline = processDescription?.[0]?.pm2_env?.status === 'online';
console.log(isOnline); // Output true if online, false otherwise
resolve(isOnline);
}
disconnect();
pm2.disconnect();
});
});
});
const timeoutPromise = new Promise<boolean>((resolve) => {
setTimeout(() => resolve(false), 10000); // 10 second timeout
});
return Promise.race([pm2Promise, timeoutPromise]);
};

View File

@@ -0,0 +1,17 @@
export function isValidEnumValue<T extends Record<string, string | number>>(
value: unknown,
enumObject: T
): value is T[keyof T] {
if (value == null) {
return false;
}
return Object.values(enumObject).includes(value as T[keyof T]);
}
export function validateEnumValue<T extends Record<string, string | number>>(
value: unknown,
enumObject: T
): T[keyof T] | undefined {
return isValidEnumValue(value, enumObject) ? (value as T[keyof T]) : undefined;
}

View File

@@ -13,7 +13,7 @@ const isGuiMode = async (): Promise<boolean> => {
// exitCode 0 means process was found, 1 means not found
return exitCode === 0;
} catch (error) {
internalLogger.error('Error checking GUI mode: %s', error);
internalLogger.error('Error checking GUI mode: %o', error as object);
return false;
}
};

View File

@@ -0,0 +1,437 @@
import { describe, expect, it } from 'vitest';
import type { ValidationResult } from '@app/core/utils/validation/validation-processor.js';
import {
createValidationProcessor,
ResultInterpreters,
} from '@app/core/utils/validation/validation-processor.js';
describe('ValidationProcessor', () => {
type TestInput = { value: number; text: string };
it('should process all validation steps when no errors occur', () => {
const steps = [
{
name: 'positiveValue',
validator: (input: TestInput) => input.value > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'nonEmptyText',
validator: (input: TestInput) => input.text.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const;
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: 5, text: 'hello' }, { failFast: false });
expect(result.isValid).toBe(true);
expect(result.errors).toEqual({});
});
it('should collect all errors when failFast is disabled', () => {
const steps = [
{
name: 'positiveValue',
validator: (input: TestInput) => input.value > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'nonEmptyText',
validator: (input: TestInput) => input.text.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const;
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: -1, text: '' }, { failFast: false });
expect(result.isValid).toBe(false);
expect(result.errors.positiveValue).toBe(false);
expect(result.errors.nonEmptyText).toBe(false);
});
it('should stop at first error when failFast is enabled', () => {
const steps = [
{
name: 'positiveValue',
validator: (input: TestInput) => input.value > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'nonEmptyText',
validator: (input: TestInput) => input.text.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const;
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: -1, text: '' }, { failFast: true });
expect(result.isValid).toBe(false);
expect(result.errors.positiveValue).toBe(false);
expect(result.errors.nonEmptyText).toBeUndefined();
});
it('should always fail fast on steps marked with alwaysFailFast', () => {
const steps = [
{
name: 'criticalCheck',
validator: (input: TestInput) => input.value !== 0,
isError: ResultInterpreters.booleanMeansSuccess,
alwaysFailFast: true,
},
{
name: 'nonEmptyText',
validator: (input: TestInput) => input.text.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const;
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: 0, text: '' }, { failFast: false });
expect(result.isValid).toBe(false);
expect(result.errors.criticalCheck).toBe(false);
expect(result.errors.nonEmptyText).toBeUndefined(); // Should not be executed
});
it('should work with different result interpreters', () => {
const steps = [
{
name: 'arrayResult',
validator: (input: TestInput) => [1, 2, 3],
isError: ResultInterpreters.errorList,
},
{
name: 'nullableResult',
validator: (input: TestInput) => (input.value > 0 ? null : 'error'),
isError: ResultInterpreters.nullableIsSuccess,
},
] as const;
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: -1, text: 'test' }, { failFast: false });
expect(result.isValid).toBe(false);
expect(result.errors.arrayResult).toEqual([1, 2, 3]);
expect(result.errors.nullableResult).toBe('error');
});
it('should handle 0-arity validators', () => {
const processor = createValidationProcessor({
steps: [
{
name: 'zeroArityValidator',
validator: () => true,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'zeroArityValidator2',
validator: () => false,
isError: ResultInterpreters.booleanMeansFailure,
},
] as const,
});
const result = processor(null);
expect(result.isValid).toBe(true);
});
it('should work with custom result interpreter', () => {
const steps = [
{
name: 'customCheck',
validator: (input: TestInput) => ({ isOk: input.value > 0, code: 'VALUE_CHECK' }),
isError: ResultInterpreters.custom((result: { isOk: boolean }) => !result.isOk),
},
] as const;
const processor = createValidationProcessor({ steps });
const validResult = processor({ value: 5, text: 'test' });
expect(validResult.isValid).toBe(true);
expect(validResult.errors).toEqual({});
const invalidResult = processor({ value: -1, text: 'test' });
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.errors.customCheck).toEqual({ isOk: false, code: 'VALUE_CHECK' });
});
it('should work with validationProcessor result interpreter', () => {
const innerProcessor = createValidationProcessor({
steps: [
{
name: 'innerCheck',
validator: (val: number) => val > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const,
});
const outerProcessor = createValidationProcessor({
steps: [
{
name: 'nestedValidation',
validator: (input: TestInput) => innerProcessor(input.value),
isError: ResultInterpreters.validationProcessor,
},
] as const,
});
const validResult = outerProcessor({ value: 5, text: 'test' });
expect(validResult.isValid).toBe(true);
const invalidResult = outerProcessor({ value: -1, text: 'test' });
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.errors.nestedValidation).toMatchObject({ isValid: false });
});
it('should handle empty steps array', () => {
const processor = createValidationProcessor<readonly []>({
steps: [],
});
const result = processor('any input' as never);
expect(result.isValid).toBe(true);
expect(result.errors).toEqual({});
});
it('should throw when validators throw errors', () => {
const steps = [
{
name: 'throwingValidator',
validator: (input: TestInput) => {
if (input.value === 0) {
throw new Error('Division by zero');
}
return true;
},
isError: ResultInterpreters.booleanMeansSuccess,
},
] as const;
const processor = createValidationProcessor({ steps });
expect(() => processor({ value: 0, text: 'test' })).toThrow('Division by zero');
});
describe('complex validation scenarios', () => {
it('should handle multi-type validation results', () => {
type ComplexInput = {
email: string;
age: number;
tags: string[];
};
const steps = [
{
name: 'emailFormat',
validator: (input: ComplexInput) =>
/\S+@\S+\.\S+/.test(input.email) ? null : 'Invalid email format',
isError: ResultInterpreters.nullableIsSuccess,
},
{
name: 'ageRange',
validator: (input: ComplexInput) => input.age >= 18 && input.age <= 120,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'tagValidation',
validator: (input: ComplexInput) => {
const invalidTags = input.tags.filter((tag) => tag.length < 2);
return invalidTags;
},
isError: ResultInterpreters.errorList,
},
] as const;
const processor = createValidationProcessor({ steps });
const validInput: ComplexInput = {
email: 'user@example.com',
age: 25,
tags: ['valid', 'tags', 'here'],
};
const validResult = processor(validInput);
expect(validResult.isValid).toBe(true);
const invalidInput: ComplexInput = {
email: 'invalid-email',
age: 150,
tags: ['ok', 'a', 'b', 'valid'],
};
const invalidResult = processor(invalidInput, { failFast: false });
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.errors.emailFormat).toBe('Invalid email format');
expect(invalidResult.errors.ageRange).toBe(false);
expect(invalidResult.errors.tagValidation).toEqual(['a', 'b']);
});
it('should preserve type safety with heterogeneous result types', () => {
const steps = [
{
name: 'stringResult',
validator: () => 'error message',
isError: (result: string) => result.length > 0,
},
{
name: 'numberResult',
validator: () => 42,
isError: (result: number) => result !== 0,
},
{
name: 'objectResult',
validator: () => ({ code: 'ERR_001', severity: 'high' }),
isError: (result: { code: string; severity: string }) => true,
},
] as const;
const processor = createValidationProcessor({ steps });
const result = processor(null, { failFast: false });
expect(result.isValid).toBe(false);
expect(result.errors.stringResult).toBe('error message');
expect(result.errors.numberResult).toBe(42);
expect(result.errors.objectResult).toEqual({ code: 'ERR_001', severity: 'high' });
});
});
describe('edge cases', () => {
it('should handle undefined vs null in nullable interpreter', () => {
const steps = [
{
name: 'nullCheck',
validator: () => null,
isError: ResultInterpreters.nullableIsSuccess,
},
{
name: 'undefinedCheck',
validator: () => undefined,
isError: ResultInterpreters.nullableIsSuccess,
},
{
name: 'zeroCheck',
validator: () => 0,
isError: ResultInterpreters.nullableIsSuccess,
},
{
name: 'falseCheck',
validator: () => false,
isError: ResultInterpreters.nullableIsSuccess,
},
] as const;
const processor = createValidationProcessor({ steps });
const result = processor(null, { failFast: false });
expect(result.isValid).toBe(false);
expect(result.errors.nullCheck).toBeUndefined();
expect(result.errors.undefinedCheck).toBeUndefined();
expect(result.errors.zeroCheck).toBe(0);
expect(result.errors.falseCheck).toBe(false);
});
it('should handle very long validation chains', () => {
// Test the real-world scenario of dynamically generated validation steps
// Note: This demonstrates a limitation of the current type system -
// dynamic step generation loses strict typing but still works at runtime
type StepInput = { value: number };
const steps = Array.from({ length: 50 }, (_, i) => ({
name: `step${i}`,
validator: (input: StepInput) => input.value > i,
isError: ResultInterpreters.booleanMeansSuccess,
}));
// For dynamic steps, we need to use a type assertion since TypeScript
// can't infer the literal string union from Array.from()
const processor = createValidationProcessor({
steps,
});
const result = processor({ value: 25 }, { failFast: false });
expect(result.isValid).toBe(false);
const errorCount = Object.keys(result.errors).length;
expect(errorCount).toBe(25);
});
it('should handle validation by sum typing their inputs', () => {
const processor = createValidationProcessor({
steps: [
{
name: 'step1',
validator: ({ age }: { age: number }) => age > 18,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'step2',
validator: ({ name }: { name: string }) => name.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
],
});
const result = processor({ age: 25, name: 'John' });
expect(result.isValid).toBe(true);
const result2 = processor({ age: 15, name: '' });
expect(result2.isValid).toBe(false);
});
it('should allow wider types as processor inputs', () => {
const sumProcessor = createValidationProcessor({
steps: [
{
name: 'step1',
validator: ({ age }: { age: number }) => age > 18,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'step2',
validator: ({ name }: { name: string }) => name.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
],
});
type Person = { age: number; name: string };
const groupProcessor = createValidationProcessor({
steps: [
{
name: 'step1',
validator: ({ age }: Person) => age > 18,
isError: ResultInterpreters.booleanMeansSuccess,
},
{
name: 'step2',
validator: ({ name }: Person) => name.length > 0,
isError: ResultInterpreters.booleanMeansSuccess,
},
],
});
const result = sumProcessor({ age: 25, name: 'John', favoriteColor: 'red' });
expect(result.isValid).toBe(true);
const result2 = groupProcessor({ name: '', favoriteColor: 'red', age: 15 });
expect(result2.isValid).toBe(false);
});
});
});

View File

@@ -0,0 +1,230 @@
/**
* @fileoverview Type-safe sequential validation processor
*
* This module provides a flexible validation system that allows you to chain multiple
* validation steps together in a type-safe manner. It supports both fail-fast and
* continue-on-error modes, with comprehensive error collection and reporting.
*
* Key features:
* - Type-safe validation pipeline creation
* - Sequential validation step execution
* - Configurable fail-fast behavior (global or per-step)
* - Comprehensive error collection with typed results
* - Helper functions for common validation result interpretations
*
* @example
* ```typescript
* const validator = createValidationProcessor({
* steps: [
* {
* name: 'required',
* validator: (input: string) => input.length > 0,
* isError: ResultInterpreters.booleanMeansSuccess
* },
* {
* name: 'email',
* validator: (input: string) => /\S+@\S+\.\S+/.test(input),
* isError: ResultInterpreters.booleanMeansSuccess
* }
* ]
* });
*
* const result = validator('user@example.com');
* if (!result.isValid) {
* console.log('Validation errors:', result.errors);
* }
* ```
*/
export type ValidationStepConfig<TInput, TResult, TName extends string = string> = {
name: TName;
validator: (input: TInput) => TResult;
isError: (result: TResult) => boolean;
alwaysFailFast?: boolean;
};
export interface ValidationPipelineConfig {
failFast?: boolean;
}
export type ValidationPipelineDefinition<
TInput,
TSteps extends readonly ValidationStepConfig<TInput, any, string>[],
> = {
steps: TSteps;
};
export type ExtractStepResults<TSteps extends readonly ValidationStepConfig<any, any, string>[]> = {
[K in TSteps[number]['name']]: Extract<TSteps[number], { name: K }> extends ValidationStepConfig<
any,
infer R,
K
>
? R
: never;
};
export type ValidationResult<TSteps extends readonly ValidationStepConfig<any, any, string>[]> = {
isValid: boolean;
errors: Partial<ExtractStepResults<TSteps>>;
};
// Util: convert a union to an intersection
type UnionToIntersection<U> = (U extends any ? (arg: U) => void : never) extends (arg: infer I) => void
? I
: never;
// Extract the *intersection* of all input types required by the steps. This guarantees that
// the resulting processor knows about every property that any individual step relies on.
// We purposely compute an intersection (not a union) so that all required fields are present.
type ExtractInputType<TSteps extends readonly ValidationStepConfig<any, any, string>[]> =
UnionToIntersection<
TSteps[number] extends ValidationStepConfig<infer TInput, any, string> ? TInput : never
>;
/**
* Creates a type-safe validation processor that executes a series of validation steps
* sequentially and collects errors from failed validations.
*
* This function returns a validation processor that can be called with input data
* and an optional configuration object. The processor will run each validation step
* in order, collecting any errors that occur.
*
* @template TSteps - A readonly array of validation step configurations that defines
* the validation pipeline. The type is constrained to ensure type safety
* across all steps and their results.
*
* @param definition - The validation pipeline definition
* @param definition.steps - An array of validation step configurations. Each step must have:
* - `name`: A unique string identifier for the step
* - `validator`: A function that takes input and returns a validation result
* - `isError`: A function that determines if the validation result represents an error
* - `alwaysFailFast`: Optional flag to always stop execution on this step's failure
*
* @returns A validation processor function that accepts:
* - `input`: The data to validate (type inferred from the first validation step)
* - `config`: Optional configuration object with:
* - `failFast`: If true, stops execution on first error (unless overridden by step config)
*
* @example Basic usage with string validation
* ```typescript
* const nameValidator = createValidationProcessor({
* steps: [
* {
* name: 'required',
* validator: (input: string) => input.trim().length > 0,
* isError: ResultInterpreters.booleanMeansSuccess
* },
* {
* name: 'minLength',
* validator: (input: string) => input.length >= 2,
* isError: ResultInterpreters.booleanMeansSuccess
* },
* {
* name: 'maxLength',
* validator: (input: string) => input.length <= 50,
* isError: ResultInterpreters.booleanMeansSuccess
* }
* ]
* });
*
* const result = nameValidator('John');
* // result.isValid: boolean
* // result.errors: { required?: boolean, minLength?: boolean, maxLength?: boolean }
* ```
*
* @example Complex validation with custom error types
* ```typescript
* type ValidationError = { message: string; code: string };
*
* const userValidator = createValidationProcessor({
* steps: [
* {
* name: 'email',
* validator: (user: { email: string }) =>
* /\S+@\S+\.\S+/.test(user.email)
* ? null
* : { message: 'Invalid email format', code: 'INVALID_EMAIL' },
* isError: (result): result is ValidationError => result !== null
* },
* {
* name: 'age',
* validator: (user: { age: number }) =>
* user.age >= 18
* ? null
* : { message: 'Must be 18 or older', code: 'UNDERAGE' },
* isError: (result): result is ValidationError => result !== null,
* alwaysFailFast: true // Stop immediately if age validation fails
* }
* ]
* });
* ```
*
* @example Using fail-fast mode
* ```typescript
* const result = validator(input, { failFast: true });
* // Stops on first error, even if subsequent steps would also fail
* ```
*
* @since 1.0.0
*/
export function createValidationProcessor<
const TSteps extends readonly ValidationStepConfig<any, any, string>[],
>(definition: { steps: TSteps }) {
// Determine the base input type required by all steps (intersection).
type BaseInput = ExtractInputType<TSteps>;
// Helper: widen input type for object literals while keeping regular objects assignable.
type InputWithExtras = BaseInput extends object
? BaseInput | (BaseInput & Record<string, unknown>)
: BaseInput;
return function processValidation(
input: InputWithExtras,
config: ValidationPipelineConfig = {}
): ValidationResult<TSteps> {
const errors: Partial<ExtractStepResults<TSteps>> = {};
let hasErrors = false;
for (const step of definition.steps) {
const result = step.validator(input as BaseInput);
const isError = step.isError(result);
if (isError) {
hasErrors = true;
(errors as any)[step.name] = result;
// Always fail fast for steps marked as such, or when global failFast is enabled
if (step.alwaysFailFast || config.failFast) {
break;
}
}
}
return {
isValid: !hasErrors,
errors,
};
};
}
/** Helper functions for common result interpretations */
export const ResultInterpreters = {
/** For boolean results: true = success, false = error */
booleanMeansSuccess: (result: boolean): boolean => !result,
/** For boolean results: false = success, true = error */
booleanMeansFailure: (result: boolean): boolean => result,
/** For nullable results: null/undefined = success, anything else = error */
nullableIsSuccess: <T>(result: T | null | undefined): boolean => result != null,
/** For array results: empty = success, non-empty = error */
errorList: <T>(result: T[]): boolean => result.length > 0,
/** For custom predicate */
custom: <T>(predicate: (result: T) => boolean) => predicate,
/** Interpreting the result of a validation processor */
validationProcessor: (result: { isValid: boolean }) => !result.isValid,
} as const;

View File

@@ -92,6 +92,7 @@ export const LOG_LEVEL = process.env.LOG_LEVEL
: process.env.ENVIRONMENT === 'production'
? 'INFO'
: 'DEBUG';
export const SUPPRESS_LOGS = process.env.SUPPRESS_LOGS === 'true';
export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK
? process.env.MOTHERSHIP_GRAPHQL_LINK
: ENVIRONMENT === 'staging'
@@ -101,7 +102,12 @@ export const MOTHERSHIP_GRAPHQL_LINK = process.env.MOTHERSHIP_GRAPHQL_LINK
export const PM2_HOME = process.env.PM2_HOME ?? join(homedir(), '.pm2');
export const PM2_PATH = join(import.meta.dirname, '../../', 'node_modules', 'pm2', 'bin', 'pm2');
export const ECOSYSTEM_PATH = join(import.meta.dirname, '../../', 'ecosystem.config.json');
export const LOGS_DIR = process.env.LOGS_DIR ?? '/var/log/unraid-api';
export const PATHS_LOGS_DIR =
process.env.PATHS_LOGS_DIR ?? process.env.LOGS_DIR ?? '/var/log/unraid-api';
export const PATHS_LOGS_FILE = process.env.PATHS_LOGS_FILE ?? '/var/log/graphql-api.log';
export const PATHS_CONFIG_MODULES =
process.env.PATHS_CONFIG_MODULES ?? '/boot/config/plugins/dynamix.my.servers/configs';
export const PATHS_LOCAL_SESSION_FILE =
process.env.PATHS_LOCAL_SESSION_FILE ?? '/var/run/unraid-api/local-session';

View File

@@ -1,77 +0,0 @@
import { ApolloClient, HttpLink, InMemoryCache, split } from '@apollo/client/core/index.js';
import { onError } from '@apollo/client/link/error/index.js';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions/index.js';
import { getMainDefinition } from '@apollo/client/utilities/index.js';
import { fetch } from 'cross-fetch';
import { createClient } from 'graphql-ws';
import WebSocket from 'ws';
import { getInternalApiAddress } from '@app/consts.js';
import { graphqlLogger } from '@app/core/log.js';
import { getters } from '@app/store/index.js';
const getWebsocketWithHeaders = () => {
return class WebsocketWithOriginHeader extends WebSocket {
constructor(address, protocols) {
super(address, protocols, {
headers: {
Origin: '/var/run/unraid-cli.sock',
'Content-Type': 'application/json',
},
});
}
};
};
export const getApiApolloClient = ({ localApiKey }: { localApiKey: string }) => {
const nginxPort = getters?.emhttp()?.nginx?.httpPort ?? 80;
graphqlLogger.debug('Internal GraphQL URL: %s', getInternalApiAddress(true, nginxPort));
const httpLink = new HttpLink({
uri: getInternalApiAddress(true, nginxPort),
fetch,
headers: {
Origin: '/var/run/unraid-cli.sock',
'x-api-key': localApiKey,
'Content-Type': 'application/json',
},
});
// Create the subscription websocket link
const wsLink = new GraphQLWsLink(
createClient({
webSocketImpl: getWebsocketWithHeaders(),
url: getInternalApiAddress(false, nginxPort),
connectionParams: () => {
return { 'x-api-key': localApiKey };
},
})
);
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
httpLink
);
const errorLink = onError(({ networkError }) => {
if (networkError) {
graphqlLogger.warn('[GRAPHQL-CLIENT] NETWORK ERROR ENCOUNTERED %o', networkError);
}
});
return new ApolloClient({
defaultOptions: {
query: {
fetchPolicy: 'no-cache',
},
mutate: {
fetchPolicy: 'no-cache',
},
},
cache: new InMemoryCache(),
link: errorLink.concat(splitLink),
});
};

View File

@@ -1,35 +0,0 @@
export const GET_CLOUD_OBJECT = /* GraphQL */ `
query getCloud {
cloud {
error
apiKey {
valid
error
}
minigraphql {
status
timeout
error
}
cloud {
status
error
ip
}
allowedOrigins
}
}
`;
export const GET_SERVERS = /* GraphQL */ `
query getServers {
servers {
name
guid
status
owner {
username
}
}
}
`;

View File

@@ -1,58 +0,0 @@
/* eslint-disable */
import * as types from './graphql.js';
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
/**
* Map of all GraphQL operations in the project.
*
* This map has several performance disadvantages:
* 1. It is not tree-shakeable, so it will include all operations in the project.
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
* 3. It does not support dead code elimination, so it will add unused operations.
*
* Therefore it is highly recommended to use the babel or swc plugin for production.
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
*/
type Documents = {
"\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n": typeof types.SendRemoteGraphQlResponseDocument,
"\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n": typeof types.RemoteGraphQlEventFragmentFragmentDoc,
"\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n": typeof types.EventsDocument,
};
const documents: Documents = {
"\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n": types.SendRemoteGraphQlResponseDocument,
"\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n": types.RemoteGraphQlEventFragmentFragmentDoc,
"\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n": types.EventsDocument,
};
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*
*
* @example
* ```ts
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
* ```
*
* The query argument is unknown!
* Please regenerate the types.
*/
export function graphql(source: string): unknown;
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n"): (typeof documents)["\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n"): (typeof documents)["\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n"): (typeof documents)["\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
}
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;

View File

@@ -1,748 +0,0 @@
/* eslint-disable */
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
/** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
DateTime: { input: string; output: string; }
/** A field whose value is a IPv4 address: https://en.wikipedia.org/wiki/IPv4. */
IPv4: { input: any; output: any; }
/** A field whose value is a IPv6 address: https://en.wikipedia.org/wiki/IPv6. */
IPv6: { input: any; output: any; }
/** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
JSON: { input: Record<string, any>; output: Record<string, any>; }
/** The `Long` scalar type represents 52-bit integers */
Long: { input: number; output: number; }
/** A field whose value is a valid TCP port within the range of 0 to 65535: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_ports */
Port: { input: number; output: number; }
/** A field whose value conforms to the standard URL format as specified in RFC3986: https://www.ietf.org/rfc/rfc3986.txt. */
URL: { input: URL; output: URL; }
};
export type AccessUrl = {
__typename?: 'AccessUrl';
ipv4?: Maybe<Scalars['URL']['output']>;
ipv6?: Maybe<Scalars['URL']['output']>;
name?: Maybe<Scalars['String']['output']>;
type: UrlType;
};
export type AccessUrlInput = {
ipv4?: InputMaybe<Scalars['URL']['input']>;
ipv6?: InputMaybe<Scalars['URL']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
type: UrlType;
};
export type ArrayCapacity = {
__typename?: 'ArrayCapacity';
bytes?: Maybe<ArrayCapacityBytes>;
};
export type ArrayCapacityBytes = {
__typename?: 'ArrayCapacityBytes';
free?: Maybe<Scalars['Long']['output']>;
total?: Maybe<Scalars['Long']['output']>;
used?: Maybe<Scalars['Long']['output']>;
};
export type ArrayCapacityBytesInput = {
free?: InputMaybe<Scalars['Long']['input']>;
total?: InputMaybe<Scalars['Long']['input']>;
used?: InputMaybe<Scalars['Long']['input']>;
};
export type ArrayCapacityInput = {
bytes?: InputMaybe<ArrayCapacityBytesInput>;
};
export type ClientConnectedEvent = {
__typename?: 'ClientConnectedEvent';
data: ClientConnectionEventData;
type: EventType;
};
export type ClientConnectionEventData = {
__typename?: 'ClientConnectionEventData';
apiKey: Scalars['String']['output'];
type: ClientType;
version: Scalars['String']['output'];
};
export type ClientDisconnectedEvent = {
__typename?: 'ClientDisconnectedEvent';
data: ClientConnectionEventData;
type: EventType;
};
export type ClientPingEvent = {
__typename?: 'ClientPingEvent';
data: PingEventData;
type: EventType;
};
export enum ClientType {
API = 'API',
DASHBOARD = 'DASHBOARD'
}
export type Config = {
__typename?: 'Config';
error?: Maybe<ConfigErrorState>;
valid?: Maybe<Scalars['Boolean']['output']>;
};
export enum ConfigErrorState {
INVALID = 'INVALID',
NO_KEY_SERVER = 'NO_KEY_SERVER',
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
WITHDRAWN = 'WITHDRAWN'
}
export type Dashboard = {
__typename?: 'Dashboard';
apps?: Maybe<DashboardApps>;
array?: Maybe<DashboardArray>;
config?: Maybe<DashboardConfig>;
display?: Maybe<DashboardDisplay>;
id: Scalars['ID']['output'];
lastPublish?: Maybe<Scalars['DateTime']['output']>;
network?: Maybe<Network>;
online?: Maybe<Scalars['Boolean']['output']>;
os?: Maybe<DashboardOs>;
services?: Maybe<Array<Maybe<DashboardService>>>;
twoFactor?: Maybe<DashboardTwoFactor>;
vars?: Maybe<DashboardVars>;
versions?: Maybe<DashboardVersions>;
vms?: Maybe<DashboardVms>;
};
export type DashboardApps = {
__typename?: 'DashboardApps';
installed?: Maybe<Scalars['Int']['output']>;
started?: Maybe<Scalars['Int']['output']>;
};
export type DashboardAppsInput = {
installed: Scalars['Int']['input'];
started: Scalars['Int']['input'];
};
export type DashboardArray = {
__typename?: 'DashboardArray';
/** Current array capacity */
capacity?: Maybe<ArrayCapacity>;
/** Current array state */
state?: Maybe<Scalars['String']['output']>;
};
export type DashboardArrayInput = {
/** Current array capacity */
capacity: ArrayCapacityInput;
/** Current array state */
state: Scalars['String']['input'];
};
export type DashboardCase = {
__typename?: 'DashboardCase';
base64?: Maybe<Scalars['String']['output']>;
error?: Maybe<Scalars['String']['output']>;
icon?: Maybe<Scalars['String']['output']>;
url?: Maybe<Scalars['String']['output']>;
};
export type DashboardCaseInput = {
base64: Scalars['String']['input'];
error?: InputMaybe<Scalars['String']['input']>;
icon: Scalars['String']['input'];
url: Scalars['String']['input'];
};
export type DashboardConfig = {
__typename?: 'DashboardConfig';
error?: Maybe<Scalars['String']['output']>;
valid?: Maybe<Scalars['Boolean']['output']>;
};
export type DashboardConfigInput = {
error?: InputMaybe<Scalars['String']['input']>;
valid: Scalars['Boolean']['input'];
};
export type DashboardDisplay = {
__typename?: 'DashboardDisplay';
case?: Maybe<DashboardCase>;
};
export type DashboardDisplayInput = {
case: DashboardCaseInput;
};
export type DashboardInput = {
apps: DashboardAppsInput;
array: DashboardArrayInput;
config: DashboardConfigInput;
display: DashboardDisplayInput;
os: DashboardOsInput;
services: Array<DashboardServiceInput>;
twoFactor?: InputMaybe<DashboardTwoFactorInput>;
vars: DashboardVarsInput;
versions: DashboardVersionsInput;
vms: DashboardVmsInput;
};
export type DashboardOs = {
__typename?: 'DashboardOs';
hostname?: Maybe<Scalars['String']['output']>;
uptime?: Maybe<Scalars['DateTime']['output']>;
};
export type DashboardOsInput = {
hostname: Scalars['String']['input'];
uptime: Scalars['DateTime']['input'];
};
export type DashboardService = {
__typename?: 'DashboardService';
name?: Maybe<Scalars['String']['output']>;
online?: Maybe<Scalars['Boolean']['output']>;
uptime?: Maybe<DashboardServiceUptime>;
version?: Maybe<Scalars['String']['output']>;
};
export type DashboardServiceInput = {
name: Scalars['String']['input'];
online: Scalars['Boolean']['input'];
uptime?: InputMaybe<DashboardServiceUptimeInput>;
version: Scalars['String']['input'];
};
export type DashboardServiceUptime = {
__typename?: 'DashboardServiceUptime';
timestamp?: Maybe<Scalars['DateTime']['output']>;
};
export type DashboardServiceUptimeInput = {
timestamp: Scalars['DateTime']['input'];
};
export type DashboardTwoFactor = {
__typename?: 'DashboardTwoFactor';
local?: Maybe<DashboardTwoFactorLocal>;
remote?: Maybe<DashboardTwoFactorRemote>;
};
export type DashboardTwoFactorInput = {
local: DashboardTwoFactorLocalInput;
remote: DashboardTwoFactorRemoteInput;
};
export type DashboardTwoFactorLocal = {
__typename?: 'DashboardTwoFactorLocal';
enabled?: Maybe<Scalars['Boolean']['output']>;
};
export type DashboardTwoFactorLocalInput = {
enabled: Scalars['Boolean']['input'];
};
export type DashboardTwoFactorRemote = {
__typename?: 'DashboardTwoFactorRemote';
enabled?: Maybe<Scalars['Boolean']['output']>;
};
export type DashboardTwoFactorRemoteInput = {
enabled: Scalars['Boolean']['input'];
};
export type DashboardVars = {
__typename?: 'DashboardVars';
flashGuid?: Maybe<Scalars['String']['output']>;
regState?: Maybe<Scalars['String']['output']>;
regTy?: Maybe<Scalars['String']['output']>;
serverDescription?: Maybe<Scalars['String']['output']>;
serverName?: Maybe<Scalars['String']['output']>;
};
export type DashboardVarsInput = {
flashGuid: Scalars['String']['input'];
regState: Scalars['String']['input'];
regTy: Scalars['String']['input'];
/** Server description */
serverDescription?: InputMaybe<Scalars['String']['input']>;
/** Name of the server */
serverName?: InputMaybe<Scalars['String']['input']>;
};
export type DashboardVersions = {
__typename?: 'DashboardVersions';
unraid?: Maybe<Scalars['String']['output']>;
};
export type DashboardVersionsInput = {
unraid: Scalars['String']['input'];
};
export type DashboardVms = {
__typename?: 'DashboardVms';
installed?: Maybe<Scalars['Int']['output']>;
started?: Maybe<Scalars['Int']['output']>;
};
export type DashboardVmsInput = {
installed: Scalars['Int']['input'];
started: Scalars['Int']['input'];
};
export type Event = ClientConnectedEvent | ClientDisconnectedEvent | ClientPingEvent | RemoteAccessEvent | RemoteGraphQlEvent | UpdateEvent;
export enum EventType {
CLIENT_CONNECTED_EVENT = 'CLIENT_CONNECTED_EVENT',
CLIENT_DISCONNECTED_EVENT = 'CLIENT_DISCONNECTED_EVENT',
CLIENT_PING_EVENT = 'CLIENT_PING_EVENT',
REMOTE_ACCESS_EVENT = 'REMOTE_ACCESS_EVENT',
REMOTE_GRAPHQL_EVENT = 'REMOTE_GRAPHQL_EVENT',
UPDATE_EVENT = 'UPDATE_EVENT'
}
export type FullServerDetails = {
__typename?: 'FullServerDetails';
apiConnectedCount?: Maybe<Scalars['Int']['output']>;
apiVersion?: Maybe<Scalars['String']['output']>;
connectionTimestamp?: Maybe<Scalars['String']['output']>;
dashboard?: Maybe<Dashboard>;
lastPublish?: Maybe<Scalars['String']['output']>;
network?: Maybe<Network>;
online?: Maybe<Scalars['Boolean']['output']>;
};
export enum Importance {
ALERT = 'ALERT',
INFO = 'INFO',
WARNING = 'WARNING'
}
export type KsServerDetails = {
__typename?: 'KsServerDetails';
accessLabel: Scalars['String']['output'];
accessUrl: Scalars['String']['output'];
apiKey?: Maybe<Scalars['String']['output']>;
description: Scalars['String']['output'];
dnsHash: Scalars['String']['output'];
flashBackupDate?: Maybe<Scalars['Int']['output']>;
flashBackupUrl: Scalars['String']['output'];
flashProduct: Scalars['String']['output'];
flashVendor: Scalars['String']['output'];
guid: Scalars['String']['output'];
ipsId?: Maybe<Scalars['String']['output']>;
keyType?: Maybe<Scalars['String']['output']>;
licenseKey: Scalars['String']['output'];
name: Scalars['String']['output'];
plgVersion?: Maybe<Scalars['String']['output']>;
signedIn: Scalars['Boolean']['output'];
};
export type LegacyService = {
__typename?: 'LegacyService';
name?: Maybe<Scalars['String']['output']>;
online?: Maybe<Scalars['Boolean']['output']>;
uptime?: Maybe<Scalars['Int']['output']>;
version?: Maybe<Scalars['String']['output']>;
};
export type Mutation = {
__typename?: 'Mutation';
remoteGraphQLResponse: Scalars['Boolean']['output'];
remoteMutation: Scalars['String']['output'];
remoteSession?: Maybe<Scalars['Boolean']['output']>;
sendNotification?: Maybe<Notification>;
sendPing?: Maybe<Scalars['Boolean']['output']>;
updateDashboard: Dashboard;
updateNetwork: Network;
};
export type MutationRemoteGraphQlResponseArgs = {
input: RemoteGraphQlServerInput;
};
export type MutationRemoteMutationArgs = {
input: RemoteGraphQlClientInput;
};
export type MutationRemoteSessionArgs = {
remoteAccess: RemoteAccessInput;
};
export type MutationSendNotificationArgs = {
notification: NotificationInput;
};
export type MutationUpdateDashboardArgs = {
data: DashboardInput;
};
export type MutationUpdateNetworkArgs = {
data: NetworkInput;
};
export type Network = {
__typename?: 'Network';
accessUrls?: Maybe<Array<AccessUrl>>;
};
export type NetworkInput = {
accessUrls: Array<AccessUrlInput>;
};
export type Notification = {
__typename?: 'Notification';
description?: Maybe<Scalars['String']['output']>;
importance?: Maybe<Importance>;
link?: Maybe<Scalars['String']['output']>;
status: NotificationStatus;
subject?: Maybe<Scalars['String']['output']>;
title?: Maybe<Scalars['String']['output']>;
};
export type NotificationInput = {
description?: InputMaybe<Scalars['String']['input']>;
importance: Importance;
link?: InputMaybe<Scalars['String']['input']>;
subject?: InputMaybe<Scalars['String']['input']>;
title?: InputMaybe<Scalars['String']['input']>;
};
export enum NotificationStatus {
FAILED_TO_SEND = 'FAILED_TO_SEND',
NOT_FOUND = 'NOT_FOUND',
PENDING = 'PENDING',
SENT = 'SENT'
}
export type PingEvent = {
__typename?: 'PingEvent';
data?: Maybe<Scalars['String']['output']>;
type: EventType;
};
export type PingEventData = {
__typename?: 'PingEventData';
source: PingEventSource;
};
export enum PingEventSource {
API = 'API',
MOTHERSHIP = 'MOTHERSHIP'
}
export type ProfileModel = {
__typename?: 'ProfileModel';
avatar?: Maybe<Scalars['String']['output']>;
cognito_id?: Maybe<Scalars['String']['output']>;
url?: Maybe<Scalars['String']['output']>;
userId?: Maybe<Scalars['ID']['output']>;
username?: Maybe<Scalars['String']['output']>;
};
export type Query = {
__typename?: 'Query';
apiVersion?: Maybe<Scalars['String']['output']>;
dashboard?: Maybe<Dashboard>;
ksServers: Array<KsServerDetails>;
online?: Maybe<Scalars['Boolean']['output']>;
remoteQuery: Scalars['String']['output'];
serverStatus: ServerStatusResponse;
servers: Array<Maybe<Server>>;
status?: Maybe<ServerStatus>;
};
export type QueryDashboardArgs = {
id: Scalars['String']['input'];
};
export type QueryRemoteQueryArgs = {
input: RemoteGraphQlClientInput;
};
export type QueryServerStatusArgs = {
apiKey: Scalars['String']['input'];
};
export enum RegistrationState {
/** Basic */
BASIC = 'BASIC',
/** BLACKLISTED */
EBLACKLISTED = 'EBLACKLISTED',
/** BLACKLISTED */
EBLACKLISTED1 = 'EBLACKLISTED1',
/** BLACKLISTED */
EBLACKLISTED2 = 'EBLACKLISTED2',
/** Trial Expired */
EEXPIRED = 'EEXPIRED',
/** GUID Error */
EGUID = 'EGUID',
/** Multiple License Keys Present */
EGUID1 = 'EGUID1',
/** Trial Requires Internet Connection */
ENOCONN = 'ENOCONN',
/** No Flash */
ENOFLASH = 'ENOFLASH',
ENOFLASH1 = 'ENOFLASH1',
ENOFLASH2 = 'ENOFLASH2',
ENOFLASH3 = 'ENOFLASH3',
ENOFLASH4 = 'ENOFLASH4',
ENOFLASH5 = 'ENOFLASH5',
ENOFLASH6 = 'ENOFLASH6',
ENOFLASH7 = 'ENOFLASH7',
/** No Keyfile */
ENOKEYFILE = 'ENOKEYFILE',
/** No Keyfile */
ENOKEYFILE1 = 'ENOKEYFILE1',
/** Missing key file */
ENOKEYFILE2 = 'ENOKEYFILE2',
/** Invalid installation */
ETRIAL = 'ETRIAL',
/** Plus */
PLUS = 'PLUS',
/** Pro */
PRO = 'PRO',
/** Trial */
TRIAL = 'TRIAL'
}
export type RemoteAccessEvent = {
__typename?: 'RemoteAccessEvent';
data: RemoteAccessEventData;
type: EventType;
};
/** Defines whether remote access event is the initiation (from connect) or the response (from the server) */
export enum RemoteAccessEventActionType {
ACK = 'ACK',
END = 'END',
INIT = 'INIT',
PING = 'PING'
}
export type RemoteAccessEventData = {
__typename?: 'RemoteAccessEventData';
apiKey: Scalars['String']['output'];
type: RemoteAccessEventActionType;
url?: Maybe<AccessUrl>;
};
export type RemoteAccessInput = {
apiKey: Scalars['String']['input'];
type: RemoteAccessEventActionType;
url?: InputMaybe<AccessUrlInput>;
};
export type RemoteGraphQlClientInput = {
apiKey: Scalars['String']['input'];
body: Scalars['String']['input'];
/** Time in milliseconds to wait for a response from the remote server (defaults to 15000) */
timeout?: InputMaybe<Scalars['Int']['input']>;
/** How long mothership should cache the result of this query in seconds, only valid on queries */
ttl?: InputMaybe<Scalars['Int']['input']>;
};
export type RemoteGraphQlEvent = {
__typename?: 'RemoteGraphQLEvent';
data: RemoteGraphQlEventData;
type: EventType;
};
export type RemoteGraphQlEventData = {
__typename?: 'RemoteGraphQLEventData';
/** Contains mutation / subscription / query data in the form of body: JSON, variables: JSON */
body: Scalars['String']['output'];
/** sha256 hash of the body */
sha256: Scalars['String']['output'];
type: RemoteGraphQlEventType;
};
export enum RemoteGraphQlEventType {
REMOTE_MUTATION_EVENT = 'REMOTE_MUTATION_EVENT',
REMOTE_QUERY_EVENT = 'REMOTE_QUERY_EVENT',
REMOTE_SUBSCRIPTION_EVENT = 'REMOTE_SUBSCRIPTION_EVENT',
REMOTE_SUBSCRIPTION_EVENT_PING = 'REMOTE_SUBSCRIPTION_EVENT_PING'
}
export type RemoteGraphQlServerInput = {
/** Body - contains an object containing data: (GQL response data) or errors: (GQL Errors) */
body: Scalars['String']['input'];
/** sha256 hash of the body */
sha256: Scalars['String']['input'];
type: RemoteGraphQlEventType;
};
export type Server = {
__typename?: 'Server';
apikey?: Maybe<Scalars['String']['output']>;
guid?: Maybe<Scalars['String']['output']>;
lanip?: Maybe<Scalars['String']['output']>;
localurl?: Maybe<Scalars['String']['output']>;
name?: Maybe<Scalars['String']['output']>;
owner?: Maybe<ProfileModel>;
remoteurl?: Maybe<Scalars['String']['output']>;
status?: Maybe<ServerStatus>;
wanip?: Maybe<Scalars['String']['output']>;
};
/** Defines server fields that have a TTL on them, for example last ping */
export type ServerFieldsWithTtl = {
__typename?: 'ServerFieldsWithTtl';
lastPing?: Maybe<Scalars['String']['output']>;
};
export type ServerModel = {
apikey: Scalars['String']['output'];
guid: Scalars['String']['output'];
lanip: Scalars['String']['output'];
localurl: Scalars['String']['output'];
name: Scalars['String']['output'];
remoteurl: Scalars['String']['output'];
wanip: Scalars['String']['output'];
};
export enum ServerStatus {
NEVER_CONNECTED = 'never_connected',
OFFLINE = 'offline',
ONLINE = 'online'
}
export type ServerStatusResponse = {
__typename?: 'ServerStatusResponse';
id: Scalars['ID']['output'];
lastPublish?: Maybe<Scalars['String']['output']>;
online: Scalars['Boolean']['output'];
};
export type Service = {
__typename?: 'Service';
name?: Maybe<Scalars['String']['output']>;
online?: Maybe<Scalars['Boolean']['output']>;
uptime?: Maybe<Uptime>;
version?: Maybe<Scalars['String']['output']>;
};
export type Subscription = {
__typename?: 'Subscription';
events?: Maybe<Array<Event>>;
remoteSubscription: Scalars['String']['output'];
servers: Array<Server>;
};
export type SubscriptionRemoteSubscriptionArgs = {
input: RemoteGraphQlClientInput;
};
export type TwoFactorLocal = {
__typename?: 'TwoFactorLocal';
enabled?: Maybe<Scalars['Boolean']['output']>;
};
export type TwoFactorRemote = {
__typename?: 'TwoFactorRemote';
enabled?: Maybe<Scalars['Boolean']['output']>;
};
export type TwoFactorWithToken = {
__typename?: 'TwoFactorWithToken';
local?: Maybe<TwoFactorLocal>;
remote?: Maybe<TwoFactorRemote>;
token?: Maybe<Scalars['String']['output']>;
};
export type TwoFactorWithoutToken = {
__typename?: 'TwoFactorWithoutToken';
local?: Maybe<TwoFactorLocal>;
remote?: Maybe<TwoFactorRemote>;
};
export enum UrlType {
DEFAULT = 'DEFAULT',
LAN = 'LAN',
MDNS = 'MDNS',
WAN = 'WAN',
WIREGUARD = 'WIREGUARD'
}
export type UpdateEvent = {
__typename?: 'UpdateEvent';
data: UpdateEventData;
type: EventType;
};
export type UpdateEventData = {
__typename?: 'UpdateEventData';
apiKey: Scalars['String']['output'];
type: UpdateType;
};
export enum UpdateType {
DASHBOARD = 'DASHBOARD',
NETWORK = 'NETWORK'
}
export type Uptime = {
__typename?: 'Uptime';
timestamp?: Maybe<Scalars['String']['output']>;
};
export type UserProfileModelWithServers = {
__typename?: 'UserProfileModelWithServers';
profile: ProfileModel;
servers: Array<Server>;
};
export type Vars = {
__typename?: 'Vars';
expireTime?: Maybe<Scalars['DateTime']['output']>;
flashGuid?: Maybe<Scalars['String']['output']>;
regState?: Maybe<RegistrationState>;
regTm2?: Maybe<Scalars['String']['output']>;
regTy?: Maybe<Scalars['String']['output']>;
};
export type SendRemoteGraphQlResponseMutationVariables = Exact<{
input: RemoteGraphQlServerInput;
}>;
export type SendRemoteGraphQlResponseMutation = { __typename?: 'Mutation', remoteGraphQLResponse: boolean };
export type RemoteGraphQlEventFragmentFragment = { __typename?: 'RemoteGraphQLEvent', remoteGraphQLEventData: { __typename?: 'RemoteGraphQLEventData', type: RemoteGraphQlEventType, body: string, sha256: string } } & { ' $fragmentName'?: 'RemoteGraphQlEventFragmentFragment' };
export type EventsSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type EventsSubscription = { __typename?: 'Subscription', events?: Array<{ __typename: 'ClientConnectedEvent', connectedEvent: EventType, connectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientDisconnectedEvent', disconnectedEvent: EventType, disconnectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientPingEvent' } | { __typename: 'RemoteAccessEvent' } | (
{ __typename: 'RemoteGraphQLEvent' }
& { ' $fragmentRefs'?: { 'RemoteGraphQlEventFragmentFragment': RemoteGraphQlEventFragmentFragment } }
) | { __typename: 'UpdateEvent' }> | null };
export const RemoteGraphQlEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode<RemoteGraphQlEventFragmentFragment, unknown>;
export const SendRemoteGraphQlResponseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendRemoteGraphQLResponse"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteGraphQLResponse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<SendRemoteGraphQlResponseMutation, SendRemoteGraphQlResponseMutationVariables>;
export const EventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientConnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"connectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"connectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientDisconnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"disconnectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"disconnectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode<EventsSubscription, EventsSubscriptionVariables>;

View File

@@ -1,2 +0,0 @@
export * from "./fragment-masking.js";
export * from "./gql.js";

View File

@@ -1,216 +0,0 @@
/* eslint-disable */
import { z } from 'zod'
import { AccessUrlInput, ArrayCapacityBytesInput, ArrayCapacityInput, ClientType, ConfigErrorState, DashboardAppsInput, DashboardArrayInput, DashboardCaseInput, DashboardConfigInput, DashboardDisplayInput, DashboardInput, DashboardOsInput, DashboardServiceInput, DashboardServiceUptimeInput, DashboardTwoFactorInput, DashboardTwoFactorLocalInput, DashboardTwoFactorRemoteInput, DashboardVarsInput, DashboardVersionsInput, DashboardVmsInput, EventType, Importance, NetworkInput, NotificationInput, NotificationStatus, PingEventSource, RegistrationState, RemoteAccessEventActionType, RemoteAccessInput, RemoteGraphQlClientInput, RemoteGraphQlEventType, RemoteGraphQlServerInput, ServerStatus, UrlType, UpdateType } from '@app/graphql/generated/client/graphql.js'
type Properties<T> = Required<{
[K in keyof T]: z.ZodType<T[K], any, T[K]>;
}>;
type definedNonNullAny = {};
export const isDefinedNonNullAny = (v: any): v is definedNonNullAny => v !== undefined && v !== null;
export const definedNonNullAnySchema = z.any().refine((v) => isDefinedNonNullAny(v));
export const ClientTypeSchema = z.nativeEnum(ClientType);
export const ConfigErrorStateSchema = z.nativeEnum(ConfigErrorState);
export const EventTypeSchema = z.nativeEnum(EventType);
export const ImportanceSchema = z.nativeEnum(Importance);
export const NotificationStatusSchema = z.nativeEnum(NotificationStatus);
export const PingEventSourceSchema = z.nativeEnum(PingEventSource);
export const RegistrationStateSchema = z.nativeEnum(RegistrationState);
export const RemoteAccessEventActionTypeSchema = z.nativeEnum(RemoteAccessEventActionType);
export const RemoteGraphQlEventTypeSchema = z.nativeEnum(RemoteGraphQlEventType);
export const ServerStatusSchema = z.nativeEnum(ServerStatus);
export const UrlTypeSchema = z.nativeEnum(UrlType);
export const UpdateTypeSchema = z.nativeEnum(UpdateType);
export function AccessUrlInputSchema(): z.ZodObject<Properties<AccessUrlInput>> {
return z.object({
ipv4: z.instanceof(URL).nullish(),
ipv6: z.instanceof(URL).nullish(),
name: z.string().nullish(),
type: UrlTypeSchema
})
}
export function ArrayCapacityBytesInputSchema(): z.ZodObject<Properties<ArrayCapacityBytesInput>> {
return z.object({
free: z.number().nullish(),
total: z.number().nullish(),
used: z.number().nullish()
})
}
export function ArrayCapacityInputSchema(): z.ZodObject<Properties<ArrayCapacityInput>> {
return z.object({
bytes: z.lazy(() => ArrayCapacityBytesInputSchema().nullish())
})
}
export function DashboardAppsInputSchema(): z.ZodObject<Properties<DashboardAppsInput>> {
return z.object({
installed: z.number(),
started: z.number()
})
}
export function DashboardArrayInputSchema(): z.ZodObject<Properties<DashboardArrayInput>> {
return z.object({
capacity: z.lazy(() => ArrayCapacityInputSchema()),
state: z.string()
})
}
export function DashboardCaseInputSchema(): z.ZodObject<Properties<DashboardCaseInput>> {
return z.object({
base64: z.string(),
error: z.string().nullish(),
icon: z.string(),
url: z.string()
})
}
export function DashboardConfigInputSchema(): z.ZodObject<Properties<DashboardConfigInput>> {
return z.object({
error: z.string().nullish(),
valid: z.boolean()
})
}
export function DashboardDisplayInputSchema(): z.ZodObject<Properties<DashboardDisplayInput>> {
return z.object({
case: z.lazy(() => DashboardCaseInputSchema())
})
}
export function DashboardInputSchema(): z.ZodObject<Properties<DashboardInput>> {
return z.object({
apps: z.lazy(() => DashboardAppsInputSchema()),
array: z.lazy(() => DashboardArrayInputSchema()),
config: z.lazy(() => DashboardConfigInputSchema()),
display: z.lazy(() => DashboardDisplayInputSchema()),
os: z.lazy(() => DashboardOsInputSchema()),
services: z.array(z.lazy(() => DashboardServiceInputSchema())),
twoFactor: z.lazy(() => DashboardTwoFactorInputSchema().nullish()),
vars: z.lazy(() => DashboardVarsInputSchema()),
versions: z.lazy(() => DashboardVersionsInputSchema()),
vms: z.lazy(() => DashboardVmsInputSchema())
})
}
export function DashboardOsInputSchema(): z.ZodObject<Properties<DashboardOsInput>> {
return z.object({
hostname: z.string(),
uptime: z.string()
})
}
export function DashboardServiceInputSchema(): z.ZodObject<Properties<DashboardServiceInput>> {
return z.object({
name: z.string(),
online: z.boolean(),
uptime: z.lazy(() => DashboardServiceUptimeInputSchema().nullish()),
version: z.string()
})
}
export function DashboardServiceUptimeInputSchema(): z.ZodObject<Properties<DashboardServiceUptimeInput>> {
return z.object({
timestamp: z.string()
})
}
export function DashboardTwoFactorInputSchema(): z.ZodObject<Properties<DashboardTwoFactorInput>> {
return z.object({
local: z.lazy(() => DashboardTwoFactorLocalInputSchema()),
remote: z.lazy(() => DashboardTwoFactorRemoteInputSchema())
})
}
export function DashboardTwoFactorLocalInputSchema(): z.ZodObject<Properties<DashboardTwoFactorLocalInput>> {
return z.object({
enabled: z.boolean()
})
}
export function DashboardTwoFactorRemoteInputSchema(): z.ZodObject<Properties<DashboardTwoFactorRemoteInput>> {
return z.object({
enabled: z.boolean()
})
}
export function DashboardVarsInputSchema(): z.ZodObject<Properties<DashboardVarsInput>> {
return z.object({
flashGuid: z.string(),
regState: z.string(),
regTy: z.string(),
serverDescription: z.string().nullish(),
serverName: z.string().nullish()
})
}
export function DashboardVersionsInputSchema(): z.ZodObject<Properties<DashboardVersionsInput>> {
return z.object({
unraid: z.string()
})
}
export function DashboardVmsInputSchema(): z.ZodObject<Properties<DashboardVmsInput>> {
return z.object({
installed: z.number(),
started: z.number()
})
}
export function NetworkInputSchema(): z.ZodObject<Properties<NetworkInput>> {
return z.object({
accessUrls: z.array(z.lazy(() => AccessUrlInputSchema()))
})
}
export function NotificationInputSchema(): z.ZodObject<Properties<NotificationInput>> {
return z.object({
description: z.string().nullish(),
importance: ImportanceSchema,
link: z.string().nullish(),
subject: z.string().nullish(),
title: z.string().nullish()
})
}
export function RemoteAccessInputSchema(): z.ZodObject<Properties<RemoteAccessInput>> {
return z.object({
apiKey: z.string(),
type: RemoteAccessEventActionTypeSchema,
url: z.lazy(() => AccessUrlInputSchema().nullish())
})
}
export function RemoteGraphQlClientInputSchema(): z.ZodObject<Properties<RemoteGraphQlClientInput>> {
return z.object({
apiKey: z.string(),
body: z.string(),
timeout: z.number().nullish(),
ttl: z.number().nullish()
})
}
export function RemoteGraphQlServerInputSchema(): z.ZodObject<Properties<RemoteGraphQlServerInput>> {
return z.object({
body: z.string(),
sha256: z.string(),
type: RemoteGraphQlEventTypeSchema
})
}

View File

@@ -1,10 +0,0 @@
import { FatalAppError } from '@app/core/errors/fatal-error.js';
import { modules } from '@app/core/index.js';
export const getCoreModule = (moduleName: string) => {
if (!Object.keys(modules).includes(moduleName)) {
throw new FatalAppError(`"${moduleName}" is not a valid core module.`);
}
return modules[moduleName];
};

View File

@@ -1,7 +0,0 @@
import { graphql } from '@app/graphql/generated/client/gql.js';
export const SEND_REMOTE_QUERY_RESPONSE = graphql(/* GraphQL */ `
mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {
remoteGraphQLResponse(input: $input)
}
`);

View File

@@ -1,36 +0,0 @@
import { graphql } from '@app/graphql/generated/client/gql.js';
export const RemoteGraphQL_Fragment = graphql(/* GraphQL */ `
fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {
remoteGraphQLEventData: data {
type
body
sha256
}
}
`);
export const EVENTS_SUBSCRIPTION = graphql(/* GraphQL */ `
subscription events {
events {
__typename
... on ClientConnectedEvent {
connectedData: data {
type
version
apiKey
}
connectedEvent: type
}
... on ClientDisconnectedEvent {
disconnectedData: data {
type
version
apiKey
}
disconnectedEvent: type
}
...RemoteGraphQLEventFragment
}
}
`);

View File

@@ -1,234 +0,0 @@
import { AccessUrl, URL_TYPE } from '@unraid/shared/network.model.js';
import type { RootState } from '@app/store/index.js';
import { logger } from '@app/core/log.js';
import { type Nginx } from '@app/core/types/states/nginx.js';
import { store } from '@app/store/index.js';
interface UrlForFieldInput {
url: string;
port?: number;
portSsl?: number;
}
interface UrlForFieldInputSecure extends UrlForFieldInput {
url: string;
portSsl: number;
}
interface UrlForFieldInputInsecure extends UrlForFieldInput {
url: string;
port: number;
}
export const getUrlForField = ({
url,
port,
portSsl,
}: UrlForFieldInputInsecure | UrlForFieldInputSecure) => {
let portToUse = '';
let httpMode = 'https://';
if (!url || url === '') {
throw new Error('No URL Provided');
}
if (port) {
portToUse = port === 80 ? '' : `:${port}`;
httpMode = 'http://';
} else if (portSsl) {
portToUse = portSsl === 443 ? '' : `:${portSsl}`;
httpMode = 'https://';
} else {
throw new Error(`No ports specified for URL: ${url}`);
}
const urlString = `${httpMode}${url}${portToUse}`;
try {
return new URL(urlString);
} catch (error: unknown) {
throw new Error(`Failed to parse URL: ${urlString}`);
}
};
const fieldIsFqdn = (field: keyof Nginx) => field?.toLowerCase().includes('fqdn');
export type NginxUrlFields = Extract<
keyof Nginx,
'lanIp' | 'lanIp6' | 'lanName' | 'lanMdns' | 'lanFqdn' | 'wanFqdn' | 'wanFqdn6'
>;
/**
*
* @param nginx Nginx Config File
* @param field The field to build the URL from
* @returns a URL, created from the combination of inputs
* @throws Error when the URL cannot be created or the URL is invalid
*/
export const getUrlForServer = ({ nginx, field }: { nginx: Nginx; field: NginxUrlFields }): URL => {
if (nginx[field]) {
if (fieldIsFqdn(field)) {
return getUrlForField({
url: nginx[field],
portSsl: nginx.httpsPort,
});
}
if (!nginx.sslEnabled) {
// Use SSL = no
return getUrlForField({ url: nginx[field], port: nginx.httpPort });
}
if (nginx.sslMode === 'yes') {
return getUrlForField({
url: nginx[field],
portSsl: nginx.httpsPort,
});
}
if (nginx.sslMode === 'auto') {
throw new Error(`Cannot get IP Based URL for field: "${field}" SSL mode auto`);
}
}
throw new Error(
`IP URL Resolver: Could not resolve any access URL for field: "${field}", is FQDN?: ${fieldIsFqdn(
field
)}`
);
};
const getUrlTypeFromFqdn = (fqdnType: string): URL_TYPE => {
switch (fqdnType) {
case 'LAN':
return URL_TYPE.LAN;
case 'WAN':
return URL_TYPE.WAN;
case 'WG':
return URL_TYPE.WIREGUARD;
default:
// HACK: This should be added as a new type (e.g. OTHER or CUSTOM)
return URL_TYPE.WIREGUARD;
}
};
export const getServerIps = (
state: RootState = store.getState()
): { urls: AccessUrl[]; errors: Error[] } => {
const { nginx } = state.emhttp;
const {
remote: { wanport },
} = state.config;
if (!nginx || Object.keys(nginx).length === 0) {
return { urls: [], errors: [new Error('Nginx Not Loaded')] };
}
const errors: Error[] = [];
const urls: AccessUrl[] = [];
try {
// Default URL
const defaultUrl = new URL(nginx.defaultUrl);
urls.push({
name: 'Default',
type: URL_TYPE.DEFAULT,
ipv4: defaultUrl,
ipv6: defaultUrl,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
try {
// Lan IP URL
const lanIp4Url = getUrlForServer({ nginx, field: 'lanIp' });
urls.push({
name: 'LAN IPv4',
type: URL_TYPE.LAN,
ipv4: lanIp4Url,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
try {
// Lan IP6 URL
const lanIp6Url = getUrlForServer({ nginx, field: 'lanIp6' });
urls.push({
name: 'LAN IPv6',
type: URL_TYPE.LAN,
ipv4: lanIp6Url,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
try {
// Lan Name URL
const lanNameUrl = getUrlForServer({ nginx, field: 'lanName' });
urls.push({
name: 'LAN Name',
type: URL_TYPE.MDNS,
ipv4: lanNameUrl,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
try {
// Lan MDNS URL
const lanMdnsUrl = getUrlForServer({ nginx, field: 'lanMdns' });
urls.push({
name: 'LAN MDNS',
type: URL_TYPE.MDNS,
ipv4: lanMdnsUrl,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
// Now Process the FQDN Urls
nginx.fqdnUrls.forEach((fqdnUrl) => {
try {
const urlType = getUrlTypeFromFqdn(fqdnUrl.interface);
const fqdnUrlToUse = getUrlForField({
url: fqdnUrl.fqdn,
portSsl: urlType === URL_TYPE.WAN ? Number(wanport) : nginx.httpsPort,
});
urls.push({
name: `FQDN ${fqdnUrl.interface}${fqdnUrl.id !== null ? ` ${fqdnUrl.id}` : ''}`,
type: getUrlTypeFromFqdn(fqdnUrl.interface),
ipv4: fqdnUrlToUse,
});
} catch (error: unknown) {
if (error instanceof Error) {
errors.push(error);
} else {
logger.warn('Uncaught error in network resolver', error);
}
}
});
return { urls, errors };
};

View File

@@ -1,28 +0,0 @@
import { mergeTypeDefs } from '@graphql-tools/merge';
import { logger } from '@app/core/log.js';
export const loadTypeDefs = async (additionalTypeDefs: string[] = []) => {
// TypeScript now knows this returns Record<string, () => Promise<string>>
const typeModules = import.meta.glob('./types/**/*.graphql', { query: '?raw', import: 'default' });
try {
const files = await Promise.all(
Object.values(typeModules).map(async (importFn) => {
const content = await importFn();
if (typeof content !== 'string') {
throw new Error('Invalid GraphQL type definition format');
}
return content;
})
);
if (!files.length) {
throw new Error('No GraphQL type definitions found');
}
files.push(...additionalTypeDefs);
return mergeTypeDefs(files);
} catch (error) {
logger.error('Failed to load GraphQL type definitions:', error);
throw error;
}
};

Some files were not shown because too many files have changed in this diff Show More