@povio/ui 5.0.0-rc.12 → 5.0.0-rc.14

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.
Files changed (35) hide show
  1. package/README.md +9 -1
  2. package/dist/{AppCanvasPage-DCjiFfQW.js → AppCanvasPage-vta1j5YQ.js} +583 -847
  3. package/dist/{CanvasComponentsFrame-CZXY0Dxi.js → CanvasComponentsFrame-D6Mh_G3q.js} +1 -1
  4. package/dist/{ComponentGallery-D4U5hXZL.js → ComponentGallery-CzKU899_.js} +2 -2
  5. package/dist/{Form-DmcByQts.d.ts → Form-DAtj2szV.d.ts} +1 -1
  6. package/dist/{Inputs-C0sUmuPv.d.ts → Inputs-C_6dVwC7.d.ts} +1 -1
  7. package/dist/{Table-BN2_J_Ne.js → Table-piX1SWvN.js} +54 -1
  8. package/dist/{UIConfigurator-CBU0G6rb.js → UIConfigurator-CjAW1SJY.js} +1521 -318
  9. package/dist/config/app-canvas-tanstack.js +1 -1
  10. package/dist/config/app-canvas.d.ts +1 -1
  11. package/dist/config/app-canvas.js +1 -1
  12. package/dist/config/ui-config.d.ts +1 -1
  13. package/dist/config/ui-configurator-vite.d.ts +9 -0
  14. package/dist/config/ui-configurator-vite.js +560 -184
  15. package/dist/config/ui-configurator.d.ts +14 -2
  16. package/dist/config/ui-configurator.js +2 -2
  17. package/dist/{configuration.context-iuZu-ie0.d.ts → configuration.context-3I7-wBII.d.ts} +3 -2
  18. package/dist/{dynamicInputs-DdcsSoYu.d.ts → dynamicInputs-R5i7AT_C.d.ts} +1 -1
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +3 -3
  21. package/dist/rhf/inputs.d.ts +1 -1
  22. package/dist/rhf.d.ts +2 -2
  23. package/dist/rhf.js +3 -3
  24. package/dist/styles/app-canvas.css +15 -42
  25. package/dist/styles/configurator.css +453 -92
  26. package/dist/{table.cva-D5oQ-QvA.js → table.cva-DlMT-zLD.js} +1 -54
  27. package/dist/tanstack/form.d.ts +1 -1
  28. package/dist/tanstack/inputs.d.ts +2 -2
  29. package/dist/tanstack/table.js +2 -2
  30. package/dist/tanstack.d.ts +4 -4
  31. package/dist/tanstack.js +3 -3
  32. package/dist/{uiConfig.context-BqKWd_MO.d.ts → uiConfig.context-BksnNQyw.d.ts} +1 -1
  33. package/dist/{useTableColumnConfig-6zn3zKl3.js → useTableColumnConfig-CNJ_ZvIN.js} +1 -1
  34. package/package.json +1 -1
  35. package/src/configurator/README.md +18 -1
@@ -1,154 +1,26 @@
1
+ import { createRequire } from "node:module";
1
2
  import { createHash, randomUUID } from "node:crypto";
