@valbuild/server 0.98.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7778,6 +7778,33 @@ async function downloadFileFromRemote(ref, filePath) {
7778
7778
  // record-level fix expands into per-entry errors that should point at the
7779
7779
  // individual entry (e.g. `?p="/public/val/logo.png"`) rather than the record.
7780
7780
 
7781
+ // Gallery entries are keyed by their file path. Remote galleries key uploaded
7782
+ // entries by a remote URL while keeping the file on disk at its local path, so
7783
+ // normalize a remote-URL key back to that local path for on-disk reads.
7784
+ function galleryKeyToLocalPath(key) {
7785
+ const res = core.Internal.remote.splitRemoteRef(key);
7786
+ return res.status === "success" ? `/${res.filePath}` : key;
7787
+ }
7788
+
7789
+ // Patch path of the record that holds a media entry. Media keys are file paths
7790
+ // that can contain dots, which sourceToPatchPath cannot round-trip, so strip
7791
+ // the (JSON-encoded) key segment off the entry source path and convert the
7792
+ // remaining parent path — whose ancestors are plain object keys / array
7793
+ // indices — instead.
7794
+ function parentPatchPathOfMediaEntry(sourcePath, entryKey) {
7795
+ const jsonKey = JSON.stringify(entryKey);
7796
+ let parent = sourcePath;
7797
+ if (parent.endsWith(jsonKey)) {
7798
+ parent = parent.slice(0, parent.length - jsonKey.length);
7799
+ }
7800
+ if (parent.endsWith(".")) {
7801
+ parent = parent.slice(0, -1);
7802
+ } else if (parent.endsWith(core.Internal.ModuleFilePathSep)) {
7803
+ parent = parent.slice(0, parent.length - core.Internal.ModuleFilePathSep.length);
7804
+ }
7805
+ return patch.sourceToPatchPath(parent);
7806
+ }
7807
+
7781
7808
  // TODO: find a better name? transformFixesToPatch?
7782
7809
  async function createFixPatch(config, apply, sourcePath, validationError, remoteFiles, moduleSource, moduleSchema) {
7783
7810
  const remainingErrors = [];
@@ -7955,6 +7982,61 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
7955
7982
  path: patch.sourceToPatchPath(sourcePath)
7956
7983
  });
7957
7984
  }
