@valbuild/server 0.108.3 → 0.108.5

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.
@@ -1040,7 +1040,7 @@ function errorMessage(e) {
1040
1040
  return String(e);
1041
1041
  }
1042
1042
 
1043
- const jsonOps$1 = new patch.JSONOps();
1043
+ const jsonOps$2 = new patch.JSONOps();
1044
1044
 
1045
1045
  /**
1046
1046
  * Classification of a single patch op against a module's serialized schema,
@@ -1295,7 +1295,7 @@ function applyJsonValuesEntryPatches(args) {
1295
1295
  // on an entry that does not exist.
1296
1296
  continue;
1297
1297
  }
1298
- const applied = patch.applyPatch(patch.deepClone(content), jsonOps$1, [{
1298
+ const applied = patch.applyPatch(patch.deepClone(content), jsonOps$2, [{
1299
1299
  op: "add",
1300
1300
  path: cls.subPath.concat(...(op.nestedFilePath ?? [])).concat("patch_id"),
1301
1301
  value: patchId
@@ -1349,7 +1349,7 @@ function applyJsonValuesEntryPatches(args) {
1349
1349
  patchId
1350
1350
  };
1351
1351
  }
1352
- const applied = patch.applyPatch(patch.deepClone(content), jsonOps$1, [rebased.value]);
1352
+ const applied = patch.applyPatch(patch.deepClone(content), jsonOps$2, [rebased.value]);
1353
1353
  if (fp.result.isErr(applied)) {
1354
1354
  return {
1355
1355
  kind: "error",
@@ -1387,6 +1387,19 @@ function resolveExistingJsonPath(moduleFilePath, importPath) {
1387
1387
  return path__namespace.posix.join(moduleDir, importPath);
1388
1388
  }
1389
1389
 
1390
+ /**
1391
+ * The outcome of {@link validateJsonValuesEntries}: the errors found inside the
1392
+ * entries, plus the content of every entry that could be loaded, keyed by entry
1393
+ * key.
1394
+ *
1395
+ * The content is part of the result because the errors are useless without it.
1396
+ * They are reported at paths that point INSIDE an entry, and the module source
1397
+ * on disk holds a `c.json(...)` marker there — so anything that has to resolve
1398
+ * one of those paths (the `val validate --fix` handlers, chiefly) needs the very
1399
+ * content this function has just loaded. Returning it costs nothing and is the
1400
+ * only way to avoid loading every entry twice.
1401
+ */
1402
+
1390
1403
  /**
1391
1404
  * Validates the content of every `.jsonValues()` entry in a module by loading
1392
1405
  * each backing `*.val.json` (via its lazy import thunk) and checking it against
@@ -1439,14 +1452,19 @@ function resolveExistingJsonPath(moduleFilePath, importPath) {
1439
1452
  */
1440
1453
  async function validateJsonValuesEntries(schema, source, modulePath) {
1441
1454
  const out = {};
1455
+ const loadedEntries = {};
1456
+ const res = {
1457
+ errors: out,
1458
+ loadedEntries
1459
+ };
1442
1460
  if (!(schema instanceof core.RecordSchema)) {
1443
- return out;
1461
+ return res;
1444
1462
  }
1445
1463
  if (!schema["executeSerialize"]().jsonValues) {
1446
- return out;
1464
+ return res;
1447
1465
  }
1448
1466
  if (source === null || typeof source !== "object" || Array.isArray(source)) {
1449
- return out;
1467
+ return res;
1450
1468
  }
1451
1469
  for (const [key, marker] of Object.entries(source)) {
1452
1470
  const entryPath = core.Internal.createValPathOfItem(modulePath, key);
@@ -1482,6 +1500,10 @@ async function validateJsonValuesEntries(schema, source, modulePath) {
1482
1500
  }];
1483
1501
  continue;
1484
1502
  }
1503
+ // Kept so a caller can substitute the content back into the module source:
1504
+ // the entries have been paid for here, and a source that still holds
1505
+ // markers cannot resolve a path INTO an entry (see the doc comment above).
1506
+ loadedEntries[key] = content;
1485
1507
  const entryErrors = schema.validateJsonEntryContent(entryPath, content);
1486
1508
  if (entryErrors) {
1487
1509
  for (const [p, errs] of Object.entries(entryErrors)) {
@@ -1489,9 +1511,155 @@ async function validateJsonValuesEntries(schema, source, modulePath) {
1489
1511
  }
1490
1512
  }
1491
1513
  }
