sculpted 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { _ as markerClassForEditId, a as DEFAULT_JSX_SOURCE_ATTRIBUTE, c as DEFAULT_OPEN_SOURCE_ENDPOINT, d as DEFAULT_TOKEN_WRITEBACK_ENDPOINT, f as DEFAULT_WRITEBACK_ENDPOINT, l as DEFAULT_SOURCE_ATTRIBUTE, p as SCULPTED_EVENTS, r as DEFAULT_EDIT_ID_ATTRIBUTE, t as DEFAULT_COMPONENT_ATTRIBUTE, u as DEFAULT_STYLE_MODULE_ENDPOINT } from "./protocol-D5heR2QM.mjs";
2
- import { S as resolveSourceParserAdapter, _ as analyzePandaCssSource, a as createStyleModuleSourcePatch, b as parsePandaSource, c as resolveProjectPath, d as toRelativeProjectPath$1, f as trustedManifestFilePath, g as createInlineCssSourcePatch, h as createStaticCssPatch, i as createStyleModuleDetachPatch, l as safeProjectSourcePath, m as createStaticCssBatchPatch, n as componentStyleModulePaths, o as readComponentStyleModule, p as trustedTokenSourceFilePath, r as createStyleModuleAttachPatch, s as normalizePath$1, t as createTokenConfigPatch, u as stripViteQuery, v as hashSource } from "./patcher-DQgKdozw.mjs";
2
+ import { S as parsePandaSource, _ as createStaticCssPatch, a as createStyleModuleSourcePatch, b as hashSource, c as resolveProjectPath, d as toRelativeProjectPath$1, f as trustedManifestFilePath, g as createStaticCssBatchPatch, h as verifyProjectWritePath, i as createStyleModuleDetachPatch, l as safeProjectSourcePath, m as verifyProjectExistingPath, n as componentStyleModulePaths, o as readComponentStyleModule, p as trustedTokenSourceFilePath, r as createStyleModuleAttachPatch, s as normalizePath$1, t as createTokenConfigPatch, u as stripViteQuery, v as createInlineCssSourcePatch, w as resolveSourceParserAdapter, y as analyzePandaCssSource } from "./patcher-nj9VhRT-.mjs";
3
3
  import ts from "typescript";
4
4
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
5
- import { loadConfigAndCreateContext } from "@pandacss/node";
6
5
  import { dirname } from "node:path";
6
+ import { loadConfigAndCreateContext } from "@pandacss/node";
7
7
  //#region src/vite/constants.ts
8
8
  const DEFAULT_EXCLUDE = ["node_modules/**", "styled-system/**"];
9
9
  const DEFAULT_RUNTIME_BOOTSTRAP_ENDPOINT = "/@sculpted/runtime";
@@ -21,6 +21,61 @@ const DEFAULT_PANDA_CONFIG_FILES = [
21
21
  ];
22
22
  //#endregion
23
23
  //#region src/vite/editorProperties.ts
