Skip to content

Commit 88087d5

Browse files
authored
feat: mount vue apps, not web components (#1639)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 5d89682 commit 88087d5

121 files changed

Lines changed: 3610 additions & 1794 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
- name: Cache APT Packages
4848
uses: awalsh128/cache-apt-pkgs-action@v1.5.3
4949
with:
50-
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system
50+
packages: bash procps python3 libvirt-dev jq zstd git build-essential libvirt-daemon-system php-cli
5151
version: 1.0
5252

5353
- name: Install pnpm
@@ -147,22 +147,28 @@ jobs:
147147
(cd ../unraid-ui && pnpm test --coverage 2>/dev/null || pnpm test) > ui-test.log 2>&1 &
148148
UI_PID=$!
149149
150+
echo "🚀 Starting Plugin tests..."
151+
(cd ../plugin && pnpm test) > plugin-test.log 2>&1 &
152+
PLUGIN_PID=$!
153+
150154
# Wait for all processes and capture exit codes
151155
wait $API_PID && echo "✅ API tests completed" || { echo "❌ API tests failed"; API_EXIT=1; }
152156
wait $CONNECT_PID && echo "✅ Connect tests completed" || { echo "❌ Connect tests failed"; CONNECT_EXIT=1; }
153157
wait $SHARED_PID && echo "✅ Shared tests completed" || { echo "❌ Shared tests failed"; SHARED_EXIT=1; }
154158
wait $WEB_PID && echo "✅ Web tests completed" || { echo "❌ Web tests failed"; WEB_EXIT=1; }
155159
wait $UI_PID && echo "✅ UI tests completed" || { echo "❌ UI tests failed"; UI_EXIT=1; }
160+
wait $PLUGIN_PID && echo "✅ Plugin tests completed" || { echo "❌ Plugin tests failed"; PLUGIN_EXIT=1; }
156161
157162
# Display all outputs
158163
echo "📋 API Test Results:" && cat api-test.log
159164
echo "📋 Connect Plugin Test Results:" && cat connect-test.log
160165
echo "📋 Shared Package Test Results:" && cat shared-test.log
161166
echo "📋 Web Package Test Results:" && cat web-test.log
162167
echo "📋 UI Package Test Results:" && cat ui-test.log
168+
echo "📋 Plugin Test Results:" && cat plugin-test.log
163169
164170
# Exit with error if any test failed
165-
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
171+
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
166172
exit 1
167173
fi
168174
@@ -379,7 +385,7 @@ jobs:
379385
uses: actions/upload-artifact@v4
380386
with:
381387
name: unraid-wc-rich
382-
path: web/.nuxt/nuxt-custom-elements/dist/unraid-components
388+
path: web/.nuxt/standalone-apps
383389

384390
build-plugin-staging-pr:
385391
name: Build and Deploy Plugin

@tailwind-shared/base-utilities.css

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@custom-variant dark (&:where(.dark, .dark *));
22

3-
@layer utilities {
4-
:host {
3+
/* Utility defaults for web components (when we were using shadow DOM) */
4+
:host {
55
--tw-divide-y-reverse: 0;
66
--tw-border-style: solid;
77
--tw-font-weight: initial;
@@ -48,21 +48,20 @@
4848
--tw-drop-shadow: initial;
4949
--tw-duration: initial;
5050
--tw-ease: initial;
51-
}
5251
}
5352

54-
@layer base {
55-
*,
56-
::after,
57-
::before,
58-
::backdrop,
59-
::file-selector-button {
60-
border-color: hsl(var(--border));
61-
}
62-
63-
53+
/* Global border color - this is what's causing the issue! */
54+
/* Commenting out since it affects all elements globally
55+
*,
56+
::after,
57+
::before,
58+
::backdrop,
59+
::file-selector-button {
60+
border-color: hsl(var(--border));
61+
}
62+
*/
6463

65-
body {
64+
body {
6665
--color-alpha: #1c1b1b;
6766
--color-beta: #f2f2f2;
6867
--color-gamma: #999999;
@@ -74,8 +73,7 @@
7473
--ring-shadow: 0 0 var(--color-beta);
7574
}
7675

77-
button:not(:disabled),
78-
[role='button']:not(:disabled) {
79-
cursor: pointer;
80-
}
76+
button:not(:disabled),
77+
[role='button']:not(:disabled) {
78+
cursor: pointer;
8179
}

@tailwind-shared/css-variables.css

Lines changed: 12 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
/* Hybrid theme system: Native CSS + Theme Store fallback */
2-
@layer base {
3-
/* Light mode defaults */
4-
:root {
2+
3+
/* Light mode defaults */
4+
:root {
5+
/* Override Tailwind v4 global styles to use webgui variables */
6+
--ui-bg: var(--background-color) !important;
7+
--ui-text: var(--text-color) !important;
8+
59
--background: 0 0% 100%;
610
--foreground: 0 0% 3.9%;
711
--muted: 0 0% 96.1%;
@@ -30,6 +34,10 @@
3034

3135
/* Dark mode */
3236
.dark {
37+
/* Override Tailwind v4 global styles to use webgui variables */
38+
--ui-bg: var(--background-color) !important;
39+
--ui-text: var(--text-color) !important;
40+
3341
--background: 0 0% 3.9%;
3442
--foreground: 0 0% 98%;
3543
--muted: 0 0% 14.9%;
@@ -62,69 +70,4 @@
6270
--background: 0 0% 3.9%;
6371
--foreground: 0 0% 98%;
6472
--border: 0 0% 14.9%;
65-
}
66-
67-
/* For web components: inherit CSS variables from the host */
68-
:host {
69-
--background: inherit;
70-
--foreground: inherit;
71-
--muted: inherit;
72-
--muted-foreground: inherit;
73-
--popover: inherit;
74-
--popover-foreground: inherit;
75-
--card: inherit;
76-
--card-foreground: inherit;
77-
--border: inherit;
78-
--input: inherit;
79-
--primary: inherit;
80-
--primary-foreground: inherit;
81-
--secondary: inherit;
82-
--secondary-foreground: inherit;
83-
--accent: inherit;
84-
--accent-foreground: inherit;
85-
--destructive: inherit;
86-
--destructive-foreground: inherit;
87-
--ring: inherit;
88-
--chart-1: inherit;
89-
--chart-2: inherit;
90-
--chart-3: inherit;
91-
--chart-4: inherit;
92-
--chart-5: inherit;
93-
}
94-
95-
/* Class-based dark mode support for web components using :host-context */
96-
:host-context(.dark) {
97-
--background: 0 0% 3.9%;
98-
--foreground: 0 0% 98%;
99-
--muted: 0 0% 14.9%;
100-
--muted-foreground: 0 0% 63.9%;
101-
--popover: 0 0% 3.9%;
102-
--popover-foreground: 0 0% 98%;
103-
--card: 0 0% 3.9%;
104-
--card-foreground: 0 0% 98%;
105-
--border: 0 0% 14.9%;
106-
--input: 0 0% 14.9%;
107-
--primary: 0 0% 98%;
108-
--primary-foreground: 0 0% 9%;
109-
--secondary: 0 0% 14.9%;
110-
--secondary-foreground: 0 0% 98%;
111-
--accent: 0 0% 14.9%;
112-
--accent-foreground: 0 0% 98%;
113-
--destructive: 0 62.8% 30.6%;
114-
--destructive-foreground: 0 0% 98%;
115-
--ring: 0 0% 83.1%;
116-
--chart-1: 220 70% 50%;
117-
--chart-2: 160 60% 45%;
118-
--chart-3: 30 80% 55%;
119-
--chart-4: 280 65% 60%;
120-
--chart-5: 340 75% 55%;
121-
}
122-
123-
/* Alternative class-based dark mode support for specific Unraid themes */
124-
:host-context(.dark[data-theme='black']),
125-
:host-context(.dark[data-theme='gray']) {
126-
--background: 0 0% 3.9%;
127-
--foreground: 0 0% 98%;
128-
--border: 0 0% 14.9%;
129-
}
130-
}
73+
}

@tailwind-shared/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
@import './css-variables.css';
33
@import './unraid-theme.css';
44
@import './base-utilities.css';
5-
@import './sonner.css';
5+
@import './sonner.css';

plugin/builder/__tests__/cli/setup-plugin-environment.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
setupPluginEnv,
55
} from "../../cli/setup-plugin-environment";
66
import { access, readFile } from "node:fs/promises";
7+
import { existsSync } from "node:fs";
78

89
// Mock fs/promises
910
vi.mock("node:fs/promises", () => ({
@@ -14,8 +15,19 @@ vi.mock("node:fs/promises", () => ({
1415
},
1516
}));
1617

18+
// Mock node:fs
19+
vi.mock("node:fs", () => ({
20+
existsSync: vi.fn(),
21+
}));
22+
1723
beforeEach(() => {
1824
vi.resetAllMocks();
25+
26+
// Mock existsSync to return true for test.txz
27+
vi.mocked(existsSync).mockImplementation((path) => {
28+
return path.toString().includes("test.txz");
29+
});
30+
1931
vi.mocked(readFile).mockImplementation((path, encoding) => {
2032
console.log("Mock readFile called with:", path, encoding);
2133

@@ -42,6 +54,7 @@ describe("validatePluginEnv", () => {
4254

4355
it("validates required fields", async () => {
4456
const validEnv = {
57+
apiVersion: "4.17.0",
4558
baseUrl: "https://example.com",
4659
txzPath: "./test.txz",
4760
pluginVersion: "2024.05.05.1232",
@@ -53,6 +66,7 @@ describe("validatePluginEnv", () => {
5366

5467
it("throws on invalid URL", async () => {
5568
const invalidEnv = {
69+
apiVersion: "4.17.0",
5670
baseUrl: "not-a-url",
5771
txzPath: "./test.txz",
5872
pluginVersion: "2024.05.05.1232",
@@ -63,6 +77,7 @@ describe("validatePluginEnv", () => {
6377

6478
it("handles tag option in non-CI mode", async () => {
6579
const envWithTag = {
80+
apiVersion: "4.17.0",
6681
baseUrl: "https://example.com",
6782
txzPath: "./test.txz",
6883
pluginVersion: "2024.05.05.1232",
@@ -77,6 +92,7 @@ describe("validatePluginEnv", () => {
7792

7893
it("reads release notes when release-notes-path is provided", async () => {
7994
const envWithNotes = {
95+
apiVersion: "4.17.0",
8096
baseUrl: "https://example.com",
8197
txzPath: "./test.txz",
8298
pluginVersion: "2024.05.05.1232",
@@ -100,6 +116,7 @@ describe("validatePluginEnv", () => {
100116
});
101117

102118
const envWithEmptyNotes = {
119+
apiVersion: "4.17.0",
103120
baseUrl: "https://example.com",
104121
txzPath: "./test.txz",
105122
pluginVersion: "2024.05.05.1232",

plugin/builder/__tests__/cli/setup-txz-environment.spec.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,57 @@ import { deployDir } from "../../utils/paths";
66

77
describe("setupTxzEnvironment", () => {
88
it("should return default values when no arguments are provided", async () => {
9-
const envArgs = {};
10-
const expected: TxzEnv = { ci: false, skipValidation: "false", compress: "1", txzOutputDir: join(startingDir, deployDir) };
9+
const envArgs = {
10+
apiVersion: "4.17.0",
11+
baseUrl: "https://example.com"
12+
};
13+
const expected: TxzEnv = {
14+
apiVersion: "4.17.0",
15+
baseUrl: "https://example.com",
16+
ci: false,
17+
skipValidation: "false",
18+
compress: "1",
19+
txzOutputDir: join(startingDir, deployDir),
20+
tag: "",
21+
buildNumber: 1
22+
};
1123

1224
const result = await validateTxzEnv(envArgs);
1325

1426
expect(result).toEqual(expected);
1527
});
1628

1729
it("should parse and return provided environment arguments", async () => {
18-
const envArgs = { ci: true, skipValidation: "true", txzOutputDir: join(startingDir, "deploy/release/test"), compress: '8' };
19-
const expected: TxzEnv = { ci: true, skipValidation: "true", compress: "8", txzOutputDir: join(startingDir, "deploy/release/test") };
30+
const envArgs = {
31+
apiVersion: "4.17.0",
32+
baseUrl: "https://example.com",
33+
ci: true,
34+
skipValidation: "true",
35+
txzOutputDir: join(startingDir, "deploy/release/test"),
36+
compress: '8'
37+
};
38+
const expected: TxzEnv = {
39+
apiVersion: "4.17.0",
40+
baseUrl: "https://example.com",
41+
ci: true,
42+
skipValidation: "true",
43+
compress: "8",
44+
txzOutputDir: join(startingDir, "deploy/release/test"),
45+
tag: "",
46+
buildNumber: 1
47+
};
2048

2149
const result = await validateTxzEnv(envArgs);
2250

2351
expect(result).toEqual(expected);
2452
});
2553

2654
it("should warn and skip validation when skipValidation is true", async () => {
27-
const envArgs = { skipValidation: "true" };
55+
const envArgs = {
56+
apiVersion: "4.17.0",
57+
baseUrl: "https://example.com",
58+
skipValidation: "true"
59+
};
2860
const consoleWarnSpy = vi
2961
.spyOn(console, "warn")
3062
.mockImplementation(() => {});
@@ -38,7 +70,11 @@ describe("setupTxzEnvironment", () => {
3870
});
3971

4072
it("should throw an error for invalid SKIP_VALIDATION value", async () => {
41-
const envArgs = { skipValidation: "invalid" };
73+
const envArgs = {
74+
apiVersion: "4.17.0",
75+
baseUrl: "https://example.com",
76+
skipValidation: "invalid"
77+
};
4278

4379
await expect(validateTxzEnv(envArgs)).rejects.toThrow(
4480
"Must be true or false"

plugin/builder/build-txz.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ const findManifestFiles = async (dir: string): Promise<string[]> => {
2929
}
3030
} else if (
3131
entry.isFile() &&
32-
(entry.name === "manifest.json" || entry.name === "ui.manifest.json")
32+
(entry.name === "manifest.json" ||
33+
entry.name === "ui.manifest.json" ||
34+
entry.name === "standalone.manifest.json")
3335
) {
3436
files.push(entry.name);
3537
}
@@ -124,19 +126,21 @@ const validateSourceDir = async (validatedEnv: TxzEnv) => {
124126

125127
const manifestFiles = await findManifestFiles(webcomponentDir);
126128
const hasManifest = manifestFiles.includes("manifest.json");
129+
const hasStandaloneManifest = manifestFiles.includes("standalone.manifest.json");
127130
const hasUiManifest = manifestFiles.includes("ui.manifest.json");
128131

129-
if (!hasManifest || !hasUiManifest) {
132+
// Accept either manifest.json (old web components) or standalone.manifest.json (new standalone apps)
133+
if ((!hasManifest && !hasStandaloneManifest) || !hasUiManifest) {
130134
console.log("Existing Manifest Files:", manifestFiles);
131135
const missingFiles: string[] = [];
132-
if (!hasManifest) missingFiles.push("manifest.json");
136+
if (!hasManifest && !hasStandaloneManifest) missingFiles.push("manifest.json or standalone.manifest.json");
133137
if (!hasUiManifest) missingFiles.push("ui.manifest.json");
134138

135139
throw new Error(
136140
`Webcomponents missing required file(s): ${missingFiles.join(", ")} - ` +
137141
`${!hasUiManifest ? "run 'pnpm build:wc' in unraid-ui for ui.manifest.json" : ""}` +
138-
`${!hasManifest && !hasUiManifest ? " and " : ""}` +
139-
`${!hasManifest ? "run 'pnpm build' in web for manifest.json" : ""}`
142+
`${(!hasManifest && !hasStandaloneManifest) && !hasUiManifest ? " and " : ""}` +
143+
`${(!hasManifest && !hasStandaloneManifest) ? "run 'pnpm build' in web for standalone.manifest.json" : ""}`
140144
);
141145
}
142146

plugin/builder/cli/setup-txz-environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const validateTxzEnv = async (
2222
): Promise<TxzEnv> => {
2323
const validatedEnv = txzEnvSchema.parse(envArgs);
2424

25-
if ("skipValidation" in validatedEnv) {
25+
if (validatedEnv.skipValidation === "true") {
2626
console.warn("skipValidation is true, skipping validation");
2727
}
2828

0 commit comments

Comments
 (0)