@glasshome/widget-sdk 1.10.3 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,6 +86,12 @@ The `area` widget renders quick-glance metric pills (CO2 traffic-light, humidity
86
86
 
87
87
  If your widget needs full visual control (a chart, a video stream, a custom animated surface), follow these patterns: keep the `<Widget>` shell neutral (no `tone`/`color`), place your custom DOM inside `<Widget.Content>`, and document the choice in the widget's README.
88
88
 
89
+ ## Troubleshooting
90
+
91
+ | Variable | Effect |
92
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `GLASSHOME_WIDGET_NO_WORKER` | Set to `1` to make `bun widget connect` check each rebuild in a fresh process again, instead of reusing one worker for the session. |
94
+
89
95
  ## Peer Dependencies
90
96
 
91
97
  | Package | Required | Notes |
@@ -1,10 +1,10 @@
1
1
  import { z } from "zod";
2
2
  export declare const ToneSchema: z.ZodEnum<{
3
- success: "success";
4
- warning: "warning";
3
+ accent: "accent";
5
4
  danger: "danger";
6
5
  info: "info";
7
6
  neutral: "neutral";
8
- accent: "accent";
7
+ success: "success";
8
+ warning: "warning";
9
9
  }>;
10
10
  export type Tone = z.infer<typeof ToneSchema>;
package/dist/index.js CHANGED
@@ -1451,7 +1451,7 @@ function Un() {
1451
1451
  //#region src/version.ts
1452
1452
  var Wn = {
1453
1453
  name: "@glasshome/widget-sdk",
1454
- version: "1.10.3",
1454
+ version: "1.11.1",
1455
1455
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
1456
1456
  keywords: [
1457
1457
  "glasshome",
@@ -1520,6 +1520,7 @@ var Wn = {
1520
1520
  "@tailwindcss/vite": "^4.1.13",
1521
1521
  clsx: "^2.1.1",
1522
1522
  "happy-dom": "^20.0.0",
1523
+ lightningcss: "^1.33.0",
1523
1524
  "tailwind-merge": "^3.5.0",
1524
1525
  tailwindcss: "^4.1.13",
1525
1526
  zod: "^4.1.13"
@@ -1529,7 +1530,7 @@ var Wn = {
1529
1530
  "@solidjs/testing-library": "^0.8.10",
1530
1531
  "@types/bun": "latest",
1531
1532
  "@types/node": "^25.2.2",
1532
- typescript: "^5",
1533
+ typescript: "^7.0.2",
1533
1534
  vite: "^8.1.0",
1534
1535
  "vite-plugin-solid": "^2.11.12",
1535
1536
  vitest: "^4.1.10"
@@ -0,0 +1,72 @@
1
+ import { copyFileSync as e, rmSync as t } from "node:fs";
2
+ import { pathToFileURL as n } from "node:url";
3
+ //#region src/vite/introspect-core.ts
4
+ function r(e) {
5
+ return typeof e?.safeParse == "function";
6
+ }
7
+ function i(e, t) {
8
+ if (!r(e) || !Array.isArray(t)) return [];
9
+ let n = [];
10
+ for (let [r, i] of t.entries()) {
11
+ let t = i?.config;
12
+ if (typeof t != "object" || !t) continue;
13
+ let a = e.safeParse(t);
14
+ a.success || n.push({
15
+ index: r,
16
+ label: i.label,
17
+ problems: (a.error?.issues ?? []).map((e) => {
18
+ let t = e.path.map(String).join(".");
19
+ return t ? `${t}: ${e.message}` : e.message;
20
+ })
21
+ });
22
+ }
23
+ return n;
24
+ }
25
+ var a = [
26
+ "window",
27
+ "document",
28
+ "navigator",
29
+ "location",
30
+ "customElements",
31
+ "HTMLElement",
32
+ "Element",
33
+ "Node",
34
+ "CSS",
35
+ "getComputedStyle",
36
+ "requestAnimationFrame",
37
+ "cancelAnimationFrame",
38
+ "matchMedia"
39
+ ];
40
+ async function o() {
41
+ let { Window: e } = await import("happy-dom"), t = new e({ url: "http://localhost" });
42
+ for (let e of a) try {
43
+ Object.defineProperty(globalThis, e, {
44
+ value: t[e],
45
+ configurable: !0,
46
+ writable: !0
47
+ });
48
+ } catch {}
49
+ }
50
+ async function s(r, i) {
51
+ let a = `${r}.introspect-${i}.mjs`;
52
+ e(r, a);
53
+ try {
54
+ return await import(n(a).href);
55
+ } finally {
56
+ t(a, { force: !0 });
57
+ }
58
+ }
59
+ async function c(e, t) {
60
+ let r = (t ? await s(e, t) : await import(n(e).href)).default, a = null;
61
+ if (r?.configSchema) {
62
+ let { z: e } = await import("zod");
63
+ a = e.toJSONSchema(r.configSchema, { unrepresentable: "any" });
64
+ }
65
+ return {
66
+ manifest: r?.manifest ?? null,
67
+ jsonSchema: a,
68
+ exampleConfigIssues: i(r?.configSchema, r?.manifest?.examples)
69
+ };
70
+ }
71
+ //#endregion
72
+ export { c as n, o as t };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,8 @@
1
1
  import type { InlineConfig, Plugin } from "vite";
2
+ import type { ExampleConfigIssue, WidgetIntrospection } from "./introspect-core";
3
+ import { createIntrospectSession, type IntrospectSession, type IntrospectSessionOptions, introspectOnce } from "./introspect-session";
4
+ export { createIntrospectSession, introspectOnce };
5
+ export type { ExampleConfigIssue, IntrospectSession, IntrospectSessionOptions, WidgetIntrospection, };
2
6
  export interface GlasshomeWidgetOptions {
3
7
  /** Entry file for the widget (default: "src/index.tsx") */
4
8
  entry?: string;
@@ -16,6 +20,8 @@ export declare function runSchemaGuard(args: {
16
20
  hashFile: string;
17
21
  widgetName: string;
18
22
  manifestPath?: string;
23
+ /** Persistent worker to reuse. Only `bun widget connect`'s watch loop passes one. */
24
+ session?: IntrospectSession;
19
25
  }): Promise<void>;
20
26
  /**
21
27
  * Packages provided by the host import map — widgets must not bundle these.
@@ -74,6 +80,8 @@ export interface BuildWidgetsOptions {
74
80
  viteConfig?: InlineConfig;
75
81
  /** Build only these widget names (subdirectory names). Skips full clean. */
76
82
  only?: string[];
83
+ /** Persistent introspection worker to reuse across rebuilds. */
84
+ session?: IntrospectSession;
77
85
  }
78
86
  /**
79
87
  * Build each widget as a separate Vite invocation so shared code is inlined
@@ -103,4 +111,3 @@ export interface GlasshomeWidgetsOptions {
103
111
  * default build, and `closeBundle()` runs the actual per-widget builds.
104
112
  */
105
113
  export declare function glasshomeWidgets(options?: GlasshomeWidgetsOptions): Plugin[];
106
- export {};
@@ -1,19 +1,193 @@
1
1
  import { deprecations as e, formatDeprecation as t } from "../deprecations.js";
2
2
  import { formatSchemaError as n, isHostProvidedModule as r, widgetManifestSchema as i } from "@glasshome/widget-contract";
3
- import { spawnSync as a } from "node:child_process";
4
- import { createHash as o } from "node:crypto";
5
- import { existsSync as s, mkdirSync as c, readFileSync as l, readdirSync as u, rmSync as d, statSync as f, writeFileSync as p } from "node:fs";
6
- import { createRequire as m } from "node:module";
7
- import { dirname as h, join as g, relative as _, resolve as v, sep as y } from "node:path";
8
- import { fileURLToPath as b } from "node:url";
9
- import x from "@tailwindcss/vite";
3
+ import { createHash as a } from "node:crypto";
4
+ import { existsSync as o, mkdirSync as s, readFileSync as c, readdirSync as l, rmSync as u, statSync as d, writeFileSync as f } from "node:fs";
5
+ import { createRequire as p } from "node:module";
6
+ import { dirname as m, join as h, relative as g, resolve as _, sep as v } from "node:path";
7
+ import { fileURLToPath as y } from "node:url";
8
+ import ee from "@tailwindcss/vite";
9
+ import { spawn as te, spawnSync as b } from "node:child_process";
10
+ //#region src/vite/introspect-session.ts
11
+ var ne = 262144e3, x = 3e4, S = 3e3;
12
+ function C(e) {
13
+ let t = m(y(import.meta.url));
14
+ return [h(t, `${e}.js`), h(t, `${e}.ts`)].find((e) => o(e)) ?? null;
15
+ }
16
+ function w(e) {
17
+ let t = C("introspect");
18
+ if (!t) return {
19
+ ok: !1,
20
+ reason: "introspect not found next to the sdk"
21
+ };
22
+ let n = b(process.execPath, [
23
+ "--conditions",
24
+ "browser",
25
+ t,
26
+ e
27
+ ], {
28
+ encoding: "utf-8",
29
+ timeout: x
30
+ });
31
+ if (n.error) return {
32
+ ok: !1,
33
+ reason: n.error.message
34
+ };
35
+ if (n.status !== 0) return {
36
+ ok: !1,
37
+ reason: (n.stderr || "").trim().split("\n")[0] || `exit ${n.status}`
38
+ };
39
+ try {
40
+ return {
41
+ ok: !0,
42
+ value: JSON.parse(n.stdout)
43
+ };
44
+ } catch {
45
+ return {
46
+ ok: !1,
47
+ reason: "introspect returned unparseable output"
48
+ };
49
+ }
50
+ }
51
+ function re() {
52
+ return {
53
+ introspect: async (e) => w(e),
54
+ dispose: async () => {},
55
+ childPid: null
56
+ };
57
+ }
58
+ function ie(e) {
59
+ if (process.env.GLASSHOME_WIDGET_NO_WORKER === "1") return re();
60
+ let t = e?.rssLimitBytes ?? ne, n = e?.timeoutMs ?? x, r = e?.slowMs ?? S, i = null, a = null, o = !1, s = !1, c = 0, l = Promise.resolve(), u = /* @__PURE__ */ new Map();
61
+ function d(e) {
62
+ for (let t of u.values()) t({
63
+ ok: !1,
64
+ reason: e
65
+ });
66
+ u.clear();
67
+ }
68
+ function f(e) {
69
+ let t = i;
70
+ i = null, a = null, o = !1, d(e), t?.kill();
71
+ }
72
+ function p(e) {
73
+ let n;
74
+ try {
75
+ n = JSON.parse(e);
76
+ } catch {
77
+ return;
78
+ }
79
+ if (typeof n.id != "number") return;
80
+ let i = u.get(n.id);
81
+ i && (u.delete(n.id), (n.rss ?? 0) > t && (o = !0), (n.importMs ?? 0) > r && console.warn(`[widget-sdk] introspection took ${n.importMs}ms`), i(n.ok ? {
82
+ ok: !0,
83
+ value: n.value
84
+ } : {
85
+ ok: !1,
86
+ reason: n.reason ?? "introspection failed"
87
+ }));
88
+ }
89
+ function m() {
90
+ if (i && a && !o) return a;
91
+ (o || i) && f("worker was replaced");
92
+ let e = C("introspect-worker");
93
+ if (!e) return Promise.reject(/* @__PURE__ */ Error("introspect-worker not found next to the sdk"));
94
+ let t = Date.now(), n = te(process.execPath, [
95
+ "--conditions",
96
+ "browser",
97
+ e
98
+ ], { stdio: [
99
+ "pipe",
100
+ "pipe",
101
+ "inherit"
102
+ ] });
103
+ i = n;
104
+ let s = new Promise((e, o) => {
105
+ let s = "", c = !1;
106
+ n.stdout?.setEncoding("utf-8"), n.stdout?.on("data", (n) => {
107
+ s += n;
108
+ let i = s.indexOf("\n");
109
+ for (; i >= 0;) {
110
+ let n = s.slice(0, i).trim();
111
+ if (s = s.slice(i + 1), n) {
112
+ if (!c && n.includes("\"ready\"")) {
113
+ c = !0;
114
+ let n = Date.now() - t;
115
+ n > r && console.warn(`[widget-sdk] introspect worker took ${n}ms to boot`), e();
116
+ } else p(n);
117
+ }
118
+ i = s.indexOf("\n");
119
+ }
120
+ });
121
+ let l = (e) => {
122
+ i === n && (i = null, a = null), d(e), c || o(Error(e));
123
+ };
124
+ n.on("error", (e) => l(e.message)), n.on("exit", (e, t) => l(`introspect worker exited (${t ?? `code ${e}`})`));
125
+ });
126
+ return a = s, s;
127
+ }
128
+ async function h(e) {
129
+ if (s) return {
130
+ ok: !1,
131
+ reason: "introspect session disposed"
132
+ };
133
+ try {
134
+ await m();
135
+ } catch (e) {
136
+ return {
137
+ ok: !1,
138
+ reason: e instanceof Error ? e.message : String(e)
139
+ };
140
+ }
141
+ let t = i;
142
+ if (!t?.stdin?.writable) return {
143
+ ok: !1,
144
+ reason: "introspect worker is not writable"
145
+ };
146
+ let r = ++c;
147
+ return new Promise((i) => {
148
+ let a = setTimeout(() => {
149
+ u.delete(r), f(`introspection timed out after ${n}ms`), i({
150
+ ok: !1,
151
+ reason: `introspection timed out after ${n}ms`
152
+ });
153
+ }, n);
154
+ u.set(r, (e) => {
155
+ clearTimeout(a), i(e);
156
+ });
157
+ try {
158
+ t.stdin?.write(`${JSON.stringify({
159
+ id: r,
160
+ outFile: e
161
+ })}\n`);
162
+ } catch (e) {
163
+ clearTimeout(a), u.delete(r), o = !0, i({
164
+ ok: !1,
165
+ reason: e instanceof Error ? e.message : String(e)
166
+ });
167
+ }
168
+ });
169
+ }
170
+ return {
171
+ introspect(e) {
172
+ let t = l.then(() => h(e));
173
+ return l = t.catch(() => void 0), t;
174
+ },
175
+ async dispose() {
176
+ s = !0, f("introspect session disposed");
177
+ },
178
+ get childPid() {
179
+ return i?.pid ?? null;
180
+ }
181
+ };
182
+ }
183
+ //#endregion
10
184
  //#region src/vite/index.ts
11
- function S(e) {
185
+ function T(e) {
12
186
  let t = JSON.stringify(e, Object.keys(e).sort());
13
- return o("sha256").update(t).digest("hex").slice(0, 16);
187
+ return a("sha256").update(t).digest("hex").slice(0, 16);
14
188
  }
15
- var C = 20;
16
- async function w(e, t) {
189
+ var E = 20;
190
+ async function D(e, t) {
17
191
  if (e === void 0) return;
18
192
  let n;
19
193
  try {
@@ -33,7 +207,7 @@ async function w(e, t) {
33
207
  let a = i.error.issues.map((e) => ` examples${e.path.length ? `[${e.path.join("][")}]` : ""}: ${e.message}`).join("\n");
34
208
  throw Error(`[widget-sdk] Invalid \`examples\` for "${t}":\n${a}\nEach example needs a \`config\` object and a \`size\` of whole positive tiles, and at most 20 are rendered.`);
35
209
  }
36
- function T(e, t) {
210
+ function O(e, t) {
37
211
  if (!e?.length) return;
38
212
  let n = e.map((e) => {
39
213
  let t = e.label ? `"${e.label}"` : `#${e.index}`;
@@ -41,42 +215,7 @@ function T(e, t) {
41
215
  }).join("\n");
42
216
  throw Error(`[widget-sdk] Example config rejected by "${t}"'s own configSchema:\n${n}\nEach example's \`config\` must be a config the widget would actually accept, or its preview renders empty.`);
43
217
  }
44
- function E(e) {
45
- let t = h(b(import.meta.url)), n = [g(t, "introspect.js"), g(t, "introspect.ts")].find((e) => s(e));
46
- if (!n) return {
47
- ok: !1,
48
- reason: `introspect not found next to ${t}`
49
- };
50
- let r = a(process.execPath, [
51
- "--conditions",
52
- "browser",
53
- n,
54
- e
55
- ], {
56
- encoding: "utf-8",
57
- timeout: 3e4
58
- });
59
- if (r.error) return {
60
- ok: !1,
61
- reason: r.error.message
62
- };
63
- if (r.status !== 0) return {
64
- ok: !1,
65
- reason: (r.stderr || "").trim().split("\n")[0] || `exit ${r.status}`
66
- };
67
- try {
68
- return {
69
- ok: !0,
70
- value: JSON.parse(r.stdout)
71
- };
72
- } catch {
73
- return {
74
- ok: !1,
75
- reason: "introspect returned unparseable output"
76
- };
77
- }
78
- }
79
- var D = [
218
+ var k = [
80
219
  "schema",
81
220
  "defaultConfig",
82
221
  "examples",
@@ -89,80 +228,80 @@ var D = [
89
228
  "minSize",
90
229
  "maxSize"
91
230
  ];
92
- function O(e, t, n) {
93
- let r = l(e, "utf-8"), i = JSON.parse(r), a = {
231
+ function A(e, t, n) {
232
+ let r = c(e, "utf-8"), i = JSON.parse(r), a = {
94
233
  ...t.manifest ?? {},
95
234
  schema: n
96
235
  };
97
- for (let e of D) a[e] !== void 0 && (i[e] = a[e]);
236
+ for (let e of k) a[e] !== void 0 && (i[e] = a[e]);
98
237
  let o = `${JSON.stringify(i, null, 2)}\n`;
99
- o !== r && p(e, o);
238
+ o !== r && f(e, o);
100
239
  }
101
- async function k(e) {
102
- if (!s(e.outFile)) return;
103
- let t = E(e.outFile);
240
+ async function j(e) {
241
+ if (!o(e.outFile)) return;
242
+ let t = e.session ? await e.session.introspect(e.outFile) : w(e.outFile);
104
243
  if (!t.ok) throw Error(`[widget-sdk] Could not read "${e.widgetName}" to check it: ${t.reason}\n Its examples and config schema could not be validated, so the build cannot continue.
105
244
  This usually means the bundle throws while being imported.`);
106
245
  let n = t.value, r = n.manifest?.name ?? e.widgetName;
107
- await w(n.manifest?.examples, r), T(n.exampleConfigIssues, r);
246
+ await D(n.manifest?.examples, r), O(n.exampleConfigIssues, r);
108
247
  let i = n.jsonSchema;
109
248
  if (!i) return;
110
- e.manifestPath && s(e.manifestPath) && O(e.manifestPath, n, i);
111
- let a = S(i), o = n.manifest?.configVersion ?? null, c = n.manifest?.name ?? e.widgetName;
112
- if (s(e.hashFile)) {
113
- let t = l(e.hashFile, "utf-8").trim(), n = null;
249
+ e.manifestPath && o(e.manifestPath) && A(e.manifestPath, n, i);
250
+ let a = T(i), s = n.manifest?.configVersion ?? null, l = n.manifest?.name ?? e.widgetName;
251
+ if (o(e.hashFile)) {
252
+ let t = c(e.hashFile, "utf-8").trim(), n = null;
114
253
  if (t.startsWith("{")) try {
115
254
  n = JSON.parse(t);
116
255
  } catch {
117
256
  n = null;
118
257
  }
119
258
  if (n) {
120
- if (n.hash !== a && n.configVersion === o) throw Error(`[widget-sdk] Config schema shape changed for "${c}" without a configVersion bump. Bump configVersion in defineWidget (currently ${o ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
121
- } else t !== a && console.warn(`[widget-sdk] Schema shape changed for "${c}" — verify configVersion was bumped`);
259
+ if (n.hash !== a && n.configVersion === s) throw Error(`[widget-sdk] Config schema shape changed for "${l}" without a configVersion bump. Bump configVersion in defineWidget (currently ${s ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
260
+ } else t !== a && console.warn(`[widget-sdk] Schema shape changed for "${l}" — verify configVersion was bumped`);
122
261
  }
123
- p(e.hashFile, `${JSON.stringify({
262
+ f(e.hashFile, `${JSON.stringify({
124
263
  hash: a,
125
- configVersion: o
264
+ configVersion: s
126
265
  })}\n`);
127
266
  }
128
- var A = "virtual:glasshome-widget", j = "\0virtual:glasshome-widget", M = "/@glasshome/preview";
129
- function N(e) {
130
- return y === "\\" ? e.split(y).join("/") : e;
267
+ var ae = "virtual:glasshome-widget", M = "\0virtual:glasshome-widget", N = "/@glasshome/preview";
268
+ function P(e) {
269
+ return v === "\\" ? e.split(v).join("/") : e;
131
270
  }
132
- function P() {
133
- let e = b(import.meta.url);
134
- return v(h(e), "../../preview");
271
+ function F() {
272
+ let e = y(import.meta.url);
273
+ return _(m(e), "../../preview");
135
274
  }
136
- function F(e) {
275
+ function I(e) {
137
276
  return r(e);
138
277
  }
139
- var I = /\.(ts|tsx|js|jsx)$/, L = /from\s+["']@glasshome\/sync-layer(\/[^"']*)?["']/;
140
- function R(e, t) {
141
- let n = N(e).split("?")[0] ?? "";
142
- return n.includes("/node_modules/") || !I.test(n) ? !1 : L.test(t);
278
+ var L = /\.(ts|tsx|js|jsx)$/, R = /from\s+["']@glasshome\/sync-layer(\/[^"']*)?["']/;
279
+ function z(e, t) {
280
+ let n = P(e).split("?")[0] ?? "";
281
+ return n.includes("/node_modules/") || !L.test(n) ? !1 : R.test(t);
143
282
  }
144
- function z() {
283
+ function B() {
145
284
  return {
146
285
  name: "glasshome-widget:sync-layer-guard",
147
286
  apply: "build",
148
287
  enforce: "pre",
149
288
  transform(e, t) {
150
- if (R(t, e)) throw Error(`Widgets must not import "@glasshome/sync-layer" directly (imported by ${N(_(process.cwd(), t.split("?")[0] ?? t))}). Import the equivalent hook from "@glasshome/widget-sdk" instead (e.g. useEntity, useEntities, useService).`);
289
+ if (z(t, e)) throw Error(`Widgets must not import "@glasshome/sync-layer" directly (imported by ${P(g(process.cwd(), t.split("?")[0] ?? t))}). Import the equivalent hook from "@glasshome/widget-sdk" instead (e.g. useEntity, useEntities, useService).`);
151
290
  return null;
152
291
  }
153
292
  };
154
293
  }
155
- function B(t, n) {
156
- let r = N(t).split("?")[0] ?? "";
157
- if (r.includes("/node_modules/") || !I.test(r)) return !1;
294
+ function V(t, n) {
295
+ let r = P(t).split("?")[0] ?? "";
296
+ if (r.includes("/node_modules/") || !L.test(r)) return !1;
158
297
  let i = e.find((e) => e.id === "direct-ui-import");
159
298
  return i?.sourcePattern ? new RegExp(i.sourcePattern).test(n) : !1;
160
299
  }
161
- function V(e = /* @__PURE__ */ new Set()) {
300
+ function H(e = /* @__PURE__ */ new Set()) {
162
301
  let t = /* @__PURE__ */ new Set();
163
302
  return {
164
303
  scan(e, n) {
165
- B(e, n) && t.add(N(_(process.cwd(), e.split("?")[0] ?? e)));
304
+ V(e, n) && t.add(P(g(process.cwd(), e.split("?")[0] ?? e)));
166
305
  },
167
306
  take() {
168
307
  let n = [...t].filter((t) => !e.has(t));
@@ -171,8 +310,8 @@ function V(e = /* @__PURE__ */ new Set()) {
171
310
  }
172
311
  };
173
312
  }
174
- function H(n) {
175
- let r = V(n);
313
+ function U(n) {
314
+ let r = H(n);
176
315
  return {
177
316
  name: "glasshome-widget:ui-import-guard",
178
317
  apply: "build",
@@ -188,15 +327,15 @@ function H(n) {
188
327
  }
189
328
  };
190
329
  }
191
- var U = "node_modules/.cache/glasshome-widgets";
192
- function W(e) {
330
+ var W = "node_modules/.cache/glasshome-widgets";
331
+ function G(e) {
193
332
  try {
194
- return m(g(process.cwd(), "package.json")).resolve(e);
333
+ return p(h(process.cwd(), "package.json")).resolve(e);
195
334
  } catch {
196
335
  return null;
197
336
  }
198
337
  }
199
- var G = [
338
+ var K = [
200
339
  "--radius",
201
340
  "--background",
202
341
  "--foreground",
@@ -206,74 +345,78 @@ var G = [
206
345
  "--muted",
207
346
  "--accent"
208
347
  ];
209
- function K(e, t) {
210
- let n = g(e, `${t}.css`);
211
- if (!s(n)) return;
212
- let r = l(n, "utf-8");
348
+ function q(e, t) {
349
+ let n = h(e, `${t}.css`);
350
+ if (!o(n)) return;
351
+ let r = c(n, "utf-8");
213
352
  for (let e of r.matchAll(/:host[^{]*\{([^}]*)\}/g)) {
214
- let n = e[1] ?? "", r = G.find((e) => n.includes(`${e}:`));
353
+ let n = e[1] ?? "", r = K.find((e) => n.includes(`${e}:`));
215
354
  if (r) throw Error(`[glasshome-widget] "${t}.css" defines theme variable "${r}" on :host. Theme variables must inherit from the document so widgets follow the household theme — never re-scope them into the widget's shadow root.`);
216
355
  }
217
356
  }
218
- function q(e, t, n) {
219
- let r = v(e, U);
220
- c(r, { recursive: !0 });
221
- let i = W("@glasshome/ui/styles");
357
+ function J(e, t, n) {
358
+ let r = _(e, W);
359
+ s(r, { recursive: !0 });
360
+ let i = G("@glasshome/ui/styles");
222
361
  if (!i) throw Error("[widget-sdk] @glasshome/ui is required to build widgets (it provides the theme every GlassHome widget ships with). Add it to your widget's dependencies.");
223
- let a = v(h(i), "../.."), o = W("@glasshome/widget-sdk/tailwind-sources"), l = o ? h(o) : null, u = [
224
- g(a, "src"),
225
- g(a, "dist"),
226
- ...l ? [g(l, "src"), g(l, "dist")] : [],
227
- h(n)
228
- ].filter((e) => s(e)), d = v(r, `${t}.tailwind.css`);
229
- p(d, [
362
+ let a = _(m(i), "../.."), c = G("@glasshome/widget-sdk/tailwind-sources"), l = c ? m(c) : null, u = [
363
+ h(a, "src"),
364
+ h(a, "dist"),
365
+ ...l ? [h(l, "src"), h(l, "dist")] : [],
366
+ m(n)
367
+ ].filter((e) => o(e)), d = _(r, `${t}.tailwind.css`);
368
+ f(d, [
230
369
  "@import \"tailwindcss\" source(none);",
231
370
  "@import \"tw-animate-css\";",
232
371
  "@import \"@glasshome/ui/styles/theme\";",
233
- ...u.map((e) => `@source "${N(e)}";`),
372
+ ...u.map((e) => `@source "${P(e)}";`),
234
373
  "@custom-variant dark {",
235
374
  " &:is(.dark *) { @slot }",
236
375
  " :host(.dark) & { @slot }",
237
376
  "}",
238
377
  ""
239
378
  ].join("\n"));
240
- let f = v(r, `${t}.entry.ts`);
241
- return p(f, [
242
- `import "${N(d)}";`,
243
- `export { default } from "${N(n)}";`,
379
+ let p = _(r, `${t}.entry.ts`);
380
+ return f(p, [
381
+ `import "${P(d)}";`,
382
+ `export { default } from "${P(n)}";`,
244
383
  ""
245
- ].join("\n")), f;
384
+ ].join("\n")), p;
385
+ }
386
+ function Y() {
387
+ let { Features: e } = p(import.meta.url)("lightningcss");
388
+ return { lightningcss: { include: e.HexAlphaColors } };
246
389
  }
247
- function J() {
248
- let e = x();
390
+ function X() {
391
+ let e = ee();
249
392
  for (let t of e) t.apply ??= "build";
250
393
  return e;
251
394
  }
252
- function Y(e) {
395
+ function Z(e) {
253
396
  let t = [];
254
- if (!s(e)) return t;
255
- for (let n of u(e)) {
256
- let r = v(e, n);
257
- if (!f(r).isDirectory()) continue;
258
- let i = v(r, "index.tsx"), a = v(r, "manifest.json");
259
- s(i) && s(a) && t.push({
397
+ if (!o(e)) return t;
398
+ for (let n of l(e)) {
399
+ let r = _(e, n);
400
+ if (!d(r).isDirectory()) continue;
401
+ let i = _(r, "index.tsx"), a = _(r, "manifest.json");
402
+ o(i) && o(a) && t.push({
260
403
  name: n,
261
404
  entry: i
262
405
  });
263
406
  }
264
407
  return t;
265
408
  }
266
- function X(e, t) {
409
+ function Q(e, t) {
267
410
  let r = [];
268
- if (s(e)) for (let a of u(e)) {
269
- if (!f(g(e, a)).isDirectory()) continue;
270
- let o = g(e, a, "manifest.json");
271
- if (!s(o)) continue;
272
- let c = i.safeParse(JSON.parse(l(o, "utf-8")));
273
- if (!c.success) throw Error(`[registry] Invalid manifest for "${a}": ${n(c.error)}`);
274
- let u = s(g(t, `${a}.css`));
411
+ if (o(e)) for (let a of l(e)) {
412
+ if (!d(h(e, a)).isDirectory()) continue;
413
+ let s = h(e, a, "manifest.json");
414
+ if (!o(s)) continue;
415
+ let l = i.safeParse(JSON.parse(c(s, "utf-8")));
416
+ if (!l.success) throw Error(`[registry] Invalid manifest for "${a}": ${n(l.error)}`);
417
+ let u = o(h(t, `${a}.css`));
275
418
  r.push({
276
- ...c.data,
419
+ ...l.data,
277
420
  bundleUrl: `./${a}.js`,
278
421
  ...u ? { cssUrl: `./${a}.css` } : {}
279
422
  });
@@ -284,10 +427,10 @@ function X(e, t) {
284
427
  baseUrl: "./",
285
428
  widgets: r
286
429
  };
287
- s(t) || c(t, { recursive: !0 }), p(g(t, "registry.json"), JSON.stringify(a, null, 2)), console.log(`[registry] Generated registry.json with ${r.length} widget(s)`);
430
+ o(t) || s(t, { recursive: !0 }), f(h(t, "registry.json"), JSON.stringify(a, null, 2)), console.log(`[registry] Generated registry.json with ${r.length} widget(s)`);
288
431
  }
289
- async function Z(e) {
290
- let { build: t } = await import("vite"), n = process.cwd(), r = v(n, e?.srcDir ?? "src"), i = v(n, e?.outDir ?? "dist"), a = Y(r);
432
+ async function $(e) {
433
+ let { build: t } = await import("vite"), n = process.cwd(), r = _(n, e?.srcDir ?? "src"), i = _(n, e?.outDir ?? "dist"), a = Z(r);
291
434
  if (a.length === 0) {
292
435
  console.warn("[glasshome-widgets] No widgets found in", r);
293
436
  return;
@@ -296,73 +439,81 @@ async function Z(e) {
296
439
  let t = new Set(e.only);
297
440
  a = a.filter((e) => t.has(e.name));
298
441
  }
299
- e?.only || s(i) && d(i, { recursive: !0 }), s(i) || c(i, { recursive: !0 });
300
- let o = /* @__PURE__ */ new Set();
442
+ e?.only || o(i) && u(i, { recursive: !0 }), o(i) || s(i, { recursive: !0 });
443
+ let c = /* @__PURE__ */ new Set();
301
444
  for (let r of a) await t({
302
445
  configFile: !1,
303
446
  root: n,
304
447
  plugins: [
305
- ...J(),
448
+ ...X(),
306
449
  ...e?.plugins ?? [],
307
- z(),
308
- H(o)
450
+ B(),
451
+ U(c)
309
452
  ],
310
453
  ...e?.viteConfig,
454
+ css: {
455
+ ...e?.viteConfig?.css,
456
+ ...Y()
457
+ },
311
458
  build: {
312
459
  lib: {
313
- entry: q(n, r.name, r.entry),
460
+ entry: J(n, r.name, r.entry),
314
461
  formats: ["es"],
315
462
  fileName: r.name,
316
463
  cssFileName: r.name
317
464
  },
318
- rollupOptions: { external: F },
465
+ rollupOptions: { external: I },
319
466
  outDir: i,
320
467
  emptyOutDir: !1,
321
468
  copyPublicDir: !1,
322
469
  ...e?.viteConfig?.build
323
470
  },
324
471
  logLevel: "warn"
325
- }), K(i, r.name), await k({
326
- outFile: g(i, `${r.name}.js`),
327
- hashFile: g(h(r.entry), ".schema-hash"),
472
+ }), q(i, r.name), await j({
473
+ outFile: h(i, `${r.name}.js`),
474
+ hashFile: h(m(r.entry), ".schema-hash"),
328
475
  widgetName: r.name,
329
- manifestPath: g(h(r.entry), "manifest.json")
476
+ manifestPath: h(m(r.entry), "manifest.json"),
477
+ session: e?.session
330
478
  });
331
- X(r, i);
479
+ Q(r, i);
332
480
  }
333
- function Q(e) {
481
+ function oe(e) {
334
482
  let t = e?.entry ?? "src/index.tsx", n = {
335
483
  name: "glasshome-widget:build",
336
484
  apply: "build",
337
485
  config() {
338
486
  let e = process.cwd();
339
- return { build: {
340
- lib: {
341
- entry: q(e, "index", v(e, t)),
342
- formats: ["es"],
343
- fileName: "index",
344
- cssFileName: "index"
345
- },
346
- rollupOptions: { external: F }
347
- } };
487
+ return {
488
+ css: Y(),
489
+ build: {
490
+ lib: {
491
+ entry: J(e, "index", _(e, t)),
492
+ formats: ["es"],
493
+ fileName: "index",
494
+ cssFileName: "index"
495
+ },
496
+ rollupOptions: { external: I }
497
+ }
498
+ };
348
499
  }
349
500
  }, r = {
350
501
  name: "glasshome-widget:dev",
351
502
  apply: "serve",
352
503
  enforce: "pre",
353
504
  config() {
354
- let e = P(), t = process.cwd();
505
+ let e = F(), t = process.cwd();
355
506
  return { server: { fs: { allow: [
356
507
  e,
357
508
  t,
358
- v(t, "..", "..")
509
+ _(t, "..", "..")
359
510
  ] } } };
360
511
  },
361
512
  configureServer(e) {
362
- let t = P(), n = g(t, "preview.html");
513
+ let t = F(), n = h(t, "preview.html");
363
514
  e.middlewares.use(async (t, r, i) => {
364
515
  if (t.url === "/" || t.url === "/index.html") try {
365
- let i = l(n, "utf-8");
516
+ let i = c(n, "utf-8");
366
517
  i = await e.transformIndexHtml(t.url, i), r.setHeader("Content-Type", "text/html"), r.statusCode = 200, r.end(i);
367
518
  } catch (e) {
368
519
  i(e);
@@ -371,37 +522,37 @@ function Q(e) {
371
522
  });
372
523
  },
373
524
  resolveId(e) {
374
- if (e === M) {
375
- let e = P();
376
- return N(g(e, "host.tsx"));
525
+ if (e === N) {
526
+ let e = F();
527
+ return P(h(e, "host.tsx"));
377
528
  }
378
- if (e === A) return j;
529
+ if (e === ae) return M;
379
530
  },
380
531
  load(e) {
381
- if (e === j) return `export { default } from "${N(v(process.cwd(), t))}";`;
532
+ if (e === M) return `export { default } from "${P(_(process.cwd(), t))}";`;
382
533
  }
383
534
  }, i = {
384
535
  name: "glasshome-widget:schema",
385
536
  apply: "build",
386
537
  async closeBundle() {
387
- K(v(process.cwd(), "dist"), "index"), await k({
388
- outFile: v(process.cwd(), "dist", "index.js"),
389
- hashFile: v(process.cwd(), ".schema-hash"),
538
+ q(_(process.cwd(), "dist"), "index"), await j({
539
+ outFile: _(process.cwd(), "dist", "index.js"),
540
+ hashFile: _(process.cwd(), ".schema-hash"),
390
541
  widgetName: "index",
391
- manifestPath: v(process.cwd(), "manifest.json")
542
+ manifestPath: _(process.cwd(), "manifest.json")
392
543
  });
393
544
  }
394
545
  };
395
546
  return [
396
- ...J(),
547
+ ...X(),
397
548
  n,
398
- z(),
399
- H(),
549
+ B(),
550
+ U(),
400
551
  i,
401
552
  r
402
553
  ];
403
554
  }
404
- function $(e) {
555
+ function se(e) {
405
556
  let t = e?.srcDir ?? "src", n = e?.outDir ?? "dist", r = [];
406
557
  return [{
407
558
  name: "glasshome-widgets:build",
@@ -420,7 +571,7 @@ function $(e) {
420
571
  if (e === "\0glasshome-noop") return "export {}";
421
572
  },
422
573
  async closeBundle() {
423
- await Z({
574
+ await $({
424
575
  srcDir: t,
425
576
  outDir: n,
426
577
  plugins: r
@@ -429,4 +580,4 @@ function $(e) {
429
580
  }];
430
581
  }
431
582
  //#endregion
432
- export { C as MAX_EXAMPLES, Z as buildWidgets, V as createUiImportTracker, Y as discoverWidgets, X as generateRegistry, Q as glasshomeWidget, $ as glasshomeWidgets, R as isDirectSyncLayerImportSource, B as isDirectUiImportSource, F as isWidgetExternal, k as runSchemaGuard };
583
+ export { E as MAX_EXAMPLES, $ as buildWidgets, ie as createIntrospectSession, H as createUiImportTracker, Z as discoverWidgets, Q as generateRegistry, oe as glasshomeWidget, se as glasshomeWidgets, w as introspectOnce, z as isDirectSyncLayerImportSource, V as isDirectUiImportSource, I as isWidgetExternal, j as runSchemaGuard };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Reads a built widget bundle's definition under a fake DOM.
3
+ *
4
+ * A widget bundle cannot be imported by the build process: `solid-js/web`
5
+ * resolves to its server build under Node's conditions and throws the moment
6
+ * the module initialises, and browser conditions then need a DOM for the
7
+ * templates Solid creates at import time. Both callers (the one-shot
8
+ * `introspect.ts` CLI and the persistent `introspect-worker.ts`) run under
9
+ * `--conditions browser` and install a DOM before importing.
10
+ */
11
+ export interface ExampleConfigIssue {
12
+ index: number;
13
+ label?: string;
14
+ problems: string[];
15
+ }
16
+ export interface WidgetIntrospection {
17
+ manifest: ({
18
+ name?: string;
19
+ configVersion?: number;
20
+ examples?: unknown;
21
+ } & Record<string, unknown>) | null;
22
+ /** JSON Schema derived from the widget's configSchema, when it has one. */
23
+ jsonSchema: object | null;
24
+ /** Examples whose config the widget's own configSchema rejects. */
25
+ exampleConfigIssues: ExampleConfigIssue[];
26
+ }
27
+ /**
28
+ * Install a fresh DOM. Unconditional: the persistent worker calls this per
29
+ * request, and skipping already-set keys would leak the previous widget's
30
+ * mutated window into the next one.
31
+ */
32
+ export declare function installDom(): Promise<void>;
33
+ export declare function introspectBundle(bundlePath: string, cacheBust?: string): Promise<WidgetIntrospection>;
@@ -0,0 +1,30 @@
1
+ import type { WidgetIntrospection } from "./introspect-core";
2
+ export type IntrospectResult = {
3
+ ok: true;
4
+ value: WidgetIntrospection;
5
+ } | {
6
+ ok: false;
7
+ reason: string;
8
+ };
9
+ export interface IntrospectSessionOptions {
10
+ /** Respawn once the worker's RSS crosses this, between requests only. */
11
+ rssLimitBytes?: number;
12
+ /** Per-request ceiling; a module-eval hang can only be cured by a kill. */
13
+ timeoutMs?: number;
14
+ /** Log spawn and import durations above this. */
15
+ slowMs?: number;
16
+ }
17
+ export interface IntrospectSession {
18
+ introspect(outFile: string): Promise<IntrospectResult>;
19
+ dispose(): Promise<void>;
20
+ readonly childPid: number | null;
21
+ }
22
+ /** One process per check. The path `build`, `capture` and the fallback take. */
23
+ export declare function introspectOnce(outFile: string): IntrospectResult;
24
+ /**
25
+ * A worker reused across rebuilds, for callers that own a session.
26
+ *
27
+ * Only `bun widget connect`'s watch loop does: it is the one call site that
28
+ * re-pays the per-check process boot tax on every save.
29
+ */
30
+ export declare function createIntrospectSession(options?: IntrospectSessionOptions): IntrospectSession;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Long-lived introspection worker for `bun widget connect`.
3
+ *
4
+ * Newline-delimited JSON over stdio. Every request carries an id: a widget
5
+ * bundle can print at module-eval time, and without id pairing one stray line
6
+ * offsets every later response.
7
+ *
8
+ * Usage: bun --conditions browser introspect-worker.js
9
+ */
10
+ export {};
@@ -0,0 +1,76 @@
1
+ import { n as e, t } from "../introspect-core-DYv9Uufi.js";
2
+ //#region src/vite/introspect-worker.ts
3
+ var n = process.stdout.write.bind(process.stdout);
4
+ process.stdout.write = ((e, ...t) => process.stderr.write(e, ...t));
5
+ function r(e) {
6
+ n(`${JSON.stringify(e)}\n`);
7
+ }
8
+ function i(e) {
9
+ return e instanceof Error ? e.message : String(e);
10
+ }
11
+ var a = null, o = 0, s = Promise.resolve();
12
+ async function c(n, s) {
13
+ a = n;
14
+ let c = performance.now();
15
+ try {
16
+ await t(), r({
17
+ id: n,
18
+ ok: !0,
19
+ value: await e(s, String(++o)),
20
+ rss: process.memoryUsage().rss,
21
+ importMs: Math.round(performance.now() - c)
22
+ });
23
+ } catch (e) {
24
+ r({
25
+ id: n,
26
+ ok: !1,
27
+ reason: i(e),
28
+ rss: process.memoryUsage().rss,
29
+ importMs: Math.round(performance.now() - c)
30
+ });
31
+ } finally {
32
+ a = null;
33
+ }
34
+ }
35
+ function l(e) {
36
+ let t;
37
+ try {
38
+ t = JSON.parse(e);
39
+ } catch {
40
+ return;
41
+ }
42
+ if (typeof t.id != "number" || typeof t.outFile != "string") return;
43
+ let { id: n, outFile: r } = t;
44
+ s = s.then(() => c(n, r));
45
+ }
46
+ function u(e) {
47
+ a !== null && r({
48
+ id: a,
49
+ ok: !1,
50
+ reason: i(e),
51
+ rss: process.memoryUsage().rss,
52
+ importMs: 0
53
+ }), process.exit(1);
54
+ }
55
+ process.on("uncaughtException", u), process.on("unhandledRejection", u);
56
+ var d = "";
57
+ process.stdin.setEncoding("utf-8"), process.stdin.on("data", (e) => {
58
+ d += e;
59
+ let t = d.indexOf("\n");
60
+ for (; t >= 0;) {
61
+ let e = d.slice(0, t).trim();
62
+ d = d.slice(t + 1), e && l(e), t = d.indexOf("\n");
63
+ }
64
+ }), process.stdin.on("end", () => process.exit(0)), process.stdin.on("close", () => process.exit(0)), process.stdin.resume();
65
+ var f = process.ppid;
66
+ setInterval(() => {
67
+ try {
68
+ process.kill(f, 0);
69
+ } catch {
70
+ process.exit(0);
71
+ }
72
+ }, 5e3), r({
73
+ id: 0,
74
+ ready: !0
75
+ });
76
+ //#endregion
@@ -1,21 +1,12 @@
1
1
  /**
2
2
  * Reads a built widget bundle's definition and prints it as JSON.
3
3
  *
4
- * Runs as its own process because a widget bundle cannot be imported by the
5
- * build: `solid-js/web` resolves to its server build under Node's conditions
6
- * and throws "Client-only API called on the server side" the moment the module
7
- * initialises, and forcing browser conditions then needs a DOM for the
8
- * templates Solid creates at import time. The build spawns this with
9
- * `--conditions browser` and a DOM in place, so the import succeeds.
10
- *
11
- * Until this existed the guard swallowed that import failure and silently did
12
- * nothing — for every widget, on every build.
4
+ * One-shot counterpart to introspect-worker.ts: a fresh process per check, used
5
+ * by `bun widget build`, the render worker's capture, and as the
6
+ * GLASSHOME_WIDGET_NO_WORKER fallback.
13
7
  *
14
8
  * Usage: bun --conditions browser introspect.js <bundle.js>
15
- * Prints: { manifest, jsonSchema } on stdout, or exits non-zero with a reason.
9
+ * Prints: { manifest, jsonSchema, exampleConfigIssues } on stdout, or exits
10
+ * non-zero with a reason.
16
11
  */
17
- export interface ExampleConfigIssue {
18
- index: number;
19
- label?: string;
20
- problems: string[];
21
- }
12
+ export {};
@@ -1,60 +1,10 @@
1
- import { pathToFileURL as e } from "node:url";
1
+ import { n as e, t } from "../introspect-core-DYv9Uufi.js";
2
2
  //#region src/vite/introspect.ts
3
- function t(e) {
4
- return typeof e?.safeParse == "function";
3
+ async function n() {
4
+ let n = process.argv[2];
5
+ n || (process.stderr.write("introspect: no bundle path given\n"), process.exit(2)), await t(), process.stdout.write(JSON.stringify(await e(n)));
5
6
  }
6
- function n(e, n) {
7
- if (!t(e) || !Array.isArray(n)) return [];
8
- let r = [];
9
- for (let [t, i] of n.entries()) {
10
- let n = i?.config;
11
- if (typeof n != "object" || !n) continue;
12
- let a = e.safeParse(n);
13
- a.success || r.push({
14
- index: t,
15
- label: i.label,
16
- problems: (a.error?.issues ?? []).map((e) => {
17
- let t = e.path.map(String).join(".");
18
- return t ? `${t}: ${e.message}` : e.message;
19
- })
20
- });
21
- }
22
- return r;
23
- }
24
- async function r() {
25
- let { Window: e } = await import("happy-dom"), t = new e({ url: "http://localhost" });
26
- for (let e of [
27
- "window",
28
- "document",
29
- "navigator",
30
- "location",
31
- "customElements",
32
- "HTMLElement",
33
- "Element",
34
- "Node",
35
- "CSS",
36
- "getComputedStyle",
37
- "requestAnimationFrame",
38
- "cancelAnimationFrame",
39
- "matchMedia"
40
- ]) globalThis[e] === void 0 && (globalThis[e] = t[e]);
41
- }
42
- async function i() {
43
- let t = process.argv[2];
44
- t || (process.stderr.write("introspect: no bundle path given\n"), process.exit(2)), await r();
45
- let i = (await import(e(t).href)).default, a = null;
46
- if (i?.configSchema) {
47
- let { z: e } = await import("zod");
48
- a = e.toJSONSchema(i.configSchema, { unrepresentable: "any" });
49
- }
50
- let o = {
51
- manifest: i?.manifest ?? null,
52
- jsonSchema: a,
53
- exampleConfigIssues: n(i?.configSchema, i?.manifest?.examples)
54
- };
55
- process.stdout.write(JSON.stringify(o));
56
- }
57
- i().catch((e) => {
7
+ n().catch((e) => {
58
8
  process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`), process.exit(1);
59
9
  });
60
10
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.10.3",
3
+ "version": "1.11.1",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
6
  "glasshome",
@@ -69,6 +69,7 @@
69
69
  "@tailwindcss/vite": "^4.1.13",
70
70
  "clsx": "^2.1.1",
71
71
  "happy-dom": "^20.0.0",
72
+ "lightningcss": "^1.33.0",
72
73
  "tailwind-merge": "^3.5.0",
73
74
  "tailwindcss": "^4.1.13",
74
75
  "zod": "^4.1.13"
@@ -78,7 +79,7 @@
78
79
  "@solidjs/testing-library": "^0.8.10",
79
80
  "@types/bun": "latest",
80
81
  "@types/node": "^25.2.2",
81
- "typescript": "^5",
82
+ "typescript": "^7.0.2",
82
83
  "vite": "^8.1.0",
83
84
  "vite-plugin-solid": "^2.11.12",
84
85
  "vitest": "^4.1.10"