1492
- return out;
1514
+ return res;
1493
1515
  }
1494
1516
 
1517
+ /**
1518
+ * Given the `source` object-literal expression of a `.jsonValues()` record /
1519
+ * router (the 3rd argument to `c.define`, via {@link analyzeValModule}),
1520
+ * extracts each entry's `c.json(() => import("..."))` import path, keyed by
1521
+ * entry key. Entries whose value is not a `c.json(...)` call are skipped.
1522
+ */
1523
+ function analyzeJsonValuesEntries(source) {
1524
+ const entries = new Map();
1525
+ if (!ts__default["default"].isObjectLiteralExpression(source)) {
1526
+ return entries;
1527
+ }
1528
+ for (const prop of source.properties) {
1529
+ if (!ts__default["default"].isPropertyAssignment(prop)) {
1530
+ continue;
1531
+ }
1532
+ const key = getPropertyKey(prop.name);
1533
+ if (key === null) {
1534
+ continue;
1535
+ }
1536
+ const entry = analyzeCJsonCall(prop.initializer);
1537
+ if (entry) {
1538
+ entries.set(key, entry);
1539
+ }
1540
+ }
1541
+ return entries;
1542
+ }
1543
+ function getPropertyKey(name) {
1544
+ if (ts__default["default"].isStringLiteralLike(name)) {
1545
+ return name.text;
1546
+ }
1547
+ if (ts__default["default"].isIdentifier(name)) {
1548
+ return name.text;
1549
+ }
1550
+ return null;
1551
+ }
1552
+ function analyzeCJsonCall(node) {
1553
+ // c.json(() => import("path"))
1554
+ if (!ts__default["default"].isCallExpression(node) || !isCJson(node.expression)) {
1555
+ return null;
1556
+ }
1557
+ const [thunk] = node.arguments;
1558
+ if (!thunk) {
1559
+ return null;
1560
+ }
1561
+ const importPath = getImportPathFromThunk(thunk);
1562
+ if (importPath === null) {
1563
+ return null;
1564
+ }
1565
+ return {
1566
+ importPath
1567
+ };
1568
+ }
1569
+ function isCJson(expr) {
1570
+ // c.json
1571
+ return ts__default["default"].isPropertyAccessExpression(expr) && ts__default["default"].isIdentifier(expr.expression) && expr.expression.text === "c" && ts__default["default"].isIdentifier(expr.name) && expr.name.text === "json";
1572
+ }
1573
+ function getImportPathFromThunk(thunk) {
1574
+ // () => import("path") (concise body) OR () => { return import("path"); }
1575
+ if (!ts__default["default"].isArrowFunction(thunk)) {
1576
+ return null;
1577
+ }
1578
+ let importCall;
1579
+ if (ts__default["default"].isCallExpression(thunk.body)) {
1580
+ importCall = thunk.body;
1581
+ } else if (ts__default["default"].isBlock(thunk.body)) {
1582
+ for (const stmt of thunk.body.statements) {
1583
+ if (ts__default["default"].isReturnStatement(stmt) && stmt.expression && ts__default["default"].isCallExpression(stmt.expression)) {
1584
+ importCall = stmt.expression;
1585
+ break;
1586
+ }
1587
+ }
1588
+ }
1589
+ if (!importCall || !ts__default["default"].isCallExpression(importCall)) {
1590
+ return null;
1591
+ }
1592
+ const isImport = importCall.expression.kind === ts__default["default"].SyntaxKind.ImportKeyword || ts__default["default"].isIdentifier(importCall.expression) && importCall.expression.text === "import";
1593
+ if (!isImport) {
1594
+ return null;
1595
+ }
1596
+ const arg = importCall.arguments[0];
1597
+ if (arg && ts__default["default"].isStringLiteralLike(arg)) {
1598
+ return arg.text;
1599
+ }
1600
+ return null;
1601
+ }
1602
+
1603
+ /**
1604
+ * Which `*.val.json` holds a `.jsonValues()` entry's content, given the module's
1605
+ * `.val.ts`.
1606
+ *
1607
+ * Tooling that maps a validation `sourcePath` back to a place in a file needs
1608
+ * this: for a jsonValues module the offending value is NOT in the `.val.ts` at
1609
+ * all — that file only holds `c.json(() => import("./x.val.json"))` — so a
1610
+ * resolver that only ever looks at the `.val.ts` can report the error but not
1611
+ * where it lives, which for a record with hundreds of entries is not much of a
1612
+ * report.
1613
+ *
1614
+ * Returns a path relative to the project root (leading slash), or undefined when
1615
+ * the module has no such entry (not a jsonValues module, unparseable, or the key
1616
+ * is not backed by a `c.json` thunk).
1617
+ */
1618
+ function findJsonEntryFilePath(moduleFilePath, valTsSourceFile, entryKey) {
1619
+ let analysis;
1620
+ try {
1621
+ analysis = analyzeValModule(valTsSourceFile);
1622
+ } catch {
1623
+ return undefined;
1624
+ }
1625
+ if (fp.result.isErr(analysis)) {
1626
+ return undefined;
1627
+ }
1628
+ const entry = analyzeJsonValuesEntries(analysis.value.source).get(entryKey);
1629
+ if (!entry) {
1630
+ return undefined;
1631
+ }
1632
+ return resolveExistingJsonPath(moduleFilePath, entry.importPath);
1633
+ }
1634
+
1635
+ const jsonOps$1 = new patch.JSONOps();
1636
+
1637
+ /**
1638
+ * Substitutes loaded `.jsonValues()` entry content back into a module's root
1639
+ * record, in place of the `c.json(...)` markers the `.val.ts` holds.
1640
+ *
1641
+ * Only entries that actually loaded are replaced: an entry whose file is missing
1642
+ * or unparseable keeps its marker, so the failure stays visible as the
1643
+ * validation error it already produced rather than becoming a silent `undefined`
1644
+ * further down.
1645
+ */
1646
+ function withLoadedJsonEntries(source, loadedEntries) {
1647
+ if (Object.keys(loadedEntries).length === 0) {
1648
+ return source;
1649
+ }
1650
+ if (!isSourceObject(source)) {
1651
+ return source;
1652
+ }
1653
+ return {
1654
+ ...source,
1655
+ ...loadedEntries
1656
+ };
1657
+ }
1658
+
1659
+ /** A non-null, non-array source object: the shape a record/router source has. */
1660
+ function isSourceObject(source) {
1661
+ return typeof source === "object" && source !== null && !Array.isArray(source);
1662
+ }
1495
1663
  async function createService(projectRoot, host = {
1496
1664
  ...ts__default["default"].sys,
1497
1665
  writeFile: (fileName, data, encoding) => {
@@ -1530,13 +1698,16 @@ class Service {
1530
1698
  getModuleFilePaths() {
1531
1699
  return Object.keys(this.extracted.sources);
1532
1700
  }
1701
+ serializedSchemaOf(moduleFilePath) {
1702
+ return this.extracted.serializedSchemas[moduleFilePath];
1703
+ }
1533
1704
  async get(moduleFilePath, modulePath, options) {
1534
1705
  const opts = options ?? {
1535
1706
  validate: true
1536
1707
  };
1537
1708
  const source = this.extracted.sources[moduleFilePath];
1538
1709
  const schema = this.extracted.schemas[moduleFilePath];
1539
- const serializedSchema = this.extracted.serializedSchemas[moduleFilePath];
1710
+ const serializedSchema = this.serializedSchemaOf(moduleFilePath);
1540
1711
  const moduleError = this.extracted.moduleErrors.find(e => e.path === moduleFilePath);
1541
1712
  // A module whose `def()` threw is recorded WITHOUT a path: the import never
1542
1713
  // got far enough to reveal one, so it cannot be matched above. Those errors
@@ -1568,6 +1739,12 @@ class Service {
1568
1739
  // entry content, or an unsupported nested `.jsonValues()`, as VALID, and CI
1569
1740
  // gates on that.
1570
1741
  let jsonValuesModuleError;
1742
+ // The source as it is on disk holds a `c.json(...)` marker for every
1743
+ // `.jsonValues()` entry, and validation reports errors at paths INSIDE those
1744
+ // entries. Resolving such a path against the marker source is impossible —
1745
+ // it throws — so the loaded content is substituted back in before anything
1746
+ // downstream (the `val validate --fix` handlers, chiefly) tries.
1747
+ let resolvedFromSource = source;
1571
1748
  if (opts.validate) {
1572
1749
  const nested = findNestedJsonValuesRecords(serializedSchema);
1573
1750
  if (nested.length > 0) {
@@ -1580,7 +1757,11 @@ class Service {
1580
1757
  // accepted cost of having no revalidation token (locked decision #3):
1581
1758
  // validation is allowed to be slower at scale, but it is not allowed to
1582
1759
  // silently skip content.
1583
- const entryErrors = await validateJsonValuesEntries(schema, source, moduleFilePath);
1760
+ const {
1761
+ errors: entryErrors,
1762
+ loadedEntries
1763
+ } = await validateJsonValuesEntries(schema, source, moduleFilePath);
1764
+ resolvedFromSource = withLoadedJsonEntries(source, loadedEntries);
1584
1765
  if (Object.keys(entryErrors).length > 0) {
1585
1766
  // Concatenate per path, never overwrite: an entry written inline is
1586
1767
  // reported BOTH by the record-level validation (which checks the
@@ -1598,7 +1779,7 @@ class Service {
1598
1779
  }
1599
1780
  }
1600
1781
  }
1601
- const resolved = core.Internal.resolvePath(modulePath, source, serializedSchema);
1782
+ const resolved = core.Internal.resolvePath(modulePath, resolvedFromSource, serializedSchema);
1602
1783
  const sourcePath = resolved.path ? [moduleFilePath, resolved.path].join(".") : moduleFilePath;
1603
1784
  if (!validation && !moduleError && !jsonValuesModuleError) {
1604
1785
  return {
@@ -1629,8 +1810,126 @@ class Service {
1629
1810
  }
1630
1811
  };
1631
1812
  }
1813
+
1814
+ /**
1815
+ * Applies a patch to a module's files.
1816
+ *
1817
+ * A `.jsonValues()` entry's content is not in the `.val.ts` — that file holds
1818
+ * only `c.json(() => import("./x.val.json"))` — so an op whose path descends
1819
+ * into an entry has to be replayed against the backing `*.val.json` instead.
1820
+ * That routing is the same one the Studio's commit flow does in
1821
+ * `ValOps.prepare`; both classify with {@link classifyJsonValuesOp} and rebase
1822
+ * with {@link rebaseContentOp}, so a CLI fix and a Studio publish write the
1823
+ * same file the same way.
1824
+ */
1632
1825
  async patch(moduleFilePath, patch) {
1633
- await patchValFile(moduleFilePath, this.projectRoot, patch, this.sourceFileHandler);
1826
+ const serializedSchema = this.serializedSchemaOf(moduleFilePath);
1827
+ const valTsOps = [];
1828
+ const entryOps = new Map();
1829
+ const classify = opPath => serializedSchema ? classifyJsonValuesOp(serializedSchema, opPath) : {
1830
+ kind: "normal"
1831
+ };
1832
+ for (const op of patch) {
1833
+ const cls = classify(op.path);
1834
+ // `move` and `copy` read a SECOND path, and `rebaseContentOp` slices
1835
+ // `from` by the same prefix it slices `path` by. So an op whose two ends
1836
+ // are not in the same place is not merely unsupported, it is silently
1837
+ // wrong: a copy INTO an entry from anywhere else gets its `from`
1838
+ // reinterpreted as a path inside the destination's `*.val.json` and reads
1839
+ // the wrong value. `ValOps.prepare` guards this on the publish path; both
1840
+ // ends have to be classified here for the same reason.
1841
+ const fromCls = op.op === "move" || op.op === "copy" ? classify(op.from) : undefined;
1842
+ if (cls.kind === "normal") {
1843
+ // Unreachable while `.jsonValues()` is root-only -- every top-level path
1844
+ // in such a module IS an entry key, so a normal destination means the
1845
+ // module has no jsonValues record and the source cannot be in one
1846
+ // either. Kept because it is the half of the guard that stops being
1847
+ // theoretical the moment root-only is relaxed.
1848
+ if (fromCls && fromCls.kind !== "normal") {
1849
+ throw Error(`Cannot ${op.op} out of the .jsonValues() entry '${fromCls.entryKey}' of ${moduleFilePath}: the entry's content is not in the .val.ts`);
1850
+ }
1851
+ valTsOps.push(op);
1852
+ continue;
1853
+ }
1854
+ if (cls.recordPath.length > 0) {
1855
+ // Nested `.jsonValues()` is rejected as a module error before any fix
1856
+ // runs, so reaching here means the contract moved without this moving
1857
+ // with it. Refuse rather than write to the wrong file.
1858
+ throw Error(`Cannot patch a nested .jsonValues() record in ${moduleFilePath}: only a root record/router is supported`);
1859
+ }
1860
+ if (cls.subPath.length === 0) {
1861
+ // Adding, removing or replacing a whole entry means writing the
1862
+ // `*.val.json` AND rewriting the `c.json(...)` reference in the
1863
+ // `.val.ts`. Nothing that reaches `Service.patch` produces those today
1864
+ // (`val validate --fix` only ever corrects values inside an entry), and
1865
+ // silently applying it to the `.val.ts` alone would leave the module
1866
+ // pointing at a file that does not exist.
1867
+ throw Error(`Cannot ${op.op} the whole .jsonValues() entry '${cls.entryKey}' of ${moduleFilePath} through Service.patch: only edits INSIDE an entry are supported`);
1868
+ }
1869
+ if (fromCls && (fromCls.kind !== "entry" || fromCls.entryKey !== cls.entryKey)) {
1870
+ throw Error(`Cannot ${op.op} into the .jsonValues() entry '${cls.entryKey}' of ${moduleFilePath} from outside it: each entry is a separate file`);
1871
+ }
1872
+ const ops = entryOps.get(cls.entryKey);
1873
+ if (ops) {
1874
+ ops.push(op);
1875
+ } else {
1876
+ entryOps.set(cls.entryKey, [op]);
1877
+ }
1878
+ }
1879
+ // When nothing is routed away, hand the patch over untouched: an empty
1880
+ // `valTsOps` would otherwise rewrite the `.val.ts` for no reason.
1881
+ if (entryOps.size === 0) {
1882
+ await patchValFile(moduleFilePath, this.projectRoot, patch, this.sourceFileHandler);
1883
+ return;
1884
+ }
1885
+ if (valTsOps.length > 0) {
1886
+ await patchValFile(moduleFilePath, this.projectRoot, valTsOps, this.sourceFileHandler);
1887
+ }
1888
+ for (const [entryKey, ops] of entryOps) {
1889
+ this.patchJsonValuesEntry(moduleFilePath, entryKey, ops);
1890
+ }
1891
+ }
1892
+
1893
+ /** Replays content ops against one `.jsonValues()` entry's `*.val.json`. */
1894
+ patchJsonValuesEntry(moduleFilePath, entryKey, ops) {
1895
+ const valTsPath = this.sourceFileHandler.resolveSourceModulePath(getSyntheticContainingPath(this.projectRoot), `.${moduleFilePath.replace(".val.ts", ".val").replace(".val.js", ".val").replace(".val.jsx", ".val").replace(".val.tsx", ".val")}`);
1896
+ const sourceFile = this.sourceFileHandler.getSourceFile(valTsPath);
1897
+ if (!sourceFile) {
1898
+ throw Error(`Source file ${valTsPath} not found`);
1899
+ }
1900
+ // The import specifier is authoritative, not the derived path: entries may
1901
+ // be hand-placed (hybrid authoring), so deriving would write a file the
1902
+ // module does not read.
1903
+ const jsonPath = findJsonEntryFilePath(moduleFilePath, sourceFile, entryKey);
1904
+ if (!jsonPath) {
1905
+ throw Error(`Could not find the '*.val.json' backing .jsonValues() entry '${entryKey}' of ${moduleFilePath}`);
1906
+ }
1907
+ const absoluteJsonPath = path__namespace["default"].join(this.projectRoot, jsonPath);
1908
+ const text = this.sourceFileHandler.host.readFile(absoluteJsonPath);
1909
+ if (text === undefined) {
1910
+ throw Error(`Could not read jsonValues entry file: ${jsonPath}`);
1911
+ }
1912
+ let content;
1913
+ try {
1914
+ content = JSON.parse(text);
1915
+ } catch (err) {
1916
+ throw new Error(`Could not parse jsonValues entry file ${jsonPath}`, {
1917
+ cause: err
1918
+ });
1919
+ }
1920
+ for (const op of ops) {
1921
+ // Root record / router only, so the prefix is exactly the entry key.
1922
+ const rebased = rebaseContentOp(op, 1);
1923
+ if (fp.result.isErr(rebased)) {
1924
+ throw Error(`Could not apply ${op.op} to jsonValues entry '${entryKey}' of ${moduleFilePath}: ${rebased.error.message}`);
1925
+ }
1926
+ const applied = patch.applyPatch(patch.deepClone(content), jsonOps$1, [rebased.value]);
1927
+ if (fp.result.isErr(applied)) {
1928
+ throw Error(`Could not apply ${op.op} to ${jsonPath}: ${applied.error.message}`);
1929
+ }
1930
+ content = applied.value;
1931
+ }
1932
+ this.sourceFileHandler.writeFile(absoluteJsonPath, JSON.stringify(content, null, 2) + "\n", "utf8");
1634
1933
  }
1635
1934
  dispose() {
1636
1935
  // No-op: the vm-based loader holds no disposable resources.
@@ -1695,92 +1994,6 @@ function encodeJwt(payload, sessionKey) {
1695
1994
  return `${jwtHeaderBase64}.${payloadBase64}.${crypto__default["default"].createHmac("sha256", sessionKey).update(`${jwtHeaderBase64}.${payloadBase64}`).digest("base64")}`;
1696
1995
  }
1697
1996
 
1698
- /**
1699
- * Given the `source` object-literal expression of a `.jsonValues()` record /
1700
- * router (the 3rd argument to `c.define`, via {@link analyzeValModule}),
1701
- * extracts each entry's `c.json(() => import("..."))` import path, keyed by
1702
- * entry key. Entries whose value is not a `c.json(...)` call are skipped.
1703
- */
1704
- function analyzeJsonValuesEntries(source) {
1705
- const entries = new Map();
1706
- if (!ts__default["default"].isObjectLiteralExpression(source)) {
1707
- return entries;
1708
- }
1709
- for (const prop of source.properties) {
1710
- if (!ts__default["default"].isPropertyAssignment(prop)) {
1711
- continue;
1712
- }
1713
- const key = getPropertyKey(prop.name);
1714
- if (key === null) {
1715
- continue;
1716
- }
1717
- const entry = analyzeCJsonCall(prop.initializer);
1718
- if (entry) {
1719
- entries.set(key, entry);
1720
- }
1721
- }
1722
- return entries;
1723
- }
1724
- function getPropertyKey(name) {
1725
- if (ts__default["default"].isStringLiteralLike(name)) {
1726
- return name.text;
1727
- }
1728
- if (ts__default["default"].isIdentifier(name)) {
1729
- return name.text;
1730
- }
1731
- return null;
1732
- }
1733
- function analyzeCJsonCall(node) {
1734
- // c.json(() => import("path"))
1735
- if (!ts__default["default"].isCallExpression(node) || !isCJson(node.expression)) {
1736
- return null;
1737
- }
1738
- const [thunk] = node.arguments;
1739
- if (!thunk) {
1740
- return null;
1741
- }
1742
- const importPath = getImportPathFromThunk(thunk);
1743
- if (importPath === null) {
1744
- return null;
1745
- }
1746
- return {
1747
- importPath
1748
- };
1749
- }
1750
- function isCJson(expr) {
1751
- // c.json
1752
- return ts__default["default"].isPropertyAccessExpression(expr) && ts__default["default"].isIdentifier(expr.expression) && expr.expression.text === "c" && ts__default["default"].isIdentifier(expr.name) && expr.name.text === "json";
1753
- }
1754
- function getImportPathFromThunk(thunk) {
1755
- // () => import("path") (concise body) OR () => { return import("path"); }
1756
- if (!ts__default["default"].isArrowFunction(thunk)) {
1757
- return null;
1758
- }
1759
- let importCall;
1760
- if (ts__default["default"].isCallExpression(thunk.body)) {
1761
- importCall = thunk.body;
1762
- } else if (ts__default["default"].isBlock(thunk.body)) {
1763
- for (const stmt of thunk.body.statements) {
1764
- if (ts__default["default"].isReturnStatement(stmt) && stmt.expression && ts__default["default"].isCallExpression(stmt.expression)) {
1765
- importCall = stmt.expression;
1766
- break;
1767
- }
1768
- }
1769
- }
1770
- if (!importCall || !ts__default["default"].isCallExpression(importCall)) {
1771
- return null;
1772
- }
1773
- const isImport = importCall.expression.kind === ts__default["default"].SyntaxKind.ImportKeyword || ts__default["default"].isIdentifier(importCall.expression) && importCall.expression.text === "import";
1774
- if (!isImport) {
1775
- return null;
1776
- }
1777
- const arg = importCall.arguments[0];
1778
- if (arg && ts__default["default"].isStringLiteralLike(arg)) {
1779
- return arg.text;
1780
- }
1781
- return null;
1782
- }
1783
-
1784
1997
  /* eslint-disable @typescript-eslint/no-unused-vars */
1785
1998
  const jsonOps = new patch.JSONOps();
1786
1999
  const tsOps = new TSOps(document => {
@@ -2655,7 +2868,9 @@ class ValOps {
2655
2868
  const res = schema["executeValidate"](path, source);
2656
2869
  // For `.jsonValues()` records, executeValidate only checks the entry
2657
2870
  // markers; load + validate each entry's backing `*.val.json` content here.
2658
- const jsonValuesErrors = await validateJsonValuesEntries(schema, source, path);
2871
+ const {
2872
+ errors: jsonValuesErrors
2873
+ } = await validateJsonValuesEntries(schema, source, path);
2659
2874
  for (const [sourcePathS, entryErrors] of Object.entries(jsonValuesErrors)) {
2660
2875
  const sourcePath = sourcePathS;
2661
2876
  if (!errors[path]) {
@@ -12520,38 +12735,6 @@ function traverseObjectLiteral(node, sourceFile) {
12520
12735
  }, {});
12521
12736
  }
12522
12737
 
12523
- /**
12524
- * Which `*.val.json` holds a `.jsonValues()` entry's content, given the module's
12525
- * `.val.ts`.
12526
- *
12527
- * Tooling that maps a validation `sourcePath` back to a place in a file needs
12528
- * this: for a jsonValues module the offending value is NOT in the `.val.ts` at
12529
- * all — that file only holds `c.json(() => import("./x.val.json"))` — so a
12530
- * resolver that only ever looks at the `.val.ts` can report the error but not
12531
- * where it lives, which for a record with hundreds of entries is not much of a
12532
- * report.
12533
- *
12534
- * Returns a path relative to the project root (leading slash), or undefined when
12535
- * the module has no such entry (not a jsonValues module, unparseable, or the key
12536
- * is not backed by a `c.json` thunk).
12537
- */
12538
- function findJsonEntryFilePath(moduleFilePath, valTsSourceFile, entryKey) {
12539
- let analysis;
12540
- try {
12541
- analysis = analyzeValModule(valTsSourceFile);
12542
- } catch {
12543
- return undefined;
12544
- }
12545
- if (fp.result.isErr(analysis)) {
12546
- return undefined;
12547
- }
12548
- const entry = analyzeJsonValuesEntries(analysis.value.source).get(entryKey);
12549
- if (!entry) {
12550
- return undefined;
12551
- }
12552
- return resolveExistingJsonPath(moduleFilePath, entry.importPath);
12553
- }
12554
-
12555
12738
  /**
12556
12739
  * Replays a `val debug` snapshot: applies its patches the way /save does and
12557
12740
  * validates the result.
@@ -12668,6 +12851,7 @@ exports.ValSourceFileHandler = ValSourceFileHandler;
12668
12851
  exports.analyzeValModule = analyzeValModule;
12669
12852
  exports.awaitValLoginConfirmation = awaitValLoginConfirmation;
12670
12853
  exports.checkRemoteRef = checkRemoteRef;
12854
+ exports.classifyJsonValuesOp = classifyJsonValuesOp;
12671
12855
  exports.compareWithCapturedReport = compareWithCapturedReport;
12672
12856
  exports.createDefaultValFSHost = createDefaultValFSHost;
12673
12857
  exports.createFixPatch = createFixPatch;
@@ -12714,6 +12898,7 @@ exports.patchSourceFile = patchSourceFile;
12714
12898
  exports.persistPersonalAccessToken = persistPersonalAccessToken;
12715
12899
  exports.readCapturedReport = readCapturedReport;
12716
12900
  exports.readPatchStore = readPatchStore;
12901
+ exports.rebaseContentOp = rebaseContentOp;
12717
12902
  exports.replaySnapshot = replaySnapshot;
12718
12903
  exports.safeReadGit = safeReadGit;
12719
12904
  exports.startValLogin = startValLogin;