7985
+ } else if (fix === "images:upload-remote" || fix === "files:upload-remote") {
7986
+ // Gallery entry: the record is keyed by the file path, so uploading to
7987
+ // remote means renaming the key from the local path to the remote URL
7988
+ // (remove the old key, add the new one with the same metadata).
7989
+ const remoteFile = remoteFiles[sourcePath];
7990
+ if (!remoteFile) {
7991
+ remainingErrors.push({
7992
+ ...validationError,
7993
+ message: "Cannot fix local to remote gallery entry: remote file was not uploaded",
7994
+ fixes: undefined
7995
+ });
7996
+ continue;
7997
+ }
7998
+ const metadata = remoteFile.metadata;
7999
+ if (!metadata) {
8000
+ remainingErrors.push({
8001
+ ...validationError,
8002
+ message: "Failed to get metadata for remote gallery file",
8003
+ fixes: undefined
8004
+ });
8005
+ continue;
8006
+ }
8007
+ // The entry is keyed by its (local) file path, which can contain dots
8008
+ // (e.g. image.png). sourceToPatchPath can't round-trip such keys, so
8009
+ // build the patch path from the parent record path plus the key (which
8010
+ // validateMediaKey carries as the error value), like the UI does.
8011
+ const entryKey = validationError.value;
8012
+ if (typeof entryKey !== "string") {
8013
+ remainingErrors.push({
8014
+ ...validationError,
8015
+ message: "Cannot rewrite remote gallery entry: missing entry key",
8016
+ fixes: undefined
8017
+ });
8018
+ continue;
8019
+ }
8020
+ const parentPath = parentPatchPathOfMediaEntry(sourcePath, entryKey);
8021
+ const removePath = parentPath.concat(entryKey);
8022
+ const addPath = parentPath.concat(remoteFile.ref);
8023
+ if (!patch.isNotRoot(removePath) || !patch.isNotRoot(addPath)) {
8024
+ remainingErrors.push({
8025
+ ...validationError,
8026
+ message: "Cannot rewrite remote gallery entry at root path",
8027
+ fixes: undefined
8028
+ });
8029
+ continue;
8030
+ }
8031
+ patch$1.push({
8032
+ op: "remove",
8033
+ path: removePath
8034
+ });
8035
+ patch$1.push({
8036
+ op: "add",
8037
+ path: addPath,
8038
+ value: metadata
8039
+ });
7958
8040
  } else if (fix === "image:download-remote" || fix === "file:download-remote") {
7959
8041
  const v = getRemoteValueFromValidationError(validationError);
7960
8042
  if (!v.success) {
@@ -8031,7 +8113,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
8031
8113
  }
8032
8114
  const gallerySource = moduleSource;
8033
8115
  for (const [entryKey, storedEntry] of Object.entries(gallerySource)) {
8034
- const filename = path__namespace["default"].join(config.projectRoot, entryKey);
8116
+ const filename = path__namespace["default"].join(config.projectRoot, galleryKeyToLocalPath(entryKey));
8035
8117
  let buffer;
8036
8118
  try {
8037
8119
  buffer = fs__default["default"].readFileSync(filename);
@@ -8556,31 +8638,39 @@ function getModulePathRange(modulePath, modulePathMap,
8556
8638
  // existing callers.
8557
8639
  target = "key") {
8558
8640
  var _range$children;
8559
- // Handle empty or invalid module paths gracefully
8560
- if (!modulePath || typeof modulePath !== "string") {
8561
- return undefined;
8562
- }
8563
- let segments;
8564
- try {
8565
- // Quote-aware splitter that correctly handles keys containing dots
8566
- // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8567
- // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8568
- segments = core.Internal.splitModulePath(modulePath);
8569
- } catch {
8570
- // Return undefined if the module path is malformed. This can happen when
8571
- // there are upstream errors in schema serialization.
8572
- return undefined;
8573
- }
8574
- if (segments.length === 0) {
8641
+ // Handle invalid module paths gracefully
8642
+ if (typeof modulePath !== "string") {
8575
8643
  return undefined;
8576
8644
  }
8577
- let range = modulePathMap[segments[0]];
8578
- for (const pathSegment of segments.slice(1)) {
8579
- var _range;
8580
- if (!range) {
8581
- break;
8645
+ let range;
8646
+ if (modulePath === "") {
8647
+ // The empty module path is the module root, stored under the "" key: the
8648
+ // source argument of `c.define`. This is what module-level diagnostics
8649
+ // (which have no module path) resolve to.
8650
+ range = modulePathMap[""];
8651
+ } else {
8652
+ let segments;
8653
+ try {
8654
+ // Quote-aware splitter that correctly handles keys containing dots
8655
+ // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8656
+ // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8657
+ segments = core.Internal.splitModulePath(modulePath);
8658
+ } catch {
8659
+ // Return undefined if the module path is malformed. This can happen when
8660
+ // there are upstream errors in schema serialization.
8661
+ return undefined;
8662
+ }
8663
+ if (segments.length === 0) {
8664
+ return undefined;
8665
+ }
8666
+ range = modulePathMap[segments[0]];
8667
+ for (const pathSegment of segments.slice(1)) {
8668
+ var _range;
8669
+ if (!range) {
8670
+ break;
8671
+ }
8672
+ range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8582
8673
  }
8583
- range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8584
8674
  }
8585
8675
  if (!range) {
8586
8676
  return undefined;
@@ -8612,7 +8702,20 @@ function createModulePathMap(sourceFile) {
8612
8702
  if (ts__default["default"].isExportAssignment(child)) {
8613
8703
  const contentNode = child.expression && ts__default["default"].isCallExpression(child.expression) && child.expression.arguments[2];
8614
8704
  if (contentNode) {
8615
- return traverse(contentNode, sourceFile);
8705
+ const map = traverse(contentNode, sourceFile) ?? {};
8706
+ // Always expose the module root under the "" key so diagnostics without
8707
+ // a module path still resolve to a location. `traverse` already emits ""
8708
+ // for primitive roots (with a tighter range), so keep that if present.
8709
+ // Object/record keys are never "" (see traverseObjectLiteral), so this
8710
+ // cannot collide with a real key.
8711
+ return {
8712
+ ...map,
8713
+ "": map[""] ?? {
8714
+ children: {},
8715
+ start: sourceFile.getLineAndCharacterOfPosition(contentNode.getStart(sourceFile)),
8716
+ end: sourceFile.getLineAndCharacterOfPosition(contentNode.getEnd())
8717
+ }
8718
+ };
8616
8719
  }
8617
8720
  }
8618
8721
  }
@@ -7778,6 +7778,33 @@ async function downloadFileFromRemote(ref, filePath) {
7778
7778
  // record-level fix expands into per-entry errors that should point at the
7779
7779
  // individual entry (e.g. `?p="/public/val/logo.png"`) rather than the record.
7780
7780
 
7781
+ // Gallery entries are keyed by their file path. Remote galleries key uploaded
7782
+ // entries by a remote URL while keeping the file on disk at its local path, so
7783
+ // normalize a remote-URL key back to that local path for on-disk reads.
7784
+ function galleryKeyToLocalPath(key) {
7785
+ const res = core.Internal.remote.splitRemoteRef(key);
7786
+ return res.status === "success" ? `/${res.filePath}` : key;
7787
+ }
7788
+
7789
+ // Patch path of the record that holds a media entry. Media keys are file paths
7790
+ // that can contain dots, which sourceToPatchPath cannot round-trip, so strip
7791
+ // the (JSON-encoded) key segment off the entry source path and convert the
7792
+ // remaining parent path — whose ancestors are plain object keys / array
7793
+ // indices — instead.
7794
+ function parentPatchPathOfMediaEntry(sourcePath, entryKey) {
7795
+ const jsonKey = JSON.stringify(entryKey);
7796
+ let parent = sourcePath;
7797
+ if (parent.endsWith(jsonKey)) {
7798
+ parent = parent.slice(0, parent.length - jsonKey.length);
7799
+ }
7800
+ if (parent.endsWith(".")) {
7801
+ parent = parent.slice(0, -1);
7802
+ } else if (parent.endsWith(core.Internal.ModuleFilePathSep)) {
7803
+ parent = parent.slice(0, parent.length - core.Internal.ModuleFilePathSep.length);
7804
+ }
7805
+ return patch.sourceToPatchPath(parent);
7806
+ }
7807
+
7781
7808
  // TODO: find a better name? transformFixesToPatch?
7782
7809
  async function createFixPatch(config, apply, sourcePath, validationError, remoteFiles, moduleSource, moduleSchema) {
7783
7810
  const remainingErrors = [];
@@ -7955,6 +7982,61 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
7955
7982
  path: patch.sourceToPatchPath(sourcePath)
7956
7983
  });
7957
7984
  }
7985
+ } else if (fix === "images:upload-remote" || fix === "files:upload-remote") {
7986
+ // Gallery entry: the record is keyed by the file path, so uploading to
7987
+ // remote means renaming the key from the local path to the remote URL
7988
+ // (remove the old key, add the new one with the same metadata).
7989
+ const remoteFile = remoteFiles[sourcePath];
7990
+ if (!remoteFile) {
7991
+ remainingErrors.push({
7992
+ ...validationError,
7993
+ message: "Cannot fix local to remote gallery entry: remote file was not uploaded",
7994
+ fixes: undefined
7995
+ });
7996
+ continue;
7997
+ }
7998
+ const metadata = remoteFile.metadata;
7999
+ if (!metadata) {
8000
+ remainingErrors.push({
8001
+ ...validationError,
8002
+ message: "Failed to get metadata for remote gallery file",
8003
+ fixes: undefined
8004
+ });
8005
+ continue;
8006
+ }
8007
+ // The entry is keyed by its (local) file path, which can contain dots
8008
+ // (e.g. image.png). sourceToPatchPath can't round-trip such keys, so
8009
+ // build the patch path from the parent record path plus the key (which
8010
+ // validateMediaKey carries as the error value), like the UI does.
8011
+ const entryKey = validationError.value;
8012
+ if (typeof entryKey !== "string") {
8013
+ remainingErrors.push({
8014
+ ...validationError,
8015
+ message: "Cannot rewrite remote gallery entry: missing entry key",
8016
+ fixes: undefined
8017
+ });
8018
+ continue;
8019
+ }
8020
+ const parentPath = parentPatchPathOfMediaEntry(sourcePath, entryKey);
8021
+ const removePath = parentPath.concat(entryKey);
8022
+ const addPath = parentPath.concat(remoteFile.ref);
8023
+ if (!patch.isNotRoot(removePath) || !patch.isNotRoot(addPath)) {
8024
+ remainingErrors.push({
8025
+ ...validationError,
8026
+ message: "Cannot rewrite remote gallery entry at root path",
8027
+ fixes: undefined
8028
+ });
8029
+ continue;
8030
+ }
8031
+ patch$1.push({
8032
+ op: "remove",
8033
+ path: removePath
8034
+ });
8035
+ patch$1.push({
8036
+ op: "add",
8037
+ path: addPath,
8038
+ value: metadata
8039
+ });
7958
8040
  } else if (fix === "image:download-remote" || fix === "file:download-remote") {
7959
8041
  const v = getRemoteValueFromValidationError(validationError);
7960
8042
  if (!v.success) {
@@ -8031,7 +8113,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
8031
8113
  }
8032
8114
  const gallerySource = moduleSource;
8033
8115
  for (const [entryKey, storedEntry] of Object.entries(gallerySource)) {
8034
- const filename = path__namespace["default"].join(config.projectRoot, entryKey);
8116
+ const filename = path__namespace["default"].join(config.projectRoot, galleryKeyToLocalPath(entryKey));
8035
8117
  let buffer;
8036
8118
  try {
8037
8119
  buffer = fs__default["default"].readFileSync(filename);
@@ -8556,31 +8638,39 @@ function getModulePathRange(modulePath, modulePathMap,
8556
8638
  // existing callers.
8557
8639
  target = "key") {
8558
8640
  var _range$children;
8559
- // Handle empty or invalid module paths gracefully
8560
- if (!modulePath || typeof modulePath !== "string") {
8561
- return undefined;
8562
- }
8563
- let segments;
8564
- try {
8565
- // Quote-aware splitter that correctly handles keys containing dots
8566
- // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8567
- // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8568
- segments = core.Internal.splitModulePath(modulePath);
8569
- } catch {
8570
- // Return undefined if the module path is malformed. This can happen when
8571
- // there are upstream errors in schema serialization.
8572
- return undefined;
8573
- }
8574
- if (segments.length === 0) {
8641
+ // Handle invalid module paths gracefully
8642
+ if (typeof modulePath !== "string") {
8575
8643
  return undefined;
8576
8644
  }
8577
- let range = modulePathMap[segments[0]];
8578
- for (const pathSegment of segments.slice(1)) {
8579
- var _range;
8580
- if (!range) {
8581
- break;
8645
+ let range;
8646
+ if (modulePath === "") {
8647
+ // The empty module path is the module root, stored under the "" key: the
8648
+ // source argument of `c.define`. This is what module-level diagnostics
8649
+ // (which have no module path) resolve to.
8650
+ range = modulePathMap[""];
8651
+ } else {
8652
+ let segments;
8653
+ try {
8654
+ // Quote-aware splitter that correctly handles keys containing dots
8655
+ // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8656
+ // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8657
+ segments = core.Internal.splitModulePath(modulePath);
8658
+ } catch {
8659
+ // Return undefined if the module path is malformed. This can happen when
8660
+ // there are upstream errors in schema serialization.
8661
+ return undefined;
8662
+ }
8663
+ if (segments.length === 0) {
8664
+ return undefined;
8665
+ }
8666
+ range = modulePathMap[segments[0]];
8667
+ for (const pathSegment of segments.slice(1)) {
8668
+ var _range;
8669
+ if (!range) {
8670
+ break;
8671
+ }
8672
+ range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8582
8673
  }
8583
- range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8584
8674
  }
8585
8675
  if (!range) {
8586
8676
  return undefined;
@@ -8612,7 +8702,20 @@ function createModulePathMap(sourceFile) {
8612
8702
  if (ts__default["default"].isExportAssignment(child)) {
8613
8703
  const contentNode = child.expression && ts__default["default"].isCallExpression(child.expression) && child.expression.arguments[2];
8614
8704
  if (contentNode) {
8615
- return traverse(contentNode, sourceFile);
8705
+ const map = traverse(contentNode, sourceFile) ?? {};
8706
+ // Always expose the module root under the "" key so diagnostics without
8707
+ // a module path still resolve to a location. `traverse` already emits ""
8708
+ // for primitive roots (with a tighter range), so keep that if present.
8709
+ // Object/record keys are never "" (see traverseObjectLiteral), so this
8710
+ // cannot collide with a real key.
8711
+ return {
8712
+ ...map,
8713
+ "": map[""] ?? {
8714
+ children: {},
8715
+ start: sourceFile.getLineAndCharacterOfPosition(contentNode.getStart(sourceFile)),
8716
+ end: sourceFile.getLineAndCharacterOfPosition(contentNode.getEnd())
8717
+ }
8718
+ };
8616
8719
  }
8617
8720
  }
8618
8721
  }
@@ -7744,6 +7744,33 @@ async function downloadFileFromRemote(ref, filePath) {
7744
7744
  // record-level fix expands into per-entry errors that should point at the
7745
7745
  // individual entry (e.g. `?p="/public/val/logo.png"`) rather than the record.
7746
7746
 
7747
+ // Gallery entries are keyed by their file path. Remote galleries key uploaded
7748
+ // entries by a remote URL while keeping the file on disk at its local path, so
7749
+ // normalize a remote-URL key back to that local path for on-disk reads.
7750
+ function galleryKeyToLocalPath(key) {
7751
+ const res = Internal.remote.splitRemoteRef(key);
7752
+ return res.status === "success" ? `/${res.filePath}` : key;
7753
+ }
7754
+
7755
+ // Patch path of the record that holds a media entry. Media keys are file paths
7756
+ // that can contain dots, which sourceToPatchPath cannot round-trip, so strip
7757
+ // the (JSON-encoded) key segment off the entry source path and convert the
7758
+ // remaining parent path — whose ancestors are plain object keys / array
7759
+ // indices — instead.
7760
+ function parentPatchPathOfMediaEntry(sourcePath, entryKey) {
7761
+ const jsonKey = JSON.stringify(entryKey);
7762
+ let parent = sourcePath;
7763
+ if (parent.endsWith(jsonKey)) {
7764
+ parent = parent.slice(0, parent.length - jsonKey.length);
7765
+ }
7766
+ if (parent.endsWith(".")) {
7767
+ parent = parent.slice(0, -1);
7768
+ } else if (parent.endsWith(Internal.ModuleFilePathSep)) {
7769
+ parent = parent.slice(0, parent.length - Internal.ModuleFilePathSep.length);
7770
+ }
7771
+ return sourceToPatchPath(parent);
7772
+ }
7773
+
7747
7774
  // TODO: find a better name? transformFixesToPatch?
7748
7775
  async function createFixPatch(config, apply, sourcePath, validationError, remoteFiles, moduleSource, moduleSchema) {
7749
7776
  const remainingErrors = [];
@@ -7921,6 +7948,61 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
7921
7948
  path: sourceToPatchPath(sourcePath)
7922
7949
  });
7923
7950
  }
7951
+ } else if (fix === "images:upload-remote" || fix === "files:upload-remote") {
7952
+ // Gallery entry: the record is keyed by the file path, so uploading to
7953
+ // remote means renaming the key from the local path to the remote URL
7954
+ // (remove the old key, add the new one with the same metadata).
7955
+ const remoteFile = remoteFiles[sourcePath];
7956
+ if (!remoteFile) {
7957
+ remainingErrors.push({
7958
+ ...validationError,
7959
+ message: "Cannot fix local to remote gallery entry: remote file was not uploaded",
7960
+ fixes: undefined
7961
+ });
7962
+ continue;
7963
+ }
7964
+ const metadata = remoteFile.metadata;
7965
+ if (!metadata) {
7966
+ remainingErrors.push({
7967
+ ...validationError,
7968
+ message: "Failed to get metadata for remote gallery file",
7969
+ fixes: undefined
7970
+ });
7971
+ continue;
7972
+ }
7973
+ // The entry is keyed by its (local) file path, which can contain dots
7974
+ // (e.g. image.png). sourceToPatchPath can't round-trip such keys, so
7975
+ // build the patch path from the parent record path plus the key (which
7976
+ // validateMediaKey carries as the error value), like the UI does.
7977
+ const entryKey = validationError.value;
7978
+ if (typeof entryKey !== "string") {
7979
+ remainingErrors.push({
7980
+ ...validationError,
7981
+ message: "Cannot rewrite remote gallery entry: missing entry key",
7982
+ fixes: undefined
7983
+ });
7984
+ continue;
7985
+ }
7986
+ const parentPath = parentPatchPathOfMediaEntry(sourcePath, entryKey);
7987
+ const removePath = parentPath.concat(entryKey);
7988
+ const addPath = parentPath.concat(remoteFile.ref);
7989
+ if (!isNotRoot(removePath) || !isNotRoot(addPath)) {
7990
+ remainingErrors.push({
7991
+ ...validationError,
7992
+ message: "Cannot rewrite remote gallery entry at root path",
7993
+ fixes: undefined
7994
+ });
7995
+ continue;
7996
+ }
7997
+ patch.push({
7998
+ op: "remove",
7999
+ path: removePath
8000
+ });
8001
+ patch.push({
8002
+ op: "add",
8003
+ path: addPath,
8004
+ value: metadata
8005
+ });
7924
8006
  } else if (fix === "image:download-remote" || fix === "file:download-remote") {
7925
8007
  const v = getRemoteValueFromValidationError(validationError);
7926
8008
  if (!v.success) {
@@ -7997,7 +8079,7 @@ async function createFixPatch(config, apply, sourcePath, validationError, remote
7997
8079
  }
7998
8080
  const gallerySource = moduleSource;
7999
8081
  for (const [entryKey, storedEntry] of Object.entries(gallerySource)) {
8000
- const filename = path__default.join(config.projectRoot, entryKey);
8082
+ const filename = path__default.join(config.projectRoot, galleryKeyToLocalPath(entryKey));
8001
8083
  let buffer;
8002
8084
  try {
8003
8085
  buffer = fs.readFileSync(filename);
@@ -8522,31 +8604,39 @@ function getModulePathRange(modulePath, modulePathMap,
8522
8604
  // existing callers.
8523
8605
  target = "key") {
8524
8606
  var _range$children;
8525
- // Handle empty or invalid module paths gracefully
8526
- if (!modulePath || typeof modulePath !== "string") {
8527
- return undefined;
8528
- }
8529
- let segments;
8530
- try {
8531
- // Quote-aware splitter that correctly handles keys containing dots
8532
- // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8533
- // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8534
- segments = Internal.splitModulePath(modulePath);
8535
- } catch {
8536
- // Return undefined if the module path is malformed. This can happen when
8537
- // there are upstream errors in schema serialization.
8538
- return undefined;
8539
- }
8540
- if (segments.length === 0) {
8607
+ // Handle invalid module paths gracefully
8608
+ if (typeof modulePath !== "string") {
8541
8609
  return undefined;
8542
8610
  }
8543
- let range = modulePathMap[segments[0]];
8544
- for (const pathSegment of segments.slice(1)) {
8545
- var _range;
8546
- if (!range) {
8547
- break;
8611
+ let range;
8612
+ if (modulePath === "") {
8613
+ // The empty module path is the module root, stored under the "" key: the
8614
+ // source argument of `c.define`. This is what module-level diagnostics
8615
+ // (which have no module path) resolve to.
8616
+ range = modulePathMap[""];
8617
+ } else {
8618
+ let segments;
8619
+ try {
8620
+ // Quote-aware splitter that correctly handles keys containing dots
8621
+ // (e.g. file refs like `"/public/val/images/logo.png"`), unlike a naive
8622
+ // split on ".". Throws on malformed input (e.g. unbalanced quotes).
8623
+ segments = Internal.splitModulePath(modulePath);
8624
+ } catch {
8625
+ // Return undefined if the module path is malformed. This can happen when
8626
+ // there are upstream errors in schema serialization.
8627
+ return undefined;
8628
+ }
8629
+ if (segments.length === 0) {
8630
+ return undefined;
8631
+ }
8632
+ range = modulePathMap[segments[0]];
8633
+ for (const pathSegment of segments.slice(1)) {
8634
+ var _range;
8635
+ if (!range) {
8636
+ break;
8637
+ }
8638
+ range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8548
8639
  }
8549
- range = (_range = range) === null || _range === void 0 || (_range = _range.children) === null || _range === void 0 ? void 0 : _range[pathSegment];
8550
8640
  }
8551
8641
  if (!range) {
8552
8642
  return undefined;
@@ -8578,7 +8668,20 @@ function createModulePathMap(sourceFile) {
8578
8668
  if (ts.isExportAssignment(child)) {
8579
8669
  const contentNode = child.expression && ts.isCallExpression(child.expression) && child.expression.arguments[2];
8580
8670
  if (contentNode) {
8581
- return traverse(contentNode, sourceFile);
8671
+ const map = traverse(contentNode, sourceFile) ?? {};
8672
+ // Always expose the module root under the "" key so diagnostics without
8673
+ // a module path still resolve to a location. `traverse` already emits ""
8674
+ // for primitive roots (with a tighter range), so keep that if present.
8675
+ // Object/record keys are never "" (see traverseObjectLiteral), so this
8676
+ // cannot collide with a real key.
8677
+ return {
8678
+ ...map,
8679
+ "": map[""] ?? {
8680
+ children: {},
8681
+ start: sourceFile.getLineAndCharacterOfPosition(contentNode.getStart(sourceFile)),
8682
+ end: sourceFile.getLineAndCharacterOfPosition(contentNode.getEnd())
8683
+ }
8684
+ };
8582
8685
  }
8583
8686
  }
8584
8687
  }
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.98.0",
19
+ "version": "0.99.0",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"
@@ -29,9 +29,9 @@
29
29
  "typescript": "^5.9.3",
30
30
  "zod": "^4.3.5",
31
31
  "zod-validation-error": "^5.0.0",
32
- "@valbuild/core": "0.97.7",
33
- "@valbuild/shared": "0.97.7",
34
- "@valbuild/ui": "0.97.7"
32
+ "@valbuild/core": "0.99.0",
33
+ "@valbuild/shared": "0.99.0",
34
+ "@valbuild/ui": "0.99.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=18.17.0"