Skip to content

Feat/onboarding internal boot#1881

Draft
Ajit-Mehrotra wants to merge 19 commits intomainfrom
feat/onboarding-internal-boot
Draft

Feat/onboarding internal boot#1881
Ajit-Mehrotra wants to merge 19 commits intomainfrom
feat/onboarding-internal-boot

Conversation

@Ajit-Mehrotra
Copy link
Contributor

@Ajit-Mehrotra Ajit-Mehrotra commented Mar 2, 2026

Summary by CodeRabbit

  • New Features

    • Added Internal Boot configuration step to the onboarding process, allowing users to set up bootable pools with customizable slot allocation, device selection, and boot partition sizing.
    • Enhanced disk information display with additional device metadata.
    • Added boot eligibility detection and boot transfer configuration options to system settings.
  • Documentation

    • Added documentation detailing onboarding interface behavior differences.

- Purpose: introduce a dedicated onboarding step for internal boot setup using existing webgui behavior and endpoints.

- Before: onboarding had no internal boot step, so users had to leave onboarding and configure it elsewhere.

- Problem: internal boot setup was disconnected from first-run setup and could not be intentionally skipped/recorded in onboarding state.

- Change: added an INTERNAL_BOOT step after core settings, hidden for partner builds, with skip support and persisted draft state.

- Implementation: added internal boot template parser/submission helper, new step UI with validation (slots/devices/size/update BIOS), and modal/step registry wiring.
- Purpose: execute internal boot configuration during onboarding confirm/apply and surface reboot follow-up.

- Before: summary only applied core settings/plugins/ssh; internal boot selections were not applied.

- Problem: users could choose internal boot but onboarding would not run mkbootpool or reflect reboot needs.

- Change: summary now calls mkbootpool endpoint without reboot, logs command output, and treats failures as warnings in best-effort flow.

- Implementation: added internal boot summary card, included internal boot in apply-change detection, and switched final CTA to reboot when internal boot apply succeeds.
- Purpose: validate onboarding step routing and summary apply behavior after internal boot integration.

- Before: tests assumed old step indexes and had no assertions for internal boot visibility or mkbootpool execution.

- Problem: regressions in partner-step visibility and internal boot apply flow would be hard to detect.

- Change: updated modal tests for INTERNAL_BOOT presence/hiding and adjusted activation-step index expectations.

- Implementation: extended summary tests to mock internal boot submission, verify no-reboot payload, success flag behavior, and warning path on failures.
- Purpose: resolve remaining type-check/test/lint failures after internal-boot onboarding integration.

- Before: web type-check failed on an unused loop variable, onboarding modal test used an outdated step index, and prettier check failed on touched onboarding files.

- Problem: CI gates for type-check/test/lint were not green for the onboarding changes.

- Change: adjusted internal-boot device loop indexing, corrected activation-step test index for ENOKEYFILE1, and applied prettier formatting updates.

- Implementation: updated onboarding step/template/test files and refreshed generated component typings for new onboarding step registration.
- Purpose: use existing GraphQL array state in onboarding to decide whether internal boot setup can run.
- Before: onboarding inferred array stoppage from  template presence, which could misreport when the page response was unexpected (redirect/empty shell).
- Problem: users could see the 'array must be stopped' message even when  because the HTML parse path failed independently of true array state.
- Change: added  query and wired  to require  for configurability.
- Behavior now: array-state gating comes from GraphQL; legacy  parsing is retained only for webgui-specific internal-boot data (eligibility button/device options/presets/defaults).
- UX: status messaging now distinguishes array-not-stopped, array-state lookup failure, and inability to load webgui internal-boot template data.
- How it works:  drives , while template fetch/parse still initializes boot-pool form options.
- Validation: ran
> unraid-monorepo@4.29.2 codegen /Users/ajitmehrotra/Projects/onboarding-internal-boot
> pnpm -r codegen

Scope: 8 of 9 workspace projects
web codegen$ graphql-codegen --config codegen.ts -r dotenv/config
web codegen: ❯ Parse Configuration
web codegen: ✔ Parse Configuration
web codegen: ❯ Generate outputs
web codegen: ❯ Generate to src/composables/gql/
web codegen: ❯ Load GraphQL schemas
web codegen: ✔ Load GraphQL schemas
web codegen: ❯ Load GraphQL documents
web codegen: ✔ Load GraphQL documents
web codegen: ❯ Generate
web codegen: ✔ Generate
web codegen: ✔ Generate to src/composables/gql/
web codegen: ✔ Generate outputs
web codegen: Done
packages/unraid-api-plugin-connect codegen$ MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config codegen.ts
packages/unraid-api-plugin-connect codegen: [STARTED] Parse Configuration
packages/unraid-api-plugin-connect codegen: [COMPLETED] Parse Configuration
packages/unraid-api-plugin-connect codegen: [STARTED] Generate outputs
packages/unraid-api-plugin-connect codegen: [STARTED] Generate to src/graphql/generated/client/
packages/unraid-api-plugin-connect codegen: [STARTED] Load GraphQL schemas
packages/unraid-api-plugin-connect codegen: [COMPLETED] Load GraphQL schemas
packages/unraid-api-plugin-connect codegen: [STARTED] Load GraphQL documents
packages/unraid-api-plugin-connect codegen: [COMPLETED] Load GraphQL documents
packages/unraid-api-plugin-connect codegen: [STARTED] Generate
packages/unraid-api-plugin-connect codegen: [COMPLETED] Generate
packages/unraid-api-plugin-connect codegen: [COMPLETED] Generate to src/graphql/generated/client/
packages/unraid-api-plugin-connect codegen: [COMPLETED] Generate outputs
packages/unraid-api-plugin-connect codegen: Done
api codegen$ graphql-codegen --config codegen.ts
api codegen: [STARTED] Parse Configuration
api codegen: [COMPLETED] Parse Configuration
api codegen: [STARTED] Generate outputs
api codegen: [STARTED] Generate to src/unraid-api/cli/generated/
api codegen: [STARTED] Load GraphQL schemas
api codegen: [COMPLETED] Load GraphQL schemas
api codegen: [STARTED] Load GraphQL documents
api codegen: [COMPLETED] Load GraphQL documents
api codegen: [STARTED] Generate
api codegen: [COMPLETED] Generate
api codegen: [COMPLETED] Generate to src/unraid-api/cli/generated/
api codegen: [COMPLETED] Generate outputs
api codegen: Done,
> unraid-monorepo@4.29.2 type-check /Users/ajitmehrotra/Projects/onboarding-internal-boot
> pnpm -r type-check

Scope: 8 of 9 workspace projects
unraid-ui type-check$ vue-tsc --noEmit
web type-check$ vue-tsc --noEmit
unraid-ui type-check: Done
web type-check: Done
api type-check$ tsc --noEmit
api type-check: Done,
> unraid-monorepo@4.29.2 test /Users/ajitmehrotra/Projects/onboarding-internal-boot
> pnpm -r test

