@valbuild/server 0.123.2 → 0.124.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.
@@ -1,7 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import { pipe, result, array } from '@valbuild/core/fp';
3
3
  import { PatchError, deepEqual, parseAndValidateArrayIndex, isNotRoot, applyPatch, JSONOps, deepClone, sourceToPatchPath } from '@valbuild/core/patch';
4
- import { derefPatch, Internal, RecordSchema, extractValModules, computeValModuleShas, VAL_EXTENSION, ImageSchema, DEFAULT_CONTENT_HOST, hasRemoteFileSchema } from '@valbuild/core';
4
+ import { derefPatch, Internal, extractValModules, computeValModuleShas, VAL_EXTENSION, ImageSchema, DEFAULT_CONTENT_HOST, hasRemoteFileSchema } from '@valbuild/core';
5
5
  export { hasRemoteFileSchema } from '@valbuild/core';
6
6
  import * as path from 'path';
7
7
  import path__default from 'path';
@@ -1368,6 +1368,43 @@ function resolveExistingJsonPath(moduleFilePath, importPath) {
1368
1368
  * only way to avoid loading every entry twice.
1369
1369
  */
1370
1370
 
1371
+ /**
1372
+ * The part of a `.jsonValues()` record this file calls. Named structurally
1373
+ * because the value may not be an instance of *this* copy of `RecordSchema` —
1374
+ * see {@link isJsonValuesRecord}.
1375
+ */
1376
+
1377
+ /**
1378
+ * Is this a `.jsonValues()` record?
1379
+ *
1380
+ * NOT `schema instanceof RecordSchema`, and the distinction is the whole point.
1381
+ * `loadValModules` evaluates the project's `*.val.ts` with a `require` rooted at
1382
+ * the PROJECT, deliberately, so the schemas are built by whatever
1383
+ * `@valbuild/core` the project resolves. When the CLI is itself installed in
1384
+ * that project, that is this very module and `instanceof` holds. When it is run
1385
+ * through `npx` / `pnpm dlx`, the tool gets its own copy of `@valbuild/core`
1386
+ * beside the project's, and `instanceof` is false for a record that is a record
1387
+ * in every way that matters — same class name, same serialized schema, same
1388
+ * methods, different realm.
1389
+ *
1390
+ * That failed OPEN: the whole check returned "no errors", so
1391
+ * `npx @valbuild/cli validate` called a module with un-extracted entries VALID
1392
+ * and a CI gate on it was green for the wrong reason. `loadValModules` avoids
1393
+ * constructor checks for exactly this reason and says so; this one was the
1394
+ * exception.
1395
+ *
1396
+ * `validateJsonEntryContent` is declared by `RecordSchema` and by nothing else,
1397
+ * so it is the brand — and checking it first keeps the cheap short-circuit
1398
+ * `instanceof` gave us, rather than serializing every non-record schema.
1399
+ */
1400
+ function isJsonValuesRecord(schema) {
1401
+ if (!("validateJsonEntryContent" in schema) || typeof schema.validateJsonEntryContent !== "function") {
1402
+ return false;
1403
+ }
1404
+ const serialized = schema["executeSerialize"]();
1405
+ return serialized.type === "record" && serialized.jsonValues === true;
1406
+ }
1407
+
1371
1408
  /**
1372
1409
  * Validates the content of every `.jsonValues()` entry in a module by loading
1373
1410
  * each backing `*.val.json` (via its lazy import thunk) and checking it against
@@ -1425,10 +1462,7 @@ async function validateJsonValuesEntries(schema, source, modulePath) {
1425
1462
  errors: out,
1426
1463
  loadedEntries
1427
1464
  };
1428
- if (!(schema instanceof RecordSchema)) {
1429
- return res;
1430
- }
1431
- if (!schema["executeSerialize"]().jsonValues) {
1465
+ if (!isJsonValuesRecord(schema)) {
1432
1466
  return res;
1433
1467
  }
1434
1468
  if (source === null || typeof source !== "object" || Array.isArray(source)) {
@@ -4050,6 +4084,17 @@ class ValOps {
4050
4084
 
4051
4085
  /** One file's bytes as they were at one commit. */
4052
4086
 
4087
+ /**
4088
+ * Where a module lives in the REPOSITORY, as `getFileAtCommit` wants it.
4089
+ *
4090
+ * A `ModuleFilePath` is project-relative (`/app/page.val.ts`); a git path is
4091
+ * repository-relative and carries the project root in front of it
4092
+ * (`examples/next/app/page.val.ts`). Only the ops know that root, which is
4093
+ * why this is here rather than computed by the history functions - and why a
4094
+ * history function that needs to read a module's own file at a commit has to
4095
+ * ask instead of concatenating.
4096
+ */
4097
+
4053
4098
  // #endregion history
4054
4099
  }
