paintress 0.5.2 → 0.6.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 +29 -13
- package/README.zh-CN.md +29 -13
- package/dist/cli.js +367 -63
- package/dist/index.js +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/{render-CCxYNL6A.js → render-BSf62388.js} +9 -3
- package/dist/{server-ssrAxbTR.js → server-DZRNNWac.js} +752 -129
- package/package.json +1 -1
- package/skills/paintress/SKILL.md +42 -16
- package/ui/dist/assets/{index-CP8lZ8lp.css → index-BFcvu7wr.css} +1 -1
- package/ui/dist/assets/index-CEhK1JXE.js +8 -0
- package/ui/dist/assets/marked.esm-DZCVybOJ.js +72 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-BnDj5nL8.js +0 -8
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { T as loadDoc, qt as VERSION, t as renderDocument } from "./render-
|
|
2
|
+
import { C as isPresetType, D as presetPathFor, E as notePathFor, O as readTextIfExists, S as findNodeIn, T as listPresets, _ as LineDecoder, a as ensureDaemon, b as componentsPathFor, c as isDaemonRunning, d as lockPath, f as pidPath, g as socketPath, h as sanitizeProjectName, k as setPreset, l as queryDaemon, m as safeJoin, n as runStdioServer, o as reuseDaemon, p as resolveProjectDir, r as TOOLS, s as attachPersistent, u as sendRequest, v as encodeLine, w as listComponents, x as ensureProjectScaffold, y as applyProjectPresets } from "./server-DZRNNWac.js";
|
|
3
|
+
import { T as loadDoc, qt as VERSION, t as renderDocument } from "./render-BSf62388.js";
|
|
4
4
|
import "./index.js";
|
|
5
5
|
import { chmodSync, closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -92,7 +92,9 @@ var Registry = class {
|
|
|
92
92
|
if (st.isDirectory()) {
|
|
93
93
|
if (skip.has(name)) continue;
|
|
94
94
|
walk(abs, relPath, depth + 1);
|
|
95
|
-
} else if (DESIGN_EXT.has(extname(name).toLowerCase()))
|
|
95
|
+
} else if (DESIGN_EXT.has(extname(name).toLowerCase())) {
|
|
96
|
+
if (!name.startsWith("__")) out.push(relPath);
|
|
97
|
+
}
|
|
96
98
|
}
|
|
97
99
|
};
|
|
98
100
|
walk(entry.rootReal, "", 0);
|
|
@@ -272,6 +274,9 @@ var WatchManager = class {
|
|
|
272
274
|
watcher.on("change", () => {
|
|
273
275
|
for (const cb of created.listeners) cb(absPath);
|
|
274
276
|
});
|
|
277
|
+
watcher.on("add", () => {
|
|
278
|
+
for (const cb of created.listeners) cb(absPath);
|
|
279
|
+
});
|
|
275
280
|
watcher.on("unlink", () => {
|
|
276
281
|
for (const cb of created.listeners) cb(absPath);
|
|
277
282
|
});
|
|
@@ -476,7 +481,7 @@ function acquireLock() {
|
|
|
476
481
|
} catch {
|
|
477
482
|
holder = 0;
|
|
478
483
|
}
|
|
479
|
-
if (isAlive(holder)) throw new Error(`paintress web 已在运行(pid ${holder})。如需重启请先 paintress
|
|
484
|
+
if (isAlive(holder)) throw new Error(`paintress web 已在运行(pid ${holder})。如需重启请先 paintress stop`);
|
|
480
485
|
try {
|
|
481
486
|
unlinkSync(p);
|
|
482
487
|
} catch {}
|
|
@@ -539,6 +544,8 @@ async function startDaemon(opts = {}) {
|
|
|
539
544
|
const idleCheckMs = opts.idleCheckMs ?? 2e3;
|
|
540
545
|
let idleSince = null;
|
|
541
546
|
let closing = false;
|
|
547
|
+
/** 进行中的关停 Promise:close() 必须等它,不能立刻返回 */
|
|
548
|
+
let closingPromise = null;
|
|
542
549
|
/** 最近一次 HTTP 活动:用来判定「浏览器还在不在」而不单靠 SSE 关闭事件 */
|
|
543
550
|
let lastHttpAt = Date.now();
|
|
544
551
|
/**
|
|
@@ -662,6 +669,7 @@ async function startDaemon(opts = {}) {
|
|
|
662
669
|
ok: true,
|
|
663
670
|
pong: true,
|
|
664
671
|
pid: process.pid,
|
|
672
|
+
port,
|
|
665
673
|
uptimeMs: Date.now() - startedAt,
|
|
666
674
|
projects: registry.all().length
|
|
667
675
|
});
|
|
@@ -680,6 +688,12 @@ async function startDaemon(opts = {}) {
|
|
|
680
688
|
const entry = registry.watch(req.project, req.dir, req.depth, req.owner === "agent" ? { owner: "agent" } : {});
|
|
681
689
|
cancelAutoDetach(entry.name);
|
|
682
690
|
watchers.watchDesigns(entry.rootReal, entry.depth, scheduleRegistryNotify);
|
|
691
|
+
try {
|
|
692
|
+
const created = ensureProjectScaffold(entry.rootReal).created;
|
|
693
|
+
if (created.length > 0 && !opts.quiet) process.stderr.write(`paintress web: 项目 ${entry.name} 初始化 ${created.map((f) => relative(entry.rootReal, f)).join("、")}\n`);
|
|
694
|
+
} catch (cause) {
|
|
695
|
+
process.stderr.write(`paintress web: 项目脚手架创建失败(${cause instanceof Error ? cause.message : String(cause)})\n`);
|
|
696
|
+
}
|
|
683
697
|
reply({
|
|
684
698
|
ok: true,
|
|
685
699
|
watched: true,
|
|
@@ -958,6 +972,190 @@ async function startDaemon(opts = {}) {
|
|
|
958
972
|
});
|
|
959
973
|
return;
|
|
960
974
|
}
|
|
975
|
+
if (segments[1] === "notes" && req.method === "GET") {
|
|
976
|
+
const name = url.searchParams.get("project") ?? "";
|
|
977
|
+
const relPath = url.searchParams.get("path") ?? "";
|
|
978
|
+
const entry = registry.get(name);
|
|
979
|
+
if (!entry) {
|
|
980
|
+
sendJson(404, {
|
|
981
|
+
ok: false,
|
|
982
|
+
error: `未注册的项目:${name}`
|
|
983
|
+
});
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
const abs = safeJoin(entry.rootReal, relPath);
|
|
987
|
+
const noteAbs = abs === null ? null : notePathFor(abs);
|
|
988
|
+
if (abs === null || noteAbs === null) {
|
|
989
|
+
sendJson(404, {
|
|
990
|
+
ok: false,
|
|
991
|
+
error: "文件不存在"
|
|
992
|
+
});
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
let markdown;
|
|
996
|
+
try {
|
|
997
|
+
markdown = readTextIfExists(noteAbs);
|
|
998
|
+
} catch (cause) {
|
|
999
|
+
sendJson(413, {
|
|
1000
|
+
ok: false,
|
|
1001
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
1002
|
+
});
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (markdown === null) {
|
|
1006
|
+
sendJson(200, {
|
|
1007
|
+
ok: true,
|
|
1008
|
+
exists: false
|
|
1009
|
+
});
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
sendJson(200, {
|
|
1013
|
+
ok: true,
|
|
1014
|
+
exists: true,
|
|
1015
|
+
path: relative(entry.rootReal, noteAbs).split(sep).join("/"),
|
|
1016
|
+
markdown
|
|
1017
|
+
});
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (segments[1] === "presets") {
|
|
1021
|
+
const qProject = url.searchParams.get("project") ?? "";
|
|
1022
|
+
if (req.method === "GET") {
|
|
1023
|
+
const entry = registry.get(qProject);
|
|
1024
|
+
if (!entry) {
|
|
1025
|
+
sendJson(404, {
|
|
1026
|
+
ok: false,
|
|
1027
|
+
error: `未注册的项目:${qProject}`
|
|
1028
|
+
});
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const { path, items } = listPresets(entry.rootReal);
|
|
1032
|
+
sendJson(200, {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
path,
|
|
1035
|
+
items
|
|
1036
|
+
});
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (req.method === "PUT" || req.method === "POST") {
|
|
1040
|
+
if (!isTrustedWrite(req)) {
|
|
1041
|
+
sendJson(403, {
|
|
1042
|
+
ok: false,
|
|
1043
|
+
error: "缺少 x-paintress 头"
|
|
1044
|
+
});
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const raw = await readBody();
|
|
1048
|
+
let body;
|
|
1049
|
+
try {
|
|
1050
|
+
body = JSON.parse(raw);
|
|
1051
|
+
} catch {
|
|
1052
|
+
sendJson(400, {
|
|
1053
|
+
ok: false,
|
|
1054
|
+
error: "请求体不是合法 JSON"
|
|
1055
|
+
});
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const name = qProject !== "" ? qProject : typeof body["project"] === "string" ? body["project"] : "";
|
|
1059
|
+
const entry = registry.get(name);
|
|
1060
|
+
if (!entry) {
|
|
1061
|
+
sendJson(404, {
|
|
1062
|
+
ok: false,
|
|
1063
|
+
error: `未注册的项目:${name}`
|
|
1064
|
+
});
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
1068
|
+
const value = body["value"];
|
|
1069
|
+
if (key === "" || typeof value !== "string" && typeof value !== "number") {
|
|
1070
|
+
sendJson(400, {
|
|
1071
|
+
ok: false,
|
|
1072
|
+
error: "缺少 key 或 value"
|
|
1073
|
+
});
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const existing = listPresets(entry.rootReal).items.find((p) => p.key === key);
|
|
1077
|
+
const typeRaw = body["type"] ?? existing?.type;
|
|
1078
|
+
if (!isPresetType(typeRaw)) {
|
|
1079
|
+
sendJson(400, {
|
|
1080
|
+
ok: false,
|
|
1081
|
+
error: "type 必须是 color 或 radius"
|
|
1082
|
+
});
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const label = typeof body["name"] === "string" && body["name"] !== "" ? body["name"] : void 0;
|
|
1086
|
+
try {
|
|
1087
|
+
const items = setPreset(entry.rootReal, {
|
|
1088
|
+
key,
|
|
1089
|
+
type: typeRaw,
|
|
1090
|
+
value: String(value),
|
|
1091
|
+
...label !== void 0 ? { name: label } : {}
|
|
1092
|
+
});
|
|
1093
|
+
notifyRegistry();
|
|
1094
|
+
sendJson(200, {
|
|
1095
|
+
ok: true,
|
|
1096
|
+
path: presetPathFor(entry.rootReal),
|
|
1097
|
+
items
|
|
1098
|
+
});
|
|
1099
|
+
} catch (cause) {
|
|
1100
|
+
sendJson(400, {
|
|
1101
|
+
ok: false,
|
|
1102
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
sendJson(405, {
|
|
1108
|
+
ok: false,
|
|
1109
|
+
error: `不支持的方法:${req.method ?? "?"}`
|
|
1110
|
+
});
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (segments[1] === "components" && req.method === "GET") {
|
|
1114
|
+
const name = url.searchParams.get("project") ?? "";
|
|
1115
|
+
const entry = registry.get(name);
|
|
1116
|
+
if (!entry) {
|
|
1117
|
+
sendJson(404, {
|
|
1118
|
+
ok: false,
|
|
1119
|
+
error: `未注册的项目:${name}`
|
|
1120
|
+
});
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const compPath = componentsPathFor(entry.rootReal);
|
|
1124
|
+
if (compPath === null) {
|
|
1125
|
+
sendJson(200, {
|
|
1126
|
+
ok: true,
|
|
1127
|
+
path: null,
|
|
1128
|
+
items: []
|
|
1129
|
+
});
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const doc = applyProjectPresets(loadDoc(compPath).doc, compPath).doc;
|
|
1133
|
+
sendJson(200, {
|
|
1134
|
+
ok: true,
|
|
1135
|
+
path: compPath,
|
|
1136
|
+
items: listComponents(compPath).map((c) => {
|
|
1137
|
+
const node = findNodeIn(doc.children, c.id);
|
|
1138
|
+
if (node === null) return c;
|
|
1139
|
+
try {
|
|
1140
|
+
const single = renderDocument({
|
|
1141
|
+
...doc,
|
|
1142
|
+
children: [node]
|
|
1143
|
+
}, {
|
|
1144
|
+
title: c.id,
|
|
1145
|
+
transparent: true
|
|
1146
|
+
});
|
|
1147
|
+
return {
|
|
1148
|
+
...c,
|
|
1149
|
+
width: Math.round(single.canvas.width),
|
|
1150
|
+
height: Math.round(single.canvas.height)
|
|
1151
|
+
};
|
|
1152
|
+
} catch {
|
|
1153
|
+
return c;
|
|
1154
|
+
}
|
|
1155
|
+
})
|
|
1156
|
+
});
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
961
1159
|
sendJson(404, {
|
|
962
1160
|
ok: false,
|
|
963
1161
|
error: `未知 API:${url.pathname}`
|
|
@@ -1001,16 +1199,20 @@ async function startDaemon(opts = {}) {
|
|
|
1001
1199
|
const token = Symbol("browser");
|
|
1002
1200
|
entry.browsers.add(token);
|
|
1003
1201
|
cancelAutoDetach(entry.name);
|
|
1004
|
-
const
|
|
1202
|
+
const notifyChange = () => {
|
|
1005
1203
|
res.write(`event: change\ndata: ${JSON.stringify({
|
|
1006
1204
|
path: relPath,
|
|
1007
1205
|
at: Date.now()
|
|
1008
1206
|
})}\n\n`);
|
|
1009
|
-
}
|
|
1207
|
+
};
|
|
1208
|
+
const releases = [watchers.acquire(abs, notifyChange)];
|
|
1209
|
+
releases.push(watchers.acquire(abs.replace(/\.(ptr|pen)$/i, ".md"), notifyChange));
|
|
1210
|
+
const presetAbs = presetPathFor(entry.rootReal);
|
|
1211
|
+
if (presetAbs !== null) releases.push(watchers.acquire(presetAbs, notifyChange));
|
|
1010
1212
|
const keepAlive = setInterval(() => res.write(": ping\n\n"), 2e4);
|
|
1011
1213
|
const done = () => {
|
|
1012
1214
|
clearInterval(keepAlive);
|
|
1013
|
-
release();
|
|
1215
|
+
for (const release of releases) release();
|
|
1014
1216
|
entry.browsers.delete(token);
|
|
1015
1217
|
maybeScheduleAutoDetach(entry.name);
|
|
1016
1218
|
};
|
|
@@ -1032,7 +1234,7 @@ async function startDaemon(opts = {}) {
|
|
|
1032
1234
|
return;
|
|
1033
1235
|
}
|
|
1034
1236
|
const loaded = loadDoc(abs);
|
|
1035
|
-
const rendered = renderDocument(loaded.doc, { title: relPath });
|
|
1237
|
+
const rendered = renderDocument(applyProjectPresets(loaded.doc, abs).doc, { title: relPath });
|
|
1036
1238
|
const dir = relPath.includes("/") ? relPath.slice(0, relPath.lastIndexOf("/") + 1) : "";
|
|
1037
1239
|
const baseHref = `/files/${entry.name.split("/").map(encodeURIComponent).join("/")}/${dir.split("/").filter((s) => s !== "").map(encodeURIComponent).join("/")}${dir ? "/" : ""}`;
|
|
1038
1240
|
let html = rendered.html.replace("<head>", `<head>\n<base href="${baseHref}">`);
|
|
@@ -1043,6 +1245,34 @@ async function startDaemon(opts = {}) {
|
|
|
1043
1245
|
send(200, html);
|
|
1044
1246
|
return;
|
|
1045
1247
|
}
|
|
1248
|
+
if (segments[0] === "render-component") {
|
|
1249
|
+
const project = url.searchParams.get("project") ?? "";
|
|
1250
|
+
const id = url.searchParams.get("id") ?? "";
|
|
1251
|
+
const entry = registry.get(project);
|
|
1252
|
+
if (!entry) {
|
|
1253
|
+
send(404, "project not found", "text/plain; charset=utf-8");
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const compPath = componentsPathFor(entry.rootReal);
|
|
1257
|
+
if (compPath === null || id === "") {
|
|
1258
|
+
send(404, "component not found", "text/plain; charset=utf-8");
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const doc = applyProjectPresets(loadDoc(compPath).doc, compPath).doc;
|
|
1262
|
+
const node = findNodeIn(doc.children, id);
|
|
1263
|
+
if (node === null) {
|
|
1264
|
+
send(404, "component not found", "text/plain; charset=utf-8");
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
send(200, renderDocument({
|
|
1268
|
+
...doc,
|
|
1269
|
+
children: [node]
|
|
1270
|
+
}, {
|
|
1271
|
+
title: id,
|
|
1272
|
+
transparent: true
|
|
1273
|
+
}).html);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1046
1276
|
if (segments[0] === "files" && segments.length >= 3) {
|
|
1047
1277
|
const hit = resolveProjectPrefix(registry, segments.slice(1));
|
|
1048
1278
|
if (hit === null) {
|
|
@@ -1171,30 +1401,39 @@ async function startDaemon(opts = {}) {
|
|
|
1171
1401
|
changes.prune();
|
|
1172
1402
|
}, idleCheckMs);
|
|
1173
1403
|
idleTimer.unref();
|
|
1174
|
-
|
|
1175
|
-
|
|
1404
|
+
/**
|
|
1405
|
+
* 关停:幂等且**可等待**。
|
|
1406
|
+
* 已经在关停中时必须返回同一个 Promise —— 否则 `close()` 会立刻 resolve,
|
|
1407
|
+
* 调用方以为锁已释放,紧接着 `startDaemon` 就撞上「已在运行」。
|
|
1408
|
+
* (v0.6.0 的 release 测试里就是这么连环挂的:idle 关停还在飞,close() 先返回了。)
|
|
1409
|
+
*/
|
|
1410
|
+
function shutdown(reason) {
|
|
1411
|
+
if (closingPromise !== null) return closingPromise;
|
|
1176
1412
|
closing = true;
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1413
|
+
closingPromise = (async () => {
|
|
1414
|
+
if (!opts.quiet) process.stderr.write(`paintress web: 退出(${reason})\n`);
|
|
1415
|
+
clearInterval(idleTimer);
|
|
1416
|
+
if (registryTimer !== null) clearTimeout(registryTimer);
|
|
1417
|
+
for (const timer of autoDetachTimers.values()) clearTimeout(timer);
|
|
1418
|
+
autoDetachTimers.clear();
|
|
1419
|
+
for (const client of registryClients) try {
|
|
1420
|
+
client.write(`event: shutdown\ndata: ${JSON.stringify({
|
|
1421
|
+
reason,
|
|
1422
|
+
at: Date.now()
|
|
1423
|
+
})}\n\n`);
|
|
1424
|
+
client.end();
|
|
1425
|
+
} catch {}
|
|
1426
|
+
registryClients.clear();
|
|
1427
|
+
for (const client of controlClients) client.end();
|
|
1428
|
+
controlClients.clear();
|
|
1429
|
+
await watchers.closeAll();
|
|
1430
|
+
await new Promise((resolve) => sockServer.close(() => resolve()));
|
|
1431
|
+
const httpClosed = new Promise((resolve) => httpServer.close(() => resolve()));
|
|
1432
|
+
httpServer.closeAllConnections();
|
|
1433
|
+
await httpClosed;
|
|
1434
|
+
releaseLock();
|
|
1435
|
+
})();
|
|
1436
|
+
return closingPromise;
|
|
1198
1437
|
}
|
|
1199
1438
|
return {
|
|
1200
1439
|
port,
|
|
@@ -1514,6 +1753,7 @@ const FLAG_KEYS = /* @__PURE__ */ new Set([
|
|
|
1514
1753
|
"gap",
|
|
1515
1754
|
"force",
|
|
1516
1755
|
"board",
|
|
1756
|
+
"parent",
|
|
1517
1757
|
NO_OPEN_FLAG
|
|
1518
1758
|
]);
|
|
1519
1759
|
const BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -1747,39 +1987,57 @@ async function main(argv) {
|
|
|
1747
1987
|
const port = flagNum(flags, "port");
|
|
1748
1988
|
const idle = flagNum(flags, "idle");
|
|
1749
1989
|
const noOpen = flags[NO_OPEN_FLAG] === true || process.env["PAINTRESS_NO_OPEN"] === "1";
|
|
1990
|
+
const url = (p) => `http://localhost:${p}`;
|
|
1991
|
+
const running = await reuseDaemon(port);
|
|
1992
|
+
if (running !== null) {
|
|
1993
|
+
console.log(JSON.stringify({
|
|
1994
|
+
ok: true,
|
|
1995
|
+
daemon: true,
|
|
1996
|
+
reused: true,
|
|
1997
|
+
pid: running.pid,
|
|
1998
|
+
port: running.port,
|
|
1999
|
+
http: url(running.port)
|
|
2000
|
+
}, null, 2));
|
|
2001
|
+
return 0;
|
|
2002
|
+
}
|
|
1750
2003
|
if (flags["daemon"] === true) {
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
if (noOpen) args.push("--no-open");
|
|
1756
|
-
const child = spawn(process.execPath, args, {
|
|
1757
|
-
detached: true,
|
|
1758
|
-
stdio: "ignore"
|
|
2004
|
+
const res = await ensureDaemon({
|
|
2005
|
+
...port !== void 0 ? { port } : {},
|
|
2006
|
+
...idle !== void 0 ? { idleTimeoutMs: idle } : {},
|
|
2007
|
+
noOpen
|
|
1759
2008
|
});
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}, null, 2));
|
|
1770
|
-
return 0;
|
|
1771
|
-
}
|
|
1772
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
1773
|
-
}
|
|
1774
|
-
throw new Error("后台启动超时(5s 内未出现控制面 socket)");
|
|
2009
|
+
console.log(JSON.stringify({
|
|
2010
|
+
ok: true,
|
|
2011
|
+
daemon: true,
|
|
2012
|
+
reused: !res.started,
|
|
2013
|
+
pid: res.pid,
|
|
2014
|
+
port: res.port,
|
|
2015
|
+
http: url(res.port)
|
|
2016
|
+
}, null, 2));
|
|
2017
|
+
return 0;
|
|
1775
2018
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
2019
|
+
let handle;
|
|
2020
|
+
try {
|
|
2021
|
+
handle = await startDaemon({
|
|
2022
|
+
...port !== void 0 ? { port } : {},
|
|
2023
|
+
...idle !== void 0 ? { idleTimeoutMs: idle } : {},
|
|
2024
|
+
emptyGraceMs: 6e4,
|
|
2025
|
+
updateCheck: true
|
|
2026
|
+
});
|
|
2027
|
+
} catch (cause) {
|
|
2028
|
+
const again = await queryDaemon();
|
|
2029
|
+
if (again === null) throw cause;
|
|
2030
|
+
console.log(JSON.stringify({
|
|
2031
|
+
ok: true,
|
|
2032
|
+
daemon: true,
|
|
2033
|
+
reused: true,
|
|
2034
|
+
pid: again.pid,
|
|
2035
|
+
port: again.port,
|
|
2036
|
+
http: url(again.port)
|
|
2037
|
+
}, null, 2));
|
|
2038
|
+
return 0;
|
|
2039
|
+
}
|
|
2040
|
+
const http = url(handle.port);
|
|
1783
2041
|
console.log(JSON.stringify({
|
|
1784
2042
|
ok: true,
|
|
1785
2043
|
pid: handle.pid,
|
|
@@ -1787,7 +2045,7 @@ async function main(argv) {
|
|
|
1787
2045
|
http
|
|
1788
2046
|
}, null, 2));
|
|
1789
2047
|
if (!noOpen) {
|
|
1790
|
-
const { openBrowser } = await import("./server-
|
|
2048
|
+
const { openBrowser } = await import("./server-DZRNNWac.js").then((n) => n.i);
|
|
1791
2049
|
openBrowser(`${http}/`);
|
|
1792
2050
|
}
|
|
1793
2051
|
await new Promise((resolve) => {
|
|
@@ -1796,8 +2054,54 @@ async function main(argv) {
|
|
|
1796
2054
|
});
|
|
1797
2055
|
return 0;
|
|
1798
2056
|
}
|
|
1799
|
-
if (!file) throw new Error("缺少 <file> 参数");
|
|
1800
2057
|
let out;
|
|
2058
|
+
if (cmd === "preset") {
|
|
2059
|
+
const action = file ?? "list";
|
|
2060
|
+
if (action === "list") out = await call("paintress_preset", {
|
|
2061
|
+
action: "list",
|
|
2062
|
+
...a1 !== void 0 ? { path: a1 } : {}
|
|
2063
|
+
});
|
|
2064
|
+
else if (action === "set") {
|
|
2065
|
+
if (!a1 || !a2 || !positionals[3] || !positionals[4]) throw new Error("用法:paintress preset set <dir> <key> <color|radius> <value> [--name 名称]");
|
|
2066
|
+
out = await call("paintress_preset", {
|
|
2067
|
+
action: "set",
|
|
2068
|
+
path: a1,
|
|
2069
|
+
key: a2,
|
|
2070
|
+
type: positionals[3],
|
|
2071
|
+
value: positionals[4],
|
|
2072
|
+
...flagStr(flags, "name") !== void 0 ? { name: flagStr(flags, "name") } : {}
|
|
2073
|
+
});
|
|
2074
|
+
} else if (action === "remove") {
|
|
2075
|
+
if (!a1 || !a2) throw new Error("用法:paintress preset remove <dir> <key>");
|
|
2076
|
+
out = await call("paintress_preset", {
|
|
2077
|
+
action: "remove",
|
|
2078
|
+
path: a1,
|
|
2079
|
+
key: a2
|
|
2080
|
+
});
|
|
2081
|
+
} else throw new Error(`未知 preset 动作:${action}(list / set / remove)`);
|
|
2082
|
+
console.log(JSON.stringify(out, null, 2));
|
|
2083
|
+
return 0;
|
|
2084
|
+
}
|
|
2085
|
+
if (cmd === "component") {
|
|
2086
|
+
const action = file ?? "list";
|
|
2087
|
+
if (action === "list") out = await call("paintress_component", {
|
|
2088
|
+
action: "list",
|
|
2089
|
+
...a1 !== void 0 ? { path: a1 } : {}
|
|
2090
|
+
});
|
|
2091
|
+
else if (action === "insert") {
|
|
2092
|
+
if (!a1 || !a2) throw new Error("用法:paintress component insert <file> <id> [--parent p] [--index n]");
|
|
2093
|
+
out = await call("paintress_component", {
|
|
2094
|
+
action: "insert",
|
|
2095
|
+
path: a1,
|
|
2096
|
+
id: a2,
|
|
2097
|
+
...flagStr(flags, "parent") !== void 0 ? { parent: flagStr(flags, "parent") } : {},
|
|
2098
|
+
...flagNum(flags, "index") !== void 0 ? { index: flagNum(flags, "index") } : {}
|
|
2099
|
+
});
|
|
2100
|
+
} else throw new Error(`未知 component 动作:${action}(list / insert)`);
|
|
2101
|
+
console.log(JSON.stringify(out, null, 2));
|
|
2102
|
+
return 0;
|
|
2103
|
+
}
|
|
2104
|
+
if (!file) throw new Error("缺少 <file> 参数");
|
|
1801
2105
|
switch (cmd) {
|
|
1802
2106
|
case "new": {
|
|
1803
2107
|
const name = flagStr(flags, "name");
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { It as Affine, it as BackgroundBlurEffect, bt as BlendMode, at as BlurEffect, Lt as BoolOrVar, z as BrowserSchema, Rt as Color, zt as ColorOrVar, Bt as CornerRadius, xt as CrossAxis, j as DocumentObjectSchema, M as DocumentSchema, ot as EffectEntry, st as Effects, B as EllipseSchema, w as FileChangedError, ct as FillEntry, St as FillRule, lt as Fills, V as FrameSchema, ut as GradientFill, Ct as GradientType, dt as GraphicsBlock, H as GroupSchema, U as IconFontSchema, wt as IconLibrary, W as IconSchema, ft as ImageFill, Tt as ImageFit, G as KNOWN_NODE_TYPES, K as LayoutBlock, Et as LayoutDir, Dt as MainAxis, pt as MeshGradientFill, q as NodeBase, J as NodeSchema, Y as NoteSchema, Vt as NumOrVar, N as PEN_DOC_VERSION, P as PTR_SPEC_VERSION, Ht as Padding, X as PathSchema, Z as PolygonSchema, Ot as PositionMode, F as PtrDocumentSchema, Q as RectangleSchema, $ as RefSchema, et as ScriptSchema, mt as ShaderEffect, ht as ShaderFill, gt as ShadowEffect, kt as ShadowKind, Ut as Sizing, _t as SolidFill, Wt as StringOrVar, At as StrokeAlign, vt as StrokeBlock, jt as StrokeCap, Mt as StrokeJoin, yt as StrokeWidth, Nt as TextAlign, Pt as TextAlignVertical, Ft as TextGrowth, tt as TextSchema, nt as UnknownNodeSchema, Gt as VARIABLE_NAME, I as VariableDef, Kt as VariableRef, L as XPaintress, R as X_PAINTRESS_KEY, g as batch, n as boardBoxes, _ as buildIndex, k as collectIds, c as commonToCss, r as computeCanvas, l as cornerRadiusToCss, u as effectsToCss, i as escapeAttr, a as escapeHtml, m as expandRefs, d as fillsToCss, A as genId, v as get, s as getLucideIcon, y as insert, T as loadDoc, h as makeVarResolver, b as move, x as query, S as remove, t as renderDocument, o as renderTree, E as saveDoc, rt as schemaForType, D as serializeDoc, f as strokeToCss, p as textToCss, C as update, O as validateDoc } from "./render-
|
|
1
|
+
export { It as Affine, it as BackgroundBlurEffect, bt as BlendMode, at as BlurEffect, Lt as BoolOrVar, z as BrowserSchema, Rt as Color, zt as ColorOrVar, Bt as CornerRadius, xt as CrossAxis, j as DocumentObjectSchema, M as DocumentSchema, ot as EffectEntry, st as Effects, B as EllipseSchema, w as FileChangedError, ct as FillEntry, St as FillRule, lt as Fills, V as FrameSchema, ut as GradientFill, Ct as GradientType, dt as GraphicsBlock, H as GroupSchema, U as IconFontSchema, wt as IconLibrary, W as IconSchema, ft as ImageFill, Tt as ImageFit, G as KNOWN_NODE_TYPES, K as LayoutBlock, Et as LayoutDir, Dt as MainAxis, pt as MeshGradientFill, q as NodeBase, J as NodeSchema, Y as NoteSchema, Vt as NumOrVar, N as PEN_DOC_VERSION, P as PTR_SPEC_VERSION, Ht as Padding, X as PathSchema, Z as PolygonSchema, Ot as PositionMode, F as PtrDocumentSchema, Q as RectangleSchema, $ as RefSchema, et as ScriptSchema, mt as ShaderEffect, ht as ShaderFill, gt as ShadowEffect, kt as ShadowKind, Ut as Sizing, _t as SolidFill, Wt as StringOrVar, At as StrokeAlign, vt as StrokeBlock, jt as StrokeCap, Mt as StrokeJoin, yt as StrokeWidth, Nt as TextAlign, Pt as TextAlignVertical, Ft as TextGrowth, tt as TextSchema, nt as UnknownNodeSchema, Gt as VARIABLE_NAME, I as VariableDef, Kt as VariableRef, L as XPaintress, R as X_PAINTRESS_KEY, g as batch, n as boardBoxes, _ as buildIndex, k as collectIds, c as commonToCss, r as computeCanvas, l as cornerRadiusToCss, u as effectsToCss, i as escapeAttr, a as escapeHtml, m as expandRefs, d as fillsToCss, A as genId, v as get, s as getLucideIcon, y as insert, T as loadDoc, h as makeVarResolver, b as move, x as query, S as remove, t as renderDocument, o as renderTree, E as saveDoc, rt as schemaForType, D as serializeDoc, f as strokeToCss, p as textToCss, C as update, O as validateDoc } from "./render-BSf62388.js";
|
package/dist/mcp/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as createMcpServer, n as runStdioServer } from "../server-
|
|
1
|
+
export { t as createMcpServer, n as runStdioServer } from "../server-DZRNNWac.js";
|
|
@@ -597,7 +597,7 @@ function collectIds(children, out = []) {
|
|
|
597
597
|
}
|
|
598
598
|
// src/doc/validate.ts
|
|
599
599
|
/** 结构校验(schema)+ 语义校验(重复 id、悬空 ref、未知类型、未解析变量) */
|
|
600
|
-
function validateDoc(input) {
|
|
600
|
+
function validateDoc(input, opts = {}) {
|
|
601
601
|
const errors = [];
|
|
602
602
|
const warnings = [];
|
|
603
603
|
const parsed = DocumentSchema.safeParse(input);
|
|
@@ -648,7 +648,13 @@ function validateDoc(input) {
|
|
|
648
648
|
}
|
|
649
649
|
};
|
|
650
650
|
walk(doc.children, "children");
|
|
651
|
-
const
|
|
651
|
+
const localNames = new Set(doc.variables ? Object.keys(doc.variables) : []);
|
|
652
|
+
const varNames = /* @__PURE__ */ new Set([...localNames, ...opts.knownVars ?? []]);
|
|
653
|
+
for (const name of opts.presetVars ?? []) if (localNames.has(name)) warnings.push({
|
|
654
|
+
level: "warning",
|
|
655
|
+
path: "<variables>",
|
|
656
|
+
message: `本地变量 $${name} 覆盖了项目预设(在查看器里调预设不会影响这份文件)`
|
|
657
|
+
});
|
|
652
658
|
const varRefs = /* @__PURE__ */ new Set();
|
|
653
659
|
const scan = (obj) => {
|
|
654
660
|
if (typeof obj === "string" && VARIABLE_NAME.test(obj)) varRefs.add(obj.slice(1));
|
|
@@ -1816,7 +1822,7 @@ function renderDocument(doc, opts = {}) {
|
|
|
1816
1822
|
`<title>${escapeAttr(title)}</title>`,
|
|
1817
1823
|
"<style>",
|
|
1818
1824
|
"* { box-sizing: border-box; margin: 0; padding: 0; }",
|
|
1819
|
-
|
|
1825
|
+
`body { margin: 0; background: ${opts.transparent === true ? "transparent" : "#ffffff"}; }`,
|
|
1820
1826
|
".ptr-canvas { position: relative; }",
|
|
1821
1827
|
"</style>",
|
|
1822
1828
|
"</head>",
|