Scope: 8 of 9 workspace projects
packages/unraid-api-plugin-health test$ echo "Error: no test specified" && exit 0
packages/unraid-shared test$ vitest run
plugin test$ vitest && pnpm run test:extractor && pnpm run test:shell-detection
unraid-ui test$ vitest
packages/unraid-api-plugin-health test: Error: no test specified
packages/unraid-api-plugin-health test: Done
web test$ vitest run
unraid-ui test:  RUN  v3.2.4 /Users/ajitmehrotra/Projects/onboarding-internal-boot/unraid-ui
packages/unraid-shared test:  RUN  v3.2.4 /Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared
web test:  RUN  v3.2.4 /Users/ajitmehrotra/Projects/onboarding-internal-boot/web
plugin test:  RUN  v3.2.4 /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test
packages/unraid-shared test: Server listening on Unix socket: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/test-ws-unix-1772218335390.sock
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect to Unix socket using ws+unix:// protocol
packages/unraid-shared test: Connecting to: ws+unix:///var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/test-ws-unix-1772218335390.sock:/
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect to Unix socket using ws+unix:// protocol
packages/unraid-shared test: Server: New WebSocket connection on path: /
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect to Unix socket using ws+unix:// protocol
packages/unraid-shared test: Client: Connected successfully!
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect to Unix socket using ws+unix:// protocol
packages/unraid-shared test: Server received: Test message
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect with /graphql path like SocketConfigService
packages/unraid-shared test: Testing SocketConfigService format: ws+unix:///var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/test-ws-unix-1772218335390.sock:/graphql
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect with /graphql path like SocketConfigService
packages/unraid-shared test: Server: New WebSocket connection on path: /graphql
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should connect with /graphql path like SocketConfigService
packages/unraid-shared test: Client: Connected to /graphql path
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Server: Client disconnected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Server: New WebSocket connection on path: /client-0
packages/unraid-shared test:  ✓ src/util/permissions.spec.ts (21 tests) 9ms
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Client 0 connected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Server: Client disconnected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Server: New WebSocket connection on path: /client-1
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Client 1 connected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Server: New WebSocket connection on path: /client-2
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should work with multiple concurrent connections
packages/unraid-shared test: Client 2 connected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should verify the exact implementation used in BaseInternalClientService
packages/unraid-shared test: Server: Client disconnected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should verify the exact implementation used in BaseInternalClientService
packages/unraid-shared test: Server: Client disconnected
packages/unraid-shared test: stdout | src/services/ws-unix-socket-test.spec.ts > WebSocket Unix Socket - Actual Connection Test > should verify the exact implementation used in BaseInternalClientService
packages/unraid-shared test: Server: Client disconnected
packages/unraid-shared test:  ✓ src/util/__tests__/key-order.test.ts (2 tests) 79ms
packages/unraid-shared test:  ✓ src/util/__tests__/permissions.test.ts (25 tests) 13ms
packages/unraid-shared test:  ✓ src/util/__tests__/processing.test.ts (18 tests) 231ms
packages/unraid-shared test:  ✓ src/services/ws-unix-socket-test.spec.ts (5 tests) 465ms
packages/unraid-shared test:    ✓ WebSocket Unix Socket - Actual Connection Test > should verify the exact implementation used in BaseInternalClientService  336ms
packages/unraid-shared test: stderr | src/util/permissions-scopes.spec.ts > convertScopesToPermissions > should handle invalid scope formats gracefully
packages/unraid-shared test: Invalid scope format (missing colon): INVALID_SCOPE
packages/unraid-shared test: Invalid scope format: :action
packages/unraid-shared test: Invalid scope format: RESOURCE:
packages/unraid-shared test: Unknown resource in scope: UNKNOWN:read:any
packages/unraid-shared test:  ✓ src/util/permissions-scopes.spec.ts (5 tests) 12ms
unraid-ui test:  ✓ src/lib/utils.test.ts (8 tests) 8ms
packages/unraid-shared test:  ✓ src/util/__tests__/create-ttl-memoized-loader.test.ts (4 tests) 2ms
packages/unraid-shared test:  ✓ src/services/internal-graphql-client-usage.spec.ts (17 tests) 22ms
packages/unraid-shared test:  ✓ src/use-permissions.directive.spec.ts (24 tests) 4ms
plugin test:  ✓ builder/utils/manifest-validator.test.ts (16 tests) 144ms
plugin test: stderr | builder/__tests__/cli/setup-txz-environment.spec.ts > setupTxzEnvironment > should parse and return provided environment arguments
plugin test: skipValidation is true, skipping validation
plugin test:  ✓ builder/__tests__/cli/setup-txz-environment.spec.ts (4 tests) 5ms
packages/unraid-shared test:  ✓ src/util/__tests__/config-definition.test.ts (7 tests) 9ms
unraid-ui test: stderr | src/composables/useTeleport.test.ts > useTeleport > should return teleportTarget ref with correct value
unraid-ui test: [Vue warn]: onMounted is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
unraid-ui test:  ✓ src/composables/useTeleport.test.ts (6 tests) 127ms
packages/unraid-shared test:  ✓ src/services/socket-config.service.spec.ts (31 tests) 7ms
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > validatePluginEnv > validates required fields
plugin test: Reading txz file from: ./test.txz
plugin test: Mock readFile called with: ./test.txz undefined
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > validatePluginEnv > handles tag option in non-CI mode
plugin test: Reading txz file from: ./test.txz
plugin test: Mock readFile called with: ./test.txz undefined
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > validatePluginEnv > reads release notes when release-notes-path is provided
plugin test: Mock readFile called with: valid-release-notes.txt utf8
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > validatePluginEnv > reads release notes when release-notes-path is provided
plugin test: Reading txz file from: ./test.txz
plugin test: Mock readFile called with: ./test.txz undefined
plugin test: stderr | builder/__tests__/cli/setup-plugin-environment.spec.ts > validatePluginEnv > handles tag option in non-CI mode
plugin test: Tag is set, will generate a TAGGED build
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should generate changelog header with version
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at processTicksAndRejections (node:internal/process/task_queues:103:5)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:18:20
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > sets up environment from CLI arguments
plugin test: Plugin version: 2026.02.27.1852
plugin test: Options: {
plugin test:   ci: false,
plugin test:   apiVersion: '4.29.2\n' +
plugin test:     '+fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git\n',
plugin test:   baseUrl: 'https://example.com',
plugin test:   pluginVersion: '2024.05.05.1232',
plugin test:   txzPath: './test.txz'
plugin test: }
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > sets up environment from CLI arguments
plugin test: Reading txz file from: ./test.txz
plugin test: Mock readFile called with: ./test.txz undefined
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > sets up environment from CLI arguments
plugin test: Plugin environment setup successfully: {
plugin test:   ci: false,
plugin test:   apiVersion: '4.29.2\n' +
plugin test:     '+fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git\n',
plugin test:   baseUrl: 'https://example.com',
plugin test:   tag: '',
plugin test:   buildNumber: 1,
plugin test:   txzPath: './test.txz',
plugin test:   pluginVersion: '2024.05.05.1232',
plugin test:   releaseNotes: 'FAST_TEST_CHANGELOG',
plugin test:   txzSha256: '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72'
plugin test: }
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should generate changelog with tag parameter
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:33:20
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > throws when required options are missing
plugin test: Plugin version: 2026.02.27.1852
plugin test: Options: {
plugin test:   ci: false,
plugin test:   apiVersion: '4.29.2\n' +
plugin test:     '+fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git\n',
plugin test:   baseUrl: 'http://localhost:5858',
plugin test:   pluginVersion: '2026.02.27.1852'
plugin test: }
plugin test: TXZ path not found at: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/deploy/dynamix.unraid.net-4.29.2
plugin test: +fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: -x86_64-1.txz
plugin test: Attempting to find TXZ using apiVersion: 4.29.2
plugin test: +fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: Could not find TXZ at: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/deploy/dynamix.unraid.net-4.29.2
plugin test: +fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: -x86_64-1.txz
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > handles optional CLI arguments
plugin test: Plugin version: 2026.02.27.1852
plugin test: Options: {
plugin test:   ci: true,
plugin test:   apiVersion: '4.29.2\n' +
plugin test:     '+fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git\n',
plugin test:   baseUrl: 'https://example.com',
plugin test:   pluginVersion: '2024.05.05.1232',
plugin test:   txzPath: './test.txz',
plugin test:   tag: 'PR1203'
plugin test: }
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > handles optional CLI arguments
plugin test: Reading txz file from: ./test.txz
plugin test: Mock readFile called with: ./test.txz undefined
plugin test: stdout | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > handles optional CLI arguments
plugin test: Plugin environment setup successfully: {
plugin test:   ci: true,
plugin test:   apiVersion: '4.29.2\n' +
plugin test:     '+fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git\n',
plugin test:   baseUrl: 'https://example.com',
plugin test:   tag: 'PR1203',
plugin test:   buildNumber: 1,
plugin test:   txzPath: './test.txz',
plugin test:   pluginVersion: '2024.05.05.1232',
plugin test:   releaseNotes: 'FAST_TEST_CHANGELOG',
plugin test:   txzSha256: '6ae8a75555209fd6c44157c0aed8016e763ff435a19cf186f76863140143ff72'
plugin test: }
plugin test: stderr | builder/__tests__/cli/setup-plugin-environment.spec.ts > setupPluginEnv > handles optional CLI arguments
plugin test: Tag is set, will generate a TAGGED build
plugin test:  ✓ builder/__tests__/cli/setup-plugin-environment.spec.ts (8 tests) 277ms
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should handle error gracefully and return tag
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at processTicksAndRejections (node:internal/process/task_queues:103:5)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:64:20
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should use conventional-changelog v7 API correctly
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at processTicksAndRejections (node:internal/process/task_queues:103:5)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:81:7
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
packages/unraid-shared test:  ✓ src/jsonforms/__tests__/settings.test.ts (3 tests) 3ms
unraid-ui test:  ✓ src/components/common/button/Button.test.ts (4 tests) 23ms
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336665/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:159:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336665/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336677/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
unraid-ui test:  Test Files  3 passed (3)
unraid-ui test:       Tests  18 passed (18)
unraid-ui test:    Start at  13:52:14
unraid-ui test:    Duration  1.86s (transform 605ms, setup 0ms, collect 2.04s, tests 159ms, environment 1.67s, prepare 230ms)
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336685/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336698/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:174:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM    WARN [ConfigFileHandler:test-config.json] Migration failed. Using defaults.
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM    WARN [ConfigFileHandler:test-config.json] Error: Migration failed
packages/unraid-shared test:     at TestConfigDefinition.migrateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:109:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:65:54�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:174:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Invalid version: must be >= 1
packages/unraid-shared test:     at TestConfigFilePersister.validate �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/__tests__/config-file.test.ts:80:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:93:34�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.loadOrMigrateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:223:20�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.onModuleInit �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:159:5�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/__tests__/config-file.test.ts:178:5
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336685/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Invalid maxRetries: must be >= 0
packages/unraid-shared test:     at TestConfigDefinition.validate �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:94:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:57:36�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:204:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336701/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336721/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)
packages/unraid-shared test:     at JSON.parse (<anonymous>)
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:92:25�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:218:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336726/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should validate changelog structure
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:95:20
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336737/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:279:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM    WARN [ConfigFileHandler:test-config.json] Migration failed. Using defaults.
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM    WARN [ConfigFileHandler:test-config.json] Error: Special validation failure: migration + defaults combo invalid
packages/unraid-shared test:     at SpecialMigrationDefinition.validate �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:266:19�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:66:52�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:279:22
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336745/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336754/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336754/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.loadOrMigrateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:223:20�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.onModuleInit �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:159:5�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/__tests__/config-file.test.ts:295:5
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336754/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336757/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336754/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   ERROR [ConfigFileHandler:test-config.json] Error: Validation failed
packages/unraid-shared test:     at TestConfigDefinition.validate �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:78:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.writeConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:105:38�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:376:41
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:155:11
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:26
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:1897:20
packages/unraid-shared test:     at new Promise (<anonymous>)
packages/unraid-shared test:     at runWithTimeout (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:1863:10)
packages/unraid-shared test:     at runTest (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:1574:12)
packages/unraid-shared test:     at runSuite (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:1729:8)
packages/unraid-shared test: Cannot write invalid config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
unraid-ui test: Done
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336770/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM    WARN [ConfigFilePersister:test-config.json] Cannot persist undefined config
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336772/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336777/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.updateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:146:29�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:406:23
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336777/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336777/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   ERROR [ConfigFileHandler:test-config.json] Failed to update config
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   ERROR [ConfigFileHandler:test-config.json] Error: Invalid version: must be >= 1
packages/unraid-shared test:     at TestConfigDefinition.validate �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:85:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.updateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:147:47�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:423:23
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /invalid/readonly/path/test-config.json
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   ERROR [ConfigFileHandler:test-config.json] [Error: ENOENT: no such file or directory, open '/invalid/readonly/path/test-config.json.tmp-2218336795ee3d5e'] {
packages/unraid-shared test:   errno: �[33m-2�[39m,
packages/unraid-shared test:   code: �[32m'ENOENT'�[39m,
packages/unraid-shared test:   syscall: �[32m'open'�[39m,
packages/unraid-shared test:   path: �[32m'/invalid/readonly/path/test-config.json.tmp-2218336795ee3d5e'�[39m
packages/unraid-shared test: }
packages/unraid-shared test: Error writing config to '/invalid/readonly/path/test-config.json'
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336802/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/__tests__/config-file-handler.test.ts:453:20
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336802/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
plugin test: stdout | builder/utils/changelog.test.ts > getStagingChangelogFromGit > should include actual commits when using gitRawCommitsOpts with tag
plugin test: Non-fatal error: Failed to get changelog from git: Error: fatal: invalid gitfile format: /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/.git
plugin test:     at catchProcessError (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:42:13)
plugin test:     at processTicksAndRejections (node:internal/process/task_queues:103:5)
plugin test:     at outputStream (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@simple-libs+child-process-utils@1.0.1/node_modules/@simple-libs/child-process-utils/src/index.ts:83:17)
plugin test:     at ConventionalGitClient.getTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/GitClient.ts:152:16)
plugin test:     at ConventionalGitClient.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@conventional-changelog+git-client@2.5.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.2.0/node_modules/@conventional-changelog/git-client/src/ConventionalGitClient.ts:100:16)
plugin test:     at ConventionalChangelog.getSemverTags (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:240:22)
plugin test:     at ConventionalChangelog.write (file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/conventional-changelog@7.1.1_conventional-commits-filter@5.0.0/node_modules/conventional-changelog/src/ConventionalChangelog.ts:591:24)
plugin test:     at getStagingChangelogFromGit (/Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.ts:154:22)
plugin test:     at /Users/ajitmehrotra/Projects/onboarding-internal-boot/plugin/builder/utils/changelog.test.ts:115:20
plugin test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/.pnpm/@vitest+runner@3.2.4/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336823/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44435  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-handler-test-1772218336802/test-config.json
packages/unraid-shared test:  ✓ src/util/__tests__/config-file-handler.test.ts (17 tests) 183ms
web test:  ✓ __test__/components/ThemeSwitcher.test.ts (10 tests) 76ms
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336904/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336904/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.loadOrMigrateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:223:20�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.onModuleInit �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:159:5�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/__tests__/config-file.test.ts:391:5
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Writing config to /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336904/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Config is unchanged, skipping write
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFilePersister:test-config.json] Config path: /var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336914/test-config.json
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error: Config file does not exist at '/var/folders/95/f6tc2xdd2t703__z22w4lxzh0000gn/T/config-test-1772218336914/test-config.json'
packages/unraid-shared test:     at ConfigFileHandler.readConfigFile �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:89:13�[90m)�[39m
packages/unraid-shared test:     at ConfigFileHandler.loadConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/util/config-file-handler.ts:56:26�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.loadOrMigrateConfig �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:223:20�[90m)�[39m
packages/unraid-shared test:     at TestConfigFilePersister.onModuleInit �[90m(/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/config-file.ts:159:5�[90m)�[39m
packages/unraid-shared test:     at �[90m/Users/ajitmehrotra/Projects/onboarding-internal-boot/packages/unraid-shared/�[39msrc/services/__tests__/config-file.test.ts:399:5
packages/unraid-shared test:     at file:///Users/ajitmehrotra/Projects/onboarding-internal-boot/node_modules/�[4m.pnpm�[24m/@vitest+runner@3.2.4/node_modules/�[4m@vitest/runner�[24m/dist/chunk-hooks.js:752:20
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM   DEBUG [ConfigFileHandler:test-config.json] Error loading config. Attempting to migrate...
packages/unraid-shared test: [Nest] 44433  - 02/27/2026, 1:52:16 PM VERBOSE [ConfigFileHandler:test-config.json] Existing config unreadable, proceeding with write
packages/unraid-shared test: …
Purpose: remove fragile dependency on /Main/PoolDevices HTML parsing for internal-boot onboarding setup data.

Before: onboarding fetched and parsed the legacy PoolDevices page to infer array availability, eligibility, and candidate devices.

Problem: in this app context the PoolDevices page can be unavailable/redirected, causing parsing to fail and blocking setup even when server state is valid.

Change: introduced GetInternalBootContext GraphQL query (array, vars.fsState, disks) and derived onboarding options directly from API state.

Behavior now: array-stopped checks come from GraphQL state, candidate devices come from unassigned non-USB disks, and pool/boot-size defaults are computed locally.

Simplification: removed template HTML parsing logic from internalBoot composable; it now focuses on mkbootpool submission and reboot post only.

Compatibility: mkbootpool.php and Boot.php invocation paths remain unchanged for apply/reboot behavior.

Validation: ran pnpm codegen, pnpm type-check, pnpm test, and pnpm lint in web successfully.
Purpose: provide onboarding with the same core boot-setup eligibility/device identity data used by webgui state files, without adding new HTTP endpoints.

Before: GraphQL exposed array/fs state and system disk data, but not vars.bootEligible or devs.ini-backed disk assignment identifiers/sectors.

Problem: onboarding could not reliably mirror webgui bootable-pool gating/device identity because mkbootpool expects emhttp disk IDs and webgui eligibility depends on bootEligible.

Change: added bootEligible to var parsing/core type/GraphQL Vars model and surfaced emhttp devs metadata on existing Disk type (emhttpDeviceId, sectors, sectorSize).

How it works: DisksService now reads store.emhttp.devices, matches entries by normalized device name, and projects emhttp assignment metadata alongside existing disk data.

Tests: updated var parser snapshot and extended disks service tests to assert emhttp metadata mapping and config access.

Validation: pnpm --filter ./api type-check, pnpm --filter ./api lint, and targeted vitest for var parser + disks service passed.
Purpose: switch internal-boot onboarding inputs to API-provided boot eligibility and emhttp-backed disk identity metadata.

Before: onboarding inferred eligibility and device IDs from non-webgui sources and local heuristics, which could drift from mkbootpool expectations.

Problem: when PoolDevices rendering is unavailable, onboarding still needs deterministic data for gating and for args[] disk IDs accepted by mkbootpool.

Change: internal boot context query now requests vars.bootEligible and disk emhttp metadata; step maps selectable devices using emhttpDeviceId plus sector data and uses bootEligible for eligibility messaging.

Behavior: array stopped gating uses array/vars state, eligibility uses vars.bootEligible, and boot-size math uses sectors*sectorSize (fallback to disk size).

Compatibility: mkbootpool.php submission path is unchanged; selected device values now align with emhttp disk IDs expected by cmdAssignDisk flow.

Validation: pnpm codegen, pnpm --filter @unraid/web type-check, pnpm --filter @unraid/web test, and pnpm --filter @unraid/web lint passed.
- Purpose: provide a dedicated onboarding query that mirrors webgui internal-boot context sourcing from emhttp state.

- Before: onboarding fetched mixed GraphQL fields (array/vars/disks) and derived boot device context from system disk data.

- Problem: migration parity was weaker because validation inputs and selectable boot devices were not sourced the same way as webgui.

- Change: added  GraphQL query with device options from  (devs.ini), plus pool/share/reserved-name context and boot defaults.

- Change: added resolver/service/model wiring and schema artifacts, including deterministic device sorting aligned to CacheDevices.page behavior.

- Change: added service unit tests and updated emhttp state snapshot expectations for .

- How it works: service reads emhttp var/disks/shares/devices, computes boot-pool defaults and constraints, and returns a single query payload consumed by onboarding.
- Purpose: port internal-boot onboarding UI logic to consume the dedicated API context with webgui-aligned inputs.

- Before: the step mixed array/vars/disks queries and inferred devices/constraints client-side from generic disk metadata.

- Problem: validation and selectable-device behavior could diverge from CacheDevices.page during migration.

- Change: switched query consumption to onboardingInternalBoot context and regenerated typed GraphQL documents.

- Change: added pool-name collision validation against reserved names, user shares, and existing pools to match webgui checks.

- Change: removed HTML-parsing/system-disk derivation paths; device options now come directly from API-provided devs.ini context.

- How it works: the step initializes defaults, slot limits, preset bounds, and validation rules from one query payload and persists the same draft selection flow.
- Purpose: revert the custom onboarding-specific internal boot GraphQL surface and return to existing API surfaces only.
- Before: Query included  with custom resolver/service/types and schema entries generated from that path.
- Problem: this diverged from the migration goal of reusing existing API data and increased maintenance/behavior drift risk.
- Change: removed  resolver/service/model files, detached providers from , and removed schema/codegen artifacts for that field.
- How it works now: API exposes no new onboarding internal-boot query; consumers must use existing /// GraphQL fields.
- Purpose: align onboarding internal-boot setup with existing API queries instead of a custom onboarding query.
- Before: onboarding step read a dedicated onboardingInternalBoot GraphQL field.
- Problem: extra query surface was unnecessary for this migration and made parity/debugging harder.
- Change: rewired onboarding internal-boot context query to existing fields (array, vars, shares, disks), updated step logic to derive options from those fields, and regenerated web GraphQL artifacts.
- How it works: onboarding now checks array state and boot eligibility from existing data, builds device options from disks, and keeps summary/apply behavior on the existing mkbootpool POST flow.
- Tracking: added docs/onboarding-internal-boot-port-differences.md to keep an explicit list of current non-1:1 differences versus webgui.
- Purpose: surface the same var.ini signals webgui uses so onboarding can match visibility and validation behavior without new endpoints.
- Before: GraphQL vars did not expose enableBootTransfer or reservedNames, so web had to infer/hardcode parts of webgui logic.
- Problem: onboarding could not reliably hide the step when already booting internally and could not run reserved-name validation parity.
- Change: added enableBootTransfer and reservedNames to Vars GraphQL model, state types/parser typing, generated schema, and API generated GraphQL types.
- How it works: clients can now read vars.enableBootTransfer and vars.reservedNames directly from the existing vars query.
- Purpose: match webgui behavior for internal-boot onboarding visibility and pool-name validation.
- Before: internal-boot step still appeared when already booting internally, and validation did not include reservedNames parity checks.
- Problem: users on internal boot saw an unnecessary step and validation could diverge from CacheDevices.page constraints.
- Change: added lightweight step-visibility query in onboarding modal using vars.enableBootTransfer, expanded internal-boot context query to include vars.reservedNames/enableBootTransfer, and aligned validation ordering/checks with webgui (reserved names, shares, pools, regex).
- How it works: step is filtered out when enableBootTransfer is no; onboarding validates pool names against the same var/share/pool sources and ordering used by webgui logic.
- Tracking: updated the internal-boot port differences document to remove resolved parity gaps and keep remaining deltas explicit.
- Purpose: make onboarding internal-boot submit the same device identifiers used by webgui.
- Before: dropdown option value preferred GraphQL disk.id, which includes server-prefixed IDs and did not match webgui mkbootpool args.
- Problem: mkbootpool requests could fail or behave unexpectedly because device args were not in emhttp devs.ini id format.
- Change: updated internal boot device option mapping to use emhttpDeviceId first, with device name fallback.
- How it works: submitted args[] now mirror webgui option values, improving compatibility with mkbootpool.php and downstream scripts.
- Purpose: make onboarding internal-boot apply robust when mkbootpool.php returns non-ideal payloads.
- Before: onboarding expected clean JSON and surfaced a generic failure when the response body was empty or noisy.
- Problem: users could see unhelpful failures even with HTTP 200, making server-side debugging difficult.
- Change: added tolerant response parsing (strict JSON first, then JSON substring fallback) and  parity with webgui ajax behavior.
- Change: when response body is empty, onboarding now attempts  and reports detailed HTTP diagnostics as fallback.
- Result: failures now return actionable output instead of opaque request-failed messages.
- Purpose: keep onboarding internal-boot context aligned with fields actually consumed by the UI.
- Before: the context query fetched extra disk fields that were not used by the onboarding step.
- Problem: unused fields increased payload and made emhttp-derived metadata origin less explicit.
- Change: removed unused disk fields from GetInternalBootContext and aliased emhttp-backed metadata to explicit names.
- Change: updated the internal-boot step to consume emhttpSectors and emhttpSectorSize while preserving emhttpDeviceId selection behavior.
- Change: regenerated GraphQL client artifacts for the updated query shape.
- Result: smaller query surface, clearer field provenance, and no behavior change in selection/size calculations.
- Purpose: make onboarding internal-boot requests match webgui PHP post expectations.
- Before: onboarding posted args to mkbootpool.php without csrf_token and relied only on same-origin cookies.
- Problem: on systems enforcing CSRF checks, mkbootpool.php can return an empty HTTP 200 response instead of JSON output.
- Change: append csrf_token from global csrf_token into form payload and also send x-csrf-token header when available.
- Change: remove HTTP fetch fallback to /boot/config/internal_boot/output.log because that filesystem path is not generally web-served.
- Result: onboarding requests now follow existing webgui request conventions and avoid noisy 404 fallback probes.
- Purpose: align onboarding internal-boot logging with the rest of onboarding and make reboot submit path reliable.
- Before: successful internal-boot apply logged full script output into onboarding console, and reboot submit used a minimal form post that could land on Boot.php without executing expected reboot flow.
- Problem: extra success log noise reduced signal in onboarding logs, and reboot flow could appear inert in some contexts.
- Change: removed verbose success output logging from summary step; success now logs the same concise status style as other onboarding actions.
- Change: reboot submit now posts directly to /plugins/dynamix/include/Boot.php, targets _top, and includes csrf_token when available.
- Change: tightened empty mkbootpool-response message to concise guidance.
- Change: added composable tests for mkbootpool payload csrf inclusion and reboot form construction.
- Result: cleaner onboarding logs and a reboot action that follows webgui/php expectations more robustly.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Walkthrough

This PR introduces internal boot functionality across the stack. It adds boot-related fields to API state types (bootEligible, enableBootTransfer, reservedNames), disk metadata fields (emhttpDeviceId, sectors, sectorSize), a new onboarding step component for internal boot configuration, supporting store state management, GraphQL queries, and helper composables for submitting boot configurations.

Changes

Cohort / File(s) Summary
API GraphQL Schema & Type Definitions
api/generated-schema.graphql, api/src/core/types/states/var.ts
Added six new fields: Disk gains emhttpDeviceId, sectors, sectorSize; Var state gains bootEligible, enableBootTransfer, reservedNames. All additions are optional except bootEligible which is required.
API Disk Service & Emhttp Integration
api/src/unraid-api/graph/resolvers/disks/disks.model.ts, api/src/unraid-api/graph/resolvers/disks/disks.service.ts, api/src/unraid-api/graph/resolvers/disks/disks.service.spec.ts
Introduced emhttp device data fetching and mapping to disks. Added internal helpers to normalize device names, parse numeric values, and enrich Disk objects with emhttpDeviceId, sectors, and sectorSize from emhttp device records.
API Var State Parsing
api/src/store/state-parsers/var.ts, api/src/__test__/store/state-parsers/var.test.ts
Extended VarIni type and parse function to handle new boot-related fields, with bootEligible converted from string to boolean using iniBooleanToJsBoolean helper.
API Test Updates
api/src/__test__/store/modules/emhttp.test.ts
Updated test expectations to include new bootEligible field in varState assertions.
Web Onboarding Internal Boot Step Component
web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
New Vue component implementing the internal boot configuration UI (739 lines). Fetches boot context via GraphQL, manages form state for pool name, slot count, device selection, boot size presets, and BIOS updates. Validates user input and persists selection to draft store.
Web Onboarding Modal & Step Registry
web/src/components/Onboarding/OnboardingModal.vue, web/src/components/Onboarding/OnboardingSteps.vue, web/src/components/Onboarding/stepRegistry.ts, web/components.d.ts
Added INTERNAL_BOOT step to onboarding flow with conditional visibility based on partner build status and boot transfer enablement. Registered step in step registry with metadata and component mapping.
Web Onboarding Store & State Management
web/src/components/Onboarding/store/onboardingDraft.ts
Extended onboarding draft store with internal boot state: internalBootSelection, internalBootInitialized, internalBootSkipped, internalBootApplySucceeded. Added setters and persistence logic with normalization helper.
Web Internal Boot Composables
web/src/components/Onboarding/composables/internalBoot.ts, web/__test__/components/Onboarding/internalBoot.test.ts
New composable exporting submitInternalBootCreation and submitInternalBootReboot functions. Handles POST requests to mkbootpool.php and Boot.php endpoints with CSRF token inclusion. Includes payload parsing and response handling with fallbacks. Comprehensive test coverage for both submission functions.
Web GraphQL Queries
web/src/components/Onboarding/graphql/getInternalBootContext.query.ts, web/src/components/Onboarding/graphql/getInternalBootStepVisibility.query.ts
New GraphQL query files defining GET_INTERNAL_BOOT_CONTEXT_QUERY and GET_INTERNAL_BOOT_STEP_VISIBILITY_QUERY for fetching boot context data and visibility flags.
Web GraphQL Type Definitions & Operations
web/src/composables/gql/graphql.ts, web/src/composables/gql/gql.ts
Extended Disk and Vars types with new optional fields. Added GetInternalBootContextQuery, GetInternalBootStepVisibilityQuery types and documents. Updated GetCoreSettingsQuery to include customization.activationCode structure.
Web Summary & Next Steps Components
web/src/components/Onboarding/steps/OnboardingSummaryStep.vue, web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
Added internal boot summary display with Pool, Slots, Boot Reserved, BIOS update, and Devices. Integrated submitInternalBootCreation into apply flow. Extended NextStepsStep with reboot button logic triggered by internal boot success.
Web Import Formatting
web/src/composables/gql/index.ts
Minor formatting: converted export statement quotes from double to single. No functional changes.
Documentation
docs/onboarding-internal-boot-port-differences.md
New documentation file detailing non-1:1 behavioral differences between onboarding internal boot port and webgui implementation across five areas: data source, device labeling, dialog behavior, reboot flow, and visibility source.
Web Test Infrastructure
web/__test__/components/Onboarding/OnboardingModal.test.ts, web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
Extended mocking and test scenarios for internal boot step. Added internalBootVisibilityResult mock, isPartnerBuild flag, internal boot selection state, and assertions for visibility and submission flows. Tests cover regular builds, partner builds, and disabled boot transfer scenarios.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Modal as OnboardingModal
    participant InternalBootStep as InternalBootStep
    participant GraphQL as Apollo<br/>(GraphQL)
    participant API as Server
    participant Store as DraftStore

    User->>Modal: Navigate to Internal Boot
    Modal->>GraphQL: GET_INTERNAL_BOOT_CONTEXT_QUERY
    GraphQL->>API: Fetch array, vars, shares, disks
    API-->>GraphQL: Return boot context
    GraphQL-->>InternalBootStep: Provide context data
    
    InternalBootStep->>Store: Load previous selection
    User->>InternalBootStep: Configure pool, devices, boot size
    InternalBootStep->>InternalBootStep: Validate form
    
    User->>InternalBootStep: Submit (Next)
    InternalBootStep->>Store: setInternalBootSelection()
    InternalBootStep->>Modal: onComplete()
    
    Modal->>Modal: Proceed to Summary step
    User->>Modal: Review & Apply
    Modal->>API: submitInternalBootCreation()
    Note over API: POST to mkbootpool.php
    API-->>Modal: Creation response
    
    Modal->>Store: setInternalBootApplySucceeded(true)
    Modal->>Modal: Show reboot button
    User->>Modal: Reboot
    Modal->>API: submitInternalBootReboot()
    Note over API: POST to Boot.php
    API-->>User: System reboots
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A boot in the making, our feature takes flight,
Pools now can boot up, configured just right!
From forms to the servers, the disks dance with glee,
Internal boot magic, as smooth as can be!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/onboarding internal boot' clearly describes the primary feature being added—internal boot functionality for the onboarding flow. It aligns with the extensive changes across API types, GraphQL schemas, UI components, and state management related to internal boot setup.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/onboarding-internal-boot

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ajit-Mehrotra Ajit-Mehrotra self-assigned this Mar 2, 2026
return null;
}

const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;

Check failure

Code scanning / CodeQL

Inefficient regular expression High

This part of the regular expression may cause exponential backtracking on strings starting with 'a' and containing many repetitions of '-'.

Copilot Autofix

AI about 4 hours ago

In general, to fix inefficient regular expressions, remove ambiguous quantified subpatterns—especially something like (X*Y)* where characters can be consumed by either X* or Y—or rewrite them into simpler, unambiguous expressions. This usually means collapsing nested quantified groups, avoiding overlapping character classes in successive tokens, or adding explicit boundaries.

Here, the current regex is:

const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;

Semantically, this means:

  • Start with a lowercase letter.
  • Then, repeat zero or more “segments”, each consisting of:
    • zero or more allowed characters [a-z0-9~._-]*
    • followed by a final character [a-z_-].

This is equivalent to: “start with a letter, then any number of allowed characters, but do not end with a digit or ~.”. We can express this more simply and without nested ambiguous quantifiers as:

^[a-z][a-z0-9~._-]*[a-z_-]$

This form has only simple quantifiers on character classes and no nested repetition, so backtracking is linear. It still enforces:

  • First char: [a-z] (lowercase letter).
  • Middle: any number (possibly zero) of allowed characters.
  • Last char: [a-z_-] (no trailing digit, ~, or .).

To preserve current semantics exactly, we must also accept 2‑character names like aa and reject 1‑character names like a (because the original pattern requires at least one repetition of ([a-z0-9~._-]*[a-z_-])). If the original behavior is intended, we need to maintain “length ≥ 2”. For clarity and to keep the engine’s work simple, we can write:

^[a-z][a-z0-9~._-]*[a-z_-]$

This already ensures a minimum length of 2 because there are two mandatory characters (first and last). Given the original pattern, this is behaviorally equivalent for all strings that fit the alphabet—they all must have at least two characters to match.

Therefore, in web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue, around line 454, replace the existing poolNamePattern definition with the simplified, unambiguous regex:

const poolNamePattern = /^[a-z][a-z0-9~._-]*[a-z_-]$/;

No extra imports or helper methods are needed; we only change this one line.

Suggested changeset 1
web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
--- a/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
+++ b/web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
@@ -451,7 +451,7 @@
     return null;
   }
 
-  const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;
+  const poolNamePattern = /^[a-z][a-z0-9~._-]*[a-z_-]$/;
   if (!poolNamePattern.test(normalizedPoolName)) {
     formError.value = 'Use only lowercase with no special characters or leading/trailing digits.';
     return null;
EOF
@@ -451,7 +451,7 @@
return null;
}

const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;
const poolNamePattern = /^[a-z][a-z0-9~._-]*[a-z_-]$/;
if (!poolNamePattern.test(normalizedPoolName)) {
formError.value = 'Use only lowercase with no special characters or leading/trailing digits.';
return null;
Copilot is powered by AI and may make mistakes. Always verify output.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1881/dynamix.unraid.net.plg

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

❌ Patch coverage is 26.68622% with 750 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.75%. Comparing base (15bd747) to head (4bf525b).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...ts/Onboarding/steps/OnboardingInternalBootStep.vue 0.16% 591 Missing ⚠️
...src/components/Onboarding/store/onboardingDraft.ts 0.00% 62 Missing ⚠️
.../components/Onboarding/composables/internalBoot.ts 74.31% 28 Missing ⚠️
...nents/Onboarding/steps/OnboardingNextStepsStep.vue 0.00% 16 Missing ⚠️
.../unraid-api/graph/resolvers/disks/disks.service.ts 75.80% 15 Missing ⚠️
...rc/unraid-api/graph/resolvers/disks/disks.model.ts 25.00% 9 Missing ⚠️
...ponents/Onboarding/steps/OnboardingSummaryStep.vue 90.72% 9 Missing ⚠️
web/src/components/Onboarding/stepRegistry.ts 0.00% 7 Missing ⚠️
.../src/unraid-api/graph/resolvers/vars/vars.model.ts 0.00% 6 Missing ⚠️
web/src/components/Onboarding/OnboardingModal.vue 91.30% 4 Missing ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1881      +/-   ##
==========================================
- Coverage   48.06%   47.75%   -0.32%     
==========================================
  Files        1002     1006       +4     
  Lines       66511    67495     +984     
  Branches     6672     6727      +55     
==========================================
+ Hits        31968    32229     +261     
- Misses      34422    35144     +722     
- Partials      121      122       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
web/src/components/Onboarding/OnboardingSteps.vue (1)

10-17: Consider centralizing StepId in a shared type.

Lines [10-17] still duplicate step IDs in this component. Exporting a single StepId type from a shared onboarding module would reduce drift risk as steps evolve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/OnboardingSteps.vue` around lines 10 - 17, This
file defines a local StepId union type that duplicates onboarding step IDs;
remove the local StepId declaration in OnboardingSteps.vue and import a single
exported StepId from a shared onboarding types/module (e.g., export StepId from
a new/shared onboarding types file), then update all references in this
component (and any other components) to use the imported StepId type; ensure the
shared module exports the same union values and update any usages of
'OVERVIEW'|'CONFIGURE_SETTINGS' etc. to the centralized StepId to prevent drift.
web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue (2)

83-90: Consider adding error handling for the reboot submission.

submitInternalBootReboot() is called without awaiting or handling potential errors. If the reboot request fails, the user has no feedback.

♻️ Suggested approach
-const handlePrimaryAction = () => {
+const handlePrimaryAction = async () => {
   if (showRebootButton.value) {
-    submitInternalBootReboot();
-    return;
+    try {
+      await submitInternalBootReboot();
+    } catch (error) {
+      console.error('Reboot request failed:', error);
+    }
+    return;
   }

   props.onComplete();
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue` around lines
83 - 90, handlePrimaryAction calls submitInternalBootReboot() without awaiting
or handling failures, so add async error handling: make handlePrimaryAction
async, await submitInternalBootReboot(), wrap the call in try/catch, show user
feedback on error (e.g., set a local error state or call an existing
notification method) and only call props.onComplete() on success; also consider
disabling the primary button or showing a loading state while awaiting
submitInternalBootReboot() to prevent duplicate submissions.

52-54: Hardcoded "Reboot" string should use i18n.

The primaryButtonText uses a hardcoded 'Reboot' string while other text uses t() for translation. This creates an inconsistency for localized builds.

♻️ Suggested fix
 const primaryButtonText = computed(() =>
-  showRebootButton.value ? 'Reboot' : t('onboarding.nextSteps.continueToDashboard')
+  showRebootButton.value ? t('common.reboot', 'Reboot') : t('onboarding.nextSteps.continueToDashboard')
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue` around lines
52 - 54, primaryButtonText uses a hardcoded 'Reboot' instead of the i18n helper;
update the computed to call t() for the reboot label (e.g.,
t('onboarding.nextSteps.reboot')) when showRebootButton.value is true, and add
the corresponding translation key/value to your locale files; reference
primaryButtonText and showRebootButton to locate and change the conditional, and
ensure existing usages of t('onboarding.nextSteps.continueToDashboard') remain
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/components/Onboarding/composables/internalBoot.ts`:
- Around line 46-56: The code is unsafely casting JSON.parse results to
MkbootpoolResponsePayload and coercing parsed.ok with Boolean(), bypassing
runtime validation and mis-handling string "false"; instead parse into unknown
(no "as" cast), implement a runtime type guard that checks required fields and
their types (e.g., verify the parsed object has the expected properties for
MkbootpoolResponsePayload), apply that guard both after JSON.parse(trimmed) and
after JSON.parse(trimmed.slice(firstBrace, lastBrace + 1)), and for parsed.ok
normalize it explicitly (if typeof ok === 'boolean' use it, else if typeof ok
=== 'string' accept only 'true'/'false' and convert accordingly), returning null
if validation fails; remove all "as MkbootpoolResponsePayload" casts and replace
them with validated, typed returns using the new type guard.
- Around line 85-119: The code processes the fetch result without checking HTTP
success and uses unsafe casts; update the logic to require response.ok when
deciding success (e.g., return ok: response.ok && parsed?.ok === true instead of
Boolean(parsed.ok)) and modify the parsing flow to remove "as
MkbootpoolResponsePayload" casts by moving JSON.parse/validation into a proper
type-guard or validator inside parseMkbootpoolPayload (ensure
parseMkbootpoolPayload returns a typed union or null after validating required
fields like ok, code, output) so you no longer need to cast parsed values before
using them.

In `@web/src/components/Onboarding/stepRegistry.ts`:
- Around line 38-42: INTERNAL_BOOT in the stepRegistry uses hardcoded English
strings for titleKey and descriptionKey; replace them with i18n keys (e.g., set
titleKey to a key like 'onboarding.internalBoot.title' and descriptionKey to
'onboarding.internalBoot.description') to match the project's localization
pattern used by other steps, and add the corresponding entries to the locale
resource files so the keys resolve at runtime.

In `@web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue`:
- Around line 454-458: The regex assigned to poolNamePattern (used to validate
normalizedPoolName) contains nested quantifiers which can lead to ReDoS; replace
the single complex pattern with a safer two-step validation: first use a simple
allowed-character pattern (e.g., ensure it starts with a lowercase letter and
only contains allowed chars) and then separately check structural rules (e.g.,
does not end or start with a digit, disallow consecutive special characters, or
other constraints). Update the validation logic where poolNamePattern is tested
and set formError.value accordingly so the tests use the new checks instead of
the vulnerable nested-quantifier regex.

In `@web/src/composables/gql/index.ts`:
- Around line 1-2: The re-exports in web/src/composables/gql/index.ts use
extensionless relative paths which breaks ESM conventions; update the two export
statements (export * from './fragment-masking' and export * from './gql') to
include the .js extension (export * from './fragment-masking.js' and export *
from './gql.js') so they match the project's ESM style and other modules like
web/src/utils/authorizationLink.ts.

---

Nitpick comments:
In `@web/src/components/Onboarding/OnboardingSteps.vue`:
- Around line 10-17: This file defines a local StepId union type that duplicates
onboarding step IDs; remove the local StepId declaration in OnboardingSteps.vue
and import a single exported StepId from a shared onboarding types/module (e.g.,
export StepId from a new/shared onboarding types file), then update all
references in this component (and any other components) to use the imported
StepId type; ensure the shared module exports the same union values and update
any usages of 'OVERVIEW'|'CONFIGURE_SETTINGS' etc. to the centralized StepId to
prevent drift.

In `@web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue`:
- Around line 83-90: handlePrimaryAction calls submitInternalBootReboot()
without awaiting or handling failures, so add async error handling: make
handlePrimaryAction async, await submitInternalBootReboot(), wrap the call in
try/catch, show user feedback on error (e.g., set a local error state or call an
existing notification method) and only call props.onComplete() on success; also
consider disabling the primary button or showing a loading state while awaiting
submitInternalBootReboot() to prevent duplicate submissions.
- Around line 52-54: primaryButtonText uses a hardcoded 'Reboot' instead of the
i18n helper; update the computed to call t() for the reboot label (e.g.,
t('onboarding.nextSteps.reboot')) when showRebootButton.value is true, and add
the corresponding translation key/value to your locale files; reference
primaryButtonText and showRebootButton to locate and change the conditional, and
ensure existing usages of t('onboarding.nextSteps.continueToDashboard') remain
unchanged.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between aaa0372 and 4bf525b.

⛔ Files ignored due to path filters (1)
  • api/src/unraid-api/cli/generated/graphql.ts is excluded by !**/generated/**
📒 Files selected for processing (27)
  • api/generated-schema.graphql
  • api/src/__test__/store/modules/emhttp.test.ts
  • api/src/__test__/store/state-parsers/var.test.ts
  • api/src/core/types/states/var.ts
  • api/src/store/state-parsers/var.ts
  • api/src/unraid-api/graph/resolvers/disks/disks.model.ts
  • api/src/unraid-api/graph/resolvers/disks/disks.service.spec.ts
  • api/src/unraid-api/graph/resolvers/disks/disks.service.ts
  • api/src/unraid-api/graph/resolvers/vars/vars.model.ts
  • docs/onboarding-internal-boot-port-differences.md
  • web/__test__/components/Onboarding/OnboardingModal.test.ts
  • web/__test__/components/Onboarding/OnboardingSummaryStep.test.ts
  • web/__test__/components/Onboarding/internalBoot.test.ts
  • web/components.d.ts
  • web/src/components/Onboarding/OnboardingModal.vue
  • web/src/components/Onboarding/OnboardingSteps.vue
  • web/src/components/Onboarding/composables/internalBoot.ts
  • web/src/components/Onboarding/graphql/getInternalBootContext.query.ts
  • web/src/components/Onboarding/graphql/getInternalBootStepVisibility.query.ts
  • web/src/components/Onboarding/stepRegistry.ts
  • web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue
  • web/src/components/Onboarding/steps/OnboardingNextStepsStep.vue
  • web/src/components/Onboarding/steps/OnboardingSummaryStep.vue
  • web/src/components/Onboarding/store/onboardingDraft.ts
  • web/src/composables/gql/gql.ts
  • web/src/composables/gql/graphql.ts
  • web/src/composables/gql/index.ts

Comment on lines +46 to +56
return JSON.parse(trimmed) as MkbootpoolResponsePayload;
} catch {
const firstBrace = trimmed.indexOf('{');
const lastBrace = trimmed.lastIndexOf('}');
if (firstBrace === -1 || lastBrace <= firstBrace) {
return null;
}

try {
return JSON.parse(trimmed.slice(firstBrace, lastBrace + 1)) as MkbootpoolResponsePayload;
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# First, let's find and read the file to see the actual code
file_path="web/src/components/Onboarding/composables/internalBoot.ts"

if [ -f "$file_path" ]; then
  echo "=== File exists: $file_path ==="
  echo "=== Full file content ==="
  cat -n "$file_path"
else
  echo "File not found at $file_path"
  # Try to find it with fd
  echo "=== Searching for internalBoot.ts ==="
  fd -a 'internalBoot\.ts$'
fi

Repository: unraid/api

Length of output: 4838


Avoid unsafe parse casts and truthy coercion for ok.

Lines 46 and 55 use as MkbootpoolResponsePayload which bypasses TypeScript's type system without runtime shape validation. Additionally, line 103's Boolean(parsed.ok) mishandles string values (e.g., "false" becomes true). Per coding guidelines, avoid casting; prefer proper typing from the start.

Proposed fix
 interface MkbootpoolResponsePayload {
   ok?: boolean;
   code?: number;
   output?: string;
 }
+
+const isMkbootpoolResponsePayload = (value: unknown): value is MkbootpoolResponsePayload => {
+  if (!value || typeof value !== 'object') return false;
+  const v = value as { ok?: unknown; code?: unknown; output?: unknown };
+  return (
+    (v.ok === undefined || typeof v.ok === 'boolean') &&
+    (v.code === undefined || typeof v.code === 'number') &&
+    (v.output === undefined || typeof v.output === 'string')
+  );
+};
@@
   try {
-    return JSON.parse(trimmed) as MkbootpoolResponsePayload;
+    const parsed: unknown = JSON.parse(trimmed);
+    return isMkbootpoolResponsePayload(parsed) ? parsed : null;
   } catch {
@@
     try {
-      return JSON.parse(trimmed.slice(firstBrace, lastBrace + 1)) as MkbootpoolResponsePayload;
+      const parsed: unknown = JSON.parse(trimmed.slice(firstBrace, lastBrace + 1));
+      return isMkbootpoolResponsePayload(parsed) ? parsed : null;
     } catch {
       return null;
     }
@@
-      ok: Boolean(parsed.ok),
+      ok: parsed.ok === true,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/composables/internalBoot.ts` around lines 46 -
56, The code is unsafely casting JSON.parse results to MkbootpoolResponsePayload
and coercing parsed.ok with Boolean(), bypassing runtime validation and
mis-handling string "false"; instead parse into unknown (no "as" cast),
implement a runtime type guard that checks required fields and their types
(e.g., verify the parsed object has the expected properties for
MkbootpoolResponsePayload), apply that guard both after JSON.parse(trimmed) and
after JSON.parse(trimmed.slice(firstBrace, lastBrace + 1)), and for parsed.ok
normalize it explicitly (if typeof ok === 'boolean' use it, else if typeof ok
=== 'string' accept only 'true'/'false' and convert accordingly), returning null
if validation fails; remove all "as MkbootpoolResponsePayload" casts and replace
them with validated, typed returns using the new type guard.

Comment on lines +85 to +119
const response = await fetch('/plugins/dynamix/include/mkbootpool.php', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
...(csrfToken ? { 'x-csrf-token': csrfToken } : {}),
},
body: payload.toString(),
});

const raw = await response.text();
const trimmedRaw = raw.trim();
const parsed = parseMkbootpoolPayload(raw);

if (parsed) {
const parsedOutput = parsed.output?.trim() ?? '';
return {
ok: Boolean(parsed.ok),
code: parsed.code,
output: parsedOutput.length > 0 ? parsedOutput : trimmedRaw || 'No output',
};
}
if (trimmedRaw.length > 0) {
return {
ok: false,
output: trimmedRaw,
};
}

return {
ok: false,
output: `mkbootpool returned an empty response (HTTP ${response.status}). Check /boot/config/internal_boot/output.log via server shell for script output.`,
};
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the file
find . -name "internalBoot.ts" -type f

Repository: unraid/api

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Read the file to see the full context
cat -n web/src/components/Onboarding/composables/internalBoot.ts

Repository: unraid/api

Length of output: 4733


🏁 Script executed:

#!/bin/bash
# Search for the parseMkbootpoolPayload function definition
rg -nP 'function\s+parseMkbootpoolPayload|const\s+parseMkbootpoolPayload' -A10 web/src/components/Onboarding/composables/

Repository: unraid/api

Length of output: 1064


🏁 Script executed:

#!/bin/bash
# Find all call sites of submitInternalBootCreation
rg -nP '\bsubmitInternalBootCreation\s*\(' --type=ts --type=tsx

Repository: unraid/api

Length of output: 81


🏁 Script executed:

#!/bin/bash
# Check the type definitions for InternalBootSubmitResult
rg -nP 'interface\s+InternalBootSubmitResult|type\s+InternalBootSubmitResult' -A5 web/src/components/Onboarding/

Repository: unraid/api

Length of output: 516


🏁 Script executed:

#!/bin/bash
# Find all call sites - retry without tsx type issue
rg -nP '\bsubmitInternalBootCreation\s*\(' -A5 -B2 --type=ts

Repository: unraid/api

Length of output: 729


🏁 Script executed:

#!/bin/bash
# Also check for any imports of this function
rg -nP 'submitInternalBootCreation' --type=ts -B2

Repository: unraid/api

Length of output: 2615


🏁 Script executed:

#!/bin/bash
# Check if response.ok is checked anywhere in the codebase
rg -nP 'response\.ok' web/src/components/Onboarding/

Repository: unraid/api

Length of output: 36


🏁 Script executed:

#!/bin/bash
# Examine the test file to see how the result is used
cat -n web/__test__/components/Onboarding/internalBoot.test.ts

Repository: unraid/api

Length of output: 3297


🏁 Script executed:

#!/bin/bash
# Check the OnboardingSummaryStep component for actual usage
rg -nP 'submitInternalBootCreation' web/src/components/Onboarding/ -B3 -A8

Repository: unraid/api

Length of output: 3846


🏁 Script executed:

#!/bin/bash
# Check if response.ok is used anywhere in response handling
rg -nP 'response\.(ok|status)' web/src/components/Onboarding/

Repository: unraid/api

Length of output: 275


🏁 Script executed:

#!/bin/bash
# Search entire codebase for any other calls to submitInternalBootCreation outside Onboarding dir
rg -nP 'submitInternalBootCreation' --type=ts

Repository: unraid/api

Length of output: 1091


🏁 Script executed:

#!/bin/bash
# Check context around the call in OnboardingSummaryStep to confirm try-catch is present
sed -n '655,680p' web/src/components/Onboarding/steps/OnboardingSummaryStep.vue

Repository: unraid/api

Length of output: 912


🏁 Script executed:

#!/bin/bash
# Check the complete try-catch block and error handling context
sed -n '655,695p' web/src/components/Onboarding/steps/OnboardingSummaryStep.vue

Repository: unraid/api

Length of output: 1421


Check response.ok before processing response body and remove unnecessary type casts.

The function doesn't check response.ok before parsing the response, allowing non-2xx HTTP responses with valid JSON bodies to return ok: true. Additionally, lines 46 and 55 use as MkbootpoolResponsePayload casts, violating the typing guideline to avoid casting.

Recommended fixes:

  1. Check response.ok when determining success (line 103 should use ok: response.ok && parsed.ok === true instead of Boolean(parsed.ok))
  2. Replace JSON.parse(trimmed) as MkbootpoolResponsePayload with proper type guards or validation to eliminate casts per coding guidelines
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/composables/internalBoot.ts` around lines 85 -
119, The code processes the fetch result without checking HTTP success and uses
unsafe casts; update the logic to require response.ok when deciding success
(e.g., return ok: response.ok && parsed?.ok === true instead of
Boolean(parsed.ok)) and modify the parsing flow to remove "as
MkbootpoolResponsePayload" casts by moving JSON.parse/validation into a proper
type-guard or validator inside parseMkbootpoolPayload (ensure
parseMkbootpoolPayload returns a typed union or null after validating required
fields like ok, code, output) so you no longer need to cast parsed values before
using them.

Comment on lines +38 to +42
INTERNAL_BOOT: {
titleKey: 'Internal Boot',
descriptionKey: 'Configure bootable pool',
icon: 'i-heroicons-circle-stack',
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded strings should use i18n keys for consistency.

Other steps use localization key patterns (e.g., 'onboarding.coreSettings.title'), but INTERNAL_BOOT uses hardcoded English strings. This will prevent localization of the internal boot step.

Suggested fix using i18n keys
   INTERNAL_BOOT: {
-    titleKey: 'Internal Boot',
-    descriptionKey: 'Configure bootable pool',
+    titleKey: 'onboarding.internalBoot.title',
+    descriptionKey: 'onboarding.internalBoot.description',
     icon: 'i-heroicons-circle-stack',
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INTERNAL_BOOT: {
titleKey: 'Internal Boot',
descriptionKey: 'Configure bootable pool',
icon: 'i-heroicons-circle-stack',
},
INTERNAL_BOOT: {
titleKey: 'onboarding.internalBoot.title',
descriptionKey: 'onboarding.internalBoot.description',
icon: 'i-heroicons-circle-stack',
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/stepRegistry.ts` around lines 38 - 42,
INTERNAL_BOOT in the stepRegistry uses hardcoded English strings for titleKey
and descriptionKey; replace them with i18n keys (e.g., set titleKey to a key
like 'onboarding.internalBoot.title' and descriptionKey to
'onboarding.internalBoot.description') to match the project's localization
pattern used by other steps, and add the corresponding entries to the locale
resource files so the keys resolve at runtime.

Comment on lines +454 to +458
const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;
if (!poolNamePattern.test(normalizedPoolName)) {
formError.value = 'Use only lowercase with no special characters or leading/trailing digits.';
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential ReDoS vulnerability in pool name regex.

The pattern ([a-z0-9~._-]*[a-z_-])* can cause exponential backtracking on inputs like "a" + "-".repeat(30). The nested quantifiers create overlapping match possibilities.

🔒 Suggested fix with a safer pattern
-  const poolNamePattern = /^[a-z]([a-z0-9~._-]*[a-z_-])*$/;
+  // Validates: starts with lowercase letter, contains allowed chars, doesn't end with digit
+  const poolNamePattern = /^[a-z][a-z0-9~._-]*[a-z_-]$|^[a-z]$/;
   if (!poolNamePattern.test(normalizedPoolName)) {

Alternatively, if the intent is to disallow consecutive special characters or enforce specific structure, consider validating in multiple steps:

// Check allowed characters first, then validate structure
const hasValidChars = /^[a-z][a-z0-9~._-]*$/.test(normalizedPoolName);
const endsWithDigit = /\d$/.test(normalizedPoolName);
if (!hasValidChars || endsWithDigit) {
  formError.value = '...';
  return null;
}
🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 454-454: Inefficient regular expression
This part of the regular expression may cause exponential backtracking on strings starting with 'a' and containing many repetitions of '-'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/Onboarding/steps/OnboardingInternalBootStep.vue` around
lines 454 - 458, The regex assigned to poolNamePattern (used to validate
normalizedPoolName) contains nested quantifiers which can lead to ReDoS; replace
the single complex pattern with a safer two-step validation: first use a simple
allowed-character pattern (e.g., ensure it starts with a lowercase letter and
only contains allowed chars) and then separately check structural rules (e.g.,
does not end or start with a digit, disallow consecutive special characters, or
other constraints). Update the validation logic where poolNamePattern is tested
and set formError.value accordingly so the tests use the new checks instead of
the vulnerable nested-quantifier regex.

Comment on lines +1 to +2
export * from './fragment-masking';
export * from './gql';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the file in question
find . -name "index.ts" -path "*/composables/gql/*" -type f

Repository: unraid/api

Length of output: 119


🏁 Script executed:

# Also check for the referenced files
find . -path "*/composables/gql/*" -type f

Repository: unraid/api

Length of output: 377


🏁 Script executed:

# Search for similar re-export patterns in the web directory
rg "export \* from" web/ --type ts -A 0 -B 0 | head -30

Repository: unraid/api

Length of output: 372


🏁 Script executed:

cat -n web/src/composables/gql/index.ts

Repository: unraid/api

Length of output: 126


🏁 Script executed:

# Check other re-export patterns in web/src to understand consistency
rg "export \* from" web/src --type ts -B 0 -A 0 | sort | uniq -c

Repository: unraid/api

Length of output: 279


🏁 Script executed:

# Check if there's any build or transpilation config that might affect this
find . -name "tsconfig.json" -o -name "nuxt.config.ts" -o -name "vite.config.ts" | head -5 | xargs cat 2>/dev/null

Repository: unraid/api

Length of output: 11785


Add .js extensions to re-exports for ESM compatibility.

Lines 1-2 use extensionless relative paths, which violates the project's ESM convention. Other files in the codebase (e.g., web/src/utils/authorizationLink.ts) consistently use .js extensions in re-exports.

Suggested fix
-export * from './fragment-masking';
-export * from './gql';
+export * from './fragment-masking.js';
+export * from './gql.js';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export * from './fragment-masking';
export * from './gql';
export * from './fragment-masking.js';
export * from './gql.js';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/composables/gql/index.ts` around lines 1 - 2, The re-exports in
web/src/composables/gql/index.ts use extensionless relative paths which breaks
ESM conventions; update the two export statements (export * from
'./fragment-masking' and export * from './gql') to include the .js extension
(export * from './fragment-masking.js' and export * from './gql.js') so they
match the project's ESM style and other modules like
web/src/utils/authorizationLink.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant