sculpted 0.3.1 → 0.3.3

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";
@@ -281,22 +281,43 @@ async function createEditorMetadata(options) {
281
281
  properties
282
282
  };
283
283
  }
284
+ function tokenSourceIdsForPandaConfigSource(options) {
285
+ return parsePandaConfigEditorMetadata(options).tokenSources.map((item) => item.id);
286
+ }
284
287
  async function readPandaConfigSource(projectRoot, pandaConfigPath) {
285
288
  const root = normalizePath(projectRoot).replace(/\/$/, "");
286
289
  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 {
290
+ for (const filePath of candidates) {
291
+ try {
292
+ await access(filePath);
293
+ } catch (error) {
294
+ if (isMissingFileError$2(error)) continue;
295
+ return {
296
+ ok: false,
297
+ reason: "panda-config-unreadable",
298
+ message: `Failed to access Panda config ${filePath}: ${errorMessage$1(error)}`
299
+ };
300
+ }
301
+ const safePath = await verifyProjectExistingPath(root, filePath);
302
+ if (!safePath.ok) return {
296
303
  ok: false,
297
304
  reason: "panda-config-unreadable",
298
- message: `Failed to read Panda config ${filePath}: ${errorMessage(error)}`
305
+ message: safePath.message
299
306
  };
307
+ try {
308
+ return {
309
+ ok: true,
310
+ filePath,
311
+ sourceText: await readFile(filePath, "utf8")
312
+ };
313
+ } catch (error) {
314
+ if (isMissingFileError$2(error)) continue;
315
+ return {
316
+ ok: false,
317
+ reason: "panda-config-unreadable",
318
+ message: `Failed to read Panda config ${filePath}: ${errorMessage$1(error)}`
319
+ };
320
+ }
300
321
  }
301
322
  return {
302
323
  ok: false,
@@ -446,7 +467,7 @@ async function resolvePandaConfigEditorMetadata(options) {
446
467
  } catch (error) {
447
468
  return {
448
469
  ok: false,
449
- message: `Failed to resolve Panda token metadata: ${errorMessage(error)}`
470
+ message: `Failed to resolve Panda token metadata: ${errorMessage$1(error)}`
450
471
  };
451
472
  }
452
473
  }
@@ -697,7 +718,7 @@ function collectColorTokenMetadata(object, options, path = []) {
697
718
  sourcePath: nextPath,
698
719
  writable: options.writable,
699
720
  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]) => ({
721
+ conditions: options.kind === "semantic-token" && isJsonObject$1(value) ? Object.entries(value).map(([condition, conditionValue]) => ({
701
722
  condition,
702
723
  value: conditionValue,
703
724
  swatch: swatchFromJsonValue(conditionValue)
@@ -822,7 +843,7 @@ function unavailableMetadataSection(reason, message) {
822
843
  message
823
844
  };
824
845
  }
825
- function isJsonObject(value) {
846
+ function isJsonObject$1(value) {
826
847
  return typeof value === "object" && value !== null && !Array.isArray(value);
827
848
  }
828
849
  function swatchFromJsonValue(value) {
@@ -848,7 +869,7 @@ function normalizePath(path) {
848
869
  function isMissingFileError$2(error) {
849
870
  return isRecord$3(error) && error.code === "ENOENT";
850
871
  }
851
- function errorMessage(error) {
872
+ function errorMessage$1(error) {
852
873
  return error instanceof Error ? error.message : String(error);
853
874
  }
854
875
  function isRecord$3(value) {
@@ -866,10 +887,33 @@ function isJsonValue$1(value) {
866
887
  return Object.values(value).every(isJsonValue$1);
867
888
  }
868
889
  //#endregion
890
+ //#region src/vite/logging.ts
891
+ function logUnexpectedDevServerError(message, error) {
892
+ console.error(`[sculpted] ${message}`, error);
893
+ }
894
+ function errorMessage(error) {
895
+ return error instanceof Error ? error.message : String(error);
896
+ }
897
+ //#endregion
898
+ //#region src/vite/requestBody.ts
899
+ const MAX_DEV_SERVER_JSON_BODY_BYTES = 1024 * 1024;
900
+ async function readRequestBody(request, maxBytes = MAX_DEV_SERVER_JSON_BODY_BYTES) {
901
+ if (!request[Symbol.asyncIterator]) return "";
902
+ let bytes = 0;
903
+ let body = "";
904
+ for await (const chunk of request) {
905
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
906
+ bytes += Buffer.byteLength(text);
907
+ if (bytes > maxBytes) throw new Error(`Request body exceeds ${maxBytes} bytes.`);
908
+ body += text;
909
+ }
910
+ return body;
911
+ }
912
+ //#endregion
869
913
  //#region src/vite/sourceLocation.ts
870
914
  async function readOpenSourceLocationRequest(request) {
871
915
  try {
872
- const body = await readRequestBody$2(request);
916
+ const body = await readRequestBody(request);
873
917
  const parsed = JSON.parse(body);
874
918
  if (!isOpenSourceLocationRequest(parsed)) return {
875
919
  ok: false,
@@ -900,6 +944,8 @@ async function resolveOpenSourceLocation(options) {
900
944
  } catch {
901
945
  return openSourceLocationFailure("file-not-found", "Component source file does not exist.", { file: toRelativeProjectPath$1(trustedPath.file, options.projectRoot) });
902
946
  }
947
+ const resolvedPath = await verifyProjectExistingPath(options.projectRoot, trustedPath.file);
948
+ if (!resolvedPath.ok) return openSourceLocationFailure("path-outside-project", resolvedPath.message, resolvedPath.details);
903
949
  return openSourceLocationSuccess({
904
950
  file: trustedPath.file,
905
951
  line,
@@ -916,6 +962,8 @@ async function resolveOpenSourceLocation(options) {
916
962
  } catch {
917
963
  return openSourceLocationFailure("file-not-found", "Source file does not exist.", { file: toRelativeProjectPath$1(safePath.file, options.projectRoot) });
918
964
  }
965
+ const resolvedPath = await verifyProjectExistingPath(options.projectRoot, safePath.file);
966
+ if (!resolvedPath.ok) return openSourceLocationFailure("path-outside-project", resolvedPath.message, resolvedPath.details);
919
967
  return openSourceLocationSuccess({
920
968
  file: safePath.file,
921
969
  line: parsed.line,
@@ -935,9 +983,9 @@ function parseSourceLocationText(source) {
935
983
  ok: false,
936
984
  response: openSourceLocationFailure("invalid-request", "Source location must include a file path.")
937
985
  };
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 {
986
+ const line = parseOptionalPositiveInteger(match[2]);
987
+ const column = parseOptionalPositiveInteger(match[3]);
988
+ if (match[2] !== void 0 && line === void 0 || match[3] !== void 0 && column === void 0) return {
941
989
  ok: false,
942
990
  response: openSourceLocationFailure("invalid-request", "Line and column numbers must be positive integers.")
943
991
  };
@@ -948,6 +996,11 @@ function parseSourceLocationText(source) {
948
996
  column
949
997
  };
950
998
  }
999
+ function parseOptionalPositiveInteger(value) {
1000
+ if (value === void 0) return void 0;
1001
+ const parsed = Number(value);
1002
+ return Number.isSafeInteger(parsed) && parsed >= 1 ? parsed : void 0;
1003
+ }
951
1004
  function openSourceLocationSuccess(options) {
952
1005
  const file = toRelativeProjectPath$1(options.file, options.projectRoot);
953
1006
  const location = [
@@ -975,15 +1028,12 @@ function openSourceLocationFailure(code, message, details) {
975
1028
  }
976
1029
  function isOpenSourceLocationRequest(value) {
977
1030
  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;
1031
+ if (value.kind === "source") return hasOnlyKeys$2(value, ["kind", "source"]) && typeof value.source === "string" && value.source.length > 0;
1032
+ if (value.kind === "component") return hasOnlyKeys$2(value, ["kind", "editId"]) && typeof value.editId === "string" && value.editId.length > 0;
980
1033
  return false;
981
1034
  }
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;
1035
+ function hasOnlyKeys$2(value, allowedKeys) {
1036
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
987
1037
  }
988
1038
  function isRecord$2(value) {
989
1039
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -1010,10 +1060,11 @@ function registerRuntimeModuleRoutes(server, options) {
1010
1060
  response.setHeader("cache-control", "no-store");
1011
1061
  response.end(runtimeModuleSourceMap);
1012
1062
  } catch (error) {
1063
+ logUnexpectedDevServerError("Failed to load Sculpted runtime module source map.", error);
1013
1064
  response.statusCode = 404;
1014
1065
  response.setHeader("content-type", "text/plain");
1015
1066
  response.setHeader("cache-control", "no-store");
1016
- response.end(`Failed to load Sculpted runtime module source map: ${error instanceof Error ? error.message : String(error)}`);
1067
+ response.end(`Failed to load Sculpted runtime module source map: ${errorMessage(error)}`);
1017
1068
  }
1018
1069
  });
1019
1070
  server.middlewares.use(options.runtimeModuleEndpoint, async (_request, response) => {
@@ -1024,10 +1075,11 @@ function registerRuntimeModuleRoutes(server, options) {
1024
1075
  response.setHeader("cache-control", "no-store");
1025
1076
  response.end(runtimeModuleSource);
1026
1077
  } catch (error) {
1078
+ logUnexpectedDevServerError("Failed to load Sculpted runtime module.", error);
1027
1079
  response.statusCode = 500;
1028
1080
  response.setHeader("content-type", "text/plain");
1029
1081
  response.setHeader("cache-control", "no-store");
1030
- response.end(`Failed to load Sculpted runtime module: ${error instanceof Error ? error.message : String(error)}`);
1082
+ response.end(`Failed to load Sculpted runtime module: ${errorMessage(error)}`);
1031
1083
  }
1032
1084
  });
1033
1085
  server.middlewares.use(options.runtimeChunkEndpoint, async (request, response) => {
@@ -1040,16 +1092,24 @@ function registerRuntimeModuleRoutes(server, options) {
1040
1092
  return;
1041
1093
  }
1042
1094
  try {
1095
+ if (!(await runtimeChunkFileNames()).has(fileName)) {
1096
+ response.statusCode = 404;
1097
+ response.setHeader("content-type", "text/plain");
1098
+ response.setHeader("cache-control", "no-store");
1099
+ response.end("Unknown Sculpted runtime chunk.");
1100
+ return;
1101
+ }
1043
1102
  const runtimeChunkSource = await readFile(new URL(`./${fileName}`, import.meta.url), "utf8");
1044
1103
  response.statusCode = 200;
1045
1104
  response.setHeader("content-type", fileName.endsWith(".map") ? "application/json" : "application/javascript");
1046
1105
  response.setHeader("cache-control", "no-store");
1047
1106
  response.end(runtimeChunkSource);
1048
1107
  } catch (error) {
1108
+ logUnexpectedDevServerError("Failed to load Sculpted runtime chunk.", error);
1049
1109
  response.statusCode = 404;
1050
1110
  response.setHeader("content-type", "text/plain");
1051
1111
  response.setHeader("cache-control", "no-store");
1052
- response.end(`Failed to load Sculpted runtime chunk: ${error instanceof Error ? error.message : String(error)}`);
1112
+ response.end(`Failed to load Sculpted runtime chunk: ${errorMessage(error)}`);
1053
1113
  }
1054
1114
  });
1055
1115
  server.middlewares.use(DEFAULT_UI_MODULE_SOURCE_MAP_ENDPOINT, async (_request, response) => {
@@ -1060,10 +1120,11 @@ function registerRuntimeModuleRoutes(server, options) {
1060
1120
  response.setHeader("cache-control", "no-store");
1061
1121
  response.end(uiModuleSourceMap);
1062
1122
  } catch (error) {
1123
+ logUnexpectedDevServerError("Failed to load Sculpted UI module source map.", error);
1063
1124
  response.statusCode = 404;
1064
1125
  response.setHeader("content-type", "text/plain");
1065
1126
  response.setHeader("cache-control", "no-store");
1066
- response.end(`Failed to load Sculpted UI module source map: ${error instanceof Error ? error.message : String(error)}`);
1127
+ response.end(`Failed to load Sculpted UI module source map: ${errorMessage(error)}`);
1067
1128
  }
1068
1129
  });
1069
1130
  server.middlewares.use(options.uiModuleEndpoint, async (_request, response) => {
@@ -1074,10 +1135,11 @@ function registerRuntimeModuleRoutes(server, options) {
1074
1135
  response.setHeader("cache-control", "no-store");
1075
1136
  response.end(uiModuleSource);
1076
1137
  } catch (error) {
1138
+ logUnexpectedDevServerError("Failed to load Sculpted UI module.", error);
1077
1139
  response.statusCode = 500;
1078
1140
  response.setHeader("content-type", "text/plain");
1079
1141
  response.setHeader("cache-control", "no-store");
1080
- response.end(`Failed to load Sculpted UI module: ${error instanceof Error ? error.message : String(error)}`);
1142
+ response.end(`Failed to load Sculpted UI module: ${errorMessage(error)}`);
1081
1143
  }
1082
1144
  });
1083
1145
  }
@@ -1096,6 +1158,22 @@ function runtimeBootstrapSource(options) {
1096
1158
  function rewriteInspectorModuleSource(source, runtimeChunkEndpoint, sourceMapEndpoint) {
1097
1159
  return source.replaceAll("from \"./", `from "${runtimeChunkEndpoint}`).replace(/\/\/# sourceMappingURL=[^\r\n]+/g, `//# sourceMappingURL=${sourceMapEndpoint}`);
1098
1160
  }
1161
+ async function runtimeChunkFileNames() {
1162
+ const [runtimeSource, uiSource] = await Promise.all([readFile(new URL("./runtime.mjs", import.meta.url), "utf8"), readFile(new URL("./ui.mjs", import.meta.url), "utf8")]);
1163
+ return runtimeChunkFileNamesFromSources([runtimeSource, uiSource]);
1164
+ }
1165
+ function runtimeChunkFileNamesFromSources(sources) {
1166
+ const fileNames = /* @__PURE__ */ new Set();
1167
+ for (const source of sources) for (const match of source.matchAll(/(?:from\s+|import\s*\()\s*["']\.\/([^"']+)["']/g)) {
1168
+ const specifier = match[1] ?? "";
1169
+ if (specifier.includes("/") || specifier.includes("\\") || /[?#]/.test(specifier)) continue;
1170
+ const fileName = runtimeChunkFileName(specifier);
1171
+ if (!fileName) continue;
1172
+ fileNames.add(fileName);
1173
+ if (fileName.endsWith(".mjs")) fileNames.add(`${fileName}.map`);
1174
+ }
1175
+ return fileNames;
1176
+ }
1099
1177
  function runtimeChunkFileName(url) {
1100
1178
  const fileName = (url ?? "").replace(/[?#].*$/, "").split("/").filter(Boolean).at(-1);
1101
1179
  if (!fileName || !/^[A-Za-z0-9._-]+\.mjs(?:\.map)?$/.test(fileName)) return void 0;
@@ -1132,23 +1210,52 @@ function registerStyleModuleRoute(server, options) {
1132
1210
  return;
1133
1211
  }
1134
1212
  let sourceText;
1213
+ let styleFileExists = true;
1135
1214
  try {
1136
- sourceText = await readFile(paths.paths.styleFile, "utf8");
1215
+ await access(paths.paths.styleFile);
1137
1216
  } catch (error) {
1138
- if (!isMissingFileError$1(error)) {
1217
+ if (isMissingFileError$1(error)) styleFileExists = false;
1218
+ else {
1219
+ logUnexpectedDevServerError("Failed to read Sculpted style module.", error);
1139
1220
  response.statusCode = 400;
1140
1221
  response.setHeader("content-type", "application/json");
1141
1222
  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)}`)));
1223
+ response.end(JSON.stringify(styleModuleFailure("write-failed", `Failed to read style module: ${errorMessage(error)}`)));
1143
1224
  return;
1144
1225
  }
1145
1226
  }
1146
- const result = readComponentStyleModule({
1147
- projectRoot: options.projectRoot,
1148
- componentSource: requestResult.componentSource,
1149
- sourceText,
1150
- cssImportSources: options.cssImportSources
1151
- });
1227
+ if (styleFileExists) {
1228
+ const safePath = await verifyProjectExistingPath(options.projectRoot, paths.paths.styleFile);
1229
+ if (!safePath.ok) {
1230
+ response.statusCode = 400;
1231
+ response.setHeader("content-type", "application/json");
1232
+ response.setHeader("cache-control", "no-store");
1233
+ response.end(JSON.stringify(styleModuleFailure(safePath.code, safePath.message, safePath.details)));
1234
+ return;
1235
+ }
1236
+ try {
1237
+ sourceText = await readFile(paths.paths.styleFile, "utf8");
1238
+ } catch (error) {
1239
+ logUnexpectedDevServerError("Failed to read Sculpted style module.", error);
1240
+ response.statusCode = 400;
1241
+ response.setHeader("content-type", "application/json");
1242
+ response.setHeader("cache-control", "no-store");
1243
+ response.end(JSON.stringify(styleModuleFailure("write-failed", `Failed to read style module: ${errorMessage(error)}`)));
1244
+ return;
1245
+ }
1246
+ }
1247
+ let result;
1248
+ try {
1249
+ result = readComponentStyleModule({
1250
+ projectRoot: options.projectRoot,
1251
+ componentSource: requestResult.componentSource,
1252
+ sourceText,
1253
+ cssImportSources: options.cssImportSources
1254
+ });
1255
+ } catch (error) {
1256
+ logUnexpectedDevServerError("Failed to inspect Sculpted style module.", error);
1257
+ result = styleModuleFailure("write-failed", `Failed to inspect style module: ${errorMessage(error)}`);
1258
+ }
1152
1259
  response.statusCode = result.ok ? 200 : 400;
1153
1260
  response.setHeader("content-type", "application/json");
1154
1261
  response.setHeader("cache-control", "no-store");
@@ -1157,9 +1264,9 @@ function registerStyleModuleRoute(server, options) {
1157
1264
  }
1158
1265
  async function readStyleModuleRequest(request) {
1159
1266
  try {
1160
- const body = await readRequestBody$1(request);
1267
+ const body = await readRequestBody(request);
1161
1268
  const parsed = JSON.parse(body);
1162
- if (!isRecord$1(parsed) || typeof parsed.componentSource !== "string") return {
1269
+ if (!isRecord$1(parsed) || !hasOnlyKeys$1(parsed, ["componentSource"]) || typeof parsed.componentSource !== "string" || parsed.componentSource.length === 0) return {
1163
1270
  ok: false,
1164
1271
  response: styleModuleFailure("invalid-edit", "Request body is not a valid style-module request.")
1165
1272
  };
@@ -1174,12 +1281,6 @@ async function readStyleModuleRequest(request) {
1174
1281
  };
1175
1282
  }
1176
1283
  }
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
1284
  function styleModuleFailure(code, message, details) {
1184
1285
  return {
1185
1286
  ok: false,
@@ -1193,6 +1294,9 @@ function styleModuleFailure(code, message, details) {
1193
1294
  function isMissingFileError$1(error) {
1194
1295
  return isRecord$1(error) && error.code === "ENOENT";
1195
1296
  }
1297
+ function hasOnlyKeys$1(value, allowedKeys) {
1298
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
1299
+ }
1196
1300
  function isRecord$1(value) {
1197
1301
  return typeof value === "object" && value !== null && !Array.isArray(value);
1198
1302
  }
@@ -1509,42 +1613,49 @@ function registerWritebackRoutes(server, options) {
1509
1613
  response.end(JSON.stringify(requestResult.response));
1510
1614
  return;
1511
1615
  }
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
- });
1616
+ let result;
1617
+ try {
1618
+ result = requestResult.request.kind === "panda-css-inline-source" ? await writeInlineCssSourcePatch({
1619
+ request: requestResult.request,
1620
+ projectRoot: options.projectRoot,
1621
+ cssImportSources: options.cssImportSources,
1622
+ sourceSyntax: options.sourceSyntax,
1623
+ attributes: options.attributes,
1624
+ onManifestEntries: options.onManifestEntries
1625
+ }) : 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({
1626
+ request: requestResult.request,
1627
+ projectRoot: options.projectRoot,
1628
+ cssImportSources: options.cssImportSources,
1629
+ styledSystemPackageName: await inferStyledSystemPackageName(options.projectRoot),
1630
+ attributes: options.attributes,
1631
+ onManifestEntries: options.onManifestEntries
1632
+ }) : requestResult.request.kind === "panda-css-batch" ? await writeStaticCssBatchPatch({
1633
+ manifest: options.manifest(),
1634
+ request: requestResult.request,
1635
+ projectRoot: options.projectRoot,
1636
+ cssImportSources: options.cssImportSources,
1637
+ sourceSyntax: options.sourceSyntax,
1638
+ onManifestEntries: options.onManifestEntries
1639
+ }) : await writeStaticCssPatch({
1640
+ manifest: options.manifest(),
1641
+ request: requestResult.request,
1642
+ projectRoot: options.projectRoot,
1643
+ cssImportSources: options.cssImportSources,
1644
+ sourceSyntax: options.sourceSyntax,
1645
+ onManifestEntries: options.onManifestEntries
1646
+ });
1647
+ } catch (error) {
1648
+ logUnexpectedDevServerError("Unhandled Sculpted writeback failure.", error);
1649
+ 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)}`);
1650
+ result = requestResult.request.kind === "panda-css-batch" ? styleEditBatchFailure(failure) : failure;
1651
+ }
1541
1652
  response.statusCode = result.ok ? 200 : 400;
1542
1653
  response.setHeader("content-type", "application/json");
1543
1654
  response.setHeader("cache-control", "no-store");
1544
1655
  response.end(JSON.stringify(result));
1545
1656
  if (result.ok && result.written && result.manifestUpdate) {
1546
1657
  notifyViteAboutWrittenFiles(server, options.projectRoot, result.manifestUpdate.changedFiles);
1547
- server.ws?.send(SCULPTED_EVENTS.manifestUpdate, result.manifestUpdate);
1658
+ sendViteEvent$1(server, SCULPTED_EVENTS.manifestUpdate, result.manifestUpdate);
1548
1659
  }
1549
1660
  });
1550
1661
  server.middlewares.use(DEFAULT_TOKEN_WRITEBACK_ENDPOINT, async (request, response) => {
@@ -1563,27 +1674,42 @@ function registerWritebackRoutes(server, options) {
1563
1674
  response.end(JSON.stringify(requestResult.response));
1564
1675
  return;
1565
1676
  }
1566
- const result = await writeTokenConfigPatch({
1567
- request: requestResult.request,
1568
- projectRoot: options.projectRoot,
1569
- pandaConfigPath: options.pandaConfigPath
1570
- });
1677
+ let result;
1678
+ try {
1679
+ result = await writeTokenConfigPatch({
1680
+ request: requestResult.request,
1681
+ projectRoot: options.projectRoot,
1682
+ pandaConfigPath: options.pandaConfigPath
1683
+ });
1684
+ } catch (error) {
1685
+ logUnexpectedDevServerError("Unhandled Sculpted token writeback failure.", error);
1686
+ result = tokenConfigEditFailure(requestResult.request.editId, "write-failed", `Unhandled Sculpted token writeback failure: ${errorMessage(error)}`);
1687
+ }
1571
1688
  response.statusCode = result.ok ? 200 : 400;
1572
1689
  response.setHeader("content-type", "application/json");
1573
1690
  response.setHeader("cache-control", "no-store");
1574
1691
  response.end(JSON.stringify(result));
1575
1692
  if (result.ok && result.written && result.metadataUpdate) {
1576
1693
  notifyViteAboutWrittenFiles(server, options.projectRoot, result.metadataUpdate.changedFiles);
1577
- server.ws?.send(SCULPTED_EVENTS.metadataUpdate, result.metadataUpdate);
1694
+ sendViteEvent$1(server, SCULPTED_EVENTS.metadataUpdate, result.metadataUpdate);
1578
1695
  }
1579
1696
  });
1580
1697
  }
1698
+ function sendViteEvent$1(server, event, payload) {
1699
+ try {
1700
+ server.ws?.send(event, payload);
1701
+ } catch {}
1702
+ }
1581
1703
  function notifyViteAboutWrittenFiles(server, projectRoot, changedFiles) {
1582
1704
  for (const changedFile of changedFiles) {
1583
1705
  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);
1706
+ try {
1707
+ const modules = server.moduleGraph?.getModulesByFile?.(absoluteFile);
1708
+ if (modules) for (const module of modules) server.moduleGraph?.invalidateModule?.(module);
1709
+ } catch {}
1710
+ try {
1711
+ server.watcher?.emit?.("change", absoluteFile);
1712
+ } catch {}
1587
1713
  }
1588
1714
  }
1589
1715
  async function readStyleEditRequest(request) {
@@ -1624,44 +1750,87 @@ async function readTokenConfigEditRequest(request) {
1624
1750
  };
1625
1751
  }
1626
1752
  }
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
1753
  function isStyleEditRequest(value) {
1634
1754
  if (!isRecord(value)) return false;
1755
+ if (!hasOnlyKeys(value, [
1756
+ "editId",
1757
+ "kind",
1758
+ "edits",
1759
+ "options"
1760
+ ])) return false;
1635
1761
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1636
1762
  if (value.kind !== "panda-css") return false;
1637
1763
  if (!Array.isArray(value.edits) || value.edits.length === 0) return false;
1764
+ if (!isStyleEditOptions(value.options, true)) return false;
1638
1765
  return value.edits.every((edit) => {
1639
1766
  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);
1767
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1768
+ "op",
1769
+ "path",
1770
+ "value"
1771
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1772
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1773
+ if (edit.op === "rename") return hasOnlyKeys(edit, [
1774
+ "op",
1775
+ "from",
1776
+ "to"
1777
+ ]) && isPath(edit.from) && isPath(edit.to);
1778
+ if (edit.op === "replace-object") return hasOnlyKeys(edit, ["op", "value"]) && isJsonObject(edit.value);
1644
1779
  return false;
1645
1780
  });
1646
1781
  }
1647
1782
  function isStyleEditBatchRequest(value) {
1648
1783
  if (!isRecord(value)) return false;
1784
+ if (!hasOnlyKeys(value, [
1785
+ "kind",
1786
+ "requests",
1787
+ "options"
1788
+ ])) return false;
1649
1789
  if (value.kind !== "panda-css-batch") return false;
1650
1790
  if (!Array.isArray(value.requests) || value.requests.length === 0) return false;
1791
+ if (!isStyleEditOptions(value.options, false)) return false;
1651
1792
  return value.requests.every(isStyleEditRequest);
1652
1793
  }
1653
1794
  function isInlineCssSourceCreateRequest(value) {
1654
1795
  if (!isRecord(value)) return false;
1796
+ if (!hasOnlyKeys(value, [
1797
+ "editId",
1798
+ "kind",
1799
+ "jsxSource",
1800
+ "edits",
1801
+ "options"
1802
+ ])) return false;
1655
1803
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1656
1804
  if (value.kind !== "panda-css-inline-source") return false;
1657
1805
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1658
- return value.options === void 0 || isRecord(value.options);
1806
+ if (value.edits !== void 0) {
1807
+ if (!Array.isArray(value.edits)) return false;
1808
+ if (!value.edits.every((edit) => {
1809
+ if (!isRecord(edit)) return false;
1810
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1811
+ "op",
1812
+ "path",
1813
+ "value"
1814
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1815
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1816
+ return false;
1817
+ })) return false;
1818
+ }
1819
+ return isStyleEditOptions(value.options, false);
1659
1820
  }
1660
1821
  function isStyleModuleEditRequest(value) {
1661
1822
  return isStyleModuleSourceCreateRequest(value) || isStyleModuleSourceAttachRequest(value) || isStyleModuleSourceDetachRequest(value);
1662
1823
  }
1663
1824
  function isStyleModuleSourceCreateRequest(value) {
1664
1825
  if (!isRecord(value)) return false;
1826
+ if (!hasOnlyKeys(value, [
1827
+ "editId",
1828
+ "kind",
1829
+ "componentSource",
1830
+ "name",
1831
+ "edits",
1832
+ "options"
1833
+ ])) return false;
1665
1834
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1666
1835
  if (value.kind !== "panda-css-style-module-source") return false;
1667
1836
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
@@ -1670,40 +1839,82 @@ function isStyleModuleSourceCreateRequest(value) {
1670
1839
  if (!Array.isArray(value.edits)) return false;
1671
1840
  if (!value.edits.every((edit) => {
1672
1841
  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);
1842
+ if (edit.op === "set") return hasOnlyKeys(edit, [
1843
+ "op",
1844
+ "path",
1845
+ "value"
1846
+ ]) && isPath(edit.path) && isJsonValue(edit.value);
1847
+ if (edit.op === "delete") return hasOnlyKeys(edit, ["op", "path"]) && isPath(edit.path);
1675
1848
  return false;
1676
1849
  })) return false;
1677
1850
  }
1678
- return value.options === void 0 || isRecord(value.options);
1851
+ return isStyleEditOptions(value.options, false);
1679
1852
  }
1680
1853
  function isStyleModuleSourceAttachRequest(value) {
1681
1854
  if (!isRecord(value)) return false;
1855
+ if (!hasOnlyKeys(value, [
1856
+ "editId",
1857
+ "kind",
1858
+ "jsxSource",
1859
+ "componentSource",
1860
+ "name",
1861
+ "options"
1862
+ ])) return false;
1682
1863
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1683
1864
  if (value.kind !== "panda-css-style-module-attach") return false;
1684
1865
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1685
1866
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
1686
1867
  if (typeof value.name !== "string" || value.name.length === 0) return false;
1687
- return value.options === void 0 || isRecord(value.options);
1868
+ return isStyleEditOptions(value.options, false);
1688
1869
  }
1689
1870
  function isStyleModuleSourceDetachRequest(value) {
1690
1871
  if (!isRecord(value)) return false;
1872
+ if (!hasOnlyKeys(value, [
1873
+ "editId",
1874
+ "kind",
1875
+ "jsxSource",
1876
+ "componentSource",
1877
+ "name",
1878
+ "options"
1879
+ ])) return false;
1691
1880
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1692
1881
  if (value.kind !== "panda-css-style-module-detach") return false;
1693
1882
  if (typeof value.jsxSource !== "string" || value.jsxSource.length === 0) return false;
1694
1883
  if (typeof value.componentSource !== "string" || value.componentSource.length === 0) return false;
1695
1884
  if (typeof value.name !== "string" || value.name.length === 0) return false;
1696
- return value.options === void 0 || isRecord(value.options);
1885
+ return isStyleEditOptions(value.options, false);
1697
1886
  }
1698
1887
  function isTokenConfigEditRequest(value) {
1699
1888
  if (!isRecord(value)) return false;
1889
+ if (!hasOnlyKeys(value, [
1890
+ "editId",
1891
+ "kind",
1892
+ "sourceTargetId",
1893
+ "section",
1894
+ "path",
1895
+ "value",
1896
+ "options"
1897
+ ])) return false;
1700
1898
  if (typeof value.editId !== "string" || value.editId.length === 0) return false;
1701
1899
  if (value.kind !== "panda-token-config") return false;
1702
1900
  if (typeof value.sourceTargetId !== "string" || value.sourceTargetId.length === 0) return false;
1703
1901
  if (value.section !== "tokens.colors" && value.section !== "semanticTokens.colors") return false;
1704
1902
  if (!isPath(value.path)) return false;
1705
1903
  if (!isJsonPrimitive(value.value)) return false;
1706
- return value.options === void 0 || isRecord(value.options);
1904
+ return isStyleEditOptions(value.options, true);
1905
+ }
1906
+ function isStyleEditOptions(value, allowExpectedSourceHash) {
1907
+ if (value === void 0) return true;
1908
+ if (!isRecord(value)) return false;
1909
+ for (const key of Object.keys(value)) {
1910
+ if (key === "write" || key === "format") continue;
1911
+ if (key === "expectedSourceHash" && allowExpectedSourceHash) continue;
1912
+ return false;
1913
+ }
1914
+ return (!("write" in value) || typeof value.write === "boolean") && (!("format" in value) || typeof value.format === "boolean") && (!allowExpectedSourceHash || !("expectedSourceHash" in value) || typeof value.expectedSourceHash === "string");
1915
+ }
1916
+ function hasOnlyKeys(value, allowedKeys) {
1917
+ return Object.keys(value).every((key) => allowedKeys.includes(key));
1707
1918
  }
1708
1919
  function styleEditBatchFailure(response) {
1709
1920
  return {
@@ -1714,14 +1925,17 @@ function styleEditBatchFailure(response) {
1714
1925
  error: response.error
1715
1926
  };
1716
1927
  }
1717
- function isNewEmptyInlineSourceEntry(entry) {
1718
- return entry.kind === "panda-css" && (entry.attribute === "class" || entry.attribute === "className") && Object.keys(entry.styleObject).length === 0;
1928
+ function isNewInlineSourceEntry(entry, request) {
1929
+ const initialSetPaths = request.edits?.flatMap((edit) => edit.op === "set" && edit.path.length === 1 ? edit.path : []) ?? [];
1930
+ 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
1931
  }
1720
1932
  async function writeInlineCssSourcePatch(options) {
1721
1933
  const parsed = parseJsxSourceFile(options.request.jsxSource);
1722
1934
  if (!parsed.ok) return styleEditFailure(options.request.editId, parsed.code, parsed.message, parsed.details);
1723
1935
  const filePath = safeProjectSourcePath(options.projectRoot, parsed.file);
1724
1936
  if (!filePath.ok) return styleEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
1937
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
1938
+ if (readPath) return readPath;
1725
1939
  let sourceText;
1726
1940
  try {
1727
1941
  sourceText = await readFile(filePath.file, "utf8");
@@ -1744,22 +1958,30 @@ async function writeInlineCssSourcePatch(options) {
1744
1958
  });
1745
1959
  if (!patch.ok || !patch.nextSource) return patch;
1746
1960
  if (options.request.options?.write !== true) return patch;
1961
+ let transformedEntries;
1747
1962
  try {
1748
- await writeFile(filePath.file, patch.nextSource, "utf8");
1963
+ transformedEntries = transformTsxSource({
1964
+ sourceText: patch.nextSource,
1965
+ filePath: filePath.file,
1966
+ projectRoot: options.projectRoot,
1967
+ attributes: options.attributes,
1968
+ cssImportSources: options.cssImportSources,
1969
+ sourceSyntax: options.sourceSyntax
1970
+ }).entries;
1749
1971
  } catch (error) {
1750
- return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
1972
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze source after inline source creation: ${error instanceof Error ? error.message : String(error)}`);
1751
1973
  }
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) ? {
1974
+ const nextEntries = transformedEntries.map((entry) => isNewInlineSourceEntry(entry, options.request) ? {
1760
1975
  ...entry,
1761
1976
  jsxSourceAliases: Array.from(new Set([...entry.jsxSourceAliases ?? [], options.request.jsxSource]))
1762
1977
  } : entry);
1978
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
1979
+ if (writePath) return writePath;
1980
+ try {
1981
+ await writeFile(filePath.file, patch.nextSource, "utf8");
1982
+ } catch (error) {
1983
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
1984
+ }
1763
1985
  options.onManifestEntries(filePath.file, nextEntries);
1764
1986
  const manifestUpdate = {
1765
1987
  version: 1,
@@ -1780,6 +2002,8 @@ async function writeStyleModulePatch(options) {
1780
2002
  if (!paths.ok) return styleEditFailure(options.request.editId, paths.code, paths.message, paths.details);
1781
2003
  const write = options.request.options?.write === true;
1782
2004
  if (options.request.kind === "panda-css-style-module-source") {
2005
+ const readPath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2006
+ if (readPath) return readPath;
1783
2007
  let sourceText;
1784
2008
  try {
1785
2009
  sourceText = await readFile(paths.paths.styleFile, "utf8");
@@ -1801,18 +2025,25 @@ async function writeStyleModulePatch(options) {
1801
2025
  });
1802
2026
  if (!patch.ok || !patch.nextSource) return patch;
1803
2027
  if (!write) return patch;
2028
+ let nextEntries;
2029
+ try {
2030
+ nextEntries = analyzePandaCssSource({
2031
+ sourceText: patch.nextSource,
2032
+ filePath: paths.paths.styleFile,
2033
+ projectRoot: options.projectRoot,
2034
+ cssImportSources: options.cssImportSources
2035
+ }).entries;
2036
+ } catch (error) {
2037
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze style module after writeback: ${error instanceof Error ? error.message : String(error)}`);
2038
+ }
2039
+ const writePath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2040
+ if (writePath) return writePath;
1804
2041
  try {
1805
2042
  await mkdir(dirname(paths.paths.styleFile), { recursive: true });
1806
2043
  await writeFile(paths.paths.styleFile, patch.nextSource, "utf8");
1807
2044
  } catch (error) {
1808
2045
  return styleEditFailure(options.request.editId, "write-failed", `Failed to write style module: ${error instanceof Error ? error.message : String(error)}`);
1809
2046
  }
1810
- const nextEntries = analyzePandaCssSource({
1811
- sourceText: patch.nextSource,
1812
- filePath: paths.paths.styleFile,
1813
- projectRoot: options.projectRoot,
1814
- cssImportSources: options.cssImportSources
1815
- }).entries;
1816
2047
  options.onManifestEntries(paths.paths.styleFile, nextEntries);
1817
2048
  const manifestUpdate = {
1818
2049
  version: 1,
@@ -1826,12 +2057,16 @@ async function writeStyleModulePatch(options) {
1826
2057
  };
1827
2058
  }
1828
2059
  let componentSourceText;
2060
+ const componentReadPath = await verifyWriteTarget(options.projectRoot, paths.paths.componentFile, options.request.editId);
2061
+ if (componentReadPath) return componentReadPath;
1829
2062
  try {
1830
2063
  componentSourceText = await readFile(paths.paths.componentFile, "utf8");
1831
2064
  } catch (error) {
1832
2065
  return styleEditFailure(options.request.editId, "write-failed", `Failed to read component module before style attachment: ${error instanceof Error ? error.message : String(error)}`);
1833
2066
  }
1834
2067
  let styleSourceText;
2068
+ const styleReadPath = await verifyWriteTarget(options.projectRoot, paths.paths.styleFile, options.request.editId);
2069
+ if (styleReadPath) return styleReadPath;
1835
2070
  try {
1836
2071
  styleSourceText = await readFile(paths.paths.styleFile, "utf8");
1837
2072
  } catch (error) {
@@ -1863,28 +2098,36 @@ async function writeStyleModulePatch(options) {
1863
2098
  });
1864
2099
  if (!patch.ok || !patch.nextSource) return patch;
1865
2100
  if (!write) return patch;
2101
+ let componentEntries;
2102
+ let analyzedStyleEntries;
1866
2103
  try {
1867
- await writeFile(paths.paths.componentFile, patch.nextSource, "utf8");
2104
+ componentEntries = transformTsxSource({
2105
+ sourceText: patch.nextSource,
2106
+ filePath: paths.paths.componentFile,
2107
+ projectRoot: options.projectRoot,
2108
+ attributes: options.attributes,
2109
+ cssImportSources: options.cssImportSources
2110
+ }).entries;
2111
+ analyzedStyleEntries = analyzePandaCssSource({
2112
+ sourceText: styleSourceText,
2113
+ filePath: paths.paths.styleFile,
2114
+ projectRoot: options.projectRoot,
2115
+ cssImportSources: options.cssImportSources
2116
+ }).entries;
1868
2117
  } catch (error) {
1869
- return styleEditFailure(options.request.editId, "write-failed", `Failed to write component module: ${error instanceof Error ? error.message : String(error)}`);
2118
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze style-module attachment after writeback: ${error instanceof Error ? error.message : String(error)}`);
1870
2119
  }
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
2120
  const styleEntries = styleModuleEntriesWithJsxAlias({
1885
2121
  entries: analyzedStyleEntries,
1886
2122
  request: options.request
1887
2123
  });
2124
+ const writePath = await verifyWriteTarget(options.projectRoot, paths.paths.componentFile, options.request.editId);
2125
+ if (writePath) return writePath;
2126
+ try {
2127
+ await writeFile(paths.paths.componentFile, patch.nextSource, "utf8");
2128
+ } catch (error) {
2129
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to write component module: ${error instanceof Error ? error.message : String(error)}`);
2130
+ }
1888
2131
  options.onManifestEntries(paths.paths.componentFile, componentEntries);
1889
2132
  options.onManifestEntries(paths.paths.styleFile, styleEntries);
1890
2133
  const manifestUpdate = {
@@ -1918,6 +2161,8 @@ async function writeStaticCssPatch(options) {
1918
2161
  if (!entry) return styleEditFailure(options.request.editId, "manifest-entry-not-found", "No manifest entry exists for edit id.");
1919
2162
  const filePath = trustedManifestFilePath(options.manifest, entry);
1920
2163
  if (!filePath.ok) return styleEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
2164
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2165
+ if (readPath) return readPath;
1921
2166
  let sourceText;
1922
2167
  try {
1923
2168
  sourceText = await readFile(filePath.file, "utf8");
@@ -1940,18 +2185,25 @@ async function writeStaticCssPatch(options) {
1940
2185
  });
1941
2186
  if (!patch.ok || !patch.nextSource) return patch;
1942
2187
  if (options.request.options?.write !== true) return patch;
2188
+ let nextEntries;
2189
+ try {
2190
+ nextEntries = analyzePandaCssSource({
2191
+ sourceText: patch.nextSource,
2192
+ filePath: filePath.file,
2193
+ projectRoot: options.projectRoot,
2194
+ cssImportSources: options.cssImportSources,
2195
+ sourceSyntax: options.sourceSyntax
2196
+ }).entries;
2197
+ } catch (error) {
2198
+ return styleEditFailure(options.request.editId, "write-failed", `Failed to analyze source after writeback: ${error instanceof Error ? error.message : String(error)}`);
2199
+ }
2200
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2201
+ if (writePath) return writePath;
1943
2202
  try {
1944
2203
  await writeFile(filePath.file, patch.nextSource, "utf8");
1945
2204
  } catch (error) {
1946
2205
  return styleEditFailure(options.request.editId, "write-failed", `Failed to write source file: ${error instanceof Error ? error.message : String(error)}`);
1947
2206
  }
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
2207
  options.onManifestEntries(filePath.file, nextEntries);
1956
2208
  const manifestUpdate = {
1957
2209
  version: 1,
@@ -1983,6 +2235,8 @@ async function writeStaticCssBatchPatch(options) {
1983
2235
  }
1984
2236
  const prepared = [];
1985
2237
  for (const group of groups.values()) {
2238
+ const readPath = await verifyWriteTarget(options.projectRoot, group.file, group.requests.map((request) => request.editId).join(" "));
2239
+ if (readPath) return styleEditBatchFailure(readPath);
1986
2240
  let sourceText;
1987
2241
  try {
1988
2242
  sourceText = await readFile(group.file, "utf8");
@@ -2004,30 +2258,52 @@ async function writeStaticCssBatchPatch(options) {
2004
2258
  sourceSyntax: options.sourceSyntax
2005
2259
  });
2006
2260
  if (!patch.ok || !patch.nextSource) return styleEditBatchFailure(patch);
2261
+ let nextEntries;
2262
+ if (write) try {
2263
+ nextEntries = analyzePandaCssSource({
2264
+ sourceText: patch.nextSource,
2265
+ filePath: group.file,
2266
+ projectRoot: options.projectRoot,
2267
+ cssImportSources: options.cssImportSources,
2268
+ sourceSyntax: options.sourceSyntax
2269
+ }).entries;
2270
+ } catch (error) {
2271
+ return styleEditBatchFailure(styleEditFailure(group.requests[0]?.editId ?? "", "write-failed", `Failed to analyze source after batch writeback: ${error instanceof Error ? error.message : String(error)}`));
2272
+ }
2007
2273
  prepared.push({
2008
2274
  file: group.file,
2009
2275
  changedFile: group.changedFile,
2010
2276
  requests: group.requests,
2011
- nextSource: patch.nextSource
2277
+ previousSource: sourceText,
2278
+ nextSource: patch.nextSource,
2279
+ nextEntries
2012
2280
  });
2013
2281
  }
2014
2282
  const changedFiles = prepared.map((item) => item.changedFile);
2015
2283
  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)}`));
2284
+ if (write) {
2285
+ const written = [];
2286
+ for (const item of prepared) {
2287
+ const writePath = await verifyWriteTarget(options.projectRoot, item.file, item.requests.map((request) => request.editId).join(" "));
2288
+ if (writePath) return styleEditBatchFailure(writePath);
2289
+ try {
2290
+ await writeFile(item.file, item.nextSource, "utf8");
2291
+ written.push(item);
2292
+ } catch (error) {
2293
+ const rollbackFailures = [];
2294
+ for (const writtenItem of [...written].reverse()) try {
2295
+ await writeFile(writtenItem.file, writtenItem.previousSource, "utf8");
2296
+ } catch (rollbackError) {
2297
+ rollbackFailures.push(`${writtenItem.changedFile}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
2298
+ }
2299
+ 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));
2300
+ }
2301
+ }
2302
+ for (const item of prepared) {
2303
+ if (!item.nextEntries) continue;
2304
+ options.onManifestEntries(item.file, item.nextEntries);
2305
+ entryIds.push(...item.nextEntries.map((nextEntry) => nextEntry.id));
2021
2306
  }
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
2307
  }
2032
2308
  const manifestUpdate = write ? {
2033
2309
  version: 1,
@@ -2060,6 +2336,8 @@ async function writeTokenConfigPatch(options) {
2060
2336
  if (!tokenSource) return tokenConfigEditFailure(options.request.editId, "manifest-entry-not-found", "No token source exists for source target id.", { sourceTargetId: options.request.sourceTargetId });
2061
2337
  const filePath = trustedTokenSourceFilePath(options.projectRoot, tokenSource);
2062
2338
  if (!filePath.ok) return tokenConfigEditFailure(options.request.editId, filePath.code, filePath.message, filePath.details);
2339
+ const readPath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2340
+ 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
2341
  let sourceText;
2064
2342
  try {
2065
2343
  sourceText = await readFile(filePath.file, "utf8");
@@ -2082,19 +2360,27 @@ async function writeTokenConfigPatch(options) {
2082
2360
  });
2083
2361
  if (!patch.ok || !patch.nextSource) return patch;
2084
2362
  if (!write) return patch;
2363
+ let nextTokenSourceIds;
2364
+ try {
2365
+ nextTokenSourceIds = tokenSourceIdsForPandaConfigSource({
2366
+ projectRoot: options.projectRoot,
2367
+ filePath: filePath.file,
2368
+ sourceText: patch.nextSource
2369
+ });
2370
+ } catch (error) {
2371
+ return tokenConfigEditFailure(options.request.editId, "write-failed", `Failed to prepare editor metadata for token writeback: ${error instanceof Error ? error.message : String(error)}`);
2372
+ }
2373
+ const writePath = await verifyWriteTarget(options.projectRoot, filePath.file, options.request.editId);
2374
+ if (writePath) return writePath;
2085
2375
  try {
2086
2376
  await writeFile(filePath.file, patch.nextSource, "utf8");
2087
2377
  } catch (error) {
2088
2378
  return tokenConfigEditFailure(options.request.editId, "write-failed", `Failed to write Panda config: ${error instanceof Error ? error.message : String(error)}`);
2089
2379
  }
2090
- const nextMetadata = await createEditorMetadata({
2091
- projectRoot: options.projectRoot,
2092
- pandaConfigPath: options.pandaConfigPath
2093
- });
2094
2380
  const metadataUpdate = {
2095
2381
  version: 1,
2096
2382
  changedFiles: [toRelativeProjectPath$1(filePath.file, options.projectRoot)],
2097
- tokenSourceIds: nextMetadata.tokenSources.status === "available" ? nextMetadata.tokenSources.items.map((item) => item.id) : []
2383
+ tokenSourceIds: nextTokenSourceIds
2098
2384
  };
2099
2385
  return {
2100
2386
  ...patch,
@@ -2111,6 +2397,11 @@ async function inferStyledSystemPackageName(projectRoot) {
2111
2397
  return;
2112
2398
  }
2113
2399
  }
2400
+ async function verifyWriteTarget(projectRoot, filePath, editId) {
2401
+ const verified = await verifyProjectWritePath(projectRoot, filePath);
2402
+ if (verified.ok) return void 0;
2403
+ return styleEditFailure(editId, verified.code, verified.message, verified.details);
2404
+ }
2114
2405
  function styleEditFailure(editId, code, message, details) {
2115
2406
  return {
2116
2407
  ok: false,
@@ -2160,21 +2451,17 @@ function isPath(value) {
2160
2451
  }
2161
2452
  function isJsonValue(value) {
2162
2453
  if (value === null) return true;
2163
- if ([
2164
- "string",
2165
- "number",
2166
- "boolean"
2167
- ].includes(typeof value)) return true;
2454
+ if (typeof value === "string" || typeof value === "boolean") return true;
2455
+ if (typeof value === "number") return Number.isFinite(value);
2168
2456
  if (Array.isArray(value)) return value.every(isJsonValue);
2169
2457
  if (!isRecord(value)) return false;
2170
2458
  return Object.values(value).every(isJsonValue);
2171
2459
  }
2172
2460
  function isJsonPrimitive(value) {
2173
- return value === null || [
2174
- "string",
2175
- "number",
2176
- "boolean"
2177
- ].includes(typeof value);
2461
+ return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
2462
+ }
2463
+ function isJsonObject(value) {
2464
+ return isRecord(value) && Object.values(value).every(isJsonValue);
2178
2465
  }
2179
2466
  //#endregion
2180
2467
  //#region src/vite.ts
@@ -2217,10 +2504,20 @@ function sculpted(options = {}) {
2217
2504
  response.end(JSON.stringify(manifest()));
2218
2505
  });
2219
2506
  nextServer.middlewares.use(editorMetadataEndpoint, async (_request, response) => {
2220
- const metadata = await createEditorMetadata({
2221
- projectRoot,
2222
- pandaConfigPath: options.panda?.configPath
2223
- });
2507
+ let metadata;
2508
+ try {
2509
+ metadata = await createEditorMetadata({
2510
+ projectRoot,
2511
+ pandaConfigPath: options.panda?.configPath
2512
+ });
2513
+ } catch (error) {
2514
+ logUnexpectedDevServerError("Failed to create Sculpted editor metadata.", error);
2515
+ response.statusCode = 500;
2516
+ response.setHeader("content-type", "text/plain");
2517
+ response.setHeader("cache-control", "no-store");
2518
+ response.end(`Failed to create Sculpted editor metadata: ${errorMessage(error)}`);
2519
+ return;
2520
+ }
2224
2521
  response.statusCode = 200;
2225
2522
  response.setHeader("content-type", "application/json");
2226
2523
  response.setHeader("cache-control", "no-store");
@@ -2242,11 +2539,17 @@ function sculpted(options = {}) {
2242
2539
  response.end(JSON.stringify(requestResult.response));
2243
2540
  return;
2244
2541
  }
2245
- const result = await resolveOpenSourceLocation({
2246
- request: requestResult.request,
2247
- manifest: manifest(),
2248
- projectRoot
2249
- });
2542
+ let result;
2543
+ try {
2544
+ result = await resolveOpenSourceLocation({
2545
+ request: requestResult.request,
2546
+ manifest: manifest(),
2547
+ projectRoot
2548
+ });
2549
+ } catch (error) {
2550
+ logUnexpectedDevServerError("Unhandled Sculpted source-open failure.", error);
2551
+ result = openSourceLocationFailure("open-failed", `Unhandled Sculpted source-open failure: ${errorMessage(error)}`);
2552
+ }
2250
2553
  response.statusCode = result.ok ? 200 : 400;
2251
2554
  response.setHeader("content-type", "application/json");
2252
2555
  response.setHeader("cache-control", "no-store");
@@ -2303,7 +2606,7 @@ function sculpted(options = {}) {
2303
2606
  sourceSyntax: options.sourceSyntax
2304
2607
  });
2305
2608
  manifestByFile.set(filePath, result.entries);
2306
- if (result.entries.length > 0) server?.ws?.send(SCULPTED_EVENTS.manifestUpdate, {
2609
+ if (result.entries.length > 0) sendViteEvent(server, SCULPTED_EVENTS.manifestUpdate, {
2307
2610
  version: 1,
2308
2611
  changedFiles: [toRelativeProjectPath$1(filePath, projectRoot)],
2309
2612
  entryIds: result.entries.map((entry) => entry.id)
@@ -2316,5 +2619,10 @@ function sculpted(options = {}) {
2316
2619
  }
2317
2620
  };
2318
2621
  }
2622
+ function sendViteEvent(server, event, payload) {
2623
+ try {
2624
+ server?.ws?.send(event, payload);
2625
+ } catch {}
2626
+ }
2319
2627
  //#endregion
2320
2628
  export { sculpted };