@glasshome/widget-sdk 1.22.2 → 1.23.0

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/dist/index.js CHANGED
@@ -3136,7 +3136,7 @@ function Co() {
3136
3136
  //#region src/version.ts
3137
3137
  var wo = {
3138
3138
  name: "@glasshome/widget-sdk",
3139
- version: "1.22.2",
3139
+ version: "1.23.0",
3140
3140
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
3141
3141
  keywords: [
3142
3142
  "dashboard",
@@ -3207,7 +3207,7 @@ var wo = {
3207
3207
  lint: "oxlint --type-aware . && oxfmt --check ."
3208
3208
  },
3209
3209
  dependencies: {
3210
- "@glasshome/widget-contract": "0.3.0",
3210
+ "@glasshome/widget-contract": "0.4.0",
3211
3211
  "@tailwindcss/vite": "^4.1.13",
3212
3212
  clsx: "^2.1.1",
3213
3213
  "happy-dom": "^20.0.0",
@@ -3217,7 +3217,7 @@ var wo = {
3217
3217
  zod: "^4.1.13"
3218
3218
  },
3219
3219
  devDependencies: {
3220
- "@glasshome/ui": "1.30.0",
3220
+ "@glasshome/ui": "1.33.0",
3221
3221
  "@solidjs/testing-library": "^0.8.10",
3222
3222
  "@types/bun": "^1.4.0",
3223
3223
  "@types/node": "^25.2.2",
@@ -0,0 +1,2 @@
1
+ import type { Introspector } from "./index";
2
+ export declare const inProcessIntrospector: Introspector;
@@ -1,7 +1,7 @@
1
1
  import { type CapabilityGrant } from "@glasshome/widget-contract";
2
2
  import type { InlineConfig, Plugin } from "vite";
3
3
  import type { ExampleConfigIssue, WidgetIntrospection } from "./introspect-core";
4
- import { createIntrospectSession, type IntrospectSession, type IntrospectSessionOptions, introspectOnce } from "./introspect-session";
4
+ import { createIntrospectSession, type IntrospectResult, type IntrospectSession, type IntrospectSessionOptions, introspectOnce } from "./introspect-session";
5
5
  export { createIntrospectSession, introspectOnce };
6
6
  export type { ExampleConfigIssue, IntrospectSession, IntrospectSessionOptions, WidgetIntrospection, };
7
7
  export interface GlasshomeWidgetOptions {
@@ -21,6 +21,7 @@ export declare function sdkImportsOf(code: string): Set<string>;
21
21
  /** SDK hooks that read a fixed entity on the widget's behalf, so the widget must declare that read. */
22
22
  export declare const HOOK_READS: Record<string, string>;
23
23
  export declare function assertHookReadsDeclared(code: string, capabilities: readonly CapabilityGrant[], widgetName: string): void;
24
+ export type Introspector = (outFile: string) => IntrospectResult | Promise<IntrospectResult>;
24
25
  export declare function runSchemaGuard(args: {
25
26
  outFile: string;
26
27
  hashFile: string;
@@ -28,6 +29,8 @@ export declare function runSchemaGuard(args: {
28
29
  manifestPath?: string;
29
30
  /** Persistent worker to reuse. Only `bun widget connect`'s watch loop passes one. */
30
31
  session?: IntrospectSession;
32
+ /** Reads the bundle when no session is given. Defaults to one subprocess per call. */
33
+ introspect?: Introspector;
31
34
  }): Promise<void>;
32
35
  /**
33
36
  * Packages provided by the host import map — widgets must not bundle these.
@@ -6,40 +6,46 @@ import { createRequire as m } from "node:module";
6
6
  import { dirname as h, join as g, relative as _, resolve as v, sep as y } from "node:path";
7
7
  import { fileURLToPath as b } from "node:url";
8
8
  import ee from "@tailwindcss/vite";
9
- import { spawn as te, spawnSync as ne } from "node:child_process";
10
- //#region src/vite/introspect-session.ts
11
- var x = 262144e3, S = 3e4, re = 3e3;
12
- function C(e) {
9
+ import { spawn as te, spawnSync as x } from "node:child_process";
10
+ //#region node_modules/@glasshome/widget-contract/dist/clock.mjs
11
+ var S = (e) => e, C = (e) => e, ne = {
12
+ now: () => Date.now(),
13
+ setTimeout: (e, t) => S(globalThis.setTimeout(e, t)),
14
+ clearTimeout: (e) => globalThis.clearTimeout(C(e)),
15
+ setInterval: (e, t) => S(globalThis.setInterval(e, t)),
16
+ clearInterval: (e) => globalThis.clearInterval(C(e))
17
+ }, re = 262144e3, w = 3e4, ie = 3e3;
18
+ function T(e) {
13
19
  let t = h(b(import.meta.url));
14
20
  return [g(t, `${e}.js`), g(t, `${e}.ts`)].find((e) => s(e)) ?? null;
15
21
  }
16
- function w(e) {
17
- let t = C("introspect");
18
- if (!t) return {
22
+ function E(e, t) {
23
+ let n = T("introspect");
24
+ if (!n) return {
19
25
  ok: !1,
20
26
  reason: "introspect not found next to the sdk"
21
27
  };
22
- let n = ne(process.execPath, [
28
+ let r = x(process.execPath, [
23
29
  "--conditions",
24
30
  "browser",
25
- t,
31
+ n,
26
32
  e
27
33
  ], {
28
34
  encoding: "utf-8",
29
- timeout: S
35
+ timeout: t?.timeoutMs ?? w
30
36
  });
31
- if (n.error) return {
37
+ if (r.error) return {
32
38
  ok: !1,
33
- reason: n.error.message
39
+ reason: r.error.message
34
40
  };
35
- if (n.status !== 0) return {
41
+ if (r.status !== 0) return {
36
42
  ok: !1,
37
- reason: (n.stderr || "").trim().split("\n")[0] || `exit ${n.status}`
43
+ reason: (r.stderr || "").trim().split("\n")[0] || `exit ${r.status}`
38
44
  };
39
45
  try {
40
46
  return {
41
47
  ok: !0,
42
- value: JSON.parse(n.stdout)
48
+ value: JSON.parse(r.stdout)
43
49
  };
44
50
  } catch {
45
51
  return {
@@ -48,27 +54,63 @@ function w(e) {
48
54
  };
49
55
  }
50
56
  }
51
- function ie() {
57
+ function ae(e) {
58
+ let t = te(process.execPath, [
59
+ "--conditions",
60
+ "browser",
61
+ e
62
+ ], { stdio: [
63
+ "pipe",
64
+ "pipe",
65
+ "inherit"
66
+ ] });
67
+ return {
68
+ write(e) {
69
+ return t.stdin?.writable ? (t.stdin.write(e), !0) : !1;
70
+ },
71
+ onLine(e) {
72
+ let n = "";
73
+ t.stdout?.setEncoding("utf-8"), t.stdout?.on("data", (t) => {
74
+ n += t;
75
+ let r = n.indexOf("\n");
76
+ for (; r >= 0;) {
77
+ let t = n.slice(0, r).trim();
78
+ n = n.slice(r + 1), t && e(t), r = n.indexOf("\n");
79
+ }
80
+ });
81
+ },
82
+ onExit(e) {
83
+ t.on("error", (t) => e(t.message)), t.on("exit", (t, n) => e(`introspect worker exited (${n ?? `code ${t}`})`));
84
+ },
85
+ kill() {
86
+ t.kill();
87
+ },
88
+ get pid() {
89
+ return t.pid ?? null;
90
+ }
91
+ };
92
+ }
93
+ function oe(e) {
52
94
  return {
53
- introspect: async (e) => w(e),
95
+ introspect: async (t) => E(t, { timeoutMs: e }),
54
96
  dispose: async () => {},
55
97
  childPid: null
56
98
  };
57
99
  }
58
- function ae(e) {
59
- if (process.env.GLASSHOME_WIDGET_NO_WORKER === "1") return ie();
60
- let t = e?.rssLimitBytes ?? x, n = e?.timeoutMs ?? S, r = e?.slowMs ?? re, i = null, a = !1, o = 0, s = Promise.resolve();
61
- function c(e, t) {
100
+ function se(e) {
101
+ if (process.env.GLASSHOME_WIDGET_NO_WORKER === "1") return oe(e?.timeoutMs);
102
+ let t = e?.rssLimitBytes ?? re, n = e?.timeoutMs ?? w, r = e?.slowMs ?? ie, i = e?.clock ?? ne, a = e?.spawnWorker ?? ae, o = null, s = !1, c = 0, l = Promise.resolve();
103
+ function u(e, t) {
62
104
  for (let n of e.pending.values()) n({
63
105
  ok: !1,
64
106
  reason: t
65
107
  });
66
108
  e.pending.clear();
67
109
  }
68
- function l(e, t) {
69
- i === e && (i = null), c(e, t), e.proc.kill();
110
+ function d(e, t) {
111
+ o === e && (o = null), u(e, t), e.proc.kill();
70
112
  }
71
- function u(e, n) {
113
+ function f(e, n) {
72
114
  let i;
73
115
  try {
74
116
  i = JSON.parse(n);
@@ -85,56 +127,38 @@ function ae(e) {
85
127
  reason: i.reason ?? "introspection failed"
86
128
  }));
87
129
  }
88
- function d(e) {
89
- let t = Date.now(), n = te(process.execPath, [
90
- "--conditions",
91
- "browser",
92
- e
93
- ], { stdio: [
94
- "pipe",
95
- "pipe",
96
- "inherit"
97
- ] }), a = {
98
- proc: n,
130
+ function p(e) {
131
+ let t = i.now(), n = {
132
+ proc: a(e),
99
133
  ready: Promise.resolve(),
100
134
  pending: /* @__PURE__ */ new Map(),
101
135
  retireAfterRequest: !1
102
136
  };
103
- return a.ready = new Promise((e, o) => {
104
- let s = "", l = !1;
105
- n.stdout?.setEncoding("utf-8"), n.stdout?.on("data", (n) => {
106
- s += n;
107
- let i = s.indexOf("\n");
108
- for (; i >= 0;) {
109
- let n = s.slice(0, i).trim();
110
- if (s = s.slice(i + 1), n) {
111
- if (!l && n.includes("\"ready\"")) {
112
- l = !0;
113
- let n = Date.now() - t;
114
- n > r && console.warn(`[widget-sdk] introspect worker took ${n}ms to boot`), e();
115
- } else u(a, n);
116
- }
117
- i = s.indexOf("\n");
118
- }
137
+ return n.ready = new Promise((e, a) => {
138
+ let s = !1;
139
+ n.proc.onLine((a) => {
140
+ if (!s && a.includes("\"ready\"")) {
141
+ s = !0;
142
+ let n = i.now() - t;
143
+ n > r && console.warn(`[widget-sdk] introspect worker took ${n}ms to boot`), e();
144
+ } else f(n, a);
145
+ }), n.proc.onExit((e) => {
146
+ s || a(Error(e)), o === n && (o = null), u(n, e);
119
147
  });
120
- let d = (e) => {
121
- l || o(Error(e)), i === a && (i = null), c(a, e);
122
- };
123
- n.on("error", (e) => d(e.message)), n.on("exit", (e, t) => d(`introspect worker exited (${t ?? `code ${e}`})`));
124
- }), a;
148
+ }), n;
125
149
  }
126
- async function f(e) {
127
- if (a) return {
150
+ async function m(e) {
151
+ if (s) return {
128
152
  ok: !1,
129
153
  reason: "introspect session disposed"
130
154
  };
131
- let t = C("introspect-worker");
155
+ let t = T("introspect-worker");
132
156
  if (!t) return {
133
157
  ok: !1,
134
158
  reason: "introspect-worker not found next to the sdk"
135
159
  };
136
- i?.retireAfterRequest && l(i, "worker was replaced");
137
- let r = i ??= d(t);
160
+ o?.retireAfterRequest && d(o, "worker was replaced");
161
+ let r = o ??= p(t);
138
162
  try {
139
163
  await r.ready;
140
164
  } catch (e) {
@@ -143,28 +167,24 @@ function ae(e) {
143
167
  reason: e instanceof Error ? e.message : String(e)
144
168
  };
145
169
  }
146
- if (!r.proc.stdin?.writable) return {
147
- ok: !1,
148
- reason: "introspect worker is not writable"
149
- };
150
- let s = ++o;
170
+ let a = ++c;
151
171
  return new Promise((t) => {
152
- let i = setTimeout(() => {
153
- r.pending.delete(s), l(r, `introspection timed out after ${n}ms`), t({
172
+ let o = i.setTimeout(() => {
173
+ r.pending.delete(a), d(r, `introspection timed out after ${n}ms`), t({
154
174
  ok: !1,
155
175
  reason: `introspection timed out after ${n}ms`
156
176
  });
157
177
  }, n);
158
- r.pending.set(s, (e) => {
159
- clearTimeout(i), t(e);
178
+ r.pending.set(a, (e) => {
179
+ i.clearTimeout(o), t(e);
160
180
  });
161
181
  try {
162
- r.proc.stdin?.write(`${JSON.stringify({
163
- id: s,
182
+ if (!r.proc.write(`${JSON.stringify({
183
+ id: a,
164
184
  outFile: e
165
- })}\n`);
185
+ })}\n`)) throw Error("introspect worker is not writable");
166
186
  } catch (e) {
167
- clearTimeout(i), r.pending.delete(s), r.retireAfterRequest = !0, t({
187
+ i.clearTimeout(o), r.pending.delete(a), r.retireAfterRequest = !0, t({
168
188
  ok: !1,
169
189
  reason: e instanceof Error ? e.message : String(e)
170
190
  });
@@ -173,33 +193,33 @@ function ae(e) {
173
193
  }
174
194
  return {
175
195
  introspect(e) {
176
- let t = s.then(() => f(e));
177
- return s = t.catch(() => void 0), t;
196
+ let t = l.then(() => m(e));
197
+ return l = t.catch(() => void 0), t;
178
198
  },
179
199
  async dispose() {
180
- a = !0, i && l(i, "introspect session disposed");
200
+ s = !0, o && d(o, "introspect session disposed");
181
201
  },
182
202
  get childPid() {
183
- return i?.proc.pid ?? null;
203
+ return o?.proc.pid ?? null;
184
204
  }
185
205
  };
186
206
  }
187
207
  //#endregion
188
208
  //#region src/vite/index.ts
189
- var T = 2;
190
- function E(e) {
191
- if (Array.isArray(e)) return `[${e.map(E).join(",")}]`;
209
+ var D = 2;
210
+ function O(e) {
211
+ if (Array.isArray(e)) return `[${e.map(O).join(",")}]`;
192
212
  if (typeof e == "object" && e) {
193
213
  let t = e;
194
- return `{${Object.keys(t).sort().filter((e) => t[e] !== void 0).map((e) => `${JSON.stringify(e)}:${E(t[e])}`).join(",")}}`;
214
+ return `{${Object.keys(t).sort().filter((e) => t[e] !== void 0).map((e) => `${JSON.stringify(e)}:${O(t[e])}`).join(",")}}`;
195
215
  }
196
216
  return JSON.stringify(e) ?? "null";
197
217
  }
198
- function oe(e) {
199
- return o("sha256").update(E(e)).digest("hex").slice(0, 16);
218
+ function ce(e) {
219
+ return o("sha256").update(O(e)).digest("hex").slice(0, 16);
200
220
  }
201
- var se = 20;
202
- async function D(e, t) {
221
+ var le = 20;
222
+ async function ue(e, t) {
203
223
  if (e === void 0) return;
204
224
  let n;
205
225
  try {
@@ -219,7 +239,7 @@ async function D(e, t) {
219
239
  let a = i.error.issues.map((e) => ` examples${e.path.length ? `[${e.path.join("][")}]` : ""}: ${e.message}`).join("\n");
220
240
  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.`);
221
241
  }
222
- function O(e, t) {
242
+ function de(e, t) {
223
243
  if (!e?.length) return;
224
244
  let n = e.map((e) => {
225
245
  let t = e.label ? `"${e.label}"` : `#${e.index}`;
@@ -227,7 +247,7 @@ function O(e, t) {
227
247
  }).join("\n");
228
248
  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.`);
229
249
  }
230
- var ce = [
250
+ var fe = [
231
251
  "schema",
232
252
  "defaultConfig",
233
253
  "examples",
@@ -240,20 +260,20 @@ var ce = [
240
260
  "minSize",
241
261
  "maxSize"
242
262
  ];
243
- function le(e, t, n) {
263
+ function pe(e, t, n) {
244
264
  let r = l(e, "utf-8"), i = JSON.parse(r), a = {
245
265
  ...t.manifest ?? {},
246
266
  schema: n
247
267
  };
248
- for (let e of ce) a[e] !== void 0 && (i[e] = a[e]);
268
+ for (let e of fe) a[e] !== void 0 && (i[e] = a[e]);
249
269
  let o = `${JSON.stringify(i, null, 2)}\n`;
250
270
  o !== r && p(e, o);
251
271
  }
252
- var ue = /import\s*(\{[^}]*\}|\*\s*as\s+[\w$]+)\s*from\s*["']@glasshome\/widget-sdk["']/g, de = /import\(\s*["']@glasshome\/widget-sdk["']\s*\)/;
272
+ var me = /import\s*(\{[^}]*\}|\*\s*as\s+[\w$]+)\s*from\s*["']@glasshome\/widget-sdk["']/g, he = /import\(\s*["']@glasshome\/widget-sdk["']\s*\)/;
253
273
  function k(e) {
254
274
  let t = /* @__PURE__ */ new Set();
255
- de.test(e) && t.add("*");
256
- for (let [, n = ""] of e.matchAll(ue)) if (!n.startsWith("{")) t.add("*");
275
+ he.test(e) && t.add("*");
276
+ for (let [, n = ""] of e.matchAll(me)) if (!n.startsWith("{")) t.add("*");
257
277
  else for (let e of n.slice(1, -1).split(",")) {
258
278
  let n = e.trim().split(/\s+as\s+/)[0];
259
279
  n && t.add(n);
@@ -271,16 +291,16 @@ function j(e, t, n) {
271
291
  }
272
292
  async function M(e) {
273
293
  if (!s(e.outFile)) return;
274
- let t = e.session ? await e.session.introspect(e.outFile) : w(e.outFile);
294
+ let t = e.session ? await e.session.introspect(e.outFile) : await (e.introspect ?? E)(e.outFile);
275
295
  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.
276
296
  This usually means the bundle throws while being imported.`);
277
297
  let n = t.value, r = n.manifest?.name ?? e.widgetName;
278
- await D(n.manifest?.examples, r), O(n.exampleConfigIssues, r);
298
+ await ue(n.manifest?.examples, r), de(n.exampleConfigIssues, r);
279
299
  let i = n.jsonSchema, a = e.manifestPath && s(e.manifestPath) ? e.manifestPath : null;
280
- i && a && le(a, n, i);
300
+ i && a && pe(a, n, i);
281
301
  let o = a ? JSON.parse(l(a, "utf-8")).capabilities : n.manifest?.capabilities;
282
302
  if (j(l(e.outFile, "utf-8"), o ?? [], r), !i) return;
283
- let c = oe(i), u = n.manifest?.configVersion ?? null, d = n.manifest?.name ?? e.widgetName, f = s(e.hashFile) ? l(e.hashFile, "utf-8") : null;
303
+ let c = ce(i), u = n.manifest?.configVersion ?? null, d = n.manifest?.name ?? e.widgetName, f = s(e.hashFile) ? l(e.hashFile, "utf-8") : null;
284
304
  if (f !== null) {
285
305
  let t = f.trim(), n = null;
286
306
  if (t.startsWith("{")) try {
@@ -288,32 +308,32 @@ async function M(e) {
288
308
  } catch {
289
309
  n = null;
290
310
  }
291
- if (n && n.hashVersion === T) {
311
+ if (n && n.hashVersion === D) {
292
312
  if (n.hash !== c && n.configVersion === u) throw Error(`[widget-sdk] Config schema shape changed for "${d}" without a configVersion bump. Bump configVersion in defineWidget (currently ${u ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
293
- } else console.warn(`[widget-sdk] Schema shape record for "${d}" was written by an older SDK, so it cannot be compared. Re-recording it under hash format ${T}. This build is not checked for a missing configVersion bump; the next one is. (${e.hashFile})`);
313
+ } else console.warn(`[widget-sdk] Schema shape record for "${d}" was written by an older SDK, so it cannot be compared. Re-recording it under hash format ${D}. This build is not checked for a missing configVersion bump; the next one is. (${e.hashFile})`);
294
314
  }
295
315
  let m = `${JSON.stringify({
296
316
  hash: c,
297
317
  configVersion: u,
298
- hashVersion: T
318
+ hashVersion: D
299
319
  })}\n`;
300
320
  m !== f && p(e.hashFile, m);
301
321
  }
302
- var fe = "virtual:glasshome-widget", N = "\0virtual:glasshome-widget", pe = "/@glasshome/preview";
303
- function P(e) {
322
+ var ge = "virtual:glasshome-widget", N = "\0virtual:glasshome-widget", P = "/@glasshome/preview";
323
+ function F(e) {
304
324
  return y === "\\" ? e.split(y).join("/") : e;
305
325
  }
306
- function F() {
326
+ function I() {
307
327
  let e = b(import.meta.url);
308
328
  return v(h(e), "../../preview");
309
329
  }
310
- function I(e) {
330
+ function L(e) {
311
331
  return r(e);
312
332
  }
313
- var L = /\.(ts|tsx|js|jsx)$/, R = /from\s+["']@glasshome\/sync-layer(\/[^"']*)?["']/;
333
+ var R = /\.(ts|tsx|js|jsx)$/, _e = /from\s+["']@glasshome\/sync-layer(\/[^"']*)?["']/;
314
334
  function z(e, t) {
315
- let n = P(e).split("?")[0] ?? "";
316
- return n.includes("/node_modules/") || !L.test(n) ? !1 : R.test(t);
335
+ let n = F(e).split("?")[0] ?? "";
336
+ return n.includes("/node_modules/") || !R.test(n) ? !1 : _e.test(t);
317
337
  }
318
338
  function B() {
319
339
  return {
@@ -321,14 +341,14 @@ function B() {
321
341
  apply: "build",
322
342
  enforce: "pre",
323
343
  transform(e, t) {
324
- if (z(t, e)) throw Error(`Widgets must not import "@glasshome/sync-layer" directly (imported by ${P(_(process.cwd(), t.split("?")[0] ?? t))}). Import the equivalent hook from "@glasshome/widget-sdk" instead (e.g. useEntity, useEntities, useService).`);
344
+ if (z(t, e)) throw Error(`Widgets must not import "@glasshome/sync-layer" directly (imported by ${F(_(process.cwd(), t.split("?")[0] ?? t))}). Import the equivalent hook from "@glasshome/widget-sdk" instead (e.g. useEntity, useEntities, useService).`);
325
345
  return null;
326
346
  }
327
347
  };
328
348
  }
329
349
  function V(t, n) {
330
- let r = P(t).split("?")[0] ?? "";
331
- if (r.includes("/node_modules/") || !L.test(r)) return !1;
350
+ let r = F(t).split("?")[0] ?? "";
351
+ if (r.includes("/node_modules/") || !R.test(r)) return !1;
332
352
  let i = e.find((e) => e.id === "direct-ui-import");
333
353
  return i?.sourcePattern ? new RegExp(i.sourcePattern).test(n) : !1;
334
354
  }
@@ -336,7 +356,7 @@ function H(e = /* @__PURE__ */ new Set()) {
336
356
  let t = /* @__PURE__ */ new Set();
337
357
  return {
338
358
  scan(e, n) {
339
- V(e, n) && t.add(P(_(process.cwd(), e.split("?")[0] ?? e)));
359
+ V(e, n) && t.add(F(_(process.cwd(), e.split("?")[0] ?? e)));
340
360
  },
341
361
  take() {
342
362
  let n = [...t].filter((t) => !e.has(t));
@@ -362,7 +382,7 @@ function U(n) {
362
382
  }
363
383
  };
364
384
  }
365
- var me = "node_modules/.cache/glasshome-widgets";
385
+ var ve = "node_modules/.cache/glasshome-widgets";
366
386
  function W(e) {
367
387
  try {
368
388
  return m(g(process.cwd(), "package.json")).resolve(e);
@@ -370,7 +390,7 @@ function W(e) {
370
390
  return null;
371
391
  }
372
392
  }
373
- var he = [
393
+ var ye = [
374
394
  "--radius",
375
395
  "--background",
376
396
  "--foreground",
@@ -389,7 +409,7 @@ function K(e, t) {
389
409
  let r = l(n, "utf-8"), i = G(r);
390
410
  i !== r && p(n, i);
391
411
  for (let e of i.matchAll(/:host[^{]*\{([^}]*)\}/g)) {
392
- let n = e[1] ?? "", r = he.find((e) => n.includes(`${e}:`));
412
+ let n = e[1] ?? "", r = ye.find((e) => n.includes(`${e}:`));
393
413
  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.`);
394
414
  }
395
415
  }
@@ -398,7 +418,7 @@ function q(e, t) {
398
418
  return [r, ...u(n, { withFileTypes: !0 }).filter((e) => e.isDirectory()).map((e) => g(n, e.name)).filter((e) => !i.has(e))];
399
419
  }
400
420
  function J(e, t, n, r) {
401
- let i = v(e, me);
421
+ let i = v(e, ve);
402
422
  c(i, { recursive: !0 });
403
423
  let a = W("@glasshome/ui/styles");
404
424
  if (!a) 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.");
@@ -412,7 +432,7 @@ function J(e, t, n, r) {
412
432
  "@import \"tailwindcss\" source(none);",
413
433
  "@import \"tw-animate-css\";",
414
434
  "@import \"@glasshome/ui/styles/theme\";",
415
- ...d.map((e) => `@source "${P(e)}";`),
435
+ ...d.map((e) => `@source "${F(e)}";`),
416
436
  "@custom-variant dark {",
417
437
  " &:is(.dark *) { @slot }",
418
438
  " :host(.dark) & { @slot }",
@@ -421,8 +441,8 @@ function J(e, t, n, r) {
421
441
  ].join("\n"));
422
442
  let m = v(i, `${t}.entry.ts`);
423
443
  return p(m, [
424
- `import "${P(f)}";`,
425
- `export { default } from "${P(n)}";`,
444
+ `import "${F(f)}";`,
445
+ `export { default } from "${F(n)}";`,
426
446
  ""
427
447
  ].join("\n")), m;
428
448
  }
@@ -505,7 +525,7 @@ async function $(e) {
505
525
  fileName: s.name,
506
526
  cssFileName: s.name
507
527
  },
508
- rollupOptions: { external: I },
528
+ rollupOptions: { external: L },
509
529
  outDir: i,
510
530
  emptyOutDir: !1,
511
531
  copyPublicDir: !1,
@@ -521,7 +541,7 @@ async function $(e) {
521
541
  });
522
542
  Q(r, i);
523
543
  }
524
- function ge(e) {
544
+ function be(e) {
525
545
  let t = e?.entry ?? "src/index.tsx", n = {
526
546
  name: "glasshome-widget:build",
527
547
  apply: "build",
@@ -536,7 +556,7 @@ function ge(e) {
536
556
  fileName: "index",
537
557
  cssFileName: "index"
538
558
  },
539
- rollupOptions: { external: I }
559
+ rollupOptions: { external: L }
540
560
  }
541
561
  };
542
562
  }
@@ -545,7 +565,7 @@ function ge(e) {
545
565
  apply: "serve",
546
566
  enforce: "pre",
547
567
  config() {
548
- let e = F(), t = process.cwd();
568
+ let e = I(), t = process.cwd();
549
569
  return { server: { fs: { allow: [
550
570
  e,
551
571
  t,
@@ -553,7 +573,7 @@ function ge(e) {
553
573
  ] } } };
554
574
  },
555
575
  configureServer(e) {
556
- let t = F(), n = g(t, "preview.html");
576
+ let t = I(), n = g(t, "preview.html");
557
577
  e.middlewares.use(async (t, r, i) => {
558
578
  if (t.url === "/" || t.url === "/index.html") try {
559
579
  let i = l(n, "utf-8");
@@ -565,14 +585,14 @@ function ge(e) {
565
585
  });
566
586
  },
567
587
  resolveId(e) {
568
- if (e === pe) {
569
- let e = F();
570
- return P(g(e, "host.tsx"));
588
+ if (e === P) {
589
+ let e = I();
590
+ return F(g(e, "host.tsx"));
571
591
  }
572
- if (e === fe) return N;
592
+ if (e === ge) return N;
573
593
  },
574
594
  load(e) {
575
- if (e === N) return `export { default } from "${P(v(process.cwd(), t))}";`;
595
+ if (e === N) return `export { default } from "${F(v(process.cwd(), t))}";`;
576
596
  }
577
597
  }, i = {
578
598
  name: "glasshome-widget:schema",
@@ -595,7 +615,7 @@ function ge(e) {
595
615
  r
596
616
  ];
597
617
  }
598
- function _e(e) {
618
+ function xe(e) {
599
619
  let t = e?.srcDir ?? "src", n = e?.outDir ?? "dist", r = [];
600
620
  return [{
601
621
  name: "glasshome-widgets:build",
@@ -623,4 +643,4 @@ function _e(e) {
623
643
  }];
624
644
  }
625
645
  //#endregion
626
- export { A as HOOK_READS, se as MAX_EXAMPLES, j as assertHookReadsDeclared, $ as buildWidgets, ae as createIntrospectSession, H as createUiImportTracker, Z as discoverWidgets, G as dropSelfReferencingVars, Q as generateRegistry, ge as glasshomeWidget, _e as glasshomeWidgets, w as introspectOnce, z as isDirectSyncLayerImportSource, V as isDirectUiImportSource, I as isWidgetExternal, M as runSchemaGuard, k as sdkImportsOf, q as widgetProjectScanDirs };
646
+ export { A as HOOK_READS, le as MAX_EXAMPLES, j as assertHookReadsDeclared, $ as buildWidgets, se as createIntrospectSession, H as createUiImportTracker, Z as discoverWidgets, G as dropSelfReferencingVars, Q as generateRegistry, be as glasshomeWidget, xe as glasshomeWidgets, E as introspectOnce, z as isDirectSyncLayerImportSource, V as isDirectUiImportSource, L as isWidgetExternal, M as runSchemaGuard, k as sdkImportsOf, q as widgetProjectScanDirs };
@@ -1,3 +1,4 @@
1
+ import { type Clock } from "@glasshome/widget-contract/clock";
1
2
  import type { WidgetIntrospection } from "./introspect-core";
2
3
  export type IntrospectResult = {
3
4
  ok: true;
@@ -13,6 +14,15 @@ export interface IntrospectSessionOptions {
13
14
  timeoutMs?: number;
14
15
  /** Log spawn and import durations above this. */
15
16
  slowMs?: number;
17
+ clock?: Clock;
18
+ spawnWorker?: (probe: string) => WorkerTransport;
19
+ }
20
+ export interface WorkerTransport {
21
+ write(line: string): boolean;
22
+ onLine(cb: (line: string) => void): void;
23
+ onExit(cb: (reason: string) => void): void;
24
+ kill(): void;
25
+ readonly pid: number | null;
16
26
  }
17
27
  export interface IntrospectSession {
18
28
  introspect(outFile: string): Promise<IntrospectResult>;
@@ -20,7 +30,9 @@ export interface IntrospectSession {
20
30
  readonly childPid: number | null;
21
31
  }
22
32
  /** One process per check. The path `build`, `capture` and the fallback take. */
23
- export declare function introspectOnce(outFile: string): IntrospectResult;
33
+ export declare function introspectOnce(outFile: string, options?: {
34
+ timeoutMs?: number;
35
+ }): IntrospectResult;
24
36
  /**
25
37
  * A worker reused across rebuilds, for callers that own a session.
26
38
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.22.2",
3
+ "version": "1.23.0",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
6
  "dashboard",
@@ -75,7 +75,7 @@
75
75
  "lint": "oxlint --type-aware . && oxfmt --check ."
76
76
  },
77
77
  "dependencies": {
78
- "@glasshome/widget-contract": "0.3.0",
78
+ "@glasshome/widget-contract": "0.4.0",
79
79
  "@tailwindcss/vite": "^4.1.13",
80
80
  "clsx": "^2.1.1",
81
81
  "happy-dom": "^20.0.0",
@@ -85,7 +85,7 @@
85
85
  "zod": "^4.1.13"
86
86
  },
87
87
  "devDependencies": {
88
- "@glasshome/ui": "1.30.0",
88
+ "@glasshome/ui": "1.33.0",
89
89
  "@solidjs/testing-library": "^0.8.10",
90
90
  "@types/bun": "^1.4.0",
91
91
  "@types/node": "^25.2.2",