4055
4100
  function isOnlyFileCheckValidationError(validationError) {
@@ -6695,6 +6740,14 @@ class ValOpsFS extends ValOps {
6695
6740
  kind: "not-supported-in-fs-mode"
6696
6741
  });
6697
6742
  }
6743
+ gitPathOfModule(
6744
+ // Unused: fs mode has no repository to be relative to. Named anyway, so
6745
+ // this reads as the same method the base class declares.
6746
+ _moduleFilePath) {
6747
+ return result.err({
6748
+ kind: "not-supported-in-fs-mode"
6749
+ });
6750
+ }
6698
6751
  // #endregion history
6699
6752
  }
6700
6753
  class FSOpsHost {
@@ -8486,6 +8539,19 @@ class ValOpsHttp extends ValOps {
8486
8539
  }
8487
8540
  return result.ok(res.value.files);
8488
8541
  }
8542
+
8543
+ /**
8544
+ * `root` in front, and never a doubled or missing slash.
8545
+ *
8546
+ * `root` is "" for a project at the repository root and something like
8547
+ * `examples/next` otherwise; a ModuleFilePath always starts with "/". Joining
8548
+ * them by hand at each call site is how one of these ends up with "//" in the
8549
+ * middle, which GitHub answers with a 404 that reads like a missing file.
8550
+ */
8551
+ gitPathOfModule(moduleFilePath) {
8552
+ const joined = `${this.root}/${moduleFilePath}`.split("/").filter(part => part !== "").join("/");
8553
+ return result.ok(joined);
8554
+ }
8489
8555
  async getFileAtCommit(commitSha, filePath, remote) {
8490
8556
  const params = new URLSearchParams({
8491
8557
  path: filePath
@@ -8945,6 +9011,129 @@ async function getModuleAtCommit(ops, commitSha, moduleFilePath) {
8945
9011
  });
8946
9012
  }
8947
9013
 
9014
+ /**
9015
+ * One `.jsonValues()` entry's content, as it was at a commit.
9016
+ *
9017
+ * The history pane renders a commit through the real field components, and a
9018
+ * `.jsonValues()` record's source is only `{_type:"json"}` markers - the
9019
+ * content lives in a separate `*.val.json` per entry, fetched on demand. The
9020
+ * live Studio fetches it from `GET /json`; the commit pane had nothing to
9021
+ * fetch from, so every entry stayed a marker and rendered as though the field
9022
+ * were empty. Which is worse than an error: an empty value is a claim, and it
9023
+ * was a false one.
9024
+ *
9025
+ * ## Why this needs the module's own `.val.ts`
9026
+ *
9027
+ * There is no derivable relationship between a record key and its entry file.
9028
+ * From the example app:
9029
+ *
9030
+ * "/support/getting-started": c.json(() => import("./content/getting-started.val.json"))
9031
+ * "/support/faq": c.json(() => import("./content/faq.val.json"))
9032
+ *
9033
+ * The key is a route and the path is whatever the author wrote. The only place
9034
+ * the two are related is the `import()` literal inside the `.val.ts`, which is
9035
+ * why `findJsonEntryFilePathsInSource` reads them out of the AST too. The
9036
+ * commit archive stores the module's SOURCE (the markers) and its schema, not
9037
+ * its text - so the text is fetched from git at that commit and parsed here.
9038
+ *
9039
+ * Two round trips per entry, then: the module, and the entry. The module's
9040
+ * parse is worth caching per (commit, module) by the caller; nothing here does,
9041
+ * because a `ValOps` is not a cache and the endpoint above it is the layer that
9042
+ * knows how long a commit stays interesting.
9043
+ */
9044
+ async function getJsonEntryAtCommit(ops, commitSha, moduleFilePath, key) {
9045
+ const moduleGitPath = ops.gitPathOfModule(moduleFilePath);
9046
+ if (result.isErr(moduleGitPath)) {
9047
+ return moduleGitPath;
9048
+ }
9049
+ const moduleRes = await ops.getFileAtCommit(commitSha, moduleGitPath.value, false);
9050
+ if (result.isErr(moduleRes)) {
9051
+ return moduleRes;
9052
+ }
9053
+ const entryPath = findEntryImportPath(moduleFilePath, moduleGitPath.value, moduleRes.value.toString("utf-8"), key);
9054
+ if (result.isErr(entryPath)) {
9055
+ return entryPath;
9056
+ }
9057
+ // The entry path resolved above is PROJECT-relative, like the module path it
9058
+ // came from; the git path needs the root in front of it, and the ops are what
9059
+ // know the root. Reusing gitPathOfModule rather than re-deriving the prefix:
9060
+ // a `.val.json` is not a module, but "project-relative path -> repo path" is
9061
+ // the same question and one implementation of it is the point.
9062
+ const entryGitPath = ops.gitPathOfModule(entryPath.value);
9063
+ if (result.isErr(entryGitPath)) {
9064
+ return entryGitPath;
9065
+ }
9066
+ const entryRes = await ops.getFileAtCommit(commitSha, entryGitPath.value, false);
9067
+ if (result.isErr(entryRes)) {
9068
+ return entryRes;
9069
+ }
9070
+ try {
9071
+ return result.ok(JSON.parse(entryRes.value.toString("utf-8")));
9072
+ } catch (err) {
9073
+ return result.err({
9074
+ kind: "file-unavailable",
9075
+ gitPath: entryGitPath.value,
9076
+ message: `not valid JSON at this commit: ${err instanceof Error ? err.message : String(err)}`
9077
+ });
9078
+ }
9079
+ }
9080
+
9081
+ /**
9082
+ * The `import()` path a key's entry is behind, from the module's text.
9083
+ *
9084
+ * Exported for its own test: this is the part with a real chance of being
9085
+ * wrong, and it is pure.
9086
+ */
9087
+ function findEntryImportPath(moduleFilePath,
9088
+ /**
9089
+ * The same module, repository-relative, for error reporting only.
9090
+ *
9091
+ * A `gitPath` in a HistoryError is a REPOSITORY path - that is what every
9092
+ * other history helper reports, and what a reader can paste into a `git show`
9093
+ * - so reporting the project-relative ModuleFilePath here named a file that
9094
+ * does not exist at that path for any project not rooted at the repo root.
9095
+ */
9096
+ moduleGitPath, valTsSource, key) {
9097
+ const sourceFile = ts.createSourceFile(moduleFilePath, valTsSource, ts.ScriptTarget.ES2015, true);
9098
+ let analysis;
9099
+ try {
9100
+ analysis = analyzeValModule(sourceFile);
9101
+ } catch (err) {
9102
+ // `file-unavailable` rather than a kind of its own: the module's TEXT is
9103
+ // the file we could not use, and adding a wire kind for "unparseable"
9104
+ // would be a new case every reader has to handle to say the same thing.
9105
+ return result.err({
9106
+ kind: "file-unavailable",
9107
+ gitPath: moduleGitPath,
9108
+ message: `could not parse the module at this commit: ${err instanceof Error ? err.message : String(err)}`
9109
+ });
9110
+ }
9111
+ if (result.isErr(analysis)) {
9112
+ return result.err({
9113
+ kind: "file-unavailable",
9114
+ gitPath: moduleGitPath,
9115
+ message: "could not read the module at this commit"
9116
+ });
9117
+ }
9118
+ const entries = analyzeJsonValuesEntries(analysis.value.source);
9119
+ const entry = entries.get(key);
9120
+ if (entry === undefined) {
9121
+ /*
9122
+ * Reported, not treated as empty. A key with no entry at this commit means
9123
+ * the entry was added later - so "there is nothing to show for it here" is
9124
+ * the true answer, and rendering an empty field instead would say the
9125
+ * author had left it blank.
9126
+ */
9127
+ return result.err({
9128
+ kind: "file-unavailable",
9129
+ gitPath: moduleGitPath,
9130
+ message: `'${key}' had no entry at this commit`
9131
+ });
9132
+ }
9133
+ const moduleDir = path__default.posix.dirname(moduleFilePath);
9134
+ return result.ok(path__default.posix.join(moduleDir, entry.importPath));
9135
+ }
9136
+
8948
9137
  const host = process.env.VAL_CONTENT_URL || DEFAULT_CONTENT_HOST;
8949
9138
  const SettingsSchema = z.object({
8950
9139
  publicProjectId: z.string(),
@@ -12240,12 +12429,60 @@ const ValServer = (valModules, options, callbacks) => {
12240
12429
  };
12241
12430
  }
12242
12431
  },
12432
+ "/history/json": {
12433
+ GET: async req => {
12434
+ // Authenticated, for the reasons on /history/files below.
12435
+ const auth = getAuth(req.cookies);
12436
+ if (auth.error) {
12437
+ return {
12438
+ status: 401,
12439
+ json: {
12440
+ message: auth.error
12441
+ }
12442
+ };
12443
+ }
12444
+ const res = await getJsonEntryAtCommit(serverOps, req.query.commit_sha, req.query.path, req.query.key);
12445
+ if (result.isErr(res)) {
12446
+ return historyErrorResponse(res.error);
12447
+ }
12448
+ return {
12449
+ status: 200,
12450
+ json: {
12451
+ path: req.query.path,
12452
+ key: req.query.key,
12453
+ content: res.value
12454
+ }
12455
+ };
12456
+ }
12457
+ },
12243
12458
  "/history/files": {
12244
12459
  GET: async req => {
12245
- // No auth, for the same reason /files has none: this is served to an
12246
- // <img> that the app's own backend may fetch during image
12247
- // optimisation, with no cookies. What it exposes is a file at a commit
12248
- // that is already in the repository.
12460
+ /*
12461
+ * Authenticated, unlike /files.
12462
+ *
12463
+ * This used to reason "same as /files" and that was wrong twice over.
12464
+ * /files is open because a `patch_id` is an unguessable UUID standing
12465
+ * in for a credential, and because it HAS to be: a draft image is
12466
+ * fetched by the app's own backend during Next image optimisation,
12467
+ * backend-to-backend, with no cookies to send.
12468
+ *
12469
+ * A commit sha is not a secret - it is in `git log`, in the GitHub UI,
12470
+ * on every PR - so the first argument does not transfer. And nothing
12471
+ * fetches this server-side; both callers are the Studio in a browser
12472
+ * that has the session cookie (the history pane's <img>, and
12473
+ * stageRestore's fetch, both same-origin so the cookie goes). So the
12474
+ * second does not either, and there is nothing to trade away by
12475
+ * asking.
12476
+ */
12477
+ const auth = getAuth(req.cookies);
12478
+ if (auth.error) {
12479
+ return {
12480
+ status: 401,
12481
+ json: {
12482
+ message: auth.error
12483
+ }
12484
+ };
12485
+ }
12249
12486
  const res = await serverOps.getFileAtCommit(req.query.commit_sha, req.query.path, req.query.remote === "true");
12250
12487
  if (result.isErr(res)) {
12251
12488
  const response = historyErrorResponse(res.error);
@@ -12284,6 +12521,11 @@ const ValServer = (valModules, options, callbacks) => {
12284
12521
  // 3) the benefit an attacker would get is an image that is not yet published (i.e. most cases: not very interesting)
12285
12522
  // Thus: attack surface + ease of attack + benefit = low probability of attack
12286
12523
  // If we couldn't argue that patch ids are secret enough, then this would be a problem.
12524
+ // Note that /history/files, which looks like the same endpoint, IS
12525
+ // authenticated: its token is a commit sha, which is published, and
12526
+ // nothing fetches it backend-to-backend. Neither half of the argument
12527
+ // above transfers. See architecture/media.md, "Why /files has no auth,
12528
+ // and /history/files does".
12287
12529
  let fileBuffer;
12288
12530
  let mimeType;
12289
12531
  const remote = query.remote === "true";
@@ -14266,37 +14508,50 @@ async function getFileMetadata(projectRoot, validationError) {
14266
14508
  }
14267
14509
 
14268
14510
  /**
14269
- * Moves ONE `.jsonValues()` entry that was written inline in the `.val.ts` into
14270
- * its own `*.val.json`, replacing the inline value with
14271
- * `c.json(() => import("./<key>.val.json"))`.
14511
+ * The two files an extraction touches, as text — nothing written yet.
14272
14512
  *
14273
- * This is the fix for the `jsonValues:extract-entry` validation error. It is not
14274
- * expressible as a patch (a patch edits one `.val.ts` and cannot create the
14275
- * backing JSON file), so it writes both files directly — JSON first, so a
14276
- * failure part-way through never leaves the module pointing at a file that does
14277
- * not exist.
14513
+ * Both paths are project-relative in the `ModuleFilePath` style (a leading
14514
+ * slash), because that is what the callers already hold and what
14515
+ * {@link getNewJsonEntryPaths} derives. Join them onto the project root to get
14516
+ * somewhere to write.
14517
+ */
14518
+
14519
+ /**
14520
+ * Works out what moving ONE inline `.jsonValues()` entry into its own
14521
+ * `*.val.json` would change, without changing anything.
14522
+ *
14523
+ * This is the whole fix for `jsonValues:extract-entry`, minus the writing. It
14524
+ * is split out because the two callers cannot share a way of applying it: the
14525
+ * CLI writes both files ({@link extractJsonValuesEntry}), while the editor has
14526
+ * to hand the same two changes back as a `WorkspaceEdit` so they go through the
14527
+ * undo stack and respect an unsaved buffer. Anything that lived in only one of
14528
+ * those would be a fix that behaves differently depending on where it was
14529
+ * invoked from, which is exactly the drift `codeActions.ts` exists to prevent.
14530
+ *
14531
+ * It does NOT check whether `jsonPath` already exists: that needs a filesystem,
14532
+ * and the editor's answer ("is there a buffer or a file here?") is not the
14533
+ * CLI's. Every caller must check before writing — overwriting an unrelated file
14534
+ * is not a fix.
14278
14535
  *
14279
14536
  * Root-only, like the rest of the `.jsonValues()` machinery: the entry is looked
14280
14537
  * up in the module's root record/router object literal.
14281
14538
  */
14282
- function extractJsonValuesEntry(moduleFilePath, rootDir, entryKey, content, sourceFileHandler) {
14283
- const valTsPath = sourceFileHandler.resolveSourceModulePath(getSyntheticContainingPath(rootDir), `.${moduleFilePath.replace(".val.ts", ".val").replace(".val.js", ".val").replace(".val.jsx", ".val").replace(".val.tsx", ".val")}`);
14284
- const sourceFile = sourceFileHandler.getSourceFile(valTsPath);
14285
- if (!sourceFile) {
14286
- throw Error(`Source file ${valTsPath} not found`);
14287
- }
14539
+ function planJsonValuesEntryExtraction({
14540
+ moduleFilePath,
14541
+ entryKey,
14542
+ content,
14543
+ valTsPath,
14544
+ valTsText
14545
+ }) {
14288
14546
  const pathsRes = getNewJsonEntryPaths(moduleFilePath, entryKey);
14289
14547
  if (result.isErr(pathsRes)) {
14290
- throw Error(formatPatchSourceError(pathsRes.error));
14548
+ return result.err(formatPatchSourceError(pathsRes.error));
14291
14549
  }
14292
14550
  const {
14293
14551
  jsonPath,
14294
14552
  importPath
14295
14553
  } = pathsRes.value;
14296
- const absoluteJsonPath = path__default.join(rootDir, jsonPath);
14297
- if (sourceFileHandler.host.fileExists(absoluteJsonPath)) {
14298
- throw Error(`Cannot extract .jsonValues() entry '${entryKey}' of ${moduleFilePath}: '${jsonPath}' already exists`);
14299
- }
14554
+ const sourceFile = ts.createSourceFile(valTsPath, valTsText, ts.ScriptTarget.ES2020);
14300
14555
 
14301
14556
  // Remove the inline property, then add the `c.json(...)` reference back. The
14302
14557
  // entry moves to the end of the record: entry ORDER in a jsonValues record is
@@ -14304,14 +14559,70 @@ function extractJsonValuesEntry(moduleFilePath, rootDir, entryKey, content, sour
14304
14559
  // insert-in-place would mean reimplementing insertValJsonEntry.
14305
14560
  const removed = removeValJsonEntry(sourceFile, [], entryKey);
14306
14561
  if (result.isErr(removed)) {
14307
- throw Error(`${valTsPath}\n${formatOpsError(removed.error, sourceFile)}`);
14562
+ return result.err(`${valTsPath}\n${formatOpsError(removed.error, sourceFile)}`);
14308
14563
  }
14309
14564
  const inserted = insertValJsonEntry(removed.value, [], entryKey, importPath);
14310
14565
  if (result.isErr(inserted)) {
14311
- throw Error(`${valTsPath}\n${formatOpsError(inserted.error, removed.value)}`);
14566
+ return result.err(`${valTsPath}\n${formatOpsError(inserted.error, removed.value)}`);
14567
+ }
14568
+ return result.ok({
14569
+ jsonPath,
14570
+ jsonContent: JSON.stringify(content, null, 2) + "\n",
14571
+ valTsPath,
14572
+ valTsContent: printSourceFile(inserted.value)
14573
+ });
14574
+ }
14575
+
14576
+ /**
14577
+ * Moves ONE `.jsonValues()` entry that was written inline in the `.val.ts` into
14578
+ * its own `*.val.json`, replacing the inline value with
14579
+ * `c.json(() => import("./<key>.val.json"))`.
14580
+ *
14581
+ * The `val validate --fix` half of {@link planJsonValuesEntryExtraction}. It is
14582
+ * not expressible as a patch (a patch edits one `.val.ts` and cannot create the
14583
+ * backing JSON file), so it writes both files directly — JSON first, so a
14584
+ * failure part-way through never leaves the module pointing at a file that does
14585
+ * not exist.
14586
+ */
14587
+ function extractJsonValuesEntry(moduleFilePath, rootDir, entryKey, content, sourceFileHandler) {
14588
+ const valTsPath = sourceFileHandler.resolveSourceModulePath(getSyntheticContainingPath(rootDir), `.${moduleFilePath.replace(".val.ts", ".val").replace(".val.js", ".val").replace(".val.jsx", ".val").replace(".val.tsx", ".val")}`);
14589
+ const valTsText = sourceFileHandler.host.readFile(valTsPath);
14590
+ if (valTsText === undefined) {
14591
+ throw Error(`Source file ${valTsPath} not found`);
14592
+ }
14593
+ const planned = planJsonValuesEntryExtraction({
14594
+ moduleFilePath,
14595
+ entryKey,
14596
+ content,
14597
+ valTsPath,
14598
+ valTsText
14599
+ });
14600
+ if (result.isErr(planned)) {
14601
+ throw Error(planned.error);
14312
14602
  }
14313
- sourceFileHandler.writeFile(absoluteJsonPath, JSON.stringify(content, null, 2) + "\n", "utf8");
14314
- sourceFileHandler.writeSourceFile(inserted.value);
14603
+ const absoluteJsonPath = path__default.join(rootDir, planned.value.jsonPath);
14604
+ if (sourceFileHandler.host.fileExists(absoluteJsonPath)) {
14605
+ throw Error(`Cannot extract .jsonValues() entry '${entryKey}' of ${moduleFilePath}: '${planned.value.jsonPath}' already exists`);
14606
+ }
14607
+ sourceFileHandler.writeFile(absoluteJsonPath, planned.value.jsonContent, "utf8");
14608
+ sourceFileHandler.writeFile(valTsPath, planned.value.valTsContent, "utf8");
14609
+ }
14610
+
14611
+ /**
14612
+ * The text of a source file the TS ops produced, exactly as
14613
+ * `ValSourceFileHandler.writeSourceFile` would have written it.
14614
+ *
14615
+ * The unescape undoes the printer's `\uXXXX` output for non-ASCII. Today the
14616
+ * ops splice printed text into `document.text` and this fix only ever prints an
14617
+ * ASCII `c.json(() => import("./..."))`, so nothing here is escaped and the
14618
+ * unescape is a no-op — but `neverAsciiEscape` is off in the printer, so that is
14619
+ * a property of what this fix happens to print, not a guarantee. Kept because
14620
+ * this replaced a `writeSourceFile` call and the text written must not change.
14621
+ *
14622
+ * https://github.com/microsoft/TypeScript/issues/36174
14623
+ */
14624
+ function printSourceFile(sourceFile) {
14625
+ return unescape(sourceFile.text.replace(/\\u/g, "%u"));
14315
14626
  }
14316
14627
  function formatOpsError(error, sourceFile) {
14317
14628
  if (error instanceof PatchError) {
@@ -14579,7 +14890,7 @@ async function handleRemoteFileUpload(ctx) {
14579
14890
  return uploadRemoteFileCore(ctx, fileRefProp, fileSourceMetadata, resolveRemoteFileSchema);
14580
14891
  }
14581
14892
 
14582
- // Gallery (s.images({ remote: true }) / s.files({ remote: true })) upload.
14893
+ // Gallery (s.images({ ... }).remote() / s.files({ ... }).remote()) upload.
14583
14894
  // Unlike a single image/file field, a gallery entry is keyed by its local file
14584
14895
  // path and the value is bare metadata (no FileSource), so we derive the file
14585
14896
  // ref from the key and synthesize the image/file schema from the record's
@@ -15551,4 +15862,4 @@ function readCapturedReport(snapshotDir) {
15551
15862
  return JSON.parse(fs.readFileSync(reportPath, "utf-8"));
15552
15863
  }
15553
15864
 
15554
- export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
15865
+ export { DEFAULT_LOGIN_EXPIRES_IN_SECONDS, DEFAULT_LOGIN_HOST, DEFAULT_LOGIN_POLL_INTERVAL_SECONDS, EXTERNAL_RESULT, Service, ValFSHost, ValLoginError, ValModuleLoader, ValOpsFS, ValOpsHttp, ValSourceFileHandler, analyzeValModule, awaitValLoginConfirmation, checkRemoteRef, classifyJsonValuesOp, compareWithCapturedReport, createDefaultValFSHost, createFixPatch, createJsonEntryPathMap, createModulePathMap, createService, createValApiRouter, createValModuleFileInspector, createValOps, createValServer, currentFixHandlers, decodeJwtWithoutVerifying, defineExternal, describePatchStoreProblems, downloadFileFromRemote, encodeJwt, err, evalValConfigFile, extractFileMetadata, extractImageMetadata, extractJsonValuesEntry, findAndEvalValConfigFile, findJsonEntryFilePath, fixHandlers, formatPatchSourceError, formatSyntaxErrorTree, getCachedRemoteFileDir, getCachedRemoteFilePath, getCompilerOptions, getExpire, getFileExt, getModulePathRange, getPersonalAccessTokenPath, getSettings, getValidationErrorFileRef, handleCheckAllFiles, handleExternalUpload, handleFileMetadata, handleJsonValuesExtractEntry, handleRemoteFileCheck, handleRemoteFileDownload, handleRemoteFileUpload, handleRemoteGalleryFileUpload, handleUniqueFolderCheck, initHandlerOptions, isExternalResult, loadValModules, ok, parsePersonalAccessTokenFile, patchSourceFile, persistPersonalAccessToken, planJsonValuesEntryExtraction, readCapturedReport, readPatchStore, rebaseContentOp, replaySnapshot, resolveRemoteFileAuth, safeReadGit, startValLogin, uploadRemoteFile, validateMetadata, verifyJwt };
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.123.2",
19
+ "version": "0.124.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": "^6.0.3",
30
30
  "zod": "^4.4.3",
31
31
  "zod-validation-error": "^5.0.0",
32
- "@valbuild/shared": "0.123.2",
33
- "@valbuild/core": "0.121.0",
34
- "@valbuild/ui": "0.123.2"
32
+ "@valbuild/core": "0.124.0",
33
+ "@valbuild/ui": "0.124.0",
34
+ "@valbuild/shared": "0.124.0"
35
35
  },
36
36
  "engines": {
37
37
  "node": "^20.19.0 || >=22"