@synopackageland/ui 0.6.1 → 0.8.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/README.md +2 -2
- package/dist/cli.js +57 -55
- package/dist/index.js +2763 -2763
- package/dist/json/catalog.d.ts +1 -1
- package/dist/json-ai-prompt.md +6 -3
- package/dist/json-preview/preview.js +720 -720
- package/dist/json.js +22 -22
- package/dist/reka-runtime.json +1 -1
- package/dist/theme.css +1 -1
- package/dist/themes/dsm7-embedded-reset.css +1 -1
- package/dist/themes/dsm7-embedded-theme.css +1 -1
- package/dist/themes/dsm7-embedded.css +1 -1
- package/dist/themes/dsm7.css +1 -1
- package/docs/json-ui.md +9 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Import the components you need into your Vue application.
|
|
|
7
7
|
|
|
8
8
|
For DSM window / full-page Portal / phone layouts, see
|
|
9
9
|
[Responsive DSM application layout](docs/adaptive-layout.md). To generate UI
|
|
10
|
-
as JSON (including with an AI and the `syno-ui` validate/render CLI), see
|
|
10
|
+
as JSON (including with an AI and the `syno-pkg-land-ui` validate/render CLI), see
|
|
11
11
|
[Declarative DSM UI](docs/json-ui.md).
|
|
12
12
|
|
|
13
13
|
Synology Package Land UI provides Vue components for Synology package applications,
|
|
@@ -136,7 +136,7 @@ import { AppRoot, ToastProvider } from '@synopackageland/ui'
|
|
|
136
136
|
- makes the app root the containing block of fixed overlays, so dialogs,
|
|
137
137
|
sheets and toasts stay inside the window.
|
|
138
138
|
|
|
139
|
-
`AppRoot` renders `<div data-ui-app data-syno-ui>` and routes every overlay
|
|
139
|
+
`AppRoot` renders `<div data-ui-app data-syno-pkg-land-ui>` and routes every overlay
|
|
140
140
|
portal (Dialog, Sheet, Toast, Popover, Select, DropdownMenu, ContextMenu,
|
|
141
141
|
Tooltip) into itself, not `document.body`, where the scoped tokens
|
|
142
142
|
and reset would not reach them. Each `AppRoot` teleports into its own element;
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFile as
|
|
2
|
+
import { readFile as S, mkdtemp as E, mkdir as M, writeFile as D, rm as R } from "node:fs/promises";
|
|
3
3
|
import { createServer as F } from "node:http";
|
|
4
4
|
import { tmpdir as H } from "node:os";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as g, win32 as U, posix as j } from "node:path";
|
|
6
6
|
import { spawn as I } from "node:child_process";
|
|
7
|
-
import { existsSync as
|
|
7
|
+
import { existsSync as f } from "node:fs";
|
|
8
8
|
import { setTimeout as L } from "node:timers/promises";
|
|
9
9
|
import { synoJsonAiPrompt as T, synoJsonSchema as W, describeSynoJsonComponent as _, closestSynoJsonComponent as V, listSynoJsonComponents as B, validateSynoJsonSpec as G } from "./json.js";
|
|
10
10
|
const K = [
|
|
11
11
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
12
12
|
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
13
13
|
], X = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"], z = ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"];
|
|
14
|
-
function Q(e = process.env, t = process.platform, n =
|
|
14
|
+
function Q(e = process.env, t = process.platform, n = f) {
|
|
15
15
|
return e.CHROME ? e.CHROME : (t === "darwin" ? K : t === "win32" ? z.flatMap((r) => e[r] ? [U.join(e[r], "Google", "Chrome", "Application", "chrome.exe")] : []) : (e.PATH ?? "").split(j.delimiter).filter(Boolean).flatMap((r) => X.map((o) => j.join(r, o)))).find(n);
|
|
16
16
|
}
|
|
17
17
|
function Y(e, t = process.env) {
|
|
@@ -42,28 +42,28 @@ async function Z(e) {
|
|
|
42
42
|
}), n.stderr.on("data", (a) => {
|
|
43
43
|
r = (r + a).slice(-4e3);
|
|
44
44
|
});
|
|
45
|
-
const o =
|
|
46
|
-
for (let a = 0; a < 100 && !
|
|
47
|
-
if (!
|
|
45
|
+
const o = g(e, "DevToolsActivePort");
|
|
46
|
+
for (let a = 0; a < 100 && !f(o) && !s && n.exitCode === null; a++) await L(100);
|
|
47
|
+
if (!f(o))
|
|
48
48
|
throw n.kill(), new Error(`Chrome failed to start: ${s?.message ?? (r || "no debugging port within 10 seconds")}`);
|
|
49
|
-
const [i] = (await
|
|
49
|
+
const [i] = (await S(o, "utf8")).split(`
|
|
50
50
|
`), c = await (await fetch(`http://127.0.0.1:${i}/json/new?about:blank`, { method: "PUT" })).json(), d = new WebSocket(c.webSocketDebuggerUrl);
|
|
51
|
-
await new Promise((a,
|
|
52
|
-
d.onopen = a, d.onerror =
|
|
51
|
+
await new Promise((a, u) => {
|
|
52
|
+
d.onopen = a, d.onerror = u;
|
|
53
53
|
});
|
|
54
54
|
let l = 1;
|
|
55
|
-
const
|
|
55
|
+
const m = /* @__PURE__ */ new Map();
|
|
56
56
|
d.onmessage = (a) => {
|
|
57
|
-
const
|
|
58
|
-
|
|
57
|
+
const u = JSON.parse(String(a.data));
|
|
58
|
+
m.get(u.id)?.(u), m.delete(u.id);
|
|
59
59
|
};
|
|
60
|
-
const b = (a,
|
|
60
|
+
const b = (a, u = {}) => new Promise((A, P) => {
|
|
61
61
|
const O = l++;
|
|
62
|
-
|
|
62
|
+
m.set(O, (w) => w.error ? P(new Error(w.error.message)) : A(w.result)), d.send(JSON.stringify({ id: O, method: a, params: u }));
|
|
63
63
|
});
|
|
64
64
|
await b("Emulation.setDeviceMetricsOverride", { width: 1100, height: 720, deviceScaleFactor: 1, mobile: !1 });
|
|
65
|
-
const
|
|
66
|
-
return { send: b, close: () => (d.close(), n.kill(),
|
|
65
|
+
const N = new Promise((a) => n.once("exit", a));
|
|
66
|
+
return { send: b, close: () => (d.close(), n.kill(), N) };
|
|
67
67
|
}
|
|
68
68
|
const q = `<!doctype html>
|
|
69
69
|
<html><head><meta charset="utf-8"><link rel="stylesheet" href="/theme.css">
|
|
@@ -76,7 +76,7 @@ function ee(e, t) {
|
|
|
76
76
|
}, s = F(async (r, o) => {
|
|
77
77
|
const i = new URL(r.url ?? "/", "http://localhost").pathname;
|
|
78
78
|
try {
|
|
79
|
-
const [c, d] = i === "/" ? [q, "text/html"] : i === "/config.json" ? [t, "application/json"] : [await
|
|
79
|
+
const [c, d] = i === "/" ? [q, "text/html"] : i === "/config.json" ? [t, "application/json"] : [await S(n[i][0]), n[i][1]];
|
|
80
80
|
o.writeHead(200, { "content-type": d }).end(c);
|
|
81
81
|
} catch {
|
|
82
82
|
o.writeHead(404).end();
|
|
@@ -105,15 +105,15 @@ async function te(e, t) {
|
|
|
105
105
|
}
|
|
106
106
|
function ne(e) {
|
|
107
107
|
return async (t) => {
|
|
108
|
-
const n = await ee(e, JSON.stringify({ spec: t.spec, state: t.state, messages: t.messages })), s = `http://127.0.0.1:${n.address().port}/`, r = await E(
|
|
108
|
+
const n = await ee(e, JSON.stringify({ spec: t.spec, state: t.state, messages: t.messages })), s = `http://127.0.0.1:${n.address().port}/`, r = await E(g(H(), "syno-pkg-land-ui-render-"));
|
|
109
109
|
let o;
|
|
110
110
|
try {
|
|
111
111
|
o = await Z(r), await M(t.out, { recursive: !0 });
|
|
112
112
|
const i = [];
|
|
113
113
|
for (const c of t.widths) {
|
|
114
114
|
await o.send("Emulation.setDeviceMetricsOverride", { width: c, height: t.height, deviceScaleFactor: 1, mobile: !1 });
|
|
115
|
-
const d = await te(o, `${s}?width=${c}`), { data: l } = await o.send("Page.captureScreenshot", { format: "png" }),
|
|
116
|
-
await D(
|
|
115
|
+
const d = await te(o, `${s}?width=${c}`), { data: l } = await o.send("Page.captureScreenshot", { format: "png" }), m = g(t.out, `${t.name}-${c}.png`);
|
|
116
|
+
await D(m, Buffer.from(l, "base64")), i.push({ width: c, screenshot: m, issues: d });
|
|
117
117
|
}
|
|
118
118
|
return i;
|
|
119
119
|
} finally {
|
|
@@ -121,7 +121,7 @@ function ne(e) {
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
const
|
|
124
|
+
const p = `Usage: syno-pkg-land-ui <command> [options]
|
|
125
125
|
|
|
126
126
|
Commands:
|
|
127
127
|
catalog [--json] List the JSON UI components
|
|
@@ -136,7 +136,7 @@ Commands:
|
|
|
136
136
|
--state <file.json> Host-provided state
|
|
137
137
|
--messages <file.json> Message catalog (enables i18n checks)
|
|
138
138
|
render <file> [options] Validate, screenshot and report layout overflow (needs Chrome, Node 22+)
|
|
139
|
-
--out <dir> Screenshot directory (default syno-ui-render)
|
|
139
|
+
--out <dir> Screenshot directory (default syno-pkg-land-ui-render)
|
|
140
140
|
--widths 390,640,960,1350 Viewport widths: phone, narrow window, DSM window, wide
|
|
141
141
|
--height <px> Viewport height (default 800)
|
|
142
142
|
--state, --messages As for validate; messages also back $computed t()
|
|
@@ -154,30 +154,30 @@ function re(e) {
|
|
|
154
154
|
}
|
|
155
155
|
return { positional: t, flags: n };
|
|
156
156
|
}
|
|
157
|
-
const
|
|
157
|
+
const y = (e) => typeof e == "string" ? e.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
|
|
158
158
|
function oe(e) {
|
|
159
159
|
return ` ${e.severity} ${e.code} ${e.path || "/"}: ${e.message}${e.hint ? `
|
|
160
160
|
hint: ${e.hint}` : ""}
|
|
161
161
|
`;
|
|
162
162
|
}
|
|
163
|
-
class
|
|
163
|
+
class h extends Error {
|
|
164
164
|
}
|
|
165
165
|
async function C(e, t) {
|
|
166
166
|
try {
|
|
167
167
|
return await e.readFile(t);
|
|
168
168
|
} catch (n) {
|
|
169
|
-
throw new
|
|
169
|
+
throw new h(`Cannot read ${t}: ${n instanceof Error ? n.message : String(n)}`);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
-
async function
|
|
172
|
+
async function $(e, t) {
|
|
173
173
|
const n = await C(e, t);
|
|
174
174
|
try {
|
|
175
175
|
return JSON.parse(n);
|
|
176
176
|
} catch (s) {
|
|
177
|
-
throw new
|
|
177
|
+
throw new h(`${t} is not valid JSON: ${s instanceof Error ? s.message : String(s)}`);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
async function
|
|
180
|
+
async function k(e, t, n) {
|
|
181
181
|
const s = await C(e, t);
|
|
182
182
|
let r;
|
|
183
183
|
try {
|
|
@@ -187,54 +187,54 @@ async function x(e, t, n) {
|
|
|
187
187
|
}
|
|
188
188
|
return G(r, n);
|
|
189
189
|
}
|
|
190
|
-
async function
|
|
190
|
+
async function x(e, t) {
|
|
191
191
|
return {
|
|
192
|
-
actions:
|
|
193
|
-
functions:
|
|
194
|
-
state: typeof t.state == "string" ? await
|
|
195
|
-
messages: typeof t.messages == "string" ? await
|
|
192
|
+
actions: y(t.actions),
|
|
193
|
+
functions: y(t.functions),
|
|
194
|
+
state: typeof t.state == "string" ? await $(e, t.state) : void 0,
|
|
195
|
+
messages: typeof t.messages == "string" ? await $(e, t.messages) : void 0
|
|
196
196
|
};
|
|
197
197
|
}
|
|
198
|
-
const
|
|
199
|
-
function
|
|
198
|
+
const J = (e, t) => e.errors.length > 0 || !!t.strict && e.warnings.length > 0;
|
|
199
|
+
function v(e) {
|
|
200
200
|
return e.errors.length || e.warnings.length ? `${e.file}: ${e.errors.length} error(s), ${e.warnings.length} warning(s)
|
|
201
201
|
${[...e.errors, ...e.warnings].map(oe).join("")}` : `${e.file}: OK
|
|
202
202
|
`;
|
|
203
203
|
}
|
|
204
204
|
async function ie(e, { positional: t, flags: n }) {
|
|
205
205
|
if (!t.length)
|
|
206
|
-
return e.stderr(
|
|
207
|
-
const s = await
|
|
208
|
-
for (const o of t) r.push({ file: o, ...await
|
|
206
|
+
return e.stderr(p), 2;
|
|
207
|
+
const s = await x(e, n), r = [];
|
|
208
|
+
for (const o of t) r.push({ file: o, ...await k(e, o, s) });
|
|
209
209
|
return e.stdout(n.json ? JSON.stringify(r, null, 2) + `
|
|
210
|
-
` : r.map(
|
|
210
|
+
` : r.map(v).join("")), r.some((o) => J(o, n)) ? 1 : 0;
|
|
211
211
|
}
|
|
212
212
|
const ae = [390, 640, 960, 1350];
|
|
213
213
|
async function ce(e, { positional: [t], flags: n }) {
|
|
214
214
|
if (!t)
|
|
215
|
-
return e.stderr(
|
|
215
|
+
return e.stderr(p), 2;
|
|
216
216
|
if (!e.render)
|
|
217
217
|
return e.stderr(`render needs a local Chrome or Chromium (set CHROME to its path) and Node 22 or newer.
|
|
218
218
|
`), 2;
|
|
219
|
-
const s = await
|
|
220
|
-
if (
|
|
219
|
+
const s = await x(e, n), r = { file: t, ...await k(e, t, s) };
|
|
220
|
+
if (J(r, n))
|
|
221
221
|
return e.stdout(n.json ? JSON.stringify({ file: t, validation: r, renders: [] }, null, 2) + `
|
|
222
|
-
` :
|
|
222
|
+
` : v(r)), 1;
|
|
223
223
|
const o = await e.render({
|
|
224
|
-
spec: await
|
|
224
|
+
spec: await $(e, t),
|
|
225
225
|
state: s.state,
|
|
226
226
|
messages: s.messages,
|
|
227
|
-
widths:
|
|
227
|
+
widths: y(n.widths)?.map(Number) ?? ae,
|
|
228
228
|
height: typeof n.height == "string" ? Number(n.height) : 800,
|
|
229
|
-
out: typeof n.out == "string" ? n.out : "syno-ui-render",
|
|
229
|
+
out: typeof n.out == "string" ? n.out : "syno-pkg-land-ui-render",
|
|
230
230
|
name: t.split(/[\\/]/).pop().replace(/\.json$/i, "")
|
|
231
231
|
}).catch((i) => {
|
|
232
|
-
throw new
|
|
232
|
+
throw new h(`render failed: ${i instanceof Error ? i.message : String(i)}`);
|
|
233
233
|
});
|
|
234
234
|
if (n.json) e.stdout(JSON.stringify({ file: t, validation: r, renders: o }, null, 2) + `
|
|
235
235
|
`);
|
|
236
236
|
else {
|
|
237
|
-
e.stdout(r.warnings.length ?
|
|
237
|
+
e.stdout(r.warnings.length ? v(r) : "");
|
|
238
238
|
for (const { width: i, screenshot: c, issues: d } of o)
|
|
239
239
|
e.stdout(`${t} @${i}px: ${d.length ? `${d.length} layout issue(s)` : "OK"} ${c}
|
|
240
240
|
` + d.map((l) => ` ${l.kind} ${l.key ?? "(outside the spec)"}: ${l.detail}
|
|
@@ -246,12 +246,14 @@ async function de(e, t) {
|
|
|
246
246
|
try {
|
|
247
247
|
return await le(re(e), t);
|
|
248
248
|
} catch (n) {
|
|
249
|
-
if (!(n instanceof
|
|
249
|
+
if (!(n instanceof h)) throw n;
|
|
250
250
|
return t.stderr(`${n.message}
|
|
251
251
|
`), 2;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
async function le(e, t) {
|
|
255
|
+
if (e.flags.help === !0)
|
|
256
|
+
return t.stdout(p), 0;
|
|
255
257
|
const [n, ...s] = e.positional, r = e.flags.json === !0;
|
|
256
258
|
switch (n) {
|
|
257
259
|
case "catalog": {
|
|
@@ -264,11 +266,11 @@ async function le(e, t) {
|
|
|
264
266
|
const o = s[0] ?? "", i = _(o);
|
|
265
267
|
if (!i) {
|
|
266
268
|
const c = V(o);
|
|
267
|
-
return t.stderr(`Unknown component ${JSON.stringify(o)}.${c ? ` Did you mean "${c}"?` : ""} Run "syno-ui catalog".
|
|
269
|
+
return t.stderr(`Unknown component ${JSON.stringify(o)}.${c ? ` Did you mean "${c}"?` : ""} Run "syno-pkg-land-ui catalog".
|
|
268
270
|
`), 2;
|
|
269
271
|
}
|
|
270
272
|
return t.stdout(r ? JSON.stringify(i, null, 2) + `
|
|
271
|
-
` :
|
|
273
|
+
` : ue(i)), 0;
|
|
272
274
|
}
|
|
273
275
|
case "schema":
|
|
274
276
|
return t.stdout(JSON.stringify(W(), null, 2) + `
|
|
@@ -280,10 +282,10 @@ async function le(e, t) {
|
|
|
280
282
|
case "render":
|
|
281
283
|
return ce(t, { positional: s, flags: e.flags });
|
|
282
284
|
default:
|
|
283
|
-
return t.stderr(
|
|
285
|
+
return t.stderr(p), 2;
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
|
-
function
|
|
288
|
+
function ue(e) {
|
|
287
289
|
const t = Object.entries(e.props).map(([n, s]) => ` ${n}${s.required ? "" : "?"}: ${JSON.stringify(s.schema)}
|
|
288
290
|
`).join("");
|
|
289
291
|
return `${e.name}: ${e.description}
|
|
@@ -297,7 +299,7 @@ example: ${JSON.stringify(e.example)}
|
|
|
297
299
|
process.exitCode = await de(process.argv.slice(2), {
|
|
298
300
|
stdout: (e) => process.stdout.write(e),
|
|
299
301
|
stderr: (e) => process.stderr.write(e),
|
|
300
|
-
readFile: (e) =>
|
|
302
|
+
readFile: (e) => S(e, "utf8"),
|
|
301
303
|
// The DevTools connection uses the WebSocket global of Node 22 and newer.
|
|
302
304
|
render: typeof WebSocket == "function" ? ne(import.meta.url) : void 0
|
|
303
305
|
});
|