24
+ const EDITOR_PROPERTY_COMMONNESS = {
25
+ display: 2.2,
26
+ color: 2.1,
27
+ background: 2,
28
+ backgroundColor: 2,
29
+ padding: 2,
30
+ paddingX: 1.9,
31
+ paddingY: 1.9,
32
+ paddingTop: 1.8,
33
+ paddingRight: 1.8,
34
+ paddingBottom: 1.8,
35
+ paddingLeft: 1.8,
36
+ margin: 1.9,
37
+ marginX: 1.8,
38
+ marginY: 1.8,
39
+ marginTop: 1.7,
40
+ marginRight: 1.7,
41
+ marginBottom: 1.7,
42
+ marginLeft: 1.7,
43
+ width: 1.9,
44
+ height: 1.8,
45
+ borderRadius: 1.8,
46
+ gap: 1.8,
47
+ fontSize: 1.8,
48
+ fontWeight: 1.7,
49
+ lineHeight: 1.7,
50
+ position: 1.7,
51
+ opacity: 1.7,
52
+ border: 1.6,
53
+ boxShadow: 1.5,
54
+ alignItems: 1.5,
55
+ justifyContent: 1.5,
56
+ flexDirection: 1.4,
57
+ overflow: 1.4,
58
+ cursor: 1.3,
59
+ transform: 1.3,
60
+ maxWidth: 1.3,
61
+ minWidth: 1.3,
62
+ top: 1.2,
63
+ right: 1.2,
64
+ bottom: 1.2,
65
+ left: 1.2,
66
+ inset: 1.2,
67
+ zIndex: 1.2,
68
+ backgroundImage: 1.1,
69
+ outline: 1.1,
70
+ transition: 1.1,
71
+ pointerEvents: 1.1,
72
+ userSelect: 1.1,
73
+ backdropFilter: .8,
74
+ mixBlendMode: .8,
75
+ backgroundAttachment: .7,
76
+ transitionTimingFunction: .7,
77
+ cornerShape: .6
78
+ };
24
79
  function editorProperty(name, label, category, cssProperty, aliases = [], tokenCategory) {
25
80
  return {
26
81
  name,
@@ -29,9 +84,13 @@ function editorProperty(name, label, category, cssProperty, aliases = [], tokenC
29
84
  cssProperty,
30
85
  aliases,
31
86
  tokenCategory,
32
- defaultUnit: defaultEditorPropertyUnit(name, category)
87
+ defaultUnit: defaultEditorPropertyUnit(name, category),
88
+ commonness: editorPropertyCommonness(name)
33
89
  };
34
90
  }
91
+ function editorPropertyCommonness(name) {
92
+ return EDITOR_PROPERTY_COMMONNESS[name] ?? 1;
93
+ }
35
94
  function defaultEditorPropertyUnit(name, category) {
36
95
  if ([
37
96
  "zIndex",
@@ -281,22 +340,43 @@ async function createEditorMetadata(options) {
281
340
  properties
282
341
  };
283
342
  }
343
+ function tokenSourceIdsForPandaConfigSource(options) {
344
+ return parsePandaConfigEditorMetadata(options).tokenSources.map((item) => item.id);
345
+ }
284
346
  async function readPandaConfigSource(projectRoot, pandaConfigPath) {
285
347
  const root = normalizePath(projectRoot).replace(/\/$/, "");
286
348
  const candidates = pandaConfigPath ? [absoluteProjectPath(root, pandaConfigPath)] : DEFAULT_PANDA_CONFIG_FILES.map((fileName) => `${root}/${fileName}`);
287
- for (const filePath of candidates) try {
288
- return {
289
- ok: true,
290
- filePath,
291
- sourceText: await readFile(filePath, "utf8")
292
- };
293
- } catch (error) {
294
- if (isMissingFileError$2(error)) continue;
295
- return {
349
+ for (const filePath of candidates) {
350
+ try {
351
+ await access(filePath);
352
+ } catch (error) {
353
+ if (isMissingFileError$2(error)) continue;
354
+ return {
355
+ ok: false,
356
+ reason: "panda-config-unreadable",
357
+ message: `Failed to access Panda config ${filePath}: ${errorMessage$1(error)}`
358
+ };
359
+ }
360
+ const safePath = await verifyProjectExistingPath(root, filePath);
361
+ if (!safePath.ok) return {
296
362
  ok: false,
297
363
  reason: "panda-config-unreadable",
298
- message: `Failed to read Panda config ${filePath}: ${errorMessage(error)}`
364
+ message: safePath.message
299
365
  };
366
+ try {
367
+ return {
368
+ ok: true,
369
+ filePath,
370
+ sourceText: await readFile(filePath, "utf8")
371
+ };
372
+ } catch (error) {
373
+ if (isMissingFileError$2(error)) continue;
374
+ return {
375
+ ok: false,
376
+ reason: "panda-config-unreadable",
377
+ message: `Failed to read Panda config ${filePath}: ${errorMessage$1(error)}`
378
+ };
379
+ }
300
380
  }
301
381
  return {
302
382
  ok: false,
@@ -446,7 +526,7 @@ async function resolvePandaConfigEditorMetadata(options) {
446
526
  } catch (error) {
447
527
  return {
448
528
  ok: false,
449
- message: `Failed to resolve Panda token metadata: ${errorMessage(error)}`
529
+ message: `Failed to resolve Panda token metadata: ${errorMessage$1(error)}`
450
530
  };
451
531
  }
452
532
  }
@@ -697,7 +777,7 @@ function collectColorTokenMetadata(object, options, path = []) {
697
777
  sourcePath: nextPath,
698
778
  writable: options.writable,
699
779
  readonlyReason: options.writable ? void 0 : "Token source target is read-only.",
700
- conditions: options.kind === "semantic-token" && isJsonObject(value) ? Object.entries(value).map(([condition, conditionValue]) => ({
780
+ conditions: options.kind === "semantic-token" && isJsonObject$1(value) ? Object.entries(value).map(([condition, conditionValue]) => ({
701
781
  condition,
702
782
  value: conditionValue,
703
783
  swatch: swatchFromJsonValue(conditionValue)
@@ -822,7 +902,7 @@ function unavailableMetadataSection(reason, message) {
822
902
  message
823
903
  };
824
904
  }
825
- function isJsonObject(value) {
905
+ function isJsonObject$1(value) {
826
906
  return typeof value === "object" && value !== null && !Array.isArray(value);
827
907
  }
828
908
  function swatchFromJsonValue(value) {
@@ -848,7 +928,7 @@ function normalizePath(path) {
848
928
  function isMissingFileError$2(error) {
849
929
  return isRecord$3(error) && error.code === "ENOENT";
850
930
  }
851
- function errorMessage(error) {
931
+ function errorMessage$1(error) {
852
932
  return error instanceof Error ? error.message : String(error);
853
933
  }
854
934
  function isRecord$3(value) {
@@ -866,10 +946,33 @@ function isJsonValue$1(value) {
866
946
  return Object.values(value).every(isJsonValue$1);
867
947
  }
868
948
  //#endregion
949
+ //#region src/vite/logging.ts
950
+ function logUnexpectedDevServerError(message, error) {
951
+ console.error(`[sculpted] ${message}`, error);
952
+ }
953
+ function errorMessage(error) {
954
+ return error instanceof Error ? error.message : String(error);
955
+ }
956
+ //#endregion
957
+ //#region src/vite/requestBody.ts
958
+ const MAX_DEV_SERVER_JSON_BODY_BYTES = 1024 * 1024;
959
+ async function readRequestBody(request, maxBytes = MAX_DEV_SERVER_JSON_BODY_BYTES) {
960
+ if (!request[Symbol.asyncIterator]) return "";
961
+ let bytes = 0;
962
+ let body = "";
963
+ for await (const chunk of request) {
964
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
965
+ bytes += Buffer.byteLength(text);
966
+ if (bytes > maxBytes) throw new Error(`Request body exceeds ${maxBytes} bytes.`);
967
+ body += text;
968
+ }
969
+ return body;
970
+ }
971
+ //#endregion
869
972
  //#region src/vite/sourceLocation.ts
870
973
  async function readOpenSourceLocationRequest(request) {
871
974
  try {
872
- const body = await readRequestBody$2(request);
975
+ const body = await readRequestBody(request);
873
976
  const parsed = JSON.parse(body);
874
977
  if (!isOpenSourceLocationRequest(parsed)) return {
875
978
  ok: false,
@@ -900,6 +1003,8 @@ async function resolveOpenSourceLocation(options) {
900
1003
  } catch {
901
1004
  return openSourceLocationFailure("file-not-found", "Component source file does not exist.", { file: toRelativeProjectPath$1(trustedPath.file, options.projectRoot) });
902
1005
  }
1006
+ const resolvedPath = await verifyProjectExistingPath(options.projectRoot, trustedPath.file);
1007
+ if (!resolvedPath.ok) return openSourceLocationFailure("path-outside-project", resolvedPath.message, resolvedPath.details);
903
1008
  return openSourceLocationSuccess({
904
1009
  file: trustedPath.file,
905
1010
  line,
@@ -916,6 +1021,8 @@ async function resolveOpenSourceLocation(options) {
916
1021
  } catch {
917
1022
  return openSourceLocationFailure("file-not-found", "Source file does not exist.", { file: toRelativeProjectPath$1(safePath.file, options.projectRoot) });
918
1023
  }
1024
+ const resolvedPath = await verifyProjectExistingPath(options.projectRoot, safePath.file);
1025
+ if (!resolvedPath.ok) return openSourceLocationFailure("path-outside-project", resolvedPath.message, resolvedPath.details);
919
1026
  return openSourceLocationSuccess({
920
1027
  file: safePath.file,
921
1028
  line: parsed.line,
@@ -935,9 +1042,9 @@ function parseSourceLocationText(source) {
935
1042
  ok: false,
936
1043
  response: openSourceLocationFailure("invalid-request", "Source location must include a file path.")
937
1044
  };
938
- const line = match[2] ? Number.parseInt(match[2], 10) : void 0;
939
- const column = match[3] ? Number.parseInt(match[3], 10) : void 0;
940
- if (line !== void 0 && line < 1 || column !== void 0 && column < 1) return {
1045
+ const line = parseOptionalPositiveInteger(match[2]);
1046
+ const column = parseOptionalPositiveInteger(match[3]);
1047
+ if (match[2] !== void 0 && line === void 0 || match[3] !== void 0 && column === void 0) return {
941
1048
  ok: false,
942
1049
  response: openSourceLocationFailure("invalid-request", "Line and column numbers must be positive integers.")
943
1050
  };
@@ -948,6 +1055,11 @@ function parseSourceLocationText(source) {
948
1055
  column
949
1056
  };
950
1057
  }
1058
+ function parseOptionalPositiveInteger(value) {
1059
+ if (value === void 0) return void 0;
1060
+ const parsed = Number(value);
1061
+ return Number.isSafeInteger(parsed) && parsed >= 1 ? parsed : void 0;
1062
+ }
951
1063
  function openSourceLocationSuccess(options) {
952
1064
  const file = toRelativeProjectPath$1(options.file, options.projectRoot);
953
1065
  const location = [
@@ -975,15 +1087,12 @@ function openSourceLocationFailure(code, message, details) {
975
1087
  }
976
1088
  function isOpenSourceLocationRequest(value) {
977
1089
  if (!isRecord$2(value)) return false;
978
- if (value.kind === "source") return typeof value.source === "string" && value.source.length > 0;
979
- if (value.kind === "component") return typeof value.editId === "string" && value.editId.length > 0;
1090
+ if (value.kind === "source") return hasOnlyKeys$2(value, ["kind", "source"]) && typeof value.source === "string" && value.source.length > 0;
1091
+ if (value.kind === "component") return hasOnlyKeys$2(value, ["kind", "editId"]) && typeof value.editId === "string" && value.editId.length > 0;
980
1092
  return false;
981
1093
  }
982
- async function readRequestBody$2(request) {
983
- if (!request[Symbol.asyncIterator]) return "";
984
- let body = "";
985
- for await (const chunk of request) body += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
986
- return body;
1094
+ function hasOnlyKeys$2(value, allowedKeys) {
1095
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
987
1096
  }
988
1097
  function isRecord$2(value) {
989
1098
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1010,10 +1119,11 @@ function registerRuntimeModuleRoutes(server, options) {
1010
1119
  response.setHeader("cache-control", "no-store");
1011
1120
  response.end(runtimeModuleSourceMap);
1012
1121
  } catch (error) {
1122
+ logUnexpectedDevServerError("Failed to load Sculpted runtime module source map.", error);
1013
1123
  response.statusCode = 404;
1014
1124
  response.setHeader("content-type", "text/plain");
1015
1125
  response.setHeader("cache-control", "no-store");
1016
- response.end(`Failed to load Sculpted runtime module source map: ${error instanceof Error ? error.message : String(error)}`);
1126
+ response.end(`Failed to load Sculpted runtime module source map: ${errorMessage(error)}`);
1017
1127
  }
1018
1128
  });
1019
1129
  server.middlewares.use(options.runtimeModuleEndpoint, async (_request, response) => {
@@ -1024,10 +1134,11 @@ function registerRuntimeModuleRoutes(server, options) {
1024
1134
  response.setHeader("cache-control", "no-store");
1025
1135
  response.end(runtimeModuleSource);
1026
1136
  } catch (error) {
1137
+ logUnexpectedDevServerError("Failed to load Sculpted runtime module.", error);
1027
1138
  response.statusCode = 500;
1028
1139
  response.setHeader("content-type", "text/plain");
1029
1140
  response.setHeader("cache-control", "no-store");
1030
- response.end(`Failed to load Sculpted runtime module: ${error instanceof Error ? error.message : String(error)}`);
1141
+ response.end(`Failed to load Sculpted runtime module: ${errorMessage(error)}`);
1031
1142
  }
1032
1143
  });
1033
1144
  server.middlewares.use(options.runtimeChunkEndpoint, async (request, response) => {
@@ -1040,16 +1151,24 @@ function registerRuntimeModuleRoutes(server, options) {
1040
1151
  return;
1041
1152
  }
1042
1153
  try {
1154
+ if (!(await runtimeChunkFileNames()).has(fileName)) {
1155
+ response.statusCode = 404;
1156
+ response.setHeader("content-type", "text/plain");
1157
+ response.setHeader("cache-control", "no-store");
1158
+ response.end("Unknown Sculpted runtime chunk.");
1159
+ return;
1160
+ }
1043
1161
  const runtimeChunkSource = await readFile(new URL(`./${fileName}`, import.meta.url), "utf8");
1044
1162
  response.statusCode = 200;
1045
1163
  response.setHeader("content-type", fileName.endsWith(".map") ? "application/json" : "application/javascript");
1046
1164
  response.setHeader("cache-control", "no-store");
1047
1165
  response.end(runtimeChunkSource);
1048
1166
  } catch (error) {
1167
+ logUnexpectedDevServerError("Failed to load Sculpted runtime chunk.", error);
1049
1168
  response.statusCode = 404;
1050
1169
  response.setHeader("content-type", "text/plain");
1051
1170
  response.setHeader("cache-control", "no-store");
1052
- response.end(`Failed to load Sculpted runtime chunk: ${error instanceof Error ? error.message : String(error)}`);
1171
+ response.end(`Failed to load Sculpted runtime chunk: ${errorMessage(error)}`);
1053
1172
  }
1054
1173
  });
1055
1174
  server.middlewares.use(DEFAULT_UI_MODULE_SOURCE_MAP_ENDPOINT, async (_request, response) => {
@@ -1060,10 +1179,11 @@ function registerRuntimeModuleRoutes(server, options) {
1060
1179
  response.setHeader("cache-control", "no-store");
1061
1180
  response.end(uiModuleSourceMap);
1062
1181
  } catch (error) {
1182
+ logUnexpectedDevServerError("Failed to load Sculpted UI module source map.", error);
1063
1183
  response.statusCode = 404;
1064
1184
  response.setHeader("content-type", "text/plain");
1065
1185
  response.setHeader("cache-control", "no-store");
1066
- response.end(`Failed to load Sculpted UI module source map: ${error instanceof Error ? error.message : String(error)}`);
1186
+ response.end(`Failed to load Sculpted UI module source map: ${errorMessage(error)}`);
1067
1187
  }
1068
1188
  });
1069
1189
  server.middlewares.use(options.uiModuleEndpoint, async (_request, response) => {
@@ -1074,10 +1194,11 @@ function registerRuntimeModuleRoutes(server, options) {
1074
1194
  response.setHeader("cache-control", "no-store");
1075
1195
  response.end(uiModuleSource);
1076
1196
  } catch (error) {
1197
+ logUnexpectedDevServerError("Failed to load Sculpted UI module.", error);
1077
1198
  response.statusCode = 500;
1078
1199
  response.setHeader("content-type", "text/plain");
1079
1200
  response.setHeader("cache-control", "no-store");
1080
- response.end(`Failed to load Sculpted UI module: ${error instanceof Error ? error.message : String(error)}`);
1201
+ response.end(`Failed to load Sculpted UI module: ${errorMessage(error)}`);
1081
1202
  }
1082
1203
  });
1083
1204
  }
@@ -1096,6 +1217,22 @@ function runtimeBootstrapSource(options) {
1096
1217
  function rewriteInspectorModuleSource(source, runtimeChunkEndpoint, sourceMapEndpoint) {
1097
1218
  return source.replaceAll("from \"./", `from "${runtimeChunkEndpoint}`).replace(/\/\/# sourceMappingURL=[^\r\n]+/g, `//# sourceMappingURL=${sourceMapEndpoint}`);
1098
1219
  }
1220
+ async function runtimeChunkFileNames() {
1221
+ const [runtimeSource, uiSource] = await Promise.all([readFile(new URL("./runtime.mjs", import.meta.url), "utf8"), readFile(new URL("./ui.mjs", import.meta.url), "utf8")]);
1222
+ return runtimeChunkFileNamesFromSources([runtimeSource, uiSource]);
1223
+ }
1224
+ function runtimeChunkFileNamesFromSources(sources) {
1225
+ const fileNames = /* @__PURE__ */ new Set();
1226
+ for (const source of sources) for (const match of source.matchAll(/(?:from\s+|import\s*\()\s*["']\.\/([^"']+)["']/g)) {
1227
+ const specifier = match[1] ?? "";
1228
+ if (specifier.includes("/") || specifier.includes("\\") || /[?#]/.test(specifier)) continue;
1229
+ const fileName = runtimeChunkFileName(specifier);
1230
+ if (!fileName) continue;
1231
+ fileNames.add(fileName);
1232
+ if (fileName.endsWith(".mjs")) fileNames.add(`${fileName}.map`);
1233
+ }
1234
+ return fileNames;
1235
+ }
1099
1236
  function runtimeChunkFileName(url) {
1100
1237
  const fileName = (url ?? "").replace(/[?#].*$/, "").split("/").filter(Boolean).at(-1);
1101
1238
  if (!fileName || !/^[A-Za-z0-9._-]+\.mjs(?:\.map)?$/.test(fileName)) return void 0;
@@ -1132,23 +1269,52 @@ function registerStyleModuleRoute(server, options) {
1132
1269
  return;
1133
1270
  }
1134
1271
  let sourceText;
1272
+ let styleFileExists = true;
1135
1273
  try {
1136
- sourceText = await readFile(paths.paths.styleFile, "utf8");
1274
+ await access(paths.paths.styleFile);
1137
1275
  } catch (error) {
1138
- if (!isMissingFileError$1(error)) {
1276
+ if (isMissingFileError$1(error)) styleFileExists = false;
1277
+ else {
1278
+ logUnexpectedDevServerError("Failed to read Sculpted style module.", error);
1139
1279
  response.statusCode = 400;
1140
1280
  response.setHeader("content-type", "application/json");
1141
1281
  response.setHeader("cache-control", "no-store");
1142
- response.end(JSON.stringify(styleModuleFailure("write-failed", `Failed to read style module: ${error instanceof Error ? error.message : String(error)}`)));
1282
+ response.end(JSON.stringify(styleModuleFailure("write-failed", `Failed to read style module: ${errorMessage(error)}`)));
1143
1283
  return;
1144
1284
  }
1145
1285
  }
1146
- const result = readComponentStyleModule({
1147
- projectRoot: options.projectRoot,
1148
- componentSource: requestResult.componentSource,
1149
- sourceText,
1150
- cssImportSources: options.cssImportSources
1151
- });
1286
+ if (styleFileExists) {
1287
+ const safePath = await verifyProjectExistingPath(options.projectRoot, paths.paths.styleFile);
1288
+ if (!safePath.ok) {
1289
+ response.statusCode = 400;
1290
+ response.setHeader("content-type", "application/json");
1291
+ response.setHeader("cache-control", "no-store");
1292
+ response.end(JSON.stringify(styleModuleFailure(safePath.code, safePath.message, safePath.details)));
1293
+ return;
1294
+ }
1295
+ try {
1296
+ sourceText = await readFile(paths.paths.styleFile, "utf8");
1297
+ } catch (error) {
1298
+ logUnexpectedDevServerError("Failed to read Sculpted style module.", error);
1299
+ response.statusCode = 400;
1300
+ response.setHeader("content-type", "application/json");
1301
+ response.setHeader("cache-control", "no-store");
1302
+ response.end(JSON.stringify(styleModuleFailure("write-failed", `Failed to read style module: ${errorMessage(error)}`)));
1303
+ return;
1304
+ }
1305
+ }
1306
+ let result;
1307
+ try {
1308
+ result = readComponentStyleModule({
1309
+ projectRoot: options.projectRoot,
1310
+ componentSource: requestResult.componentSource,
1311
+ sourceText,
1312
+ cssImportSources: options.cssImportSources
1313
+ });
1314
+ } catch (error) {
1315
+ logUnexpectedDevServerError("Failed to inspect Sculpted style module.", error);
1316
+ result = styleModuleFailure("write-failed", `Failed to inspect style module: ${errorMessage(error)}`);
1317
+ }
1152
1318
  response.statusCode = result.ok ? 200 : 400;
1153
1319
  response.setHeader("content-type", "application/json");
1154
1320
  response.setHeader("cache-control", "no-store");
@@ -1157,9 +1323,9 @@ function registerStyleModuleRoute(server, options) {
1157
1323
  }
1158
1324
  async function readStyleModuleRequest(request) {
1159
1325
  try {
1160
- const body = await readRequestBody$1(request);
1326
+ const body = await readRequestBody(request);
1161
1327
  const parsed = JSON.parse(body);
1162
- if (!isRecord$1(parsed) || typeof parsed.componentSource !== "string") return {
1328
+ if (!isRecord$1(parsed) || !hasOnlyKeys$1(parsed, ["componentSource"]) || typeof parsed.componentSource !== "string" || parsed.componentSource.length === 0) return {
1163
1329
  ok: false,
1164
1330
  response: styleModuleFailure("invalid-edit", "Request body is not a valid style-module request.")
1165
1331
  };
@@ -1174,12 +1340,6 @@ async function readStyleModuleRequest(request) {
1174
1340
  };
1175
1341
  }
1176
1342
  }
1177
- async function readRequestBody$1(request) {
1178
- if (!request[Symbol.asyncIterator]) return "";
1179
- let body = "";
1180
- for await (const chunk of request) body += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
1181
- return body;
1182
- }
1183
1343
  function styleModuleFailure(code, message, details) {
1184
1344
  return {
1185
1345
  ok: false,
@@ -1193,6 +1353,9 @@ function styleModuleFailure(code, message, details) {
1193
1353
  function isMissingFileError$1(error) {
1194
1354
  return isRecord$1(error) && error.code === "ENOENT";
1195
1355
  }
1356
+ function hasOnlyKeys$1(value, allowedKeys) {
1357
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
1358
+ }
1196
1359
  function isRecord$1(value) {
1197
1360
  return typeof value === "object" && value !== null && !Array.isArray(value);
1198
1361
  }
@@ -1509,42 +1672,49 @@ function registerWritebackRoutes(server, options) {
1509
1672
  response.end(JSON.stringify(requestResult.response));
1510
1673
  return;
1511
1674
  }
1512
- const result = requestResult.request.kind === "panda-css-inline-source" ? await writeInlineCssSourcePatch({
1513
- request: requestResult.request,
1514
- projectRoot: options.projectRoot,
1515
- cssImportSources: options.cssImportSources,
1516
- sourceSyntax: options.sourceSyntax,
1517
- attributes: options.attributes,
1518
- onManifestEntries: options.onManifestEntries
1519
- }) : requestResult.request.kind === "panda-css-style-module-source" || requestResult.request.kind === "panda-css-style-module-attach" || requestResult.request.kind === "panda-css-style-module-detach" ? await writeStyleModulePatch({
1520
- request: requestResult.request,
1521
- projectRoot: options.projectRoot,
1522
- cssImportSources: options.cssImportSources,
1523
- styledSystemPackageName: await inferStyledSystemPackageName(options.projectRoot),
1524
- attributes: options.attributes,
1525
- onManifestEntries: options.onManifestEntries
1526
- }) : requestResult.request.kind === "panda-css-batch" ? await writeStaticCssBatchPatch({
1527
- manifest: options.manifest(),
1528
- request: requestResult.request,
1529
- projectRoot: options.projectRoot,
1530
- cssImportSources: options.cssImportSources,
1531
- sourceSyntax: options.sourceSyntax,
1532
- onManifestEntries: options.onManifestEntries
1533
- }) : await writeStaticCssPatch({
1534
- manifest: options.manifest(),
1535
- request: requestResult.request,
1536
- projectRoot: options.projectRoot,
1537
- cssImportSources: options.cssImportSources,
1538
- sourceSyntax: options.sourceSyntax,
1539
- onManifestEntries: options.onManifestEntries
1540
- });
1675
+ let result;
1676
+ try {
1677
+ result = requestResult.request.kind === "panda-css-inline-source" ? await writeInlineCssSourcePatch({
1678
+ request: requestResult.request,
1679
+ projectRoot: options.projectRoot,
1680
+ cssImportSources: options.cssImportSources,
1681
+ sourceSyntax: options.sourceSyntax,
1682
+ attributes: options.attributes,
1683
+ onManifestEntries: options.onManifestEntries
1684
+ }) : requestResult.request.kind === "panda-css-style-module-source" || requestResult.request.kind === "panda-css-style-module-attach" || requestResult.request.kind === "panda-css-style-module-detach" ? await writeStyleModulePatch({
1685
+ request: requestResult.request,
1686
+ projectRoot: options.projectRoot,
1687
+ cssImportSources: options.cssImportSources,
1688
+ styledSystemPackageName: await inferStyledSystemPackageName(options.projectRoot),
1689
+ attributes: options.attributes,
1690
+ onManifestEntries: options.onManifestEntries
1691
+ }) : requestResult.request.kind === "panda-css-batch" ? await writeStaticCssBatchPatch({
1692
+ manifest: options.manifest(),
1693
+ request: requestResult.request,
1694
+ projectRoot: options.projectRoot,
1695
+ cssImportSources: options.cssImportSources,
1696
+ sourceSyntax: options.sourceSyntax,
1697
+ onManifestEntries: options.onManifestEntries
1698
+ }) : await writeStaticCssPatch({
1699
+ manifest: options.manifest(),
1700
+ request: requestResult.request,
1701
+ projectRoot: options.projectRoot,
1702
+ cssImportSources: options.cssImportSources,
1703
+ sourceSyntax: options.sourceSyntax,
1704
+ onManifestEntries: options.onManifestEntries
1705
+ });
1706
+ } catch (error) {
1707
+ logUnexpectedDevServerError("Unhandled Sculpted writeback failure.", error);
1708
+ const failure = styleEditFailure(requestResult.request.kind === "panda-css-batch" ? requestResult.request.requests.map((item) => item.editId).join(" ") : requestResult.request.editId, "write-failed", `Unhandled Sculpted writeback failure: ${errorMessage(error)}`);
1709
+ result = requestResult.request.kind === "panda-css-batch" ? styleEditBatchFailure(failure) : failure;
1710
+ }
1541
1711
  response.statusCode = result.ok ? 200 : 400;
1542
1712
  response.setHeader("content-type", "application/json");
1543
1713
  response.setHeader("cache-control", "no-store");
1544
1714
  response.end(JSON.stringify(result));
1545
1715
  if (result.ok && result.written && result.manifestUpdate) {
1546
1716
  notifyViteAboutWrittenFiles(server, options.projectRoot, result.manifestUpdate.changedFiles);
1547
- server.ws?.send(SCULPTED_EVENTS.manifestUpdate, result.manifestUpdate);
1717
+ sendViteEvent$1(server, SCULPTED_EVENTS.manifestUpdate, result.manifestUpdate);
1548
1718
  }
1549
1719
  });
1550
1720
  server.middlewares.use(DEFAULT_TOKEN_WRITEBACK_ENDPOINT, async (request, response) => {
@@ -1563,27 +1733,42 @@ function registerWritebackRoutes(server, options) {
1563
1733
  response.end(JSON.stringify(requestResult.response));
1564
1734
  return;
1565
1735
  }
1566
- const result = await writeTokenConfigPatch({
1567
- request: requestResult.request,
1568
- projectRoot: options.projectRoot,
1569
- pandaConfigPath: options.pandaConfigPath
1570
- });
1736
+ let result;
1737
+ try {
1738
+ result = await writeTokenConfigPatch({
1739
+ request: requestResult.request,
1740
+ projectRoot: options.projectRoot,
1741
+ pandaConfigPath: options.pandaConfigPath
1742
+ });
1743
+ } catch (error) {
1744
+ logUnexpectedDevServerError("Unhandled Sculpted token writeback failure.", error);
1745
+ result = tokenConfigEditFailure(requestResult.request.editId, "write-failed", `Unhandled Sculpted token writeback failure: ${errorMessage(error)}`);
1746
+ }
1571
1747
  response.statusCode = result.ok ? 200 : 400;
1572
1748
  response.setHeader("content-type", "application/json");
1573
1749
  response.setHeader("cache-control", "no-store");
1574
1750
  response.end(JSON.stringify(result));
1575
1751
  if (result.ok && result.written && result.metadataUpdate) {
1576
1752
  notifyViteAboutWrittenFiles(server, options.projectRoot, result.metadataUpdate.changedFiles);
1577
- server.ws?.send(SCULPTED_EVENTS.metadataUpdate, result.metadataUpdate);
1753
+ sendViteEvent$1(server, SCULPTED_EVENTS.metadataUpdate, result.metadataUpdate);
1578
1754
  }
1579
1755
  });
1580
1756
  }
1757
+ function sendViteEvent$1(server, event, payload) {
1758
+ try {
1759
+ server.ws?.send(event, payload);
1760
+ } catch {}
1761
+ }
1581
1762
  function notifyViteAboutWrittenFiles(server, projectRoot, changedFiles) {
1582
1763
  for (const changedFile of changedFiles) {
1583
1764
  const absoluteFile = normalizePath$1(`${projectRoot.replace(/\/$/, "")}/${changedFile}`);
1584
- const modules = server.moduleGraph?.getModulesByFile?.(absoluteFile);
1585
- if (modules) for (const module of modules) server.moduleGraph?.invalidateModule?.(module);
1586
- server.watcher?.emit?.("change", absoluteFile);
1765
+ try {
1766
+ const modules = server.moduleGraph?.getModulesByFile?.(absoluteFile);
1767
+ if (modules) for (const module of modules) server.moduleGraph?.invalidateModule?.(module);
1768
+ } catch {}
1769
+ try {
1770
+ server.watcher?.emit?.("change", absoluteFile);
1771
+ } catch {}
1587
1772
  }
1588
1773
  }
1589
1774
  async function readStyleEditRequest(request) {
@@ -1624,44 +1809,87 @@ async function readTokenConfigEditRequest(request) {
1624
1809
  };
1625
1810
  }
1626
1811
  }
1627
- async function readRequestBody(request) {
1628
- if (!request[Symbol.asyncIterator]) return "";
1629
- let body = "";
1630
- for await (const chunk of request) body += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
1631
- return body;
1632
- }
1633
1812
  function isStyleEditRequest(value) {
1634
1813
  if (!isRecord(value)) return false;
1814
+ if (!hasOnlyKeys(value, [
1815
+ "editId",
1816
+ "kind",
1817
+ "edits",
1818
+ "options"
1819
+ ])) return false;
1635
1820
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1636
1821
  if (value.kind !== "panda-css") return false;
1637
1822
  if (!Array.isArray(value.edits) || value.edits.length === 0) return false;
1823
+ if (!isStyleEditOptions(value.options, true)) return false;
1638
1824
  return value.edits.every((edit) => {
1639
1825
  if (!isRecord(edit)) return false;
1640
- if (edit.op === "set") return isPath(edit.path) && isJsonValue(edit.value);
1641
- if (edit.op === "delete") return isPath(edit.path);
1642
- if (edit.op === "rename") return isPath(edit.from) && isPath(edit.to);
1643
- if (edit.op === "replace-object") return isRecord(edit.value);
1826
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1827
+ "op",
1828
+ "path",
1829
+ "value"
1830
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1831
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1832
+ if (edit.op === "rename") return hasOnlyKeys(edit, [
1833
+ "op",
1834
+ "from",
1835
+ "to"
1836
+ ]) && isPath(edit.from) && isPath(edit.to);
1837
+ if (edit.op === "replace-object") return hasOnlyKeys(edit, ["op", "value"]) && isJsonObject(edit.value);
1644
1838
  return false;
1645
1839
  });
1646
1840
  }
1647
1841
  function isStyleEditBatchRequest(value) {
1648
1842
  if (!isRecord(value)) return false;
1843
+ if (!hasOnlyKeys(value, [
1844
+ "kind",
1845
+ "requests",
1846
+ "options"
1847
+ ])) return false;
1649
1848
  if (value.kind !== "panda-css-batch") return false;
1650
1849
  if (!Array.isArray(value.requests) || value.requests.length === 0) return false;
1850
+ if (!isStyleEditOptions(value.options, false)) return false;
1651
1851
  return value.requests.every(isStyleEditRequest);
1652
1852
  }
1653
1853
  function isInlineCssSourceCreateRequest(value) {
1654
1854
  if (!isRecord(value)) return false;
1855
+ if (!hasOnlyKeys(value, [
1856
+ "editId",
1857
+ "kind",
1858
+ "jsxSource",
1859
+ "edits",
1860
+ "options"
1861
+ ])) return false;
1655
1862
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1656
1863
  if (value.kind !== "panda-css-inline-source") return false;
1657
1864
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1658
- return value.options === void 0 || isRecord(value.options);
1865
+ if (value.edits !== void 0) {
1866
+ if (!Array.isArray(value.edits)) return false;
1867
+ if (!value.edits.every((edit) => {
1868
+ if (!isRecord(edit)) return false;
1869
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1870
+ "op",
1871
+ "path",
1872
+ "value"
1873
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1874
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1875
+ return false;
1876
+ })) return false;
1877
+ }
1878
+ return isStyleEditOptions(value.options, false);
1659
1879
  }
1660
1880
  function isStyleModuleEditRequest(value) {
1661
1881
  return isStyleModuleSourceCreateRequest(value) || isStyleModuleSourceAttachRequest(value) || isStyleModuleSourceDetachRequest(value);
1662
1882
  }
1663
1883
  function isStyleModuleSourceCreateRequest(value) {
1664
1884
  if (!isRecord(value)) return false;
1885
+ if (!hasOnlyKeys(value, [
1886
+ "editId",
1887
+ "kind",
1888
+ "componentSource",
1889
+ "name",
1890
+ "edits",
1891
+ "options"
1892
+ ])) return false;
1665
1893
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1666
1894
  if (value.kind !== "panda-css-style-module-source") return false;
1667
1895
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
@@ -1670,40 +1898,82 @@ function isStyleModuleSourceCreateRequest(value) {
1670
1898
  if (!Array.isArray(value.edits)) return false;
1671
1899
  if (!value.edits.every((edit) => {
1672
1900
  if (!isRecord(edit)) return false;
1673
- if (edit.op === "set") return isPath(edit.path) && isJsonValue(edit.value);
1674
- if (edit.op === "delete") return isPath(edit.path);
1901
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1902
+ "op",
1903
+ "path",
1904
+ "value"
1905
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1906
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1675
1907
  return false;
1676
1908
  })) return false;
1677
1909
  }
1678
- return value.options === void 0 || isRecord(value.options);
1910
+ return isStyleEditOptions(value.options, false);
1679
1911
  }
1680
1912
  function isStyleModuleSourceAttachRequest(value) {
1681
1913
  if (!isRecord(value)) return false;
1914
+ if (!hasOnlyKeys(value, [
1915
+ "editId",
1916
+ "kind",
1917
+ "jsxSource",
1918
+ "componentSource",
1919
+ "name",
1920
+ "options"
1921
+ ])) return false;
1682
1922
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1683
1923
  if (value.kind !== "panda-css-style-module-attach") return false;
1684
1924
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1685
1925
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
1686
1926
  if (typeof value.name !== "string" || value.name.length === 0) return false;
1687
- return value.options === void 0 || isRecord(value.options);
1927
+ return isStyleEditOptions(value.options, false);
1688
1928
  }
1689
1929
  function isStyleModuleSourceDetachRequest(value) {
1690
1930
  if (!isRecord(value)) return false;
1931
+ if (!hasOnlyKeys(value, [
1932
+ "editId",
1933
+ "kind",
1934
+ "jsxSource",
1935
+ "componentSource",
1936
+ "name",
1937
+ "options"
1938
+ ])) return false;
1691
1939
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1692
1940
  if (value.kind !== "panda-css-style-module-detach") return false;
1693
1941
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1694
1942
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
1695
1943
  if (typeof value.name !== "string" || value.name.length === 0) return false;
1696
- return value.options === void 0 || isRecord(value.options);
1944
+ return isStyleEditOptions(value.options, false);
1697
1945
  }
1698
1946
  function isTokenConfigEditRequest(value) {
1699
1947
  if (!isRecord(value)) return false;
1948
+ if (!hasOnlyKeys(value, [
1949
+ "editId",
1950
+ "kind",
1951
+ "sourceTargetId",
1952
+ "section",
1953
+ "path",
1954
+ "value",
1955
+ "options"
1956
+ ])) return false;
1700
1957
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1701
1958
  if (value.kind !== "panda-token-config") return false;
1702
1959
  if (typeof value.sourceTargetId !== "string" || value.sourceTargetId.length === 0) return false;
1703
1960
  if (value.section !== "tokens.colors" && value.section !== "semanticTokens.colors") return false;
1704
1961
  if (!isPath(value.path)) return false;
1705
1962
  if (!isJsonPrimitive(value.value)) return false;
1706
- return value.options === void 0 || isRecord(value.options);
1963
+ return isStyleEditOptions(value.options, true);
1964
+ }
1965
+ function isStyleEditOptions(value, allowExpectedSourceHash) {
1966
+ if (value === void 0) return true;
1967
+ if (!isRecord(value)) return false;
1968
+ for (const key of Object.keys(value)) {
1969
+ if (key === "write" || key === "format") continue;
1970
+ if (key === "expectedSourceHash" && allowExpectedSourceHash) continue;
1971
+ return false;
1972
+ }
1973
+ return (!("write" in value) || typeof value.write === "boolean") && (!("format" in value) || typeof value.format === "boolean") && (!allowExpectedSourceHash || !("expectedSourceHash" in value) || typeof value.expectedSourceHash === "string");
1974
+ }
1975
+ function hasOnlyKeys(value, allowedKeys) {
1976
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
1707
1977
  }
1708
1978
  function styleEditBatchFailure(response) {
1709
1979
  return {
@@ -1714,14 +1984,17 @@ function styleEditBatchFailure(response) {
1714
1984
  error: response.error
1715
1985
  };
1716
1986
  }
1717
- function isNewEmptyInlineSourceEntry(entry) {
1718
- return entry.kind === "panda-css" && (entry.attribute === "class" || entry.attribute === "className") && Object.keys(entry.styleObject).length === 0;
1987
+ function isNewInlineSourceEntry(entry, request) {
1988
+ const initialSetPaths = request.edits?.flatMap((edit) => edit.op === "set" && edit.path.length === 1 ? edit.path : []) ?? [];
1989
+ return entry.kind === "panda-css" && (entry.attribute === "class" || entry.attribute === "className") && (initialSetPaths.length === 0 ? Object.keys(entry.styleObject).length === 0 : initialSetPaths.every((path) => path in entry.styleObject));
1719
1990
  }
1720
1991
  async function writeInlineCssSourcePatch(options) {
1721
1992
  const parsed = parseJsxSourceFile(options.request.jsxSource);
1722
1993
  if (!parsed.ok) return styleEditFailure(options.request.editId, parsed.code, parsed.message, parsed.details);
1723
1994
  const filePath = safeProjectSourcePath(options.projectRoot, parsed.file);
1724
1995
  if (!filePath.ok) return styleEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
1996
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
1997
+ if (readPath) return readPath;
1725
1998
  let sourceText;
1726
1999
  try {
1727
2000
  sourceText = await readFile(filePath.file, "utf8");
@@ -1744,22 +2017,30 @@ async function writeInlineCssSourcePatch(options) {
1744
2017
  });
1745
2018
  if (!patch.ok || !patch.nextSource) return patch;
1746
2019
  if (options.request.options?.write !== true) return patch;
2020
+ let transformedEntries;
1747
2021
  try {
1748
- await writeFile(filePath.file, patch.nextSource, "utf8");
2022
+ transformedEntries = transformTsxSource({
2023
+ sourceText: patch.nextSource,
2024
+ filePath: filePath.file,
2025
+ projectRoot: options.projectRoot,
2026
+ attributes: options.attributes,
2027
+ cssImportSources: options.cssImportSources,
2028
+ sourceSyntax: options.sourceSyntax
2029
+ }).entries;
1749
2030
  } catch (error) {
1750
- return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
2031
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze source after inline source creation: ${error instanceof Error ? error.message : String(error)}`);
1751
2032
  }
1752
- const nextEntries = transformTsxSource({
1753
- sourceText: patch.nextSource,
1754
- filePath: filePath.file,
1755
- projectRoot: options.projectRoot,
1756
- attributes: options.attributes,
1757
- cssImportSources: options.cssImportSources,
1758
- sourceSyntax: options.sourceSyntax
1759
- }).entries.map((entry) => isNewEmptyInlineSourceEntry(entry) ? {
2033
+ const nextEntries = transformedEntries.map((entry) => isNewInlineSourceEntry(entry, options.request) ? {
1760
2034
  ...entry,
1761
2035
  jsxSourceAliases: Array.from(new Set([...entry.jsxSourceAliases ?? [], options.request.jsxSource]))
1762
2036
  } : entry);
2037
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2038
+ if (writePath) return writePath;
2039
+ try {
2040
+ await writeFile(filePath.file, patch.nextSource, "utf8");
2041
+ } catch (error) {
2042
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
2043
+ }
1763
2044
  options.onManifestEntries(filePath.file, nextEntries);
1764
2045
  const manifestUpdate = {
1765
2046
  version: 1,
@@ -1780,6 +2061,8 @@ async function writeStyleModulePatch(options) {
1780
2061
  if (!paths.ok) return styleEditFailure(options.request.editId, paths.code, paths.message, paths.details);
1781
2062
  const write = options.request.options?.write === true;
1782
2063
  if (options.request.kind === "panda-css-style-module-source") {
2064
+ const readPath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2065
+ if (readPath) return readPath;
1783
2066
  let sourceText;
1784
2067
  try {
1785
2068
  sourceText = await readFile(paths.paths.styleFile, "utf8");
@@ -1801,18 +2084,25 @@ async function writeStyleModulePatch(options) {
1801
2084
  });
1802
2085
  if (!patch.ok || !patch.nextSource) return patch;
1803
2086
  if (!write) return patch;
2087
+ let nextEntries;
2088
+ try {
2089
+ nextEntries = analyzePandaCssSource({
2090
+ sourceText: patch.nextSource,
2091
+ filePath: paths.paths.styleFile,
2092
+ projectRoot: options.projectRoot,
2093
+ cssImportSources: options.cssImportSources
2094
+ }).entries;
2095
+ } catch (error) {
2096
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze style module after writeback: ${error instanceof Error ? error.message : String(error)}`);
2097
+ }
2098
+ const writePath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2099
+ if (writePath) return writePath;
1804
2100
  try {
1805
2101
  await mkdir(dirname(paths.paths.styleFile), { recursive: true });
1806
2102
  await writeFile(paths.paths.styleFile, patch.nextSource, "utf8");
1807
2103
  } catch (error) {
1808
2104
  return styleEditFailure(options.request.editId, "write-failed", `Failed to write style module: ${error instanceof Error ? error.message : String(error)}`);
1809
2105
  }
1810
- const nextEntries = analyzePandaCssSource({
1811
- sourceText: patch.nextSource,
1812
- filePath: paths.paths.styleFile,
1813
- projectRoot: options.projectRoot,
1814
- cssImportSources: options.cssImportSources
1815
- }).entries;
1816
2106
  options.onManifestEntries(paths.paths.styleFile, nextEntries);
1817
2107
  const manifestUpdate = {
1818
2108
  version: 1,
@@ -1826,12 +2116,16 @@ async function writeStyleModulePatch(options) {
1826
2116
  };
1827
2117
  }
1828
2118
  let componentSourceText;
2119
+ const componentReadPath = await verifyWriteTarget(options.projectRoot, paths.paths.componentFile, options.request.editId);
2120
+ if (componentReadPath) return componentReadPath;
1829
2121
  try {
1830
2122
  componentSourceText = await readFile(paths.paths.componentFile, "utf8");
1831
2123
  } catch (error) {
1832
2124
  return styleEditFailure(options.request.editId, "write-failed", `Failed to read component module before style attachment: ${error instanceof Error ? error.message : String(error)}`);
1833
2125
  }
1834
2126
  let styleSourceText;
2127
+ const styleReadPath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2128
+ if (styleReadPath) return styleReadPath;
1835
2129
  try {
1836
2130
  styleSourceText = await readFile(paths.paths.styleFile, "utf8");
1837
2131
  } catch (error) {
@@ -1863,28 +2157,36 @@ async function writeStyleModulePatch(options) {
1863
2157
  });
1864
2158
  if (!patch.ok || !patch.nextSource) return patch;
1865
2159
  if (!write) return patch;
2160
+ let componentEntries;
2161
+ let analyzedStyleEntries;
1866
2162
  try {
1867
- await writeFile(paths.paths.componentFile, patch.nextSource, "utf8");
2163
+ componentEntries = transformTsxSource({
2164
+ sourceText: patch.nextSource,
2165
+ filePath: paths.paths.componentFile,
2166
+ projectRoot: options.projectRoot,
2167
+ attributes: options.attributes,
2168
+ cssImportSources: options.cssImportSources
2169
+ }).entries;
2170
+ analyzedStyleEntries = analyzePandaCssSource({
2171
+ sourceText: styleSourceText,
2172
+ filePath: paths.paths.styleFile,
2173
+ projectRoot: options.projectRoot,
2174
+ cssImportSources: options.cssImportSources
2175
+ }).entries;
1868
2176
  } catch (error) {
1869
- return styleEditFailure(options.request.editId, "write-failed", `Failed to write component module: ${error instanceof Error ? error.message : String(error)}`);
2177
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze style-module attachment after writeback: ${error instanceof Error ? error.message : String(error)}`);
1870
2178
  }
1871
- const componentEntries = transformTsxSource({
1872
- sourceText: patch.nextSource,
1873
- filePath: paths.paths.componentFile,
1874
- projectRoot: options.projectRoot,
1875
- attributes: options.attributes,
1876
- cssImportSources: options.cssImportSources
1877
- }).entries;
1878
- const analyzedStyleEntries = analyzePandaCssSource({
1879
- sourceText: styleSourceText,
1880
- filePath: paths.paths.styleFile,
1881
- projectRoot: options.projectRoot,
1882
- cssImportSources: options.cssImportSources
1883
- }).entries;
1884
2179
  const styleEntries = styleModuleEntriesWithJsxAlias({
1885
2180
  entries: analyzedStyleEntries,
1886
2181
  request: options.request
1887
2182
  });
2183
+ const writePath = await verifyWriteTarget(options.projectRoot, paths.paths.componentFile, options.request.editId);
2184
+ if (writePath) return writePath;
2185
+ try {
2186
+ await writeFile(paths.paths.componentFile, patch.nextSource, "utf8");
2187
+ } catch (error) {
2188
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to write component module: ${error instanceof Error ? error.message : String(error)}`);
2189
+ }
1888
2190
  options.onManifestEntries(paths.paths.componentFile, componentEntries);
1889
2191
  options.onManifestEntries(paths.paths.styleFile, styleEntries);
1890
2192
  const manifestUpdate = {
@@ -1918,6 +2220,8 @@ async function writeStaticCssPatch(options) {
1918
2220
  if (!entry) return styleEditFailure(options.request.editId, "manifest-entry-not-found", "No manifest entry exists for edit id.");
1919
2221
  const filePath = trustedManifestFilePath(options.manifest, entry);
1920
2222
  if (!filePath.ok) return styleEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
2223
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2224
+ if (readPath) return readPath;
1921
2225
  let sourceText;
1922
2226
  try {
1923
2227
  sourceText = await readFile(filePath.file, "utf8");
@@ -1940,18 +2244,25 @@ async function writeStaticCssPatch(options) {
1940
2244
  });
1941
2245
  if (!patch.ok || !patch.nextSource) return patch;
1942
2246
  if (options.request.options?.write !== true) return patch;
2247
+ let nextEntries;
2248
+ try {
2249
+ nextEntries = analyzePandaCssSource({
2250
+ sourceText: patch.nextSource,
2251
+ filePath: filePath.file,
2252
+ projectRoot: options.projectRoot,
2253
+ cssImportSources: options.cssImportSources,
2254
+ sourceSyntax: options.sourceSyntax
2255
+ }).entries;
2256
+ } catch (error) {
2257
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze source after writeback: ${error instanceof Error ? error.message : String(error)}`);
2258
+ }
2259
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2260
+ if (writePath) return writePath;
1943
2261
  try {
1944
2262
  await writeFile(filePath.file, patch.nextSource, "utf8");
1945
2263
  } catch (error) {
1946
2264
  return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
1947
2265
  }
1948
- const nextEntries = analyzePandaCssSource({
1949
- sourceText: patch.nextSource,
1950
- filePath: filePath.file,
1951
- projectRoot: options.projectRoot,
1952
- cssImportSources: options.cssImportSources,
1953
- sourceSyntax: options.sourceSyntax
1954
- }).entries;
1955
2266
  options.onManifestEntries(filePath.file, nextEntries);
1956
2267
  const manifestUpdate = {
1957
2268
  version: 1,
@@ -1983,6 +2294,8 @@ async function writeStaticCssBatchPatch(options) {
1983
2294
  }
1984
2295
  const prepared = [];
1985
2296
  for (const group of groups.values()) {
2297
+ const readPath = await verifyWriteTarget(options.projectRoot, group.file, group.requests.map((request) => request.editId).join(" "));
2298
+ if (readPath) return styleEditBatchFailure(readPath);
1986
2299
  let sourceText;
1987
2300
  try {
1988
2301
  sourceText = await readFile(group.file, "utf8");
@@ -2004,30 +2317,52 @@ async function writeStaticCssBatchPatch(options) {
2004
2317
  sourceSyntax: options.sourceSyntax
2005
2318
  });
2006
2319
  if (!patch.ok || !patch.nextSource) return styleEditBatchFailure(patch);
2320
+ let nextEntries;
2321
+ if (write) try {
2322
+ nextEntries = analyzePandaCssSource({
2323
+ sourceText: patch.nextSource,
2324
+ filePath: group.file,
2325
+ projectRoot: options.projectRoot,
2326
+ cssImportSources: options.cssImportSources,
2327
+ sourceSyntax: options.sourceSyntax
2328
+ }).entries;
2329
+ } catch (error) {
2330
+ return styleEditBatchFailure(styleEditFailure(group.requests[0]?.editId ?? "", "write-failed", `Failed to analyze source after batch writeback: ${error instanceof Error ? error.message : String(error)}`));
2331
+ }
2007
2332
  prepared.push({
2008
2333
  file: group.file,
2009
2334
  changedFile: group.changedFile,
2010
2335
  requests: group.requests,
2011
- nextSource: patch.nextSource
2336
+ previousSource: sourceText,
2337
+ nextSource: patch.nextSource,
2338
+ nextEntries
2012
2339
  });
2013
2340
  }
2014
2341
  const changedFiles = prepared.map((item) => item.changedFile);
2015
2342
  const entryIds = [];
2016
- if (write) for (const item of prepared) {
2017
- try {
2018
- await writeFile(item.file, item.nextSource, "utf8");
2019
- } catch (error) {
2020
- return styleEditBatchFailure(styleEditFailure(item.requests[0]?.editId ?? "", "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`));
2343
+ if (write) {
2344
+ const written = [];
2345
+ for (const item of prepared) {
2346
+ const writePath = await verifyWriteTarget(options.projectRoot, item.file, item.requests.map((request) => request.editId).join(" "));
2347
+ if (writePath) return styleEditBatchFailure(writePath);
2348
+ try {
2349
+ await writeFile(item.file, item.nextSource, "utf8");
2350
+ written.push(item);
2351
+ } catch (error) {
2352
+ const rollbackFailures = [];
2353
+ for (const writtenItem of [...written].reverse()) try {
2354
+ await writeFile(writtenItem.file, writtenItem.previousSource, "utf8");
2355
+ } catch (rollbackError) {
2356
+ rollbackFailures.push(`${writtenItem.changedFile}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
2357
+ }
2358
+ return styleEditBatchFailure(styleEditFailure(item.requests[0]?.editId ?? "", "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`, rollbackFailures.length > 0 ? { rollbackFailures } : void 0));
2359
+ }
2360
+ }
2361
+ for (const item of prepared) {
2362
+ if (!item.nextEntries) continue;
2363
+ options.onManifestEntries(item.file, item.nextEntries);
2364
+ entryIds.push(...item.nextEntries.map((nextEntry) => nextEntry.id));
2021
2365
  }
2022
- const nextEntries = analyzePandaCssSource({
2023
- sourceText: item.nextSource,
2024
- filePath: item.file,
2025
- projectRoot: options.projectRoot,
2026
- cssImportSources: options.cssImportSources,
2027
- sourceSyntax: options.sourceSyntax
2028
- }).entries;
2029
- options.onManifestEntries(item.file, nextEntries);
2030
- entryIds.push(...nextEntries.map((nextEntry) => nextEntry.id));
2031
2366
  }
2032
2367
  const manifestUpdate = write ? {
2033
2368
  version: 1,
@@ -2060,6 +2395,8 @@ async function writeTokenConfigPatch(options) {
2060
2395
  if (!tokenSource) return tokenConfigEditFailure(options.request.editId, "manifest-entry-not-found", "No token source exists for source target id.", { sourceTargetId: options.request.sourceTargetId });
2061
2396
  const filePath = trustedTokenSourceFilePath(options.projectRoot, tokenSource);
2062
2397
  if (!filePath.ok) return tokenConfigEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
2398
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2399
+ if (readPath) return tokenConfigEditFailure(options.request.editId, readPath.error?.code ?? "path-outside-project", readPath.error?.message ?? "Token source path resolves outside the project root.");
2063
2400
  let sourceText;
2064
2401
  try {
2065
2402
  sourceText = await readFile(filePath.file, "utf8");
@@ -2082,19 +2419,27 @@ async function writeTokenConfigPatch(options) {
2082
2419
  });
2083
2420
  if (!patch.ok || !patch.nextSource) return patch;
2084
2421
  if (!write) return patch;
2422
+ let nextTokenSourceIds;
2423
+ try {
2424
+ nextTokenSourceIds = tokenSourceIdsForPandaConfigSource({
2425
+ projectRoot: options.projectRoot,
2426
+ filePath: filePath.file,
2427
+ sourceText: patch.nextSource
2428
+ });
2429
+ } catch (error) {
2430
+ return tokenConfigEditFailure(options.request.editId, "write-failed", `Failed to prepare editor metadata for token writeback: ${error instanceof Error ? error.message : String(error)}`);
2431
+ }
2432
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2433
+ if (writePath) return writePath;
2085
2434
  try {
2086
2435
  await writeFile(filePath.file, patch.nextSource, "utf8");
2087
2436
  } catch (error) {
2088
2437
  return tokenConfigEditFailure(options.request.editId, "write-failed", `Failed to write Panda config: ${error instanceof Error ? error.message : String(error)}`);
2089
2438
  }
2090
- const nextMetadata = await createEditorMetadata({
2091
- projectRoot: options.projectRoot,
2092
- pandaConfigPath: options.pandaConfigPath
2093
- });
2094
2439
  const metadataUpdate = {
2095
2440
  version: 1,
2096
2441
  changedFiles: [toRelativeProjectPath$1(filePath.file, options.projectRoot)],
2097
- tokenSourceIds: nextMetadata.tokenSources.status === "available" ? nextMetadata.tokenSources.items.map((item) => item.id) : []
2442
+ tokenSourceIds: nextTokenSourceIds
2098
2443
  };
2099
2444
  return {
2100
2445
  ...patch,
@@ -2111,6 +2456,11 @@ async function inferStyledSystemPackageName(projectRoot) {
2111
2456
  return;
2112
2457
  }
2113
2458
  }
2459
+ async function verifyWriteTarget(projectRoot, filePath, editId) {
2460
+ const verified = await verifyProjectWritePath(projectRoot, filePath);
2461
+ if (verified.ok) return void 0;
2462
+ return styleEditFailure(editId, verified.code, verified.message, verified.details);
2463
+ }
2114
2464
  function styleEditFailure(editId, code, message, details) {
2115
2465
  return {
2116
2466
  ok: false,
@@ -2160,21 +2510,17 @@ function isPath(value) {
2160
2510
  }
2161
2511
  function isJsonValue(value) {
2162
2512
  if (value === null) return true;
2163
- if ([
2164
- "string",
2165
- "number",
2166
- "boolean"
2167
- ].includes(typeof value)) return true;
2513
+ if (typeof value === "string" || typeof value === "boolean") return true;
2514
+ if (typeof value === "number") return Number.isFinite(value);
2168
2515
  if (Array.isArray(value)) return value.every(isJsonValue);
2169
2516
  if (!isRecord(value)) return false;
2170
2517
  return Object.values(value).every(isJsonValue);
2171
2518
  }
2172
2519
  function isJsonPrimitive(value) {
2173
- return value === null || [
2174
- "string",
2175
- "number",
2176
- "boolean"
2177
- ].includes(typeof value);
2520
+ return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
2521
+ }
2522
+ function isJsonObject(value) {
2523
+ return isRecord(value) && Object.values(value).every(isJsonValue);
2178
2524
  }
2179
2525
  //#endregion
2180
2526
  //#region src/vite.ts
@@ -2217,10 +2563,20 @@ function sculpted(options = {}) {
2217
2563
  response.end(JSON.stringify(manifest()));
2218
2564
  });
2219
2565
  nextServer.middlewares.use(editorMetadataEndpoint, async (_request, response) => {
2220
- const metadata = await createEditorMetadata({
2221
- projectRoot,
2222
- pandaConfigPath: options.panda?.configPath
2223
- });
2566
+ let metadata;
2567
+ try {
2568
+ metadata = await createEditorMetadata({
2569
+ projectRoot,
2570
+ pandaConfigPath: options.panda?.configPath
2571
+ });
2572
+ } catch (error) {
2573
+ logUnexpectedDevServerError("Failed to create Sculpted editor metadata.", error);
2574
+ response.statusCode = 500;
2575
+ response.setHeader("content-type", "text/plain");
2576
+ response.setHeader("cache-control", "no-store");
2577
+ response.end(`Failed to create Sculpted editor metadata: ${errorMessage(error)}`);
2578
+ return;
2579
+ }
2224
2580
  response.statusCode = 200;
2225
2581
  response.setHeader("content-type", "application/json");
2226
2582
  response.setHeader("cache-control", "no-store");
@@ -2242,11 +2598,17 @@ function sculpted(options = {}) {
2242
2598
  response.end(JSON.stringify(requestResult.response));
2243
2599
  return;
2244
2600
  }
2245
- const result = await resolveOpenSourceLocation({
2246
- request: requestResult.request,
2247
- manifest: manifest(),
2248
- projectRoot
2249
- });
2601
+ let result;
2602
+ try {
2603
+ result = await resolveOpenSourceLocation({
2604
+ request: requestResult.request,
2605
+ manifest: manifest(),
2606
+ projectRoot
2607
+ });
2608
+ } catch (error) {
2609
+ logUnexpectedDevServerError("Unhandled Sculpted source-open failure.", error);
2610
+ result = openSourceLocationFailure("open-failed", `Unhandled Sculpted source-open failure: ${errorMessage(error)}`);
2611
+ }
2250
2612
  response.statusCode = result.ok ? 200 : 400;
2251
2613
  response.setHeader("content-type", "application/json");
2252
2614
  response.setHeader("cache-control", "no-store");
@@ -2303,7 +2665,7 @@ function sculpted(options = {}) {
2303
2665
  sourceSyntax: options.sourceSyntax
2304
2666
  });
2305
2667
  manifestByFile.set(filePath, result.entries);
2306
- if (result.entries.length > 0) server?.ws?.send(SCULPTED_EVENTS.manifestUpdate, {
2668
+ if (result.entries.length > 0) sendViteEvent(server, SCULPTED_EVENTS.manifestUpdate, {
2307
2669
  version: 1,
2308
2670
  changedFiles: [toRelativeProjectPath$1(filePath, projectRoot)],
2309
2671
  entryIds: result.entries.map((entry) => entry.id)
@@ -2316,5 +2678,10 @@ function sculpted(options = {}) {
2316
2678
  }
2317
2679
  };
2318
2680
  }
2681
+ function sendViteEvent(server, event, payload) {
2682
+ try {
2683
+ server?.ws?.send(event, payload);
2684
+ } catch {}
2685
+ }
2319
2686
  //#endregion
2320
2687
  export { sculpted };