@glasshome/widget-sdk 1.7.0 → 1.8.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.
@@ -32,6 +32,14 @@ var e = "https://glasshome.app/docs/widget-sdk/config", t = [
32
32
  docsUrl: e,
33
33
  sourcePattern: "widgetFields\\.areaId\\b"
34
34
  },
35
+ {
36
+ id: "area-by-field-name",
37
+ since: "1.8.0",
38
+ removeIn: "2.0.0",
39
+ replacement: "field.area()",
40
+ docsUrl: e,
41
+ sourcePattern: "\\bareaId\\s*:\\s*z\\.string\\("
42
+ },
35
43
  {
36
44
  id: "raw-zod-config",
37
45
  since: "1.4.0",
@@ -71,6 +71,17 @@ declare function entity(domain: string, o?: EntitiesOpts): z.ZodDefault<z.ZodArr
71
71
  declare function area(o?: {
72
72
  title?: string;
73
73
  }): z.ZodOptional<z.ZodString>;
74
+ /**
75
+ * Icon name (e.g. "mdi:lightbulb"). The host renders its icon picker; the
76
+ * widget only declares that this field is an icon.
77
+ */
78
+ declare function icon(o: {
79
+ title?: string;
80
+ default: string;
81
+ }): z.ZodDefault<z.ZodString>;
82
+ declare function icon(o?: {
83
+ title?: string;
84
+ }): z.ZodOptional<z.ZodString>;
74
85
  declare function stringList(o: {
75
86
  title: string;
76
87
  description?: string;
@@ -89,6 +100,7 @@ export declare const field: {
89
100
  entities: typeof entities;
90
101
  entity: typeof entity;
91
102
  area: typeof area;
103
+ icon: typeof icon;
92
104
  stringList: typeof stringList;
93
105
  group: typeof group;
94
106
  };
@@ -4,7 +4,7 @@ import { z } from "zod";
4
4
  *
5
5
  * @deprecated since 1.4.0, removed in 2.0.0. Use the config API instead:
6
6
  * `defineConfig` + `field.*` (`field.title()`, `field.entities()`, `field.entity()`,
7
- * `field.area()`). See https://glasshome.app/docs/widget-sdk/config
7
+ * `field.area()`, `field.icon()`). See https://glasshome.app/docs/widget-sdk/config
8
8
  */
9
9
  export declare const widgetFields: {
10
10
  /**
package/dist/index.js CHANGED
@@ -1246,16 +1246,23 @@ function Xt(e) {
1246
1246
  });
1247
1247
  }
1248
1248
  function Zt(e) {
1249
+ let t = l.string().meta({
1250
+ formType: "icon-picker",
1251
+ title: e?.title ?? "Icon"
1252
+ });
1253
+ return e?.default === void 0 ? t.optional() : t.default(e.default);
1254
+ }
1255
+ function Qt(e) {
1249
1256
  return l.array(l.string()).default([]).meta($({
1250
1257
  title: e.title,
1251
1258
  description: e.description
1252
1259
  }));
1253
1260
  }
1254
- function Qt(e, t) {
1261
+ function $t(e, t) {
1255
1262
  let n = l.object(e);
1256
1263
  return n.default(Se(n)).meta({ title: t.title });
1257
1264
  }
1258
- var $t = {
1265
+ var en = {
1259
1266
  title: Ut,
1260
1267
  text: Wt,
1261
1268
  number: Gt,
@@ -1264,15 +1271,16 @@ var $t = {
1264
1271
  entities: Jt,
1265
1272
  entity: Yt,
1266
1273
  area: Xt,
1267
- stringList: Zt,
1268
- group: Qt
1274
+ icon: Zt,
1275
+ stringList: Qt,
1276
+ group: $t
1269
1277
  };
1270
- function en(e) {
1278
+ function tn(e) {
1271
1279
  return l.object(e);
1272
1280
  }
1273
1281
  //#endregion
1274
1282
  //#region src/shared/spline.ts
1275
- function tn(e) {
1283
+ function nn(e) {
1276
1284
  if (e.length < 2) return "";
1277
1285
  let t = e[0], n = e[1];
1278
1286
  if (!t || !n) return "";
@@ -1299,14 +1307,14 @@ function tn(e) {
1299
1307
  }
1300
1308
  //#endregion
1301
1309
  //#region src/theme.ts
1302
- function nn() {
1310
+ function rn() {
1303
1311
  return document.documentElement.classList.contains("dark");
1304
1312
  }
1305
1313
  //#endregion
1306
1314
  //#region src/version.ts
1307
- var rn = {
1315
+ var an = {
1308
1316
  name: "@glasshome/widget-sdk",
1309
- version: "1.7.0",
1317
+ version: "1.8.1",
1310
1318
  description: "SDK for building GlassHome dashboard widgets with SolidJS",
1311
1319
  keywords: [
1312
1320
  "glasshome",
@@ -1369,11 +1377,12 @@ var rn = {
1369
1377
  dependencies: {
1370
1378
  "@glasshome/widget-contract": "0.2.0",
1371
1379
  "@modular-forms/solid": "^0.25.1",
1380
+ "@tailwindcss/vite": "^4.1.13",
1372
1381
  clsx: "^2.1.1",
1382
+ "happy-dom": "^20.0.0",
1373
1383
  "tailwind-merge": "^3.5.0",
1374
- zod: "^4.1.13",
1375
- "@tailwindcss/vite": "^4.1.13",
1376
- tailwindcss: "^4.1.13"
1384
+ tailwindcss: "^4.1.13",
1385
+ zod: "^4.1.13"
1377
1386
  },
1378
1387
  devDependencies: {
1379
1388
  "@types/bun": "latest",
@@ -1394,4 +1403,4 @@ var rn = {
1394
1403
  engines: { node: ">=18" }
1395
1404
  }.version;
1396
1405
  //#endregion
1397
- export { rn as SDK_VERSION, Vt as ToneSchema, U as Widget, Pe as WidgetContent, e as WidgetCtx, Ct as WidgetDialog, Re as WidgetIcon, o as WidgetManifestSchema, Me as WidgetSliderFill, Be as WidgetStatus, Ue as WidgetTitle, tt as WidgetValue, ee as byDomain, We as calculateLightGroup, Ge as calculateSensorGroup, Xe as countActiveEntities, en as defineConfig, Ce as defineWidget, i as deprecations, $t as field, a as formatDeprecation, s as formatSchemaError, Ye as getEntityAttribute, u as getForecasts, d as getStream, f as getWebRtcClientConfig, p as hassMediaUrl, n as injectTokens, nn as isDark, Je as isEntityActive, tn as monotoneCubicPath, m as sendWebRtcCandidate, h as startWebRtcSession, g as state, Bt as svgColors, _ as trackEntityHistory, v as untrackEntityHistory, y as useArea, b as useCamera, te as useConnection, x as useCurrency, ne as useEntities, S as useEntity, re as useEntityHistory, ie as useEntityStatistics, C as useForecast, ae as useHassConfig, wt as useIntersectionPause, oe as useLocale, Tt as useReducedMotion, Te as useService, ce as useStore, le as useTemperatureUnit, Oe as useToggle, De as useTurnOff, Ee as useTurnOn, pe as useUnitSystem, t as useWidgetContext, Et as useWidgetDialog, Dt as useWidgetEntityGroup, Lt as useWidgetGestures, Ht as widgetFields, c as z };
1406
+ export { an as SDK_VERSION, Vt as ToneSchema, U as Widget, Pe as WidgetContent, e as WidgetCtx, Ct as WidgetDialog, Re as WidgetIcon, o as WidgetManifestSchema, Me as WidgetSliderFill, Be as WidgetStatus, Ue as WidgetTitle, tt as WidgetValue, ee as byDomain, We as calculateLightGroup, Ge as calculateSensorGroup, Xe as countActiveEntities, tn as defineConfig, Ce as defineWidget, i as deprecations, en as field, a as formatDeprecation, s as formatSchemaError, Ye as getEntityAttribute, u as getForecasts, d as getStream, f as getWebRtcClientConfig, p as hassMediaUrl, n as injectTokens, rn as isDark, Je as isEntityActive, nn as monotoneCubicPath, m as sendWebRtcCandidate, h as startWebRtcSession, g as state, Bt as svgColors, _ as trackEntityHistory, v as untrackEntityHistory, y as useArea, b as useCamera, te as useConnection, x as useCurrency, ne as useEntities, S as useEntity, re as useEntityHistory, ie as useEntityStatistics, C as useForecast, ae as useHassConfig, wt as useIntersectionPause, oe as useLocale, Tt as useReducedMotion, Te as useService, ce as useStore, le as useTemperatureUnit, Oe as useToggle, De as useTurnOff, Ee as useTurnOn, pe as useUnitSystem, t as useWidgetContext, Et as useWidgetDialog, Dt as useWidgetEntityGroup, Lt as useWidgetGestures, Ht as widgetFields, c as z };
@@ -4,7 +4,15 @@ export interface GlasshomeWidgetOptions {
4
4
  entry?: string;
5
5
  }
6
6
  /**
7
- * Configuration-drift guard (D-11). Imports the built bundle, derives the JSON
7
+ * Most previews anyone will look at, and the ceiling the hub renders to.
8
+ *
9
+ * Declaring more than this used to mean the extras were quietly not rendered:
10
+ * the gallery looked complete and was not. Failing the build instead makes the
11
+ * author decide which states matter rather than discovering the loss never.
12
+ */
13
+ export declare const MAX_EXAMPLES = 20;
14
+ /**
15
+ * Configuration-drift guard (D-11). Reads the built bundle, derives the JSON
8
16
  * Schema from its configSchema, and compares against the recorded
9
17
  * `.schema-hash`. A shape change without a configVersion bump fails the build:
10
18
  * tsc cannot catch this class of break because the old persisted config still
@@ -1,63 +1,115 @@
1
1
  import { formatSchemaError as e, isHostProvidedModule as t, widgetManifestSchema as n } from "@glasshome/widget-contract";
2
- import { createHash as r } from "node:crypto";
3
- import { existsSync as i, mkdirSync as a, readFileSync as o, readdirSync as s, rmSync as c, statSync as l, writeFileSync as u } from "node:fs";
4
- import { createRequire as d } from "node:module";
5
- import { dirname as f, join as p, resolve as m, sep as h } from "node:path";
6
- import { fileURLToPath as g } from "node:url";
7
- import _ from "@tailwindcss/vite";
2
+ import { spawnSync as r } from "node:child_process";
3
+ import { createHash as i } from "node:crypto";
4
+ import { existsSync as a, mkdirSync as o, readFileSync as s, readdirSync as c, rmSync as l, statSync as u, writeFileSync as d } from "node:fs";
5
+ import { createRequire as f } from "node:module";
6
+ import { dirname as p, join as m, resolve as h, sep as g } from "node:path";
7
+ import { fileURLToPath as _ } from "node:url";
8
+ import v from "@tailwindcss/vite";
8
9
  //#region src/vite/index.ts
9
- function v(e) {
10
+ function y(e) {
10
11
  let t = JSON.stringify(e, Object.keys(e).sort());
11
- return r("sha256").update(t).digest("hex").slice(0, 16);
12
+ return i("sha256").update(t).digest("hex").slice(0, 16);
12
13
  }
13
- async function y(e) {
14
- if (!i(e.outFile)) return;
15
- let t;
14
+ var b = 20;
15
+ async function x(e, t) {
16
+ if (e === void 0) return;
17
+ let n;
16
18
  try {
17
- t = (await import(`${e.outFile}?t=${Date.now()}`)).default;
19
+ ({z: n} = await import("zod"));
18
20
  } catch {
19
21
  return;
20
22
  }
21
- if (!t?.configSchema) return;
22
- let n;
23
+ let r = n.number().int().positive(), i = n.array(n.object({
24
+ config: n.record(n.string(), n.unknown()),
25
+ size: n.object({
26
+ w: r,
27
+ h: r
28
+ }),
29
+ label: n.string().optional()
30
+ })).max(20).safeParse(e);
31
+ if (i.success) return;
32
+ let a = i.error.issues.map((e) => ` examples${e.path.length ? `[${e.path.join("][")}]` : ""}: ${e.message}`).join("\n");
33
+ 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.`);
34
+ }
35
+ function S(e) {
36
+ let t = p(_(import.meta.url)), n = [m(t, "introspect.js"), m(t, "introspect.ts")].find((e) => a(e));
37
+ if (!n) return {
38
+ ok: !1,
39
+ reason: `introspect not found next to ${t}`
40
+ };
41
+ let i = r(process.execPath, [
42
+ "--conditions",
43
+ "browser",
44
+ n,
45
+ e
46
+ ], {
47
+ encoding: "utf-8",
48
+ timeout: 3e4
49
+ });
50
+ if (i.error) return {
51
+ ok: !1,
52
+ reason: i.error.message
53
+ };
54
+ if (i.status !== 0) return {
55
+ ok: !1,
56
+ reason: (i.stderr || "").trim().split("\n")[0] || `exit ${i.status}`
57
+ };
23
58
  try {
24
- let { z: e } = await import("zod");
25
- n = e.toJSONSchema(t.configSchema, { unrepresentable: "any" });
59
+ return {
60
+ ok: !0,
61
+ value: JSON.parse(i.stdout)
62
+ };
26
63
  } catch {
64
+ return {
65
+ ok: !1,
66
+ reason: "introspect returned unparseable output"
67
+ };
68
+ }
69
+ }
70
+ async function C(e) {
71
+ if (!a(e.outFile)) return;
72
+ let t = S(e.outFile);
73
+ if (!t.ok) {
74
+ console.warn(`[widget-sdk] Could not read "${e.widgetName}" to check it: ${t.reason}\n Its examples and config schema were NOT validated.`);
27
75
  return;
28
76
  }
29
- if (e.manifestPath && i(e.manifestPath)) {
30
- let t = JSON.parse(o(e.manifestPath, "utf-8"));
31
- t.schema = n, u(e.manifestPath, JSON.stringify(t, null, 2));
77
+ let n = t.value;
78
+ await x(n.manifest?.examples, n.manifest?.name ?? e.widgetName);
79
+ let r = n.jsonSchema;
80
+ if (!r) return;
81
+ if (e.manifestPath && a(e.manifestPath)) {
82
+ let t = JSON.parse(s(e.manifestPath, "utf-8"));
83
+ t.schema = r, d(e.manifestPath, JSON.stringify(t, null, 2));
32
84
  }
33
- let r = v(n), a = t.manifest?.configVersion ?? null, s = t.manifest?.name ?? e.widgetName;
34
- if (i(e.hashFile)) {
35
- let t = o(e.hashFile, "utf-8").trim(), n = null;
85
+ let i = y(r), o = n.manifest?.configVersion ?? null, c = n.manifest?.name ?? e.widgetName;
86
+ if (a(e.hashFile)) {
87
+ let t = s(e.hashFile, "utf-8").trim(), n = null;
36
88
  if (t.startsWith("{")) try {
37
89
  n = JSON.parse(t);
38
90
  } catch {
39
91
  n = null;
40
92
  }
41
93
  if (n) {
42
- if (n.hash !== r && n.configVersion === a) throw Error(`[widget-sdk] Config schema shape changed for "${s}" without a configVersion bump. Bump configVersion in defineWidget (currently ${a ?? "unset"}) or revert the schema change, then rebuild. Recorded in ${e.hashFile}`);
43
- } else t !== r && console.warn(`[widget-sdk] Schema shape changed for "${s}" — verify configVersion was bumped`);
94
+ if (n.hash !== i && 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}`);
95
+ } else t !== i && console.warn(`[widget-sdk] Schema shape changed for "${c}" — verify configVersion was bumped`);
44
96
  }
45
- u(e.hashFile, `${JSON.stringify({
46
- hash: r,
47
- configVersion: a
97
+ d(e.hashFile, `${JSON.stringify({
98
+ hash: i,
99
+ configVersion: o
48
100
  })}\n`);
49
101
  }
50
- var b = "virtual:glasshome-widget", x = "\0virtual:glasshome-widget", S = "/@glasshome/preview";
51
- function C(e) {
52
- return h === "\\" ? e.split(h).join("/") : e;
102
+ var w = "virtual:glasshome-widget", T = "\0virtual:glasshome-widget", E = "/@glasshome/preview";
103
+ function D(e) {
104
+ return g === "\\" ? e.split(g).join("/") : e;
53
105
  }
54
- function w() {
55
- return m(f(g(import.meta.url)), "../../preview");
106
+ function O() {
107
+ return h(p(_(import.meta.url)), "../../preview");
56
108
  }
57
- function T(e) {
109
+ function k(e) {
58
110
  return t(e);
59
111
  }
60
- function E() {
112
+ function A() {
61
113
  return {
62
114
  name: "glasshome-widget:sync-layer-guard",
63
115
  apply: "build",
@@ -67,15 +119,15 @@ function E() {
67
119
  }
68
120
  };
69
121
  }
70
- var D = "node_modules/.cache/glasshome-widgets";
71
- function O(e) {
122
+ var j = "node_modules/.cache/glasshome-widgets";
123
+ function M(e) {
72
124
  try {
73
- return d(p(process.cwd(), "package.json")).resolve(e);
125
+ return f(m(process.cwd(), "package.json")).resolve(e);
74
126
  } catch {
75
127
  return null;
76
128
  }
77
129
  }
78
- var k = [
130
+ var N = [
79
131
  "--radius",
80
132
  "--background",
81
133
  "--foreground",
@@ -85,88 +137,88 @@ var k = [
85
137
  "--muted",
86
138
  "--accent"
87
139
  ];
88
- function A(e, t) {
89
- let n = p(e, `${t}.css`);
90
- if (!i(n)) return;
91
- let r = o(n, "utf-8");
140
+ function P(e, t) {
141
+ let n = m(e, `${t}.css`);
142
+ if (!a(n)) return;
143
+ let r = s(n, "utf-8");
92
144
  for (let e of r.matchAll(/:host[^{]*\{([^}]*)\}/g)) {
93
- let n = e[1] ?? "", r = k.find((e) => n.includes(`${e}:`));
145
+ let n = e[1] ?? "", r = N.find((e) => n.includes(`${e}:`));
94
146
  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.`);
95
147
  }
96
148
  }
97
- function j(e, t, n) {
98
- let r = m(e, D);
99
- a(r, { recursive: !0 });
100
- let o = O("@glasshome/ui/styles");
101
- if (!o) 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.");
102
- let s = m(f(o), "../.."), c = O("@glasshome/widget-sdk/tailwind-sources"), l = c ? f(c) : null, d = [
103
- p(s, "src"),
104
- p(s, "dist"),
105
- ...l ? [p(l, "src"), p(l, "dist")] : [],
106
- f(n)
107
- ].filter((e) => i(e)), h = m(r, `${t}.tailwind.css`);
108
- u(h, [
149
+ function F(e, t, n) {
150
+ let r = h(e, j);
151
+ o(r, { recursive: !0 });
152
+ let i = M("@glasshome/ui/styles");
153
+ 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.");
154
+ let s = h(p(i), "../.."), c = M("@glasshome/widget-sdk/tailwind-sources"), l = c ? p(c) : null, u = [
155
+ m(s, "src"),
156
+ m(s, "dist"),
157
+ ...l ? [m(l, "src"), m(l, "dist")] : [],
158
+ p(n)
159
+ ].filter((e) => a(e)), f = h(r, `${t}.tailwind.css`);
160
+ d(f, [
109
161
  "@import \"tailwindcss\" source(none);",
110
162
  "@import \"tw-animate-css\";",
111
163
  "@import \"@glasshome/ui/styles/theme\";",
112
- ...d.map((e) => `@source "${C(e)}";`),
164
+ ...u.map((e) => `@source "${D(e)}";`),
113
165
  "@custom-variant dark {",
114
166
  " &:is(.dark *) { @slot }",
115
167
  " :host(.dark) & { @slot }",
116
168
  "}",
117
169
  ""
118
170
  ].join("\n"));
119
- let g = m(r, `${t}.entry.ts`);
120
- return u(g, [
121
- `import "${C(h)}";`,
122
- `export { default } from "${C(n)}";`,
171
+ let g = h(r, `${t}.entry.ts`);
172
+ return d(g, [
173
+ `import "${D(f)}";`,
174
+ `export { default } from "${D(n)}";`,
123
175
  ""
124
176
  ].join("\n")), g;
125
177
  }
126
- function M() {
127
- let e = _();
178
+ function I() {
179
+ let e = v();
128
180
  for (let t of e) t.apply ??= "build";
129
181
  return e;
130
182
  }
131
- function N(e) {
183
+ function L(e) {
132
184
  let t = [];
133
- if (!i(e)) return t;
134
- for (let n of s(e)) {
135
- let r = m(e, n);
136
- if (!l(r).isDirectory()) continue;
137
- let a = m(r, "index.tsx"), o = m(r, "manifest.json");
138
- i(a) && i(o) && t.push({
185
+ if (!a(e)) return t;
186
+ for (let n of c(e)) {
187
+ let r = h(e, n);
188
+ if (!u(r).isDirectory()) continue;
189
+ let i = h(r, "index.tsx"), o = h(r, "manifest.json");
190
+ a(i) && a(o) && t.push({
139
191
  name: n,
140
- entry: a
192
+ entry: i
141
193
  });
142
194
  }
143
195
  return t;
144
196
  }
145
- function P(t, r) {
146
- let c = [];
147
- if (i(t)) for (let a of s(t)) {
148
- if (!l(p(t, a)).isDirectory()) continue;
149
- let s = p(t, a, "manifest.json");
150
- if (!i(s)) continue;
151
- let u = n.safeParse(JSON.parse(o(s, "utf-8")));
152
- if (!u.success) throw Error(`[registry] Invalid manifest for "${a}": ${e(u.error)}`);
153
- let d = i(p(r, `${a}.css`));
154
- c.push({
155
- ...u.data,
156
- bundleUrl: `./${a}.js`,
157
- ...d ? { cssUrl: `./${a}.css` } : {}
197
+ function R(t, r) {
198
+ let i = [];
199
+ if (a(t)) for (let o of c(t)) {
200
+ if (!u(m(t, o)).isDirectory()) continue;
201
+ let c = m(t, o, "manifest.json");
202
+ if (!a(c)) continue;
203
+ let l = n.safeParse(JSON.parse(s(c, "utf-8")));
204
+ if (!l.success) throw Error(`[registry] Invalid manifest for "${o}": ${e(l.error)}`);
205
+ let d = a(m(r, `${o}.css`));
206
+ i.push({
207
+ ...l.data,
208
+ bundleUrl: `./${o}.js`,
209
+ ...d ? { cssUrl: `./${o}.css` } : {}
158
210
  });
159
211
  }
160
- let d = {
212
+ let l = {
161
213
  version: 1,
162
214
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
163
215
  baseUrl: "./",
164
- widgets: c
216
+ widgets: i
165
217
  };
166
- i(r) || a(r, { recursive: !0 }), u(p(r, "registry.json"), JSON.stringify(d, null, 2)), console.log(`[registry] Generated registry.json with ${c.length} widget(s)`);
218
+ a(r) || o(r, { recursive: !0 }), d(m(r, "registry.json"), JSON.stringify(l, null, 2)), console.log(`[registry] Generated registry.json with ${i.length} widget(s)`);
167
219
  }
168
- async function F(e) {
169
- let { build: t } = await import("vite"), n = process.cwd(), r = m(n, e?.srcDir ?? "src"), o = m(n, e?.outDir ?? "dist"), s = N(r);
220
+ async function z(e) {
221
+ let { build: t } = await import("vite"), n = process.cwd(), r = h(n, e?.srcDir ?? "src"), i = h(n, e?.outDir ?? "dist"), s = L(r);
170
222
  if (s.length === 0) {
171
223
  console.warn("[glasshome-widgets] No widgets found in", r);
172
224
  return;
@@ -175,38 +227,38 @@ async function F(e) {
175
227
  let t = new Set(e.only);
176
228
  s = s.filter((e) => t.has(e.name));
177
229
  }
178
- e?.only || i(o) && c(o, { recursive: !0 }), i(o) || a(o, { recursive: !0 });
230
+ e?.only || a(i) && l(i, { recursive: !0 }), a(i) || o(i, { recursive: !0 });
179
231
  for (let r of s) await t({
180
232
  configFile: !1,
181
233
  root: n,
182
234
  plugins: [
183
- ...M(),
235
+ ...I(),
184
236
  ...e?.plugins ?? [],
185
- E()
237
+ A()
186
238
  ],
187
239
  ...e?.viteConfig,
188
240
  build: {
189
241
  lib: {
190
- entry: j(n, r.name, r.entry),
242
+ entry: F(n, r.name, r.entry),
191
243
  formats: ["es"],
192
244
  fileName: r.name,
193
245
  cssFileName: r.name
194
246
  },
195
- rollupOptions: { external: T },
196
- outDir: o,
247
+ rollupOptions: { external: k },
248
+ outDir: i,
197
249
  emptyOutDir: !1,
198
250
  copyPublicDir: !1,
199
251
  ...e?.viteConfig?.build
200
252
  },
201
253
  logLevel: "warn"
202
- }), A(o, r.name), await y({
203
- outFile: p(o, `${r.name}.js`),
204
- hashFile: p(f(r.entry), ".schema-hash"),
254
+ }), P(i, r.name), await C({
255
+ outFile: m(i, `${r.name}.js`),
256
+ hashFile: m(p(r.entry), ".schema-hash"),
205
257
  widgetName: r.name
206
258
  });
207
- P(r, o);
259
+ R(r, i);
208
260
  }
209
- function I(e) {
261
+ function B(e) {
210
262
  let t = e?.entry ?? "src/index.tsx", n = {
211
263
  name: "glasshome-widget:build",
212
264
  apply: "build",
@@ -214,12 +266,12 @@ function I(e) {
214
266
  let e = process.cwd();
215
267
  return { build: {
216
268
  lib: {
217
- entry: j(e, "index", m(e, t)),
269
+ entry: F(e, "index", h(e, t)),
218
270
  formats: ["es"],
219
271
  fileName: "index",
220
272
  cssFileName: "index"
221
273
  },
222
- rollupOptions: { external: T }
274
+ rollupOptions: { external: k }
223
275
  } };
224
276
  }
225
277
  }, r = {
@@ -227,18 +279,18 @@ function I(e) {
227
279
  apply: "serve",
228
280
  enforce: "pre",
229
281
  config() {
230
- let e = w(), t = process.cwd();
282
+ let e = O(), t = process.cwd();
231
283
  return { server: { fs: { allow: [
232
284
  e,
233
285
  t,
234
- m(t, "..", "..")
286
+ h(t, "..", "..")
235
287
  ] } } };
236
288
  },
237
289
  configureServer(e) {
238
- let t = p(w(), "preview.html");
290
+ let t = m(O(), "preview.html");
239
291
  e.middlewares.use(async (n, r, i) => {
240
292
  if (n.url === "/" || n.url === "/index.html") try {
241
- let i = o(t, "utf-8");
293
+ let i = s(t, "utf-8");
242
294
  i = await e.transformIndexHtml(n.url, i), r.setHeader("Content-Type", "text/html"), r.statusCode = 200, r.end(i);
243
295
  } catch (e) {
244
296
  i(e);
@@ -247,33 +299,33 @@ function I(e) {
247
299
  });
248
300
  },
249
301
  resolveId(e) {
250
- if (e === S) return C(p(w(), "host.tsx"));
251
- if (e === b) return x;
302
+ if (e === E) return D(m(O(), "host.tsx"));
303
+ if (e === w) return T;
252
304
  },
253
305
  load(e) {
254
- if (e === x) return `export { default } from "${C(m(process.cwd(), t))}";`;
306
+ if (e === T) return `export { default } from "${D(h(process.cwd(), t))}";`;
255
307
  }
256
308
  }, i = {
257
309
  name: "glasshome-widget:schema",
258
310
  apply: "build",
259
311
  async closeBundle() {
260
- A(m(process.cwd(), "dist"), "index"), await y({
261
- outFile: m(process.cwd(), "dist", "index.js"),
262
- hashFile: m(process.cwd(), ".schema-hash"),
312
+ P(h(process.cwd(), "dist"), "index"), await C({
313
+ outFile: h(process.cwd(), "dist", "index.js"),
314
+ hashFile: h(process.cwd(), ".schema-hash"),
263
315
  widgetName: "index",
264
- manifestPath: m(process.cwd(), "manifest.json")
316
+ manifestPath: h(process.cwd(), "manifest.json")
265
317
  });
266
318
  }
267
319
  };
268
320
  return [
269
- ...M(),
321
+ ...I(),
270
322
  n,
271
- E(),
323
+ A(),
272
324
  i,
273
325
  r
274
326
  ];
275
327
  }
276
- function L(e) {
328
+ function V(e) {
277
329
  let t = e?.srcDir ?? "src", n = e?.outDir ?? "dist", r = [];
278
330
  return [{
279
331
  name: "glasshome-widgets:build",
@@ -292,7 +344,7 @@ function L(e) {
292
344
  if (e === "\0glasshome-noop") return "export {}";
293
345
  },
294
346
  async closeBundle() {
295
- await F({
347
+ await z({
296
348
  srcDir: t,
297
349
  outDir: n,
298
350
  plugins: r
@@ -301,4 +353,4 @@ function L(e) {
301
353
  }];
302
354
  }
303
355
  //#endregion
304
- export { F as buildWidgets, N as discoverWidgets, P as generateRegistry, I as glasshomeWidget, L as glasshomeWidgets, T as isWidgetExternal, y as runSchemaGuard };
356
+ export { b as MAX_EXAMPLES, z as buildWidgets, L as discoverWidgets, R as generateRegistry, B as glasshomeWidget, V as glasshomeWidgets, k as isWidgetExternal, C as runSchemaGuard };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Reads a built widget bundle's definition and prints it as JSON.
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.
13
+ *
14
+ * Usage: bun --conditions browser introspect.js <bundle.js>
15
+ * Prints: { manifest, jsonSchema } on stdout, or exits non-zero with a reason.
16
+ */
17
+ interface Introspection {
18
+ manifest: {
19
+ name?: string;
20
+ configVersion?: number;
21
+ examples?: unknown;
22
+ } | null;
23
+ /** JSON Schema derived from the widget's configSchema, when it has one. */
24
+ jsonSchema: object | null;
25
+ }
26
+ declare function installDom(): Promise<void>;
27
+ declare function main(): Promise<void>;
@@ -0,0 +1,37 @@
1
+ //#region src/vite/introspect.ts
2
+ async function e() {
3
+ let { Window: e } = await import("happy-dom"), t = new e({ url: "http://localhost" });
4
+ for (let e of [
5
+ "window",
6
+ "document",
7
+ "navigator",
8
+ "location",
9
+ "customElements",
10
+ "HTMLElement",
11
+ "Element",
12
+ "Node",
13
+ "CSS",
14
+ "getComputedStyle",
15
+ "requestAnimationFrame",
16
+ "cancelAnimationFrame",
17
+ "matchMedia"
18
+ ]) globalThis[e] === void 0 && (globalThis[e] = t[e]);
19
+ }
20
+ async function t() {
21
+ let t = process.argv[2];
22
+ t || (process.stderr.write("introspect: no bundle path given\n"), process.exit(2)), await e();
23
+ let n = (await import(t)).default, r = null;
24
+ if (n?.configSchema) {
25
+ let { z: e } = await import("zod");
26
+ r = e.toJSONSchema(n.configSchema, { unrepresentable: "any" });
27
+ }
28
+ let i = {
29
+ manifest: n?.manifest ?? null,
30
+ jsonSchema: r
31
+ };
32
+ process.stdout.write(JSON.stringify(i));
33
+ }
34
+ t().catch((e) => {
35
+ process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`), process.exit(1);
36
+ });
37
+ //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/widget-sdk",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "SDK for building GlassHome dashboard widgets with SolidJS",
5
5
  "keywords": [
6
6
  "glasshome",
@@ -63,11 +63,12 @@
63
63
  "dependencies": {
64
64
  "@glasshome/widget-contract": "0.2.0",
65
65
  "@modular-forms/solid": "^0.25.1",
66
+ "@tailwindcss/vite": "^4.1.13",
66
67
  "clsx": "^2.1.1",
68
+ "happy-dom": "^20.0.0",
67
69
  "tailwind-merge": "^3.5.0",
68
- "zod": "^4.1.13",
69
- "@tailwindcss/vite": "^4.1.13",
70
- "tailwindcss": "^4.1.13"
70
+ "tailwindcss": "^4.1.13",
71
+ "zod": "^4.1.13"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@types/bun": "latest",