2
- import { lstat, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
3
- import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
- //#region src/app-canvas/manifest.ts
5
- function createCanvasWorkspace(name, id) {
6
- return {
7
- id,
8
- name: name.trim() || "Workspace",
9
- enabledPages: null,
10
- positions: {}
11
- };
12
- }
13
- function createCanvasManifest() {
14
- return {
15
- version: 2,
16
- workspaces: [createCanvasWorkspace("All pages", "default")],
17
- activeWorkspaceId: "default",
18
- examples: {},
19
- breakpoint: "desktop"
20
- };
21
- }
22
- function isRecord(value) {
23
- return value !== null && typeof value === "object" && !Array.isArray(value);
24
- }
25
- function isJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
26
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
27
- if (typeof value === "number") return Number.isFinite(value);
28
- if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
29
- ancestors.add(value);
30
- const valid = Object.values(value).every((child) => isJSON(child, ancestors, depth + 1));
31
- ancestors.delete(value);
32
- return valid;
33
- }
34
- function safeJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
35
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
36
- if (typeof value === "number") return Number.isFinite(value);
37
- if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
38
- if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
39
- ancestors.add(value);
40
- const valid = Object.entries(value).every(([key, child]) => ![
41
- "__proto__",
42
- "prototype",
43
- "constructor"
44
- ].includes(key) && safeJSON(child, ancestors, depth + 1));
45
- ancestors.delete(value);
46
- return valid;
47
- }
48
- const stringMap = (value) => isRecord(value) && Object.values(value).every((item) => typeof item === "string");
49
- const variantValue = (value) => value === null || typeof value === "string" || typeof value === "boolean";
50
- function validCvaEdits(value) {
51
- return isRecord(value) && Object.entries(value).every(([slot, definition]) => {
52
- if (!/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*$/.test(slot) || !isRecord(definition) || !Array.isArray(definition.base) || !definition.base.every((item) => typeof item === "string") || !isRecord(definition.config)) return false;
53
- const config = definition.config;
54
- return isRecord(config.variants) && Object.values(config.variants).every(stringMap) && isRecord(config.defaultVariants) && Object.values(config.defaultVariants).every(variantValue) && Array.isArray(config.compoundVariants) && config.compoundVariants.every((compound) => isRecord(compound) && Object.values(compound).every((item) => variantValue(item) || Array.isArray(item) && (item.every((part) => typeof part === "string") || item.every((part) => typeof part === "boolean"))));
55
- });
56
- }
57
- /** Returns an independent JSON configuration, or undefined for invalid/unset overrides. */
58
- function parseCanvasConfiguration(value) {
59
- if (!isRecord(value) || !safeJSON(value) || !isRecord(value.theme) || ![
60
- "light",
61
- "dark",
62
- "theme"
63
- ].every((key) => stringMap(value.theme && value.theme[key])) || !isRecord(value.config) || value.overrides !== void 0 && !validCvaEdits(value.overrides)) return void 0;
64
- return JSON.parse(JSON.stringify(value));
65
- }
66
- function parsePositions(input) {
67
- return Object.fromEntries(Object.entries(isRecord(input) ? input : {}).flatMap(([breakpoint, entries]) => {
68
- if (!isRecord(entries)) return [];
69
- return [[breakpoint, Object.fromEntries(Object.entries(entries).flatMap(([id, point]) => isRecord(point) && typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y) ? [[id, {
70
- x: point.x,
71
- y: point.y
72
- }]] : []))]];
73
- }));
74
- }
75
- /** Sanitize either stored JSON text or an imported value, migrating v1 positions into the default workspace. */
76
- function parseCanvasManifest(input) {
77
- const fallback = createCanvasManifest();
78
- let value = input;
79
- if (typeof value === "string") try {
80
- value = JSON.parse(value);
81
- } catch {
82
- return fallback;
83
- }
84
- if (!isRecord(value) || value.version !== 1 && value.version !== 2) return fallback;
85
- const examples = Object.fromEntries(Object.entries(isRecord(value.examples) ? value.examples : {}).flatMap(([id, example]) => {
86
- if (!isRecord(example)) return [];
87
- const clean = {};
88
- if (typeof example.title === "string") clean.title = example.title;
89
- if (isRecord(example.params)) clean.params = Object.fromEntries(Object.entries(example.params).filter((entry) => typeof entry[1] === "string"));
90
- if (isRecord(example.search)) clean.search = Object.fromEntries(Object.entries(example.search).filter(([, item]) => isJSON(item)));
91
- return [[id, clean]];
92
- }));
93
- const workspaces = [];
94
- const ids = /* @__PURE__ */ new Set();
95
- if (value.version === 1) workspaces.push({
96
- ...createCanvasWorkspace("All pages", "default"),
97
- positions: parsePositions(value.positions)
98
- });
99
- else if (Array.isArray(value.workspaces)) for (const entry of value.workspaces) {
100
- if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim() || ids.has(entry.id)) continue;
101
- ids.add(entry.id);
102
- const configuration = parseCanvasConfiguration(entry.configuration);
103
- workspaces.push({
104
- id: entry.id,
105
- name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : "Workspace",
106
- enabledPages: Array.isArray(entry.enabledPages) ? [...new Set(entry.enabledPages.filter((id) => typeof id === "string" && id.length > 0))] : null,
107
- positions: parsePositions(entry.positions),
108
- ...configuration ? { configuration } : {}
109
- });
110
- }
111
- if (!workspaces.length) workspaces.push(createCanvasWorkspace("All pages", "default"));
112
- return {
113
- version: 2,
114
- workspaces,
115
- activeWorkspaceId: typeof value.activeWorkspaceId === "string" && workspaces.some((workspace) => workspace.id === value.activeWorkspaceId) ? value.activeWorkspaceId : workspaces[0].id,
116
- examples,
117
- breakpoint: typeof value.breakpoint === "string" && value.breakpoint.length > 0 ? value.breakpoint : fallback.breakpoint
118
- };
119
- }
120
- //#endregion
121
- //#region src/app-canvas/storage-protocol.ts
122
- const canvasStorageMetaName = "povio-ui-canvas-storage";
3
+ import { access, lstat, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ //#region src/configurator/repository-storage-protocol.ts
8
+ const componentConfigurationMetaName = "povio-ui-component-configuration";
9
+ /** Dev-server event sent when a repository storage file changes, so other tabs reload it. */
10
+ const repositoryStorageChangedEvent = "povio-ui:repository-storage-changed";
123
11
  //#endregion
124
- //#region src/app-canvas/storage-vite.ts
12
+ //#region src/configurator/repository-storage-vite.ts
125
13
  var StorageError = class extends Error {
126
14
  constructor(message, status) {
127
15
  super(message);
128
16
  this.status = status;
129
17
  }
130
18
  };
131
- const maxBytes = 262144;
132
- const inside = (root, path) => {
19
+ const inside$1 = (root, path) => {
133
20
  const rel = relative(root, path);
134
21
  return !!rel && !isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
135
22
  };
136
- const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
137
- function validPositions(value) {
138
- return record(value) && Object.values(value).every((entries) => record(entries) && Object.values(entries).every((position) => record(position) && typeof position.x === "number" && Number.isFinite(position.x) && typeof position.y === "number" && Number.isFinite(position.y)));
139
- }
140
- function decodeManifest(value) {
141
- if (!record(value) || ![1, 2].includes(Number(value.version)) || typeof value.breakpoint !== "string" || !record(value.examples)) throw new StorageError("Invalid canvas manifest.", 400);
142
- if (value.version === 1) {
143
- if (!validPositions(value.positions)) throw new StorageError("Invalid canvas positions.", 400);
144
- } else if (value.version !== 2 || !Array.isArray(value.workspaces) || !value.workspaces.length || typeof value.activeWorkspaceId !== "string" || !value.workspaces.every((workspace) => record(workspace) && typeof workspace.id === "string" && workspace.id.length > 0 && typeof workspace.name === "string" && (workspace.enabledPages === null || Array.isArray(workspace.enabledPages) && workspace.enabledPages.every((id) => typeof id === "string")) && validPositions(workspace.positions) && (workspace.configuration === void 0 || parseCanvasConfiguration(workspace.configuration) !== void 0))) throw new StorageError("Invalid canvas workspaces.", 400);
145
- if (!Object.values(value.examples).every((example) => record(example) && (example.title === void 0 || typeof example.title === "string") && (example.params === void 0 || record(example.params) && Object.values(example.params).every((param) => typeof param === "string")) && (example.search === void 0 || record(example.search)))) throw new StorageError("Invalid canvas examples.", 400);
146
- if (value.version === 2) {
147
- const workspaces = value.workspaces;
148
- if (new Set(workspaces.map((workspace) => workspace.id)).size !== workspaces.length || !workspaces.some((workspace) => workspace.id === value.activeWorkspaceId)) throw new StorageError("Invalid canvas workspace identity.", 400);
149
- }
150
- return parseCanvasManifest(value);
151
- }
23
+ const record$2 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
152
24
  const revisionOf = (text) => createHash("sha256").update(text).digest("hex");
153
25
  const respond$1 = (res, status, body) => {
154
26
  res.writeHead(status, {
@@ -157,32 +29,33 @@ const respond$1 = (res, status, body) => {
157
29
  });
158
30
  res.end(JSON.stringify(body));
159
31
  };
160
- async function readBody(req) {
32
+ async function readBody(req, label, maxBytes) {
161
33
  let size = 0;
162
34
  const chunks = [];
163
35
  for await (const chunk of req) {
164
36
  size += Buffer.byteLength(chunk);
165
- if (size > maxBytes) throw new StorageError("Canvas manifest exceeds 256 KB.", 413);
37
+ if (size > maxBytes) throw new StorageError(`${label} file exceeds ${maxBytes / 1024} KB.`, 413);
166
38
  chunks.push(Buffer.from(chunk));
167
39
  }
168
40
  try {
169
41
  return JSON.parse(Buffer.concat(chunks).toString("utf8"));
170
42
  } catch {
171
- throw new StorageError("Expected JSON canvas data.", 400);
43
+ throw new StorageError(`Expected JSON ${label.toLowerCase()} data.`, 400);
172
44
  }
173
45
  }
174
46
  /** Fixed-path development persistence only. Never commits or pushes repository changes. */
175
- async function configureCanvasStorage(server, filename = "povio.canvas.json") {
47
+ async function configureRepositoryStorage(server, { filename, route, field, label, decode, maxBytes = 262144 }) {
48
+ const noun = label.toLowerCase();
176
49
  const root = await realpath(server.config.root);
177
50
  const filePath = resolve(root, filename);
178
51
  const parent = dirname(filePath);
179
- if (!inside(root, filePath) || !filePath.endsWith(".json")) throw new Error("Povio UI canvasFile must be a JSON file inside this app's Vite root.");
52
+ if (!inside$1(root, filePath) || !filePath.endsWith(".json")) throw new Error(`Povio UI ${noun} file must be a JSON file inside this app's Vite root.`);
180
53
  const guard = async () => {
181
- if (await realpath(parent) !== parent) throw new StorageError("Canvas file parent cannot be a symbolic link.", 409);
54
+ if (await realpath(parent) !== parent) throw new StorageError(`${label} file parent cannot be a symbolic link.`, 409);
182
55
  try {
183
56
  const stat = await lstat(filePath);
184
- if (stat.isSymbolicLink() || !stat.isFile()) throw new StorageError("Canvas file must be a regular file, not a symbolic link.", 409);
185
- if (stat.size > maxBytes) throw new StorageError("Canvas manifest exceeds 256 KB.", 413);
57
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new StorageError(`${label} file must be a regular file, not a symbolic link.`, 409);
58
+ if (stat.size > maxBytes) throw new StorageError(`${label} file exceeds ${maxBytes / 1024} KB.`, 413);
186
59
  } catch (error) {
187
60
  if (error.code !== "ENOENT") throw error;
188
61
  }
@@ -190,7 +63,7 @@ async function configureCanvasStorage(server, filename = "povio.canvas.json") {
190
63
  await guard();
191
64
  const file = relative(root, filePath).split(sep).join("/");
192
65
  const connection = {
193
- endpoint: `${new URL(server.config.base, "http://localhost").pathname.replace(/\/?$/, "/")}__povio_ui/canvas`,
66
+ endpoint: `${new URL(server.config.base, "http://localhost").pathname.replace(/\/?$/, "/")}__povio_ui/${route}`,
194
67
  token: randomUUID(),
195
68
  file
196
69
  };
@@ -212,7 +85,7 @@ async function configureCanvasStorage(server, filename = "povio.canvas.json") {
212
85
  }
213
86
  };
214
87
  const handle = async (req, res) => {
215
- if (req.headers["x-povio-canvas-token"] !== connection.token) throw new StorageError("Invalid canvas storage token.", 403);
88
+ if (req.headers["x-povio-ui-storage-token"] !== connection.token) throw new StorageError(`Invalid ${noun} storage token.`, 403);
216
89
  const origin = req.headers.origin;
217
90
  let validOrigin = true;
218
91
  if (origin) try {
@@ -220,18 +93,18 @@ async function configureCanvasStorage(server, filename = "povio.canvas.json") {
220
93
  } catch {
221
94
  validOrigin = false;
222
95
  }
223
- if (!validOrigin || req.headers["sec-fetch-site"] === "cross-site") throw new StorageError("Canvas storage requires the app's origin.", 403);
96
+ if (!validOrigin || req.headers["sec-fetch-site"] === "cross-site") throw new StorageError(`${label} storage requires the app's origin.`, 403);
224
97
  if (req.method === "GET") {
225
98
  await pending;
226
99
  const current = await readCurrent();
227
- let manifest = null;
100
+ let value = null;
228
101
  if (current.text !== null) try {
229
- manifest = decodeManifest(JSON.parse(current.text));
102
+ value = decode(JSON.parse(current.text));
230
103
  } catch {
231
- throw new StorageError("Repository canvas file is invalid. Correct it before saving.", 409);
104
+ throw new StorageError(`Repository ${noun} file is invalid. Correct it before saving.`, 409);
232
105
  }
233
106
  return respond$1(res, 200, {
234
- manifest,
107
+ [field]: value,
235
108
  revision: current.revision,
236
109
  file
237
110
  });
@@ -241,13 +114,13 @@ async function configureCanvasStorage(server, filename = "povio.canvas.json") {
241
114
  throw new StorageError("Use GET or POST.", 405);
242
115
  }
243
116
  if (!req.headers["content-type"]?.startsWith("application/json")) throw new StorageError("Expected application/json.", 415);
244
- const body = await readBody(req);
245
- if (!record(body) || !(body.revision === null || typeof body.revision === "string")) throw new StorageError("Expected manifest and revision.", 400);
246
- const manifest = decodeManifest(body.manifest);
247
- const text = `${JSON.stringify(manifest, null, 2)}\n`;
248
- if (Buffer.byteLength(text) > maxBytes) throw new StorageError("Canvas manifest exceeds 256 KB.", 413);
117
+ const body = await readBody(req, label, maxBytes);
118
+ if (!record$2(body) || !(body.revision === null || typeof body.revision === "string")) throw new StorageError(`Expected ${field} and revision.`, 400);
119
+ const value = decode(body[field]);
120
+ const text = `${JSON.stringify(value, null, 2)}\n`;
121
+ if (Buffer.byteLength(text) > maxBytes) throw new StorageError(`${label} file exceeds ${maxBytes / 1024} KB.`, 413);
249
122
  const write = pending.then(async () => {
250
- if ((await readCurrent()).revision !== body.revision) throw new StorageError("Canvas file changed. Reload its saved layout before saving again.", 409);
123
+ if ((await readCurrent()).revision !== body.revision) throw new StorageError(`${label} file changed. Reload it before saving again.`, 409);
251
124
  const temporary = `${filePath}.povio-${randomUUID()}.tmp`;
252
125
  try {
253
126
  await writeFile(temporary, text, {
@@ -270,11 +143,479 @@ async function configureCanvasStorage(server, filename = "povio.canvas.json") {
270
143
  };
271
144
  server.middlewares.use((req, res, next) => {
272
145
  if (req.url?.split("?")[0] !== connection.endpoint) return next();
273
- handle(req, res).catch((error) => respond$1(res, error instanceof StorageError ? error.status : 500, { error: error instanceof Error ? error.message : "Could not access canvas file." }));
146
+ handle(req, res).catch((error) => respond$1(res, error instanceof StorageError ? error.status : 500, { error: error instanceof Error ? error.message : `Could not access ${noun} file.` }));
274
147
  });
148
+ const isFile = (path) => resolve(path) === filePath;
275
149
  return {
276
150
  connection,
277
- isStorageFile: (path) => path === filePath || path.startsWith(`${filePath}.povio-`)
151
+ isFile,
152
+ isStorageFile: (path) => isFile(path) || resolve(path).startsWith(`${filePath}.povio-`),
153
+ revisionOf
154
+ };
155
+ }
156
+ //#endregion
157
+ //#region src/app-canvas/manifest.ts
158
+ function createCanvasWorkspace(name, id) {
159
+ return {
160
+ id,
161
+ name: name.trim() || "Workspace",
162
+ enabledPages: null,
163
+ positions: {}
164
+ };
165
+ }
166
+ function createCanvasManifest() {
167
+ return {
168
+ version: 2,
169
+ workspaces: [createCanvasWorkspace("All pages", "default")],
170
+ activeWorkspaceId: "default",
171
+ examples: {},
172
+ breakpoint: "desktop"
173
+ };
174
+ }
175
+ function isRecord$1(value) {
176
+ return value !== null && typeof value === "object" && !Array.isArray(value);
177
+ }
178
+ function isJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
179
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
180
+ if (typeof value === "number") return Number.isFinite(value);
181
+ if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
182
+ ancestors.add(value);
183
+ const valid = Object.values(value).every((child) => isJSON(child, ancestors, depth + 1));
184
+ ancestors.delete(value);
185
+ return valid;
186
+ }
187
+ function parsePositions(input) {
188
+ return Object.fromEntries(Object.entries(isRecord$1(input) ? input : {}).flatMap(([breakpoint, entries]) => {
189
+ if (!isRecord$1(entries)) return [];
190
+ return [[breakpoint, Object.fromEntries(Object.entries(entries).flatMap(([id, point]) => isRecord$1(point) && typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y) ? [[id, {
191
+ x: point.x,
192
+ y: point.y
193
+ }]] : []))]];
194
+ }));
195
+ }
196
+ /** Sanitize either stored JSON text or an imported value, migrating v1 positions into the default workspace. */
197
+ function parseCanvasManifest(input) {
198
+ const fallback = createCanvasManifest();
199
+ let value = input;
200
+ if (typeof value === "string") try {
201
+ value = JSON.parse(value);
202
+ } catch {
203
+ return fallback;
204
+ }
205
+ if (!isRecord$1(value) || value.version !== 1 && value.version !== 2) return fallback;
206
+ const examples = Object.fromEntries(Object.entries(isRecord$1(value.examples) ? value.examples : {}).flatMap(([id, example]) => {
207
+ if (!isRecord$1(example)) return [];
208
+ const clean = {};
209
+ if (typeof example.title === "string") clean.title = example.title;
210
+ if (isRecord$1(example.params)) clean.params = Object.fromEntries(Object.entries(example.params).filter((entry) => typeof entry[1] === "string"));
211
+ if (isRecord$1(example.search)) clean.search = Object.fromEntries(Object.entries(example.search).filter(([, item]) => isJSON(item)));
212
+ return [[id, clean]];
213
+ }));
214
+ const workspaces = [];
215
+ const ids = /* @__PURE__ */ new Set();
216
+ if (value.version === 1) workspaces.push({
217
+ ...createCanvasWorkspace("All pages", "default"),
218
+ positions: parsePositions(value.positions)
219
+ });
220
+ else if (Array.isArray(value.workspaces)) for (const entry of value.workspaces) {
221
+ if (!isRecord$1(entry) || typeof entry.id !== "string" || !entry.id.trim() || ids.has(entry.id)) continue;
222
+ ids.add(entry.id);
223
+ workspaces.push({
224
+ id: entry.id,
225
+ name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : "Workspace",
226
+ enabledPages: Array.isArray(entry.enabledPages) ? [...new Set(entry.enabledPages.filter((id) => typeof id === "string" && id.length > 0))] : null,
227
+ positions: parsePositions(entry.positions)
228
+ });
229
+ }
230
+ if (!workspaces.length) workspaces.push(createCanvasWorkspace("All pages", "default"));
231
+ return {
232
+ version: 2,
233
+ workspaces,
234
+ activeWorkspaceId: typeof value.activeWorkspaceId === "string" && workspaces.some((workspace) => workspace.id === value.activeWorkspaceId) ? value.activeWorkspaceId : workspaces[0].id,
235
+ examples,
236
+ breakpoint: typeof value.breakpoint === "string" && value.breakpoint.length > 0 ? value.breakpoint : fallback.breakpoint
237
+ };
238
+ }
239
+ //#endregion
240
+ //#region src/app-canvas/storage-vite.ts
241
+ const record$1 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
242
+ function validPositions(value) {
243
+ return record$1(value) && Object.values(value).every((entries) => record$1(entries) && Object.values(entries).every((position) => record$1(position) && typeof position.x === "number" && Number.isFinite(position.x) && typeof position.y === "number" && Number.isFinite(position.y)));
244
+ }
245
+ function decodeManifest(value) {
246
+ if (!record$1(value) || ![1, 2].includes(Number(value.version)) || typeof value.breakpoint !== "string" || !record$1(value.examples)) throw new StorageError("Invalid canvas manifest.", 400);
247
+ if (value.version === 1) {
248
+ if (!validPositions(value.positions)) throw new StorageError("Invalid canvas positions.", 400);
249
+ } else if (value.version !== 2 || !Array.isArray(value.workspaces) || !value.workspaces.length || typeof value.activeWorkspaceId !== "string" || !value.workspaces.every((workspace) => record$1(workspace) && typeof workspace.id === "string" && workspace.id.length > 0 && typeof workspace.name === "string" && (workspace.enabledPages === null || Array.isArray(workspace.enabledPages) && workspace.enabledPages.every((id) => typeof id === "string")) && validPositions(workspace.positions))) throw new StorageError("Invalid canvas workspaces.", 400);
250
+ if (!Object.values(value.examples).every((example) => record$1(example) && (example.title === void 0 || typeof example.title === "string") && (example.params === void 0 || record$1(example.params) && Object.values(example.params).every((param) => typeof param === "string")) && (example.search === void 0 || record$1(example.search)))) throw new StorageError("Invalid canvas examples.", 400);
251
+ if (value.version === 2) {
252
+ const workspaces = value.workspaces;
253
+ if (new Set(workspaces.map((workspace) => workspace.id)).size !== workspaces.length || !workspaces.some((workspace) => workspace.id === value.activeWorkspaceId)) throw new StorageError("Invalid canvas workspace identity.", 400);
254
+ }
255
+ return parseCanvasManifest(value);
256
+ }
257
+ /** Fixed-path development persistence for canvas workspaces. Never commits or pushes repository changes. */
258
+ function configureCanvasStorage(server, filename = "povio.canvas.json") {
259
+ return configureRepositoryStorage(server, {
260
+ filename,
261
+ route: "canvas",
262
+ field: "manifest",
263
+ label: "Canvas",
264
+ decode: decodeManifest
265
+ });
266
+ }
267
+ //#endregion
268
+ //#region src/app-canvas/storage-protocol.ts
269
+ const canvasStorageMetaName = "povio-ui-canvas-storage";
270
+ //#endregion
271
+ //#region src/configurator/theme-versions.ts
272
+ const themeVersionsMetaName = "povio-ui-theme-versions";
273
+ function createThemeVersion(name, id, configuration) {
274
+ return {
275
+ id,
276
+ name: name.trim() || "Theme",
277
+ ...configuration ? { configuration: structuredClone(configuration) } : {}
278
+ };
279
+ }
280
+ function createThemeVersions() {
281
+ return {
282
+ version: 1,
283
+ versions: [createThemeVersion("Default", "default")],
284
+ activeVersionId: "default"
285
+ };
286
+ }
287
+ function isRecord(value) {
288
+ return value !== null && typeof value === "object" && !Array.isArray(value);
289
+ }
290
+ function safeJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
291
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
292
+ if (typeof value === "number") return Number.isFinite(value);
293
+ if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
294
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
295
+ ancestors.add(value);
296
+ const valid = Object.entries(value).every(([key, child]) => ![
297
+ "__proto__",
298
+ "prototype",
299
+ "constructor"
300
+ ].includes(key) && safeJSON(child, ancestors, depth + 1));
301
+ ancestors.delete(value);
302
+ return valid;
303
+ }
304
+ const stringMap = (value) => isRecord(value) && Object.values(value).every((item) => typeof item === "string");
305
+ const variantValue = (value) => value === null || typeof value === "string" || typeof value === "boolean";
306
+ function validCvaEdits(value) {
307
+ return isRecord(value) && Object.entries(value).every(([slot, definition]) => {
308
+ if (!/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*$/.test(slot) || !isRecord(definition) || !Array.isArray(definition.base) || !definition.base.every((item) => typeof item === "string") || !isRecord(definition.config)) return false;
309
+ const config = definition.config;
310
+ return isRecord(config.variants) && Object.values(config.variants).every(stringMap) && isRecord(config.defaultVariants) && Object.values(config.defaultVariants).every(variantValue) && Array.isArray(config.compoundVariants) && config.compoundVariants.every((compound) => isRecord(compound) && Object.values(compound).every((item) => variantValue(item) || Array.isArray(item) && (item.every((part) => typeof part === "string") || item.every((part) => typeof part === "boolean"))));
311
+ });
312
+ }
313
+ /** Returns an independent JSON configuration, or undefined for invalid/unset overrides. */
314
+ function parseUIConfiguration(value) {
315
+ if (!isRecord(value) || !safeJSON(value) || !isRecord(value.theme) || ![
316
+ "light",
317
+ "dark",
318
+ "theme"
319
+ ].every((key) => stringMap(value.theme && value.theme[key])) || !isRecord(value.config) || value.overrides !== void 0 && !validCvaEdits(value.overrides)) return void 0;
320
+ return JSON.parse(JSON.stringify(value));
321
+ }
322
+ /** Returns an independent component configuration, or undefined when invalid. */
323
+ function parseComponentConfiguration(value) {
324
+ if (!isRecord(value) || !safeJSON(value) || !isRecord(value.config) || value.overrides !== void 0 && !validCvaEdits(value.overrides)) return void 0;
325
+ const copy = JSON.parse(JSON.stringify(value));
326
+ return {
327
+ config: copy.config ?? {},
328
+ overrides: copy.overrides ?? {}
329
+ };
330
+ }
331
+ /** Sanitize stored JSON text or a repository value. */
332
+ function parseThemeVersions(input) {
333
+ const fallback = createThemeVersions();
334
+ let value = input;
335
+ if (typeof value === "string") try {
336
+ value = JSON.parse(value);
337
+ } catch {
338
+ return fallback;
339
+ }
340
+ if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.versions)) return fallback;
341
+ const versions = [];
342
+ const ids = /* @__PURE__ */ new Set();
343
+ for (const entry of value.versions) {
344
+ if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim() || ids.has(entry.id)) continue;
345
+ ids.add(entry.id);
346
+ const configuration = parseUIConfiguration(entry.configuration);
347
+ versions.push({
348
+ id: entry.id,
349
+ name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : "Theme",
350
+ ...configuration ? { configuration } : {}
351
+ });
352
+ }
353
+ if (!versions.length) return fallback;
354
+ return {
355
+ version: 1,
356
+ versions,
357
+ activeVersionId: typeof value.activeVersionId === "string" && versions.some((version) => version.id === value.activeVersionId) ? value.activeVersionId : versions[0].id
358
+ };
359
+ }
360
+ //#endregion
361
+ //#region src/configurator/theme-versions-vite.ts
362
+ const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
363
+ function decodeThemeVersions(value) {
364
+ if (!record(value) || value.version !== 1 || !Array.isArray(value.versions) || !value.versions.length || typeof value.activeVersionId !== "string" || !value.versions.every((version) => record(version) && typeof version.id === "string" && version.id.length > 0 && typeof version.name === "string" && (version.configuration === void 0 || parseUIConfiguration(version.configuration) !== void 0))) throw new StorageError("Invalid theme versions.", 400);
365
+ const ids = value.versions.map((version) => version.id);
366
+ if (new Set(ids).size !== ids.length || !ids.includes(value.activeVersionId)) throw new StorageError("Invalid theme version identity.", 400);
367
+ return parseThemeVersions(value);
368
+ }
369
+ /** Theme versions shared through the repository, separate from canvas layout. Never commits or pushes. */
370
+ function configureThemeVersionsStorage(server, filename = "povio.themes.json") {
371
+ return configureRepositoryStorage(server, {
372
+ filename,
373
+ route: "themes",
374
+ field: "themes",
375
+ label: "Theme versions",
376
+ decode: decodeThemeVersions,
377
+ maxBytes: 4194304
378
+ });
379
+ }
380
+ //#endregion
381
+ //#region src/configurator/component-configuration-vite.ts
382
+ /** The component settings Apply theme writes beside the theme CSS. Hot updates reach the app like the CSS. */
383
+ function configureComponentConfigurationStorage(server, filename) {
384
+ return configureRepositoryStorage(server, {
385
+ filename,
386
+ route: "configuration",
387
+ field: "configuration",
388
+ label: "UI configuration",
389
+ decode: (value) => {
390
+ const configuration = parseComponentConfiguration(value);
391
+ if (!configuration) throw new StorageError("Invalid UI configuration.", 400);
392
+ return configuration;
393
+ }
394
+ });
395
+ }
396
+ //#endregion
397
+ //#region src/configurator/theme-writer-protocol.ts
398
+ /** Shared by the browser configurator and the isolated Node Vite entry. */
399
+ const themeWriterMetaName = "povio-ui-theme-writer";
400
+ //#endregion
401
+ //#region src/configurator/fontsource-vite.ts
402
+ const execute = promisify(execFile);
403
+ const fontId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
404
+ const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
405
+ var FontError = class extends Error {
406
+ constructor(message, status = 400) {
407
+ super(message);
408
+ this.status = status;
409
+ }
410
+ };
411
+ const dependencies = {
412
+ fetch: (...args) => globalThis.fetch(...args),
413
+ install: async (command, args, cwd) => {
414
+ await execute(command, args, {
415
+ cwd,
416
+ shell: false,
417
+ timeout: 18e4,
418
+ maxBuffer: 1048576,
419
+ env: {
420
+ ...process.env,
421
+ npm_config_ignore_scripts: "true",
422
+ YARN_ENABLE_SCRIPTS: "false"
423
+ }
424
+ });
425
+ }
426
+ };
427
+ const exists = async (file) => {
428
+ try {
429
+ await access(file);
430
+ return true;
431
+ } catch {
432
+ return false;
433
+ }
434
+ };
435
+ const inside = (root, file) => {
436
+ const path = relative(root, file);
437
+ return !!path && !isAbsolute(path) && path !== ".." && !path.startsWith(`..${sep}`);
438
+ };
439
+ async function manager(root) {
440
+ let directory = root;
441
+ while (true) {
442
+ let declared = "";
443
+ try {
444
+ declared = JSON.parse(await readFile(resolve(directory, "package.json"), "utf8")).packageManager ?? "";
445
+ } catch {}
446
+ if (/^(bun|npm|pnpm|yarn)@/.test(declared)) return {
447
+ name: declared.split("@")[0],
448
+ modernYarn: /^yarn@[2-9]/.test(declared)
449
+ };
450
+ for (const [file, name] of [
451
+ ["bun.lock", "bun"],
452
+ ["bun.lockb", "bun"],
453
+ ["pnpm-lock.yaml", "pnpm"],
454
+ ["package-lock.json", "npm"],
455
+ ["yarn.lock", "yarn"]
456
+ ]) if (await exists(resolve(directory, file))) return {
457
+ name,
458
+ modernYarn: name === "yarn" && (await readFile(resolve(directory, file), "utf8")).includes("__metadata:")
459
+ };
460
+ const parent = dirname(directory);
461
+ if (parent === directory) break;
462
+ directory = parent;
463
+ }
464
+ throw new FontError("No existing package manager was detected. Add a lockfile or packageManager field first.", 409);
465
+ }
466
+ async function metadata(fetcher, url) {
467
+ const response = await fetcher(url, {
468
+ redirect: "error",
469
+ signal: AbortSignal.timeout(2e4)
470
+ });
471
+ if (!response.ok) throw new FontError(response.status === 404 ? "Fontsource font/package was not found." : "Fontsource metadata could not be loaded.", response.status === 404 ? 400 : 502);
472
+ return response.json();
473
+ }
474
+ /** Internal service factory: injection is for server tests, never a plugin/client execution option. */
475
+ async function createFontsourceInstaller(rootInput, cssName, deps = dependencies) {
476
+ const root = await realpath(rootInput), css = resolve(root, cssName), packageFile = resolve(root, "package.json");
477
+ if (!inside(root, css) || !css.endsWith(".css")) throw new FontError("fontCssFile must be a CSS file inside the app root.");
478
+ if (!await exists(css) || !await exists(packageFile)) return void 0;
479
+ const guard = async () => {
480
+ if (await realpath(css) !== css || await realpath(packageFile) !== packageFile) throw new FontError("Font CSS and package.json must not use symbolic links.", 409);
481
+ };
482
+ await guard();
483
+ let pending = Promise.resolve();
484
+ const install = (id) => {
485
+ const task = pending.then(async () => {
486
+ if (typeof id !== "string" || id.length > 100 || !fontId.test(id)) throw new FontError("Expected a valid Fontsource font ID.");
487
+ await guard();
488
+ const font = await metadata(deps.fetch, `https://api.fontsource.org/v1/fonts/${id}`);
489
+ if (font.id !== id || typeof font.family !== "string" || !font.family.trim() || !Array.isArray(font.weights) || !Array.isArray(font.styles)) throw new FontError("Invalid Fontsource font metadata.", 502);
490
+ let variable = false;
491
+ if (font.variable) variable = !!(await metadata(deps.fetch, `https://api.fontsource.org/v1/variable/${id}`)).axes?.wght;
492
+ const packageName = `${variable ? "@fontsource-variable" : "@fontsource"}/${id}`;
493
+ const registry = await metadata(deps.fetch, `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`);
494
+ if (registry.name !== packageName || typeof registry.version !== "string" || !versionPattern.test(registry.version)) throw new FontError("Invalid npm package version metadata.", 502);
495
+ const weights = font.weights.filter((weight) => typeof weight === "number" && Number.isInteger(weight) && weight >= 1 && weight <= 1e3);
496
+ const styles = ["normal", "italic"].filter((style) => font.styles.includes(style));
497
+ if (!weights.length || !styles.length) throw new FontError("This font has no supported styles.");
498
+ const imports = variable ? styles.map((style) => `${packageName}/wght${style === "italic" ? "-italic" : ""}.css`) : weights.flatMap((weight) => styles.map((style) => `${packageName}/${weight}${style === "italic" ? "-italic" : ""}.css`));
499
+ const require = createRequire(packageFile);
500
+ const installed = async (exact = false) => {
501
+ try {
502
+ const declared = JSON.parse(await readFile(packageFile, "utf8"));
503
+ if (!declared.dependencies?.[packageName] && !declared.devDependencies?.[packageName]) return false;
504
+ const pkg = JSON.parse(await readFile(require.resolve(`${packageName}/package.json`), "utf8"));
505
+ if (exact && pkg.version !== registry.version) return false;
506
+ if (!versionPattern.test(pkg.version) || pkg.version.split(".")[0] !== registry.version.split(".")[0]) return false;
507
+ await Promise.all(imports.map((item) => access(require.resolve(item))));
508
+ return true;
509
+ } catch {
510
+ return false;
511
+ }
512
+ };
513
+ const alreadyInstalled = await installed();
514
+ if (!alreadyInstalled) {
515
+ const pm = await manager(root);
516
+ const spec = `${packageName}@${registry.version}`;
517
+ const args = pm.name === "npm" ? [
518
+ "install",
519
+ "--save-exact",
520
+ "--ignore-scripts",
521
+ spec
522
+ ] : pm.name === "yarn" ? [
523
+ "add",
524
+ "--exact",
525
+ ...pm.modernYarn ? [] : ["--ignore-scripts"],
526
+ spec
527
+ ] : [
528
+ "add",
529
+ "--exact",
530
+ "--ignore-scripts",
531
+ spec
532
+ ];
533
+ if (pm.name === "pnpm") args[1] = "--save-exact";
534
+ try {
535
+ await deps.install(pm.name, args, root);
536
+ } catch {
537
+ throw new FontError("Font installation failed. Existing CSS imports were left unchanged.", 502);
538
+ }
539
+ if (!await installed(true)) throw new FontError("Installed font files could not be verified. CSS imports were left unchanged.", 502);
540
+ }
541
+ await guard();
542
+ const source = await readFile(css, "utf8");
543
+ const existing = new Set([...source.matchAll(/@import\s+(?:url\(\s*)?["']([^"']+)["']/g)].map((match) => match[1]));
544
+ const missing = imports.filter((item) => !existing.has(item));
545
+ if (missing.length) {
546
+ const additions = missing.map((item) => `@import "${item}";`).join("\n");
547
+ const charset = source.match(/^(?:\uFEFF)?@charset\s+["'][^"']+["'];\s*/i)?.[0] ?? "";
548
+ const next = `${charset}${additions}\n${source.slice(charset.length)}`;
549
+ const temporary = `${css}.fontsource-${randomUUID()}.tmp`;
550
+ try {
551
+ await writeFile(temporary, next, { flag: "wx" });
552
+ await guard();
553
+ await rename(temporary, css);
554
+ } finally {
555
+ await unlink(temporary).catch(() => {});
556
+ }
557
+ }
558
+ return {
559
+ family: `${font.family}${variable ? " Variable" : ""}`,
560
+ packageName,
561
+ imports,
562
+ alreadyInstalled
563
+ };
564
+ });
565
+ pending = task.then(() => {}, () => {});
566
+ return task;
567
+ };
568
+ return {
569
+ cssFile: relative(root, css).split(sep).join("/"),
570
+ install
571
+ };
572
+ }
573
+ async function configureFontsource(server, cssName, token, base) {
574
+ const installer = await createFontsourceInstaller(server.config.root, cssName);
575
+ if (!installer) return void 0;
576
+ const endpoint = `${base}__povio_ui/fontsource`;
577
+ server.middlewares.use((req, res, next) => {
578
+ if (req.url?.split("?")[0] !== endpoint) return next();
579
+ handle(req, res).catch((error) => respond(res, error instanceof FontError ? error.status : 500, { error: error instanceof Error ? error.message : "Could not install font." }));
580
+ });
581
+ function respond(res, status, body) {
582
+ res.writeHead(status, {
583
+ "Content-Type": "application/json",
584
+ "Cache-Control": "no-store"
585
+ });
586
+ res.end(JSON.stringify(body));
587
+ }
588
+ async function handle(req, res) {
589
+ if (req.method !== "POST") throw new FontError("Use POST to install a font.", 405);
590
+ if (req.headers["x-povio-ui-theme-token"] !== token) throw new FontError("Invalid theme token.", 403);
591
+ if (req.headers["sec-fetch-site"] === "cross-site") throw new FontError("Font installation requires the app origin.", 403);
592
+ if (req.headers.origin) {
593
+ let sameOrigin = false;
594
+ try {
595
+ sameOrigin = new URL(req.headers.origin).origin === `${server.config.server.https ? "https" : "http"}://${req.headers.host}`;
596
+ } catch {}
597
+ if (!sameOrigin) throw new FontError("Font installation requires the app origin.", 403);
598
+ }
599
+ if (!req.headers["content-type"]?.startsWith("application/json")) throw new FontError("Expected application/json.", 415);
600
+ let size = 0;
601
+ const chunks = [];
602
+ for await (const chunk of req) {
603
+ size += Buffer.byteLength(chunk);
604
+ if (size > 1024) throw new FontError("Font request is too large.", 413);
605
+ chunks.push(Buffer.from(chunk));
606
+ }
607
+ let data;
608
+ try {
609
+ data = JSON.parse(Buffer.concat(chunks).toString("utf8"));
610
+ } catch {
611
+ throw new FontError("Expected JSON containing a font ID.");
612
+ }
613
+ const id = data && typeof data === "object" && !Array.isArray(data) ? data.id : void 0;
614
+ respond(res, 200, await installer.install(id));
615
+ }
616
+ return {
617
+ endpoint,
618
+ cssFile: installer.cssFile
278
619
  };
279
620
  }
280
621
  //#endregion
@@ -347,10 +688,6 @@ function updateThemeSource(source, exportedCss) {
347
688
  return source;
348
689
  }
349
690
  //#endregion
350
- //#region src/configurator/theme-writer-protocol.ts
351
- /** Shared by the browser configurator and the isolated Node Vite entry. */
352
- const themeWriterMetaName = "povio-ui-theme-writer";
353
- //#endregion
354
691
  //#region src/configurator/vite.ts
355
692
  var ThemeWriteError = class extends Error {
356
693
  constructor(message, status) {
@@ -384,24 +721,30 @@ function uiConfigurator(options = {}) {
384
721
  let connection;
385
722
  let pendingWrite = Promise.resolve();
386
723
  let canvasStorage;
724
+ let themeVersionsStorage;
725
+ let componentStorage;
387
726
  return {
388
727
  name: "vite-plugin-povio-ui-configurator",
389
728
  apply: (_config, environment) => environment.command === "serve" && !environment.isPreview,
390
729
  async configureServer(server) {
391
730
  if (options.canvasFile !== false) canvasStorage = await configureCanvasStorage(server, options.canvasFile);
731
+ if (options.themeVersionsFile !== false) themeVersionsStorage = await configureThemeVersionsStorage(server, options.themeVersionsFile);
392
732
  const root = await realpath(server.config.root);
393
733
  const configuredFile = resolve(server.config.root, options.themeFile ?? "src/styles/theme.css");
394
734
  const themeFile = await realpath(configuredFile);
395
735
  const file = relative(root, themeFile);
396
736
  if (!file || isAbsolute(file) || file === ".." || file.startsWith(`..${sep}`)) throw new Error("Povio UI themeFile must be inside this app's Vite root.");
397
737
  if (!themeFile.endsWith(".css")) throw new Error("Povio UI themeFile must be a CSS file.");
398
- const endpoint = `${new URL(server.config.base, "http://localhost").pathname.replace(/\/?$/, "/")}__povio_ui/theme`;
738
+ if (options.configurationFile !== false) componentStorage = await configureComponentConfigurationStorage(server, options.configurationFile ?? join(dirname(file), "ui-configuration.json"));
739
+ const base = new URL(server.config.base, "http://localhost").pathname.replace(/\/?$/, "/");
740
+ const endpoint = `${base}__povio_ui/theme`;
399
741
  connection = {
400
742
  endpoint,
401
743
  token: randomUUID(),
402
744
  file: file.split(sep).join("/")
403
745
  };
404
746
  const token = connection.token;
747
+ if (options.fontCssFile !== false) connection.fontsource = await configureFontsource(server, options.fontCssFile ?? "src/styles/globals.css", token, base);
405
748
  const save = async (request, response) => {
406
749
  if (request.method !== "POST") {
407
750
  response.setHeader("Allow", "POST");
@@ -436,26 +779,59 @@ function uiConfigurator(options = {}) {
436
779
  });
437
780
  });
438
781
  },
439
- handleHotUpdate(context) {
440
- if (canvasStorage?.isStorageFile(context.file)) return [];
782
+ async handleHotUpdate(context) {
783
+ const storage = [canvasStorage, themeVersionsStorage].find((item) => item?.isStorageFile(context.file));
784
+ if (!storage) return;
785
+ if (storage.isFile(context.file)) {
786
+ const text = await Promise.resolve(context.read()).catch(() => null);
787
+ const change = {
788
+ endpoint: storage.connection.endpoint,
789
+ revision: text ? storage.revisionOf(text) : null
790
+ };
791
+ context.server.ws.send({
792
+ type: "custom",
793
+ event: repositoryStorageChangedEvent,
794
+ data: change
795
+ });
796
+ }
797
+ return [];
441
798
  },
442
799
  transformIndexHtml() {
443
800
  if (!connection) return;
444
- return [{
445
- tag: "meta",
446
- attrs: {
447
- name: themeWriterMetaName,
448
- content: JSON.stringify(connection)
449
- },
450
- injectTo: "head"
451
- }, ...canvasStorage ? [{
452
- tag: "meta",
453
- attrs: {
454
- name: canvasStorageMetaName,
455
- content: JSON.stringify(canvasStorage.connection)
801
+ return [
802
+ {
803
+ tag: "meta",
804
+ attrs: {
805
+ name: themeWriterMetaName,
806
+ content: JSON.stringify(connection)
807
+ },
808
+ injectTo: "head"
456
809
  },
457
- injectTo: "head"
458
- }] : []];
810
+ ...canvasStorage ? [{
811
+ tag: "meta",
812
+ attrs: {
813
+ name: canvasStorageMetaName,
814
+ content: JSON.stringify(canvasStorage.connection)
815
+ },
816
+ injectTo: "head"
817
+ }] : [],
818
+ ...componentStorage ? [{
819
+ tag: "meta",
820
+ attrs: {
821
+ name: componentConfigurationMetaName,
822
+ content: JSON.stringify(componentStorage.connection)
823
+ },
824
+ injectTo: "head"
825
+ }] : [],
826
+ ...themeVersionsStorage ? [{
827
+ tag: "meta",
828
+ attrs: {
829
+ name: themeVersionsMetaName,
830
+ content: JSON.stringify(themeVersionsStorage.connection)
831
+ },
832
+ injectTo: "head"
833
+ }] : []
834
+ ];
459
835
  }
460
836
  };
461
837
  }