From 88087d5201992298cdafa791d5d1b5bb23dcd72b Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Wed, 3 Sep 2025 15:42:21 -0400 Subject: [PATCH] feat: mount vue apps, not web components (#1639) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary by CodeRabbit * **New Features** * Standalone web bundle with auto-mount utilities and a self-contained test page. * New responsive modal components for consistent mobile/desktop dialogs. * Header actions to copy OS/API versions. * **Improvements** * Refreshed UI styles (muted borders), accessibility and animation refinements. * Theming updates and Tailwind v4–aligned, component-scoped styles. * Runtime GraphQL endpoint override and CSRF header support. * **Bug Fixes** * Safer network fetching and improved manifest/asset loading with duplicate protection. * **Tests/Chores** * Parallel plugin tests, new extractor test suite, and updated build/test scripts. --- .github/workflows/main.yml | 12 +- @tailwind-shared/base-utilities.css | 34 +- @tailwind-shared/css-variables.css | 81 +-- @tailwind-shared/index.css | 2 +- .../cli/setup-plugin-environment.spec.ts | 17 + .../cli/setup-txz-environment.spec.ts | 48 +- plugin/builder/build-txz.ts | 14 +- plugin/builder/cli/setup-txz-environment.ts | 2 +- plugin/package.json | 3 +- plugin/plugins/dynamix.unraid.net.plg | 23 +- .../include/web-components-extractor.php | 187 +++--- .../include/UnraidCheck.php | 53 +- plugin/tests/test-extractor.php | 369 ++++++++++++ plugin/tests/test-extractor.sh | 16 + plugin/vitest.config.ts | 3 + pnpm-lock.yaml | 540 ------------------ unraid-ui/eslint.config.ts | 7 + unraid-ui/src/components.ts | 1 + .../src/components/brand/BrandButton.vue | 25 +- .../components/brand/brand-button.variants.ts | 4 + .../components/common/badge/badge.variants.ts | 2 +- .../src/components/common/button/Button.vue | 43 +- .../common/button/button.variants.ts | 8 +- unraid-ui/src/components/common/index.ts | 1 + .../responsive-modal/ResponsiveModal.vue | 67 +++ .../ResponsiveModalFooter.vue | 31 + .../ResponsiveModalHeader.vue | 33 ++ .../responsive-modal/ResponsiveModalTitle.vue | 26 + .../common/responsive-modal/index.ts | 8 + .../components/common/sheet/SheetContent.vue | 13 +- .../components/common/sheet/sheet.variants.ts | 4 +- .../components/common/tabs/TabsTrigger.vue | 10 +- .../common/tooltip/TooltipContent.vue | 2 +- .../common/tooltip/TooltipTrigger.vue | 4 +- .../components/form/combobox/ComboboxList.vue | 2 +- .../src/components/form/switch/Switch.vue | 2 + .../ui/accordion/AccordionTrigger.vue | 4 +- .../src/components/ui/dialog/DialogClose.vue | 25 + .../components/ui/dialog/DialogContent.vue | 22 +- .../components/ui/dialog/DialogTrigger.vue | 2 +- .../ui/dropdown-menu/DropdownMenuContent.vue | 2 +- .../dropdown-menu/DropdownMenuSubTrigger.vue | 4 +- .../ui/dropdown-menu/DropdownMenuTrigger.vue | 2 + .../components/ui/select/SelectContent.vue | 2 +- .../components/ui/select/SelectTrigger.vue | 4 +- unraid-ui/src/composables/useTeleport.ts | 21 +- unraid-ui/src/forms/AccordionLayout.vue | 2 +- unraid-ui/src/forms/ObjectArrayField.vue | 6 +- unraid-ui/src/forms/SteppedLayout.vue | 2 +- unraid-ui/src/forms/UnraidSettingsLayout.vue | 2 +- .../src/helpers/ensure-teleport-container.ts | 23 + unraid-ui/src/index.ts | 3 + unraid-ui/src/lib/utils.ts | 2 + .../Activation/WelcomeModal.test.ts | 17 +- .../components/HeaderOsVersion.test.ts | 30 - web/__test__/components/Modal.test.ts | 3 - web/__test__/components/Registration.test.ts | 64 +-- web/__test__/components/UpdateOs.test.ts | 29 +- web/__test__/components/UserProfile.test.ts | 64 ++- web/__test__/mocks/ui-components.ts | 50 ++ web/assets/main.css | 74 ++- web/components/ApiKey/ApiKeyCreate.vue | 211 ++++--- web/components/ApiKey/ApiKeyManager.vue | 98 +++- web/components/ApiKeyAuthorize.ce.vue | 16 +- web/components/Auth.ce.vue | 6 +- web/components/Brand/Avatar.vue | 6 +- web/components/ColorSwitcher.ce.vue | 2 +- .../ConnectSettings/ConnectSettings.ce.vue | 12 +- web/components/DownloadApiLogs.ce.vue | 4 +- web/components/HeaderOsVersion.ce.vue | 108 ++-- web/components/Modal.vue | 17 +- web/components/Modals.ce.vue | 2 - web/components/Notifications/Indicator.vue | 4 +- web/components/Notifications/Item.vue | 43 +- web/components/Notifications/List.vue | 27 +- web/components/Notifications/Sidebar.vue | 59 +- web/components/RClone/RCloneOverview.vue | 2 +- web/components/Registration.ce.vue | 247 +++++--- web/components/Registration/Item.vue | 53 -- web/components/SsoButton.ce.vue | 24 +- web/components/UpdateOs.ce.vue | 11 +- web/components/UpdateOs/ChangelogModal.vue | 55 +- .../UpdateOs/CheckUpdateResponseModal.vue | 200 ++++--- .../UpdateOs/RawChangelogRenderer.vue | 4 +- web/components/UpdateOs/Status.vue | 109 ++-- web/components/UserProfile.ce.vue | 89 ++- web/components/UserProfile/Beta.vue | 4 +- .../UserProfile/DropdownContent.vue | 43 +- web/components/UserProfile/DropdownError.vue | 2 +- web/components/UserProfile/DropdownItem.vue | 36 +- .../UserProfile/DropdownTrigger.vue | 34 +- web/components/UserProfile/ServerState.vue | 10 +- web/components/UserProfile/ServerStateBuy.vue | 11 +- web/components/UserProfile/ServerStatus.vue | 23 + web/components/UserProfile/Trial.vue | 9 +- web/components/UserProfile/UptimeExpire.vue | 44 +- web/components/Wrapper/vue-mount-app.ts | 285 +++++++++ web/components/standalone-mount.ts | 221 +++++++ web/composables/dateTime.ts | 4 - web/composables/useClipboardWithToast.ts | 49 +- web/helpers/create-apollo-client.ts | 38 +- web/layouts/default.vue | 6 +- web/nuxt.config.ts | 144 ++--- web/package.json | 9 +- web/pages/index.vue | 20 +- web/pages/login.vue | 2 +- web/pages/webComponents.vue | 22 +- web/pages/welcome.vue | 2 +- web/public/images/UN-logotype-gradient.svg | 1 + .../add-timestamp-standalone-manifest.js | 35 ++ .../add-timestamp-webcomponent-manifest.js | 7 + web/scripts/clean-unraid.sh | 134 +++++ web/scripts/deploy-dev.sh | 136 +++-- web/scripts/validate-custom-elements-css.js | 37 +- web/store/apiKey.ts | 16 +- web/store/theme.ts | 14 + web/test-standalone.html | 327 +++++++++++ web/themes/default.ts | 26 +- web/themes/types.d.ts | 8 + web/vite.standalone.config.ts | 98 ++++ web/vite.test.config.ts | 26 + 121 files changed, 3632 insertions(+), 1816 deletions(-) create mode 100755 plugin/tests/test-extractor.php create mode 100755 plugin/tests/test-extractor.sh create mode 100644 unraid-ui/src/components/common/responsive-modal/ResponsiveModal.vue create mode 100644 unraid-ui/src/components/common/responsive-modal/ResponsiveModalFooter.vue create mode 100644 unraid-ui/src/components/common/responsive-modal/ResponsiveModalHeader.vue create mode 100644 unraid-ui/src/components/common/responsive-modal/ResponsiveModalTitle.vue create mode 100644 unraid-ui/src/components/common/responsive-modal/index.ts create mode 100644 unraid-ui/src/helpers/ensure-teleport-container.ts delete mode 100644 web/components/Registration/Item.vue create mode 100644 web/components/UserProfile/ServerStatus.vue create mode 100644 web/components/Wrapper/vue-mount-app.ts create mode 100644 web/components/standalone-mount.ts create mode 100644 web/public/images/UN-logotype-gradient.svg create mode 100644 web/scripts/add-timestamp-standalone-manifest.js create mode 100755 web/scripts/clean-unraid.sh create mode 100644 web/test-standalone.html create mode 100644 web/vite.standalone.config.ts create mode 100644 web/vite.test.config.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fbe1dcd59..a50c4e46f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,7 +47,7 @@ jobs: - name: Cache APT Packages uses: awalsh128/cache-apt-pkgs-action@v1.5.3 with: - packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system + packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system php-cli version: 1.0 - name: Install pnpm @@ -147,12 +147,17 @@ jobs: (cd ../unraid-ui && pnpm test --coverage 2>/dev/null || pnpm test) > ui-test.log 2>&1 & UI_PID=$! + echo "πŸš€ Starting Plugin tests..." + (cd ../plugin && pnpm test) > plugin-test.log 2>&1 & + PLUGIN_PID=$! + # Wait for all processes and capture exit codes wait $API_PID && echo "βœ… API tests completed" || { echo "❌ API tests failed"; API_EXIT=1; } wait $CONNECT_PID && echo "βœ… Connect tests completed" || { echo "❌ Connect tests failed"; CONNECT_EXIT=1; } wait $SHARED_PID && echo "βœ… Shared tests completed" || { echo "❌ Shared tests failed"; SHARED_EXIT=1; } wait $WEB_PID && echo "βœ… Web tests completed" || { echo "❌ Web tests failed"; WEB_EXIT=1; } wait $UI_PID && echo "βœ… UI tests completed" || { echo "❌ UI tests failed"; UI_EXIT=1; } + wait $PLUGIN_PID && echo "βœ… Plugin tests completed" || { echo "❌ Plugin tests failed"; PLUGIN_EXIT=1; } # Display all outputs echo "πŸ“‹ API Test Results:" && cat api-test.log @@ -160,9 +165,10 @@ jobs: echo "πŸ“‹ Shared Package Test Results:" && cat shared-test.log echo "πŸ“‹ Web Package Test Results:" && cat web-test.log echo "πŸ“‹ UI Package Test Results:" && cat ui-test.log + echo "πŸ“‹ Plugin Test Results:" && cat plugin-test.log # Exit with error if any test failed - if [[ ${API_EXIT:-0} -eq 1 || ${CONNECT_EXIT:-0} -eq 1 || ${SHARED_EXIT:-0} -eq 1 || ${WEB_EXIT:-0} -eq 1 || ${UI_EXIT:-0} -eq 1 ]]; then + if [[ ${API_EXIT:-0} -eq 1 || ${CONNECT_EXIT:-0} -eq 1 || ${SHARED_EXIT:-0} -eq 1 || ${WEB_EXIT:-0} -eq 1 || ${UI_EXIT:-0} -eq 1 || ${PLUGIN_EXIT:-0} -eq 1 ]]; then exit 1 fi @@ -379,7 +385,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: unraid-wc-rich - path: web/.nuxt/nuxt-custom-elements/dist/unraid-components + path: web/.nuxt/standalone-apps build-plugin-staging-pr: name: Build and Deploy Plugin diff --git a/@tailwind-shared/base-utilities.css b/@tailwind-shared/base-utilities.css index 6818e30d6..89437858c 100644 --- a/@tailwind-shared/base-utilities.css +++ b/@tailwind-shared/base-utilities.css @@ -1,7 +1,7 @@ @custom-variant dark (&:where(.dark, .dark *)); -@layer utilities { - :host { +/* Utility defaults for web components (when we were using shadow DOM) */ +:host { --tw-divide-y-reverse: 0; --tw-border-style: solid; --tw-font-weight: initial; @@ -48,21 +48,20 @@ --tw-drop-shadow: initial; --tw-duration: initial; --tw-ease: initial; - } } -@layer base { - *, - ::after, - ::before, - ::backdrop, - ::file-selector-button { - border-color: hsl(var(--border)); - } +/* Global border color - this is what's causing the issue! */ +/* Commenting out since it affects all elements globally +*, +::after, +::before, +::backdrop, +::file-selector-button { + border-color: hsl(var(--border)); +} +*/ - - - body { +body { --color-alpha: #1c1b1b; --color-beta: #f2f2f2; --color-gamma: #999999; @@ -74,8 +73,7 @@ --ring-shadow: 0 0 var(--color-beta); } - button:not(:disabled), - [role='button']:not(:disabled) { - cursor: pointer; - } +button:not(:disabled), +[role='button']:not(:disabled) { + cursor: pointer; } \ No newline at end of file diff --git a/@tailwind-shared/css-variables.css b/@tailwind-shared/css-variables.css index 9ddc00dcf..9861d76ff 100644 --- a/@tailwind-shared/css-variables.css +++ b/@tailwind-shared/css-variables.css @@ -1,7 +1,11 @@ /* Hybrid theme system: Native CSS + Theme Store fallback */ -@layer base { - /* Light mode defaults */ - :root { + +/* Light mode defaults */ +:root { + /* Override Tailwind v4 global styles to use webgui variables */ + --ui-bg: var(--background-color) !important; + --ui-text: var(--text-color) !important; + --background: 0 0% 100%; --foreground: 0 0% 3.9%; --muted: 0 0% 96.1%; @@ -30,6 +34,10 @@ /* Dark mode */ .dark { + /* Override Tailwind v4 global styles to use webgui variables */ + --ui-bg: var(--background-color) !important; + --ui-text: var(--text-color) !important; + --background: 0 0% 3.9%; --foreground: 0 0% 98%; --muted: 0 0% 14.9%; @@ -62,69 +70,4 @@ --background: 0 0% 3.9%; --foreground: 0 0% 98%; --border: 0 0% 14.9%; - } - - /* For web components: inherit CSS variables from the host */ - :host { - --background: inherit; - --foreground: inherit; - --muted: inherit; - --muted-foreground: inherit; - --popover: inherit; - --popover-foreground: inherit; - --card: inherit; - --card-foreground: inherit; - --border: inherit; - --input: inherit; - --primary: inherit; - --primary-foreground: inherit; - --secondary: inherit; - --secondary-foreground: inherit; - --accent: inherit; - --accent-foreground: inherit; - --destructive: inherit; - --destructive-foreground: inherit; - --ring: inherit; - --chart-1: inherit; - --chart-2: inherit; - --chart-3: inherit; - --chart-4: inherit; - --chart-5: inherit; - } - - /* Class-based dark mode support for web components using :host-context */ - :host-context(.dark) { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } - - /* Alternative class-based dark mode support for specific Unraid themes */ - :host-context(.dark[data-theme='black']), - :host-context(.dark[data-theme='gray']) { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --border: 0 0% 14.9%; - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/@tailwind-shared/index.css b/@tailwind-shared/index.css index b26482e77..098634405 100644 --- a/@tailwind-shared/index.css +++ b/@tailwind-shared/index.css @@ -2,4 +2,4 @@ @import './css-variables.css'; @import './unraid-theme.css'; @import './base-utilities.css'; -@import './sonner.css'; \ No newline at end of file +@import './sonner.css'; diff --git a/plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts b/plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts index 0006d608e..6408356a5 100644 --- a/plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts +++ b/plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts @@ -4,6 +4,7 @@ import { setupPluginEnv, } from "../../cli/setup-plugin-environment"; import { access, readFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; // Mock fs/promises vi.mock("node:fs/promises", () => ({ @@ -14,8 +15,19 @@ vi.mock("node:fs/promises", () => ({ }, })); +// Mock node:fs +vi.mock("node:fs", () => ({ + existsSync: vi.fn(), +})); + beforeEach(() => { vi.resetAllMocks(); + + // Mock existsSync to return true for test.txz + vi.mocked(existsSync).mockImplementation((path) => { + return path.toString().includes("test.txz"); + }); + vi.mocked(readFile).mockImplementation((path, encoding) => { console.log("Mock readFile called with:", path, encoding); @@ -42,6 +54,7 @@ describe("validatePluginEnv", () => { it("validates required fields", async () => { const validEnv = { + apiVersion: "4.17.0", baseUrl: "https://example.com", txzPath: "./test.txz", pluginVersion: "2024.05.05.1232", @@ -53,6 +66,7 @@ describe("validatePluginEnv", () => { it("throws on invalid URL", async () => { const invalidEnv = { + apiVersion: "4.17.0", baseUrl: "not-a-url", txzPath: "./test.txz", pluginVersion: "2024.05.05.1232", @@ -63,6 +77,7 @@ describe("validatePluginEnv", () => { it("handles tag option in non-CI mode", async () => { const envWithTag = { + apiVersion: "4.17.0", baseUrl: "https://example.com", txzPath: "./test.txz", pluginVersion: "2024.05.05.1232", @@ -77,6 +92,7 @@ describe("validatePluginEnv", () => { it("reads release notes when release-notes-path is provided", async () => { const envWithNotes = { + apiVersion: "4.17.0", baseUrl: "https://example.com", txzPath: "./test.txz", pluginVersion: "2024.05.05.1232", @@ -100,6 +116,7 @@ describe("validatePluginEnv", () => { }); const envWithEmptyNotes = { + apiVersion: "4.17.0", baseUrl: "https://example.com", txzPath: "./test.txz", pluginVersion: "2024.05.05.1232", diff --git a/plugin/builder/__tests__/cli/setup-txz-environment.spec.ts b/plugin/builder/__tests__/cli/setup-txz-environment.spec.ts index 8a52470b5..fcd48a8d5 100644 --- a/plugin/builder/__tests__/cli/setup-txz-environment.spec.ts +++ b/plugin/builder/__tests__/cli/setup-txz-environment.spec.ts @@ -6,8 +6,20 @@ import { deployDir } from "../../utils/paths"; describe("setupTxzEnvironment", () => { it("should return default values when no arguments are provided", async () => { - const envArgs = {}; - const expected: TxzEnv = { ci: false, skipValidation: "false", compress: "1", txzOutputDir: join(startingDir, deployDir) }; + const envArgs = { + apiVersion: "4.17.0", + baseUrl: "https://example.com" + }; + const expected: TxzEnv = { + apiVersion: "4.17.0", + baseUrl: "https://example.com", + ci: false, + skipValidation: "false", + compress: "1", + txzOutputDir: join(startingDir, deployDir), + tag: "", + buildNumber: 1 + }; const result = await validateTxzEnv(envArgs); @@ -15,8 +27,24 @@ describe("setupTxzEnvironment", () => { }); it("should parse and return provided environment arguments", async () => { - const envArgs = { ci: true, skipValidation: "true", txzOutputDir: join(startingDir, "deploy/release/test"), compress: '8' }; - const expected: TxzEnv = { ci: true, skipValidation: "true", compress: "8", txzOutputDir: join(startingDir, "deploy/release/test") }; + const envArgs = { + apiVersion: "4.17.0", + baseUrl: "https://example.com", + ci: true, + skipValidation: "true", + txzOutputDir: join(startingDir, "deploy/release/test"), + compress: '8' + }; + const expected: TxzEnv = { + apiVersion: "4.17.0", + baseUrl: "https://example.com", + ci: true, + skipValidation: "true", + compress: "8", + txzOutputDir: join(startingDir, "deploy/release/test"), + tag: "", + buildNumber: 1 + }; const result = await validateTxzEnv(envArgs); @@ -24,7 +52,11 @@ describe("setupTxzEnvironment", () => { }); it("should warn and skip validation when skipValidation is true", async () => { - const envArgs = { skipValidation: "true" }; + const envArgs = { + apiVersion: "4.17.0", + baseUrl: "https://example.com", + skipValidation: "true" + }; const consoleWarnSpy = vi .spyOn(console, "warn") .mockImplementation(() => {}); @@ -38,7 +70,11 @@ describe("setupTxzEnvironment", () => { }); it("should throw an error for invalid SKIP_VALIDATION value", async () => { - const envArgs = { skipValidation: "invalid" }; + const envArgs = { + apiVersion: "4.17.0", + baseUrl: "https://example.com", + skipValidation: "invalid" + }; await expect(validateTxzEnv(envArgs)).rejects.toThrow( "Must be true or false" diff --git a/plugin/builder/build-txz.ts b/plugin/builder/build-txz.ts index 524676e8a..be97ab311 100644 --- a/plugin/builder/build-txz.ts +++ b/plugin/builder/build-txz.ts @@ -29,7 +29,9 @@ const findManifestFiles = async (dir: string): Promise => { } } else if ( entry.isFile() && - (entry.name === "manifest.json" || entry.name === "ui.manifest.json") + (entry.name === "manifest.json" || + entry.name === "ui.manifest.json" || + entry.name === "standalone.manifest.json") ) { files.push(entry.name); } @@ -124,19 +126,21 @@ const validateSourceDir = async (validatedEnv: TxzEnv) => { const manifestFiles = await findManifestFiles(webcomponentDir); const hasManifest = manifestFiles.includes("manifest.json"); + const hasStandaloneManifest = manifestFiles.includes("standalone.manifest.json"); const hasUiManifest = manifestFiles.includes("ui.manifest.json"); - if (!hasManifest || !hasUiManifest) { + // Accept either manifest.json (old web components) or standalone.manifest.json (new standalone apps) + if ((!hasManifest && !hasStandaloneManifest) || !hasUiManifest) { console.log("Existing Manifest Files:", manifestFiles); const missingFiles: string[] = []; - if (!hasManifest) missingFiles.push("manifest.json"); + if (!hasManifest && !hasStandaloneManifest) missingFiles.push("manifest.json or standalone.manifest.json"); if (!hasUiManifest) missingFiles.push("ui.manifest.json"); throw new Error( `Webcomponents missing required file(s): ${missingFiles.join(", ")} - ` + `${!hasUiManifest ? "run 'pnpm build:wc' in unraid-ui for ui.manifest.json" : ""}` + - `${!hasManifest && !hasUiManifest ? " and " : ""}` + - `${!hasManifest ? "run 'pnpm build' in web for manifest.json" : ""}` + `${(!hasManifest && !hasStandaloneManifest) && !hasUiManifest ? " and " : ""}` + + `${(!hasManifest && !hasStandaloneManifest) ? "run 'pnpm build' in web for standalone.manifest.json" : ""}` ); } diff --git a/plugin/builder/cli/setup-txz-environment.ts b/plugin/builder/cli/setup-txz-environment.ts index 49f0a7c96..93fdca68a 100644 --- a/plugin/builder/cli/setup-txz-environment.ts +++ b/plugin/builder/cli/setup-txz-environment.ts @@ -22,7 +22,7 @@ export const validateTxzEnv = async ( ): Promise => { const validatedEnv = txzEnvSchema.parse(envArgs); - if ("skipValidation" in validatedEnv) { + if (validatedEnv.skipValidation === "true") { console.warn("skipValidation is true, skipping validation"); } diff --git a/plugin/package.json b/plugin/package.json index 6597eac28..b58d8e1c4 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -32,7 +32,8 @@ "env:validate": "test -f .env || (echo 'Error: .env file missing. Run npm run env:init first' && exit 1)", "env:clean": "rm -f .env", "// Testing": "", - "test": "vitest" + "test": "vitest && pnpm run test:extractor", + "test:extractor": "bash ./tests/test-extractor.sh" }, "devDependencies": { "http-server": "14.1.1", diff --git a/plugin/plugins/dynamix.unraid.net.plg b/plugin/plugins/dynamix.unraid.net.plg index 719532146..1bf4db846 100755 --- a/plugin/plugins/dynamix.unraid.net.plg +++ b/plugin/plugins/dynamix.unraid.net.plg @@ -161,7 +161,7 @@ exit 0 sed -i 's||\n|' "/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php" fi fi - + ]]> @@ -272,6 +272,27 @@ exit 0 [ -f "$FILE-" ] && mv -f "$FILE-" "$FILE" done + # Restore CSS files from backup + echo "Restoring original CSS files..." + CSS_DIR="/usr/local/emhttp/plugins/dynamix/styles" + BACKUP_DIR="$CSS_DIR/.unraid-api-backup" + + if [ -d "$BACKUP_DIR" ]; then + for backup_file in "$BACKUP_DIR"/*.css; do + if [ -f "$backup_file" ]; then + filename=$(basename "$backup_file") + original_file="$CSS_DIR/$filename" + echo " Restoring $filename..." + cp "$backup_file" "$original_file" + fi + done + # Remove backup directory after restoration + rm -rf "$BACKUP_DIR" + echo "CSS restoration complete." + else + echo "No CSS backup found, skipping restoration." + fi + # Handle the unraid-components directory DIR=/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components # Remove the archive's contents before restoring diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php index ae8e439e0..8d214bfb9 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php @@ -4,10 +4,6 @@ $docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp'; class WebComponentsExtractor { private const PREFIXED_PATH = '/plugins/dynamix.my.servers/unraid-components/'; - private const RICH_COMPONENTS_ENTRY = 'unraid-components.client.mjs'; - private const RICH_COMPONENTS_ENTRY_JS = 'unraid-components.client.js'; - private const UI_ENTRY = 'src/register.ts'; - private const UI_STYLES_ENTRY = 'style.css'; private static ?WebComponentsExtractor $instance = null; @@ -21,15 +17,6 @@ class WebComponentsExtractor return self::$instance; } - private function findManifestFiles(string $manifestName): array - { - $basePath = '/usr/local/emhttp' . self::PREFIXED_PATH; - $escapedBasePath = escapeshellarg($basePath); - $escapedManifestName = escapeshellarg($manifestName); - $command = "find {$escapedBasePath} -name {$escapedManifestName}"; - exec($command, $files); - return $files; - } public function getAssetPath(string $asset, string $subfolder = ''): string { @@ -42,6 +29,11 @@ class WebComponentsExtractor $relative = str_replace($basePath, '', $fullPath); return dirname($relative); } + + private function sanitizeForId(string $input): string + { + return preg_replace('/[^a-zA-Z0-9-]/', '-', $input); + } public function getManifestContents(string $manifestPath): array { @@ -49,117 +41,108 @@ class WebComponentsExtractor return $contents ? json_decode($contents, true) : []; } - - private function getRichComponentsFile(): string + private function processManifestFiles(): string { - $manifestFiles = $this->findManifestFiles('manifest.json'); + // Find all manifest files (*.manifest.json or manifest.json) + $manifestFiles = $this->findAllManifestFiles(); + if (empty($manifestFiles)) { + return ''; + } + + $scripts = []; + + // Process each manifest file foreach ($manifestFiles as $manifestPath) { $manifest = $this->getManifestContents($manifestPath); + if (empty($manifest)) { + continue; + } + $subfolder = $this->getRelativePath($manifestPath); - foreach ($manifest as $key => $value) { - // Skip timestamp entries - if ($key === 'ts' || !is_array($value)) { + // Process each entry in the manifest + foreach ($manifest as $key => $entry) { + // Skip if not an array with a 'file' key + if (!is_array($entry) || !isset($entry['file']) || empty($entry['file'])) { continue; } - // Check for both old format (direct key match) and new format (path-based key) - $matchesMjs = strpos($key, self::RICH_COMPONENTS_ENTRY) !== false; - $matchesJs = strpos($key, self::RICH_COMPONENTS_ENTRY_JS) !== false; + // Build the file path + $filePath = ($subfolder ? $subfolder . '/' : '') . $entry['file']; + $fullPath = $this->getAssetPath($filePath); - if (($matchesMjs || $matchesJs) && isset($value["file"])) { - return ($subfolder ? $subfolder . '/' : '') . $value["file"]; + // Determine file type and generate appropriate tag + $extension = pathinfo($entry['file'], PATHINFO_EXTENSION); + + // Sanitize subfolder and key for ID generation + $sanitizedSubfolder = $subfolder ? $this->sanitizeForId($subfolder) . '-' : ''; + $sanitizedKey = $this->sanitizeForId($key); + + // Escape attributes for HTML safety + $safeId = htmlspecialchars('unraid-' . $sanitizedSubfolder . $sanitizedKey, ENT_QUOTES, 'UTF-8'); + $safePath = htmlspecialchars($fullPath, ENT_QUOTES, 'UTF-8'); + + if ($extension === 'js' || $extension === 'mjs') { + // Generate script tag with unique ID based on subfolder and key + $scripts[] = ''; + // Also emit any CSS referenced by this entry (Vite manifest "css": []) + if (!empty($entry['css']) && is_array($entry['css'])) { + foreach ($entry['css'] as $cssFile) { + if (!is_string($cssFile) || $cssFile === '') continue; + $cssPath = ($subfolder ? $subfolder . '/' : '') . $cssFile; + $cssFull = $this->getAssetPath($cssPath); + $cssId = htmlspecialchars( + 'unraid-' . $sanitizedSubfolder . $sanitizedKey . '-css-' . $this->sanitizeForId(basename($cssFile)), + ENT_QUOTES, + 'UTF-8' + ); + $cssHref = htmlspecialchars($cssFull, ENT_QUOTES, 'UTF-8'); + $scripts[] = ''; + } + } + } elseif ($extension === 'css') { + // Generate link tag for CSS files with unique ID + $scripts[] = ''; } } } - return ''; - } - - private function getRichComponentsScript(): string - { - $jsFile = $this->getRichComponentsFile(); - if (empty($jsFile)) { - return ''; + + if (empty($scripts)) { + return ''; } - // Add a unique identifier to prevent duplicate script loading - $scriptId = 'unraid-rich-components-script'; - return ' + + // Add deduplication script + $deduplicationScript = ' '; + + return implode("\n", $scripts) . $deduplicationScript; } - - private function getUnraidUiScriptHtml(): string + + private function findAllManifestFiles(): array { - $manifestFiles = $this->findManifestFiles('ui.manifest.json'); + $basePath = '/usr/local/emhttp' . self::PREFIXED_PATH; + $escapedBasePath = escapeshellarg($basePath); - if (empty($manifestFiles)) { - error_log("No ui.manifest.json found"); - return ''; - } - - $manifestPath = $manifestFiles[0]; // Use the first found manifest - $manifest = $this->getManifestContents($manifestPath); - $subfolder = $this->getRelativePath($manifestPath); - - if (!isset($manifest[self::UI_ENTRY]) || !isset($manifest[self::UI_STYLES_ENTRY])) { - error_log("Required entries not found in ui.manifest.json"); - return ''; - } - - $jsFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_ENTRY]['file']; - $cssFile = ($subfolder ? $subfolder . '/' : '') . $manifest[self::UI_STYLES_ENTRY]['file']; + // Find all files ending with .manifest.json or exactly named manifest.json + $command = "find {$escapedBasePath} -type f \\( -name '*.manifest.json' -o -name 'manifest.json' \\) 2>/dev/null"; + exec($command, $files); - // Read the CSS file content - $cssPath = '/usr/local/emhttp' . $this->getAssetPath($cssFile); - $cssContent = @file_get_contents($cssPath); - - if ($cssContent === false) { - error_log("Failed to read CSS file: " . $cssPath); - $cssContent = ''; - } - - // Escape the CSS content for JavaScript - $escapedCssContent = json_encode($cssContent); - - // Use a data attribute to ensure this only runs once per page - return ''; + return $files; } public function getScriptTagHtml(): string @@ -168,12 +151,12 @@ class WebComponentsExtractor static $scriptsOutput = false; if ($scriptsOutput) { - return ''; + return ''; } try { $scriptsOutput = true; - return $this->getRichComponentsScript() . $this->getUnraidUiScriptHtml(); + return $this->processManifestFiles(); } catch (\Exception $e) { error_log("Error in WebComponentsExtractor::getScriptTagHtml: " . $e->getMessage()); $scriptsOutput = false; // Reset on error diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php index a474ea9ba..4520cda38 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php @@ -100,6 +100,57 @@ class UnraidOsCheck return []; } + /** + * Safe version of http_get_contents that falls back to system function if defined + * @param string $url The URL to fetch + * @param array $opts Array of options to pass to curl_setopt() + * @param array $getinfo Empty array passed by reference, will contain results of curl_getinfo and curl_error + * Note: CURLINFO_HEADER_OUT exposes request headers (not response headers) for curl_getinfo + * @return string|false $out The fetched content + */ + private function safe_http_get_contents(string $url, array $opts = [], array &$getinfo = NULL) { + // Use system http_get_contents if it exists + if (function_exists('http_get_contents')) { + return http_get_contents($url, $opts, $getinfo); + } + + // Otherwise use our implementation + $ch = curl_init(); + if(isset($getinfo)) { + curl_setopt($ch, CURLINFO_HEADER_OUT, TRUE); + } + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + curl_setopt($ch, CURLOPT_TIMEOUT, 45); + curl_setopt($ch, CURLOPT_ENCODING, ""); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_REFERER, ""); + curl_setopt($ch, CURLOPT_FAILONERROR, true); + // Explicitly enforce TLS verification + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + if(is_array($opts) && $opts) { + foreach($opts as $key => $val) { + curl_setopt($ch, $key, $val); + } + } + $out = curl_exec($ch); + if(isset($getinfo)) { + $getinfo = curl_getinfo($ch); + } + if (curl_errno($ch)) { + $msg = curl_error($ch) . " {$url}"; + if(isset($getinfo)) { + $getinfo['error'] = $msg; + } + my_logger($msg, "safe_http_get_contents"); + } + curl_close($ch); + return $out; + } + /** @todo clean up this method to be more extensible */ public function checkForUpdate() { @@ -136,7 +187,7 @@ class UnraidOsCheck $urlbase = $parsedAltUrl ?? $defaultUrl; $url = $urlbase.'?'.http_build_query($params); $curlinfo = []; - $response = http_get_contents($url,[],$curlinfo); + $response = $this->safe_http_get_contents($url,[],$curlinfo); if (array_key_exists('error', $curlinfo)) { $response = json_encode(array('error' => $curlinfo['error']), JSON_PRETTY_PRINT); } diff --git a/plugin/tests/test-extractor.php b/plugin/tests/test-extractor.php new file mode 100755 index 000000000..43286bdf0 --- /dev/null +++ b/plugin/tests/test-extractor.php @@ -0,0 +1,369 @@ +#!/usr/bin/env php +verbose = getenv('VERBOSE') === '1' || in_array('--verbose', $_SERVER['argv'] ?? []); + } + + public function run() { + $this->setup(); + $this->runTests(); + $this->teardown(); + return $this->reportResults(); + } + + private function setup() { + echo "Setting up test environment...\n"; + + // Create temp directory + $this->testDir = sys_get_temp_dir() . '/extractor_test_' . uniqid(); + $this->componentDir = $this->testDir . '/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components'; + + // Create directory structure + @mkdir($this->componentDir . '/standalone-apps', 0777, true); + @mkdir($this->componentDir . '/ui-components', 0777, true); + @mkdir($this->componentDir . '/other', 0777, true); + + // Create test manifest files + file_put_contents($this->componentDir . '/standalone-apps/standalone.manifest.json', json_encode([ + 'standalone-apps-RlN0czLV.css' => [ + 'file' => 'standalone-apps-RlN0czLV.css', + 'src' => 'standalone-apps-RlN0czLV.css' + ], + 'standalone-apps.js' => [ + 'file' => 'standalone-apps.js', + 'src' => 'standalone-apps.js', + 'css' => ['app-styles.css', 'theme.css'] + ], + 'ts' => 1234567890 + ], JSON_PRETTY_PRINT)); + + file_put_contents($this->componentDir . '/ui-components/ui.manifest.json', json_encode([ + 'ui-styles' => [ + 'file' => 'ui-components.css' + ], + 'ui-script' => [ + 'file' => 'components.mjs' + ], + 'invalid-entry' => [ + 'notAFile' => 'should be skipped' + ], + 'empty-file' => [ + 'file' => '' + ] + ], JSON_PRETTY_PRINT)); + + file_put_contents($this->componentDir . '/other/manifest.json', json_encode([ + 'app-entry' => [ + 'file' => 'app.js', + 'css' => ['main.css'] + ], + 'app-styles' => [ + 'file' => 'app.css' + ] + ], JSON_PRETTY_PRINT)); + + // Create duplicate key in different subfolder to test collision prevention + file_put_contents($this->componentDir . '/standalone-apps/collision.manifest.json', json_encode([ + 'app-entry' => [ + 'file' => 'collision-app.js' + ] + ], JSON_PRETTY_PRINT)); + + // Create manifest with special characters for HTML escaping test + file_put_contents($this->componentDir . '/ui-components/special.manifest.json', json_encode([ + 'test"with>quotes' => [ + 'file' => 'test-file.js' + ], + 'test&with [ + 'file' => 'special\'file".css' + ] + ], JSON_PRETTY_PRINT)); + + // Copy and modify the extractor for testing + $this->prepareExtractor(); + } + + private function prepareExtractor() { + $extractorPath = dirname(__DIR__) . '/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php'; + $extractorContent = file_get_contents($extractorPath); + + // Modify paths for test environment + $extractorContent = str_replace( + "'/usr/local/emhttp' . self::PREFIXED_PATH", + "'" . $this->testDir . "/usr/local/emhttp' . self::PREFIXED_PATH", + $extractorContent + ); + + file_put_contents($this->testDir . '/extractor.php', $extractorContent); + } + + private function getExtractorOutput() { + $_SERVER['DOCUMENT_ROOT'] = '/usr/local/emhttp'; + require_once $this->testDir . '/extractor.php'; + + $extractor = WebComponentsExtractor::getInstance(); + return $extractor->getScriptTagHtml(); + } + + private function runTests() { + echo "\n"; + echo "========================================\n"; + echo " WebComponentsExtractor Test Suite\n"; + echo "========================================\n"; + echo "\n"; + + $output = $this->getExtractorOutput(); + + if ($this->verbose) { + echo self::YELLOW . "Generated output:" . self::NC . "\n"; + echo $output . "\n\n"; + } + + // Test: Script Tag Generation + echo "Test: Script Tag Generation\n"; + echo "----------------------------\n"; + $this->test( + "Generates script tag for standalone-apps.js", + strpos($output, 'script id="unraid-standalone-apps-standalone-apps-js"') !== false + ); + $this->test( + "Generates script tag for components.mjs", + strpos($output, 'script id="unraid-ui-components-ui-script"') !== false + ); + $this->test( + "Generates script tag for app.js", + strpos($output, 'script id="unraid-other-app-entry"') !== false + ); + + // Test: CSS Link Generation + echo "\nTest: CSS Link Generation\n"; + echo "--------------------------\n"; + $this->test( + "Generates link tag for standalone CSS", + strpos($output, 'link id="unraid-standalone-apps-standalone-apps-RlN0czLV-css"') !== false + ); + $this->test( + "Generates link tag for UI styles", + strpos($output, 'link id="unraid-ui-components-ui-styles"') !== false + ); + $this->test( + "Generates link tag for app styles", + strpos($output, 'link id="unraid-other-app-styles"') !== false + ); + + // Test: Invalid Entries Handling + echo "\nTest: Invalid Entries Handling\n"; + echo "-------------------------------\n"; + $this->test( + "Skips entries without 'file' key", + strpos($output, 'notAFile') === false + ); + $this->test( + "Skips invalid entry content", + strpos($output, 'should be skipped') === false + ); + $this->test( + "Skips entries with empty file value", + strpos($output, 'empty-file') === false && strpos($output, 'id="unraid-ui-components-empty-file"') === false + ); + + // Test: Deduplication Script + echo "\nTest: Deduplication Script\n"; + echo "---------------------------\n"; + $this->test( + "Includes deduplication script", + strpos($output, 'Remove duplicate resource tags') !== false + ); + $this->test( + "Deduplication targets correct elements", + strpos($output, 'document.querySelectorAll') !== false + ); + $this->test( + "Deduplication only targets data-unraid elements", + strpos($output, 'querySelectorAll(\'[data-unraid="1"]\')') !== false + ); + + // Test: Path Construction + echo "\nTest: Path Construction\n"; + echo "------------------------\n"; + $this->test( + "Correctly constructs standalone-apps path", + strpos($output, '/plugins/dynamix.my.servers/unraid-components/standalone-apps/standalone-apps.js') !== false + ); + $this->test( + "Correctly constructs ui-components path", + strpos($output, '/plugins/dynamix.my.servers/unraid-components/ui-components/components.mjs') !== false + ); + $this->test( + "Correctly constructs generic manifest path", + strpos($output, '/plugins/dynamix.my.servers/unraid-components/other/app.js') !== false + ); + + // Test: ID Collision Prevention + echo "\nTest: ID Collision Prevention\n"; + echo "------------------------------\n"; + $this->test( + "Different IDs for same key in different subfolders (standalone-apps)", + strpos($output, 'id="unraid-standalone-apps-app-entry"') !== false + ); + $this->test( + "Different IDs for same key in different subfolders (other)", + strpos($output, 'id="unraid-other-app-entry"') !== false + ); + $appEntryCount = substr_count($output, 'id="unraid-') - substr_count($output, 'id="unraid-ui-'); + $this->test( + "Both app-entry scripts are present with unique IDs", + preg_match_all('/id="unraid-[^"]*app-entry"/', $output, $matches) === 2 + ); + + // Test: HTML Attribute Escaping + echo "\nTest: HTML Attribute Escaping\n"; + echo "------------------------------\n"; + $this->test( + "Properly escapes quotes in ID attributes", + strpos($output, '"test"with>quotes"') === false + ); + $this->test( + "Properly escapes special characters in ID", + strpos($output, 'unraid-ui-components-test-with-quotes') !== false || + strpos($output, 'unraid-ui-components-test-with-special') !== false + ); + $this->test( + "Properly escapes special characters in src/href attributes", + strpos($output, "special'file\"") === false && + (strpos($output, 'special'file"') !== false || + strpos($output, "special'file"") !== false || + strpos($output, "special'file"") === false) + ); + + // Test: Data-Unraid Attribute + echo "\nTest: Data-Unraid Attribute\n"; + echo "----------------------------\n"; + $this->test( + "Script tags have data-unraid attribute", + preg_match('/]+data-unraid="1"/', $output) > 0 + ); + $this->test( + "Link tags have data-unraid attribute", + preg_match('/]+data-unraid="1"/', $output) > 0 + ); + + // Test: CSS Loading from Manifest + echo "\nTest: CSS Loading from Manifest\n"; + echo "--------------------------------\n"; + $this->test( + "Loads CSS from JS entry css array (app-styles.css)", + strpos($output, 'id="unraid-standalone-apps-standalone-apps-js-css-app-styles-css"') !== false + ); + $this->test( + "Loads CSS from JS entry css array (theme.css)", + strpos($output, 'id="unraid-standalone-apps-standalone-apps-js-css-theme-css"') !== false + ); + $this->test( + "CSS from manifest has correct href path (app-styles.css)", + strpos($output, '/plugins/dynamix.my.servers/unraid-components/standalone-apps/app-styles.css') !== false + ); + $this->test( + "CSS from manifest has correct href path (theme.css)", + strpos($output, '/plugins/dynamix.my.servers/unraid-components/standalone-apps/theme.css') !== false + ); + $this->test( + "Loads CSS from other JS entry (main.css)", + strpos($output, 'id="unraid-other-app-entry-css-main-css"') !== false + ); + $this->test( + "CSS from manifest has data-unraid attribute", + preg_match('/]+id="unraid-[^"]*-css-[^"]+"[^>]+data-unraid="1"/', $output) > 0 + ); + + // Test: Duplicate Prevention + echo "\nTest: Duplicate Prevention\n"; + echo "---------------------------\n"; + // Reset singleton for duplicate test + $reflection = new ReflectionClass('WebComponentsExtractor'); + $instance = $reflection->getProperty('instance'); + $instance->setAccessible(true); + $instance->setValue(null, null); + + $extractor = WebComponentsExtractor::getInstance(); + $first = $extractor->getScriptTagHtml(); + $second = $extractor->getScriptTagHtml(); + $this->test( + "Second call returns 'already loaded' message", + strpos($second, 'Resources already loaded') !== false + ); + } + + private function test($name, $condition) { + if ($condition) { + echo " " . self::GREEN . "βœ“" . self::NC . " " . $name . "\n"; + $this->passed++; + } else { + echo " " . self::RED . "βœ—" . self::NC . " " . $name . "\n"; + $this->failed++; + if ($this->verbose) { + echo " " . self::YELLOW . "Condition failed" . self::NC . "\n"; + } + } + } + + private function teardown() { + // Clean up temp directory + if ($this->testDir && is_dir($this->testDir)) { + $this->removeDirectory($this->testDir); + } + } + + private function removeDirectory($dir) { + if (!is_dir($dir)) return; + $files = array_diff(scandir($dir), ['.', '..']); + foreach ($files as $file) { + $path = $dir . '/' . $file; + is_dir($path) ? $this->removeDirectory($path) : unlink($path); + } + rmdir($dir); + } + + private function reportResults() { + echo "\n"; + echo "========================================\n"; + echo "Test Results:\n"; + echo " Passed: " . self::GREEN . $this->passed . self::NC . "\n"; + echo " Failed: " . self::RED . $this->failed . self::NC . "\n"; + echo "========================================\n"; + echo "\n"; + + if ($this->failed === 0) { + echo self::GREEN . "All tests passed!" . self::NC . "\n"; + return 0; + } else { + echo self::RED . "Some tests failed." . self::NC . "\n"; + return 1; + } + } +} + +// Run tests +$test = new ExtractorTest(); +exit($test->run()); \ No newline at end of file diff --git a/plugin/tests/test-extractor.sh b/plugin/tests/test-extractor.sh new file mode 100755 index 000000000..f1d946636 --- /dev/null +++ b/plugin/tests/test-extractor.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# WebComponentsExtractor Integration Test +# +# This script runs the PHP test suite for WebComponentsExtractor +# Exit codes: +# 0 - All tests passed +# 1 - One or more tests failed + +set -e # Exit on error + +# Get the directory of this script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Run the PHP test suite +exec php "$SCRIPT_DIR/test-extractor.php" "$@" \ No newline at end of file diff --git a/plugin/vitest.config.ts b/plugin/vitest.config.ts index e2ec33294..63ec6f7b2 100644 --- a/plugin/vitest.config.ts +++ b/plugin/vitest.config.ts @@ -3,5 +3,8 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, + env: { + TEST: "true", + }, }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2d69d4dc..f36d75863 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1263,9 +1263,6 @@ importers: nuxt: specifier: 3.18.1 version: 3.18.1(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.18.0)(@vue/compiler-sfc@3.5.20)(db0@0.3.2)(eslint@9.34.0(jiti@2.5.1))(ioredis@5.6.1)(lightningcss@1.30.1)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.46.2)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(xml2js@0.6.2)(yaml@2.8.1) - nuxt-custom-elements: - specifier: 2.0.0-beta.32 - version: 2.0.0-beta.32(webpack@5.98.0(esbuild@0.23.1)) prettier: specifier: 3.6.2 version: 3.6.2 @@ -1926,10 +1923,6 @@ packages: resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} engines: {node: '>=18'} - '@discoveryjs/json-ext@0.5.7': - resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} - engines: {node: '>=10.0.0'} - '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -5029,9 +5022,6 @@ packages: '@types/eslint-config-prettier@6.11.3': resolution: {integrity: sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==} - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -5053,9 +5043,6 @@ packages: '@types/graphql-type-uuid@0.2.6': resolution: {integrity: sha512-/R8hDcg/XLkuLblzIfKtx/EFZMzhpDBdYQCk5dwNUwDrGdWohv+3BVWvgNk80kaJD2u+THos1PAqp9lEyxxrsA==} - '@types/html-minifier-terser@6.1.0': - resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} - '@types/http-cache-semantics@4.0.4': resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} @@ -5908,51 +5895,6 @@ packages: peerDependencies: vue: ^3.5.0 - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - '@whatwg-node/disposablestack@0.0.5': resolution: {integrity: sha512-9lXugdknoIequO4OYvIjhygvfSEgnO8oASLqLelnDhkRjgBZhc39shC3QSlZuyDO9bgYSIVa2cHAiN+St3ty4w==} engines: {node: '>=18.0.0'} @@ -5993,12 +5935,6 @@ packages: resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} engines: {node: '>=8'} - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - abbrev@3.0.0: resolution: {integrity: sha512-+/kfrslGQ7TNV2ecmQwMJj/B65g5KVq1/L3SGVZ3tCYGqlzFuFCGBZJtMP99wH3NpEUyAjn0zPdPUg0D+DwrOA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -6035,10 +5971,6 @@ packages: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -6086,11 +6018,6 @@ packages: ajv: optional: true - ajv-keywords@5.1.0: - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -6655,10 +6582,6 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - ci-info@4.3.0: resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} engines: {node: '>=8'} @@ -6675,10 +6598,6 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - clean-css@5.3.3: - resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} - engines: {node: '>= 10.0'} - clean-regexp@1.0.0: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} @@ -6839,14 +6758,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -7110,9 +7021,6 @@ packages: peerDependencies: postcss: ^8.0.9 - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -7500,22 +7408,12 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - dom-converter@0.2.0: - resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} - - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} @@ -7523,9 +7421,6 @@ packages: dompurify@3.2.6: resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -7671,9 +7566,6 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -8102,10 +7994,6 @@ packages: '@vue/compiler-sfc': ^3.3.0 eslint: '>=9.0.0' - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8157,10 +8045,6 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -8816,10 +8700,6 @@ packages: resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - gzip-size@6.0.0: - resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} - engines: {node: '>=10'} - gzip-size@7.0.0: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8922,29 +8802,9 @@ packages: html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} - html-minifier-terser@6.1.0: - resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} - engines: {node: '>=12'} - hasBin: true - html-sloppy-escaper@0.1.0: resolution: {integrity: sha512-ONSUC5HwiImkny/29ApddyM+BxpqjgTZ+pOag6y39Q5FQgJuWypPLl7cGDpPYp1RtC5+6Wi5yQld3zAXhlO3xg==} - html-webpack-plugin@5.6.3: - resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==} - engines: {node: '>=10.13.0'} - peerDependencies: - '@rspack/core': 0.x || 1.x - webpack: ^5.20.0 - peerDependenciesMeta: - '@rspack/core': - optional: true - webpack: - optional: true - - htmlparser2@6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} - http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -9472,10 +9332,6 @@ packages: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} - jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -9781,10 +9637,6 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} - local-pkg@0.5.1: resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} @@ -10434,9 +10286,6 @@ packages: nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} - nuxt-custom-elements@2.0.0-beta.32: - resolution: {integrity: sha512-uT8v+3f6N68/r0wOgcxyb2h9nxiDm8yeEQK8Ura4jr5ifMGsZebFZ272XgeNM2DH39vZ3RLeP1g3MVQQY4+nFg==} - nuxt@3.18.1: resolution: {integrity: sha512-y2pLKty6R8MCCFlAUsJNJcOuT6M3EovzEpi7/U3WXQsnzf2MzP+5I67ScfmwSqZ3UUMgXvfc9H4+KC4Ifnq5wg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -11251,9 +11100,6 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} - pretty-error@4.0.0: - resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} - pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -11556,10 +11402,6 @@ packages: peerDependencies: vue: '>= 3.2.0' - relateurl@0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} - relay-runtime@12.0.0: resolution: {integrity: sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==} @@ -11572,9 +11414,6 @@ packages: remove-trailing-spaces@1.0.8: resolution: {integrity: sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA==} - renderkid@3.0.0: - resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -11765,10 +11604,6 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - schema-utils@4.3.2: - resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} - engines: {node: '>= 10.13.0'} - scslre@0.3.0: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} @@ -11935,10 +11770,6 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - sirv@2.0.4: - resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} - engines: {node: '>= 10'} - sirv@3.0.1: resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} @@ -12350,22 +12181,6 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} - terser-webpack-plugin@5.3.14: - resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - terser@5.43.1: resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} engines: {node: '>=10'} @@ -12916,9 +12731,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utila@0.4.0: - resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} - utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -13250,9 +13062,6 @@ packages: peerDependencies: typescript: '>=5.0.0' - vue-web-component-wrapper@1.6.9: - resolution: {integrity: sha512-65Ybkjg2Y+MAMsx62AgEL2n5RCfzSvqbzJLEy6WvPalLATGEUhfT88Lbx5EYC6yzvZArfLKHq7Zl75Dg29ssNQ==} - vue-web-component-wrapper@1.7.7: resolution: {integrity: sha512-2uy6VdN8AwSzCeqc9tV4ZK2HKtgZ/NWL1rvdgOsddF1UFtszBZHKyQT9sDBUc4BpyXmP7f8tmI1rI0n/A6Qptw==} @@ -13287,10 +13096,6 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - watchpack@2.4.4: - resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} - engines: {node: '>=10.13.0'} - wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} @@ -13305,28 +13110,9 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-bundle-analyzer@4.10.2: - resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==} - engines: {node: '>= 10.13.0'} - hasBin: true - - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} - engines: {node: '>=10.13.0'} - webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.98.0: - resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -14449,8 +14235,6 @@ snapshots: gonzales-pe: 4.3.0 node-source-walk: 7.0.1 - '@discoveryjs/json-ext@0.5.7': {} - '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -17835,12 +17619,6 @@ snapshots: '@types/eslint-config-prettier@6.11.3': {} - '@types/eslint-scope@3.7.7': - dependencies: - '@types/eslint': 9.6.1 - '@types/estree': 1.0.8 - optional: true - '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.8 @@ -17876,8 +17654,6 @@ snapshots: dependencies: graphql: 16.11.0 - '@types/html-minifier-terser@6.1.0': {} - '@types/http-cache-semantics@4.0.4': {} '@types/http-errors@2.0.4': {} @@ -18915,97 +18691,6 @@ snapshots: dependencies: vue: 3.5.20(typescript@5.9.2) - '@webassemblyjs/ast@1.14.1': - dependencies: - '@webassemblyjs/helper-numbers': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - optional: true - - '@webassemblyjs/floating-point-hex-parser@1.13.2': - optional: true - - '@webassemblyjs/helper-api-error@1.13.2': - optional: true - - '@webassemblyjs/helper-buffer@1.14.1': - optional: true - - '@webassemblyjs/helper-numbers@1.13.2': - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.13.2 - '@webassemblyjs/helper-api-error': 1.13.2 - '@xtuc/long': 4.2.2 - optional: true - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - optional: true - - '@webassemblyjs/helper-wasm-section@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/wasm-gen': 1.14.1 - optional: true - - '@webassemblyjs/ieee754@1.13.2': - dependencies: - '@xtuc/ieee754': 1.2.0 - optional: true - - '@webassemblyjs/leb128@1.13.2': - dependencies: - '@xtuc/long': 4.2.2 - optional: true - - '@webassemblyjs/utf8@1.13.2': - optional: true - - '@webassemblyjs/wasm-edit@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/helper-wasm-section': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-opt': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wast-printer': 1.14.1 - optional: true - - '@webassemblyjs/wasm-gen@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - optional: true - - '@webassemblyjs/wasm-opt@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-buffer': 1.14.1 - '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - optional: true - - '@webassemblyjs/wasm-parser@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/helper-api-error': 1.13.2 - '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/ieee754': 1.13.2 - '@webassemblyjs/leb128': 1.13.2 - '@webassemblyjs/utf8': 1.13.2 - optional: true - - '@webassemblyjs/wast-printer@1.14.1': - dependencies: - '@webassemblyjs/ast': 1.14.1 - '@xtuc/long': 4.2.2 - optional: true - '@whatwg-node/disposablestack@0.0.5': dependencies: tslib: 2.8.1 @@ -19054,12 +18739,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@xtuc/ieee754@1.2.0': - optional: true - - '@xtuc/long@4.2.2': - optional: true - abbrev@3.0.0: {} abind@1.0.5: {} @@ -19089,10 +18768,6 @@ snapshots: acorn-walk@8.3.2: {} - acorn-walk@8.3.4: - dependencies: - acorn: 8.15.0 - acorn@7.4.1: {} acorn@8.14.0: {} @@ -19120,12 +18795,6 @@ snapshots: optionalDependencies: ajv: 8.17.1 - ajv-keywords@5.1.0(ajv@8.17.1): - dependencies: - ajv: 8.17.1 - fast-deep-equal: 3.1.3 - optional: true - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -19806,9 +19475,6 @@ snapshots: chownr@3.0.0: {} - chrome-trace-event@1.0.4: - optional: true - ci-info@4.3.0: {} citty@0.1.6: @@ -19827,10 +19493,6 @@ snapshots: dependencies: clsx: 2.1.1 - clean-css@5.3.3: - dependencies: - source-map: 0.6.1 - clean-regexp@1.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -19978,10 +19640,6 @@ snapshots: commander@2.20.3: {} - commander@7.2.0: {} - - commander@8.3.0: {} - commander@9.5.0: optional: true @@ -20263,14 +19921,6 @@ snapshots: dependencies: postcss: 8.5.6 - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - css-select@5.1.0: dependencies: boolbase: 1.0.0 @@ -20642,16 +20292,6 @@ snapshots: dom-accessibility-api@0.6.3: {} - dom-converter@0.2.0: - dependencies: - utila: 0.4.0 - - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -20660,10 +20300,6 @@ snapshots: domelementtype@2.3.0: {} - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - domhandler@5.0.3: dependencies: domelementtype: 2.3.0 @@ -20672,12 +20308,6 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -20807,8 +20437,6 @@ snapshots: strip-ansi: 6.0.1 optional: true - entities@2.2.0: {} - entities@4.5.0: {} entities@6.0.1: {} @@ -21404,12 +21032,6 @@ snapshots: '@vue/compiler-sfc': 3.5.20 eslint: 9.34.0(jiti@2.5.1) - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - optional: true - eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -21492,9 +21114,6 @@ snapshots: dependencies: estraverse: 5.3.0 - estraverse@4.3.0: - optional: true - estraverse@5.3.0: {} estree-walker@2.0.2: {} @@ -22288,10 +21907,6 @@ snapshots: graphql@16.11.0: {} - gzip-size@6.0.0: - dependencies: - duplexer: 0.1.2 - gzip-size@7.0.0: dependencies: duplexer: 0.1.2 @@ -22402,37 +22017,10 @@ snapshots: html-escaper@3.0.3: {} - html-minifier-terser@6.1.0: - dependencies: - camel-case: 4.1.2 - clean-css: 5.3.3 - commander: 8.3.0 - he: 1.2.0 - param-case: 3.0.4 - relateurl: 0.2.7 - terser: 5.43.1 - html-sloppy-escaper@0.1.0: dependencies: html-escaper: 3.0.3 - html-webpack-plugin@5.6.3(webpack@5.98.0(esbuild@0.23.1)): - dependencies: - '@types/html-minifier-terser': 6.1.0 - html-minifier-terser: 6.1.0 - lodash: 4.17.21 - pretty-error: 4.0.0 - tapable: 2.2.2 - optionalDependencies: - webpack: 5.98.0(esbuild@0.23.1) - - htmlparser2@6.1.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - entities: 2.2.0 - http-cache-semantics@4.1.1: {} http-errors@2.0.0: @@ -22984,13 +22572,6 @@ snapshots: dependencies: '@isaacs/cliui': 8.0.2 - jest-worker@27.5.1: - dependencies: - '@types/node': 22.18.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - optional: true - jiti@1.21.7: {} jiti@2.0.0-beta.3: {} @@ -23293,9 +22874,6 @@ snapshots: load-tsconfig@0.2.5: {} - loader-runner@4.3.0: - optional: true - local-pkg@0.5.1: dependencies: mlly: 1.7.4 @@ -23958,21 +23536,6 @@ snapshots: nullthrows@1.1.1: {} - nuxt-custom-elements@2.0.0-beta.32(webpack@5.98.0(esbuild@0.23.1)): - dependencies: - change-case: 5.4.4 - clone: 2.1.2 - defu: 6.1.4 - html-webpack-plugin: 5.6.3(webpack@5.98.0(esbuild@0.23.1)) - lodash-es: 4.17.21 - vue-web-component-wrapper: 1.6.9 - webpack-bundle-analyzer: 4.10.2 - transitivePeerDependencies: - - '@rspack/core' - - bufferutil - - utf-8-validate - - webpack - nuxt@3.18.1(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.18.0)(@vue/compiler-sfc@3.5.20)(db0@0.3.2)(eslint@9.34.0(jiti@2.5.1))(ioredis@5.6.1)(lightningcss@1.30.1)(magicast@0.3.5)(meow@13.2.0)(optionator@0.9.4)(rollup@4.46.2)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(typescript@5.9.2)(vite@7.1.3(@types/node@22.18.0)(jiti@2.5.1)(lightningcss@1.30.1)(stylus@0.57.0)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vue-tsc@3.0.6(typescript@5.9.2))(xml2js@0.6.2)(yaml@2.8.1): dependencies: '@nuxt/cli': 3.27.0(magicast@0.3.5) @@ -25004,11 +24567,6 @@ snapshots: pretty-bytes@6.1.1: {} - pretty-error@4.0.0: - dependencies: - lodash: 4.17.21 - renderkid: 3.0.0 - pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -25405,8 +24963,6 @@ snapshots: - '@vue/composition-api' - typescript - relateurl@0.2.7: {} - relay-runtime@12.0.0: dependencies: '@babel/runtime': 7.27.6 @@ -25421,14 +24977,6 @@ snapshots: remove-trailing-spaces@1.0.8: {} - renderkid@3.0.0: - dependencies: - css-select: 4.3.0 - dom-converter: 0.2.0 - htmlparser2: 6.1.0 - lodash: 4.17.21 - strip-ansi: 6.0.1 - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -25620,14 +25168,6 @@ snapshots: scheduler@0.26.0: {} - schema-utils@4.3.2: - dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) - optional: true - scslre@0.3.0: dependencies: '@eslint-community/regexpp': 4.12.1 @@ -25894,12 +25434,6 @@ snapshots: dependencies: semver: 7.7.2 - sirv@2.0.4: - dependencies: - '@polka/url': 1.0.0-next.28 - mrmime: 2.0.1 - totalist: 3.0.1 - sirv@3.0.1: dependencies: '@polka/url': 1.0.0-next.28 @@ -26350,18 +25884,6 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - terser-webpack-plugin@5.3.14(esbuild@0.23.1)(webpack@5.98.0(esbuild@0.23.1)): - dependencies: - '@jridgewell/trace-mapping': 0.3.29 - jest-worker: 27.5.1 - schema-utils: 4.3.2 - serialize-javascript: 6.0.2 - terser: 5.43.1 - webpack: 5.98.0(esbuild@0.23.1) - optionalDependencies: - esbuild: 0.23.1 - optional: true - terser@5.43.1: dependencies: '@jridgewell/source-map': 0.3.6 @@ -26944,8 +26466,6 @@ snapshots: util-deprecate@1.0.2: {} - utila@0.4.0: {} - utils-merge@1.0.1: {} uuid@10.0.0: {} @@ -27355,8 +26875,6 @@ snapshots: '@vue/language-core': 3.0.6(typescript@5.9.2) typescript: 5.9.2 - vue-web-component-wrapper@1.6.9: {} - vue-web-component-wrapper@1.7.7: {} vue@3.5.20(typescript@5.9.2): @@ -27401,12 +26919,6 @@ snapshots: dependencies: xml-name-validator: 5.0.0 - watchpack@2.4.4: - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - optional: true - wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -27417,60 +26929,8 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-bundle-analyzer@4.10.2: - dependencies: - '@discoveryjs/json-ext': 0.5.7 - acorn: 8.15.0 - acorn-walk: 8.3.4 - commander: 7.2.0 - debounce: 1.2.1 - escape-string-regexp: 4.0.0 - gzip-size: 6.0.0 - html-escaper: 2.0.2 - opener: 1.5.2 - picocolors: 1.1.1 - sirv: 2.0.4 - ws: 7.5.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - webpack-sources@3.3.3: - optional: true - webpack-virtual-modules@0.6.2: {} - webpack@5.98.0(esbuild@0.23.1): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.15.0 - browserslist: 4.25.1 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.3 - es-module-lexer: 1.7.0 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 4.3.2 - tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(esbuild@0.23.1)(webpack@5.98.0(esbuild@0.23.1)) - watchpack: 2.4.4 - webpack-sources: 3.3.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - optional: true - whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 diff --git a/unraid-ui/eslint.config.ts b/unraid-ui/eslint.config.ts index 83f4c47dd..3f2797bf7 100644 --- a/unraid-ui/eslint.config.ts +++ b/unraid-ui/eslint.config.ts @@ -87,6 +87,13 @@ const commonGlobals = { HTMLElement: 'readonly', HTMLInputElement: 'readonly', CustomEvent: 'readonly', + MouseEvent: 'readonly', + KeyboardEvent: 'readonly', + FocusEvent: 'readonly', + PointerEvent: 'readonly', + TouchEvent: 'readonly', + WheelEvent: 'readonly', + DragEvent: 'readonly', }; export default [// Base config from recommended configs diff --git a/unraid-ui/src/components.ts b/unraid-ui/src/components.ts index 182d8c1f0..5501213dc 100644 --- a/unraid-ui/src/components.ts +++ b/unraid-ui/src/components.ts @@ -17,6 +17,7 @@ export * from '@/components/common/tabs'; export * from '@/components/common/tooltip'; export * from '@/components/common/toast'; export * from '@/components/common/popover'; +export * from '@/components/common/responsive-modal'; export * from '@/components/modals'; export * from '@/components/common/accordion'; export * from '@/components/common/dialog'; diff --git a/unraid-ui/src/components/brand/BrandButton.vue b/unraid-ui/src/components/brand/BrandButton.vue index abc881889..89f1fd46a 100644 --- a/unraid-ui/src/components/brand/BrandButton.vue +++ b/unraid-ui/src/components/brand/BrandButton.vue @@ -7,7 +7,6 @@ export interface BrandButtonProps { variant?: BrandButtonVariants['variant']; size?: BrandButtonVariants['size']; padding?: BrandButtonVariants['padding']; - btnType?: 'button' | 'submit' | 'reset'; class?: string; click?: () => void; disabled?: boolean; @@ -26,7 +25,6 @@ const props = withDefaults(defineProps(), { variant: 'fill', size: '16px', padding: 'default', - btnType: 'button', class: undefined, click: undefined, disabled: false, @@ -51,22 +49,37 @@ const classes = computed(() => { iconSize: props.size ?? '16px', }; }); + const needsBrandGradientBackground = computed(() => { return ['outline-solid', 'outline-primary'].includes(props.variant ?? ''); }); + +const isLink = computed(() => Boolean(props.href)); +const isButton = computed(() => !isLink.value);