@valbuild/server 0.143.0 → 0.144.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.
@@ -20273,7 +20273,7 @@ function readFileOrUndefined$1(absolutePath) {
20273
20273
  * be uploaded first). On the way DOWN the same playlists are rewritten back to
20274
20274
  * relative names, which is what lets a published stream be plain static files.
20275
20275
  */
20276
- function isRecord$2(value) {
20276
+ function isRecord$3(value) {
20277
20277
  return typeof value === "object" && value !== null && !Array.isArray(value);
20278
20278
  }
20279
20279
  function isPlaylist$1(ref) {
@@ -20282,14 +20282,14 @@ function isPlaylist$1(ref) {
20282
20282
 
20283
20283
  /** The paths a video value holds, each with where in the value it sits. */
20284
20284
  function videoPathsOf(video) {
20285
- if (!isRecord$2(video) || typeof video.path !== "string") {
20285
+ if (!isRecord$3(video) || typeof video.path !== "string") {
20286
20286
  return [];
20287
20287
  }
20288
20288
  const found = [{
20289
20289
  at: ["path"],
20290
20290
  path: video.path
20291
20291
  }];
20292
- if (isRecord$2(video.poster) && typeof video.poster.path === "string") {
20292
+ if (isRecord$3(video.poster) && typeof video.poster.path === "string") {
20293
20293
  found.push({
20294
20294
  at: ["poster", "path"],
20295
20295
  path: video.poster.path
@@ -20297,7 +20297,7 @@ function videoPathsOf(video) {
20297
20297
  }
20298
20298
  if (Array.isArray(video.captions)) {
20299
20299
  video.captions.forEach((track, index) => {
20300
- if (isRecord$2(track) && typeof track.path === "string") {
20300
+ if (isRecord$3(track) && typeof track.path === "string") {
20301
20301
  found.push({
20302
20302
  at: ["captions", String(index), "path"],
20303
20303
  path: track.path
@@ -20368,7 +20368,7 @@ function metadataOf(localRef, role, video) {
20368
20368
  height: video.height
20369
20369
  };
20370
20370
  }
20371
- if (role === "poster" && isRecord$2(video.poster)) {
20371
+ if (role === "poster" && isRecord$3(video.poster)) {
20372
20372
  return {
20373
20373
  mimeType: video.poster.mimeType ?? mimeType,
20374
20374
  width: video.poster.width,
@@ -20384,7 +20384,7 @@ function metadataOf(localRef, role, video) {
20384
20384
  * Places a video's local files on Val Remote, each once, and remembers where.
20385
20385
  *
20386
20386
  * The one implementation of "upload a video" for every fix that does it: a
20387
- * video field (`video:upload-remote`), and an entry of an `s.videoset()`
20387
+ * video field (`video:upload-remote`), and an entry of an `s.videodir()`
20388
20388
  * (`videos:upload-remote`). What differs between the two is which files go up
20389
20389
  * and what their refs are hashed against, so those are the arguments.
20390
20390
  *
@@ -20477,7 +20477,7 @@ function resolveVideo(ctx) {
20477
20477
  }
20478
20478
  const resolved = core.Internal.resolvePath(modulePath, ctx.valModule.source, ctx.valModule.schema);
20479
20479
  const video = resolved.source;
20480
- if (((_resolved$schema = resolved.schema) === null || _resolved$schema === void 0 ? void 0 : _resolved$schema.type) !== "video" || !isRecord$2(video) || typeof video.path !== "string") {
20480
+ if (((_resolved$schema = resolved.schema) === null || _resolved$schema === void 0 ? void 0 : _resolved$schema.type) !== "video" || !isRecord$3(video) || typeof video.path !== "string") {
20481
20481
  return {
20482
20482
  success: false,
20483
20483
  result: {
@@ -20497,7 +20497,7 @@ function resolveVideo(ctx) {
20497
20497
  * The paths of a video FIELD that its fix moves.
20498
20498
  *
20499
20499
  * All of them for a video of its own. For a field picked from an
20500
- * `s.videoset()` (`referencedModule`), only the poster and the captions: the
20500
+ * `s.videodir()` (`referencedModule`), only the poster and the captions: the
20501
20501
  * video file is the set's — its key there — and moving it from here would
20502
20502
  * leave the field naming a file the set does not have. The set's own fix
20503
20503
  * (`videos:upload-remote`) moves it, and rewrites the fields that name it.
@@ -20670,6 +20670,192 @@ async function handleVideoDownloadRemote(ctx) {
20670
20670
  };
20671
20671
  }
20672
20672
 
20673
+ /**
20674
+ * The fields that name an entry of a media collection by its key, and the
20675
+ * patches that point them at a new key.
20676
+ *
20677
+ * A collection — `s.imagedir()`, `s.filedir()`, `s.videodir()` — is a record
20678
+ * keyed by each entry's file path, and a field picked from it
20679
+ * (`s.image(imagesVal)`, `s.file(filesVal)`, `s.video(videosVal)`) stores that
20680
+ * key as its `path`. So when `--fix` renames a key — moving an entry of a
20681
+ * `.remote()` collection to Val Remote renames it from the local path to the
20682
+ * ref — every field holding the old key would name an entry the collection no
20683
+ * longer has, and nothing could repair it afterwards.
20684
+ *
20685
+ * Two kinds of value name a key:
20686
+ *
20687
+ * - **A field whose schema is backed by the collection** (`referencedModule`).
20688
+ * - **An inline image in rich text** whose `img` schema is backed by it
20689
+ * (`s.richtext({ img: s.image(imagesVal) })`). The field's own schema is
20690
+ * `richtext`, so a walk looking only for image leaves goes straight past
20691
+ * them — the Studio's reference scan learned that once already
20692
+ * (`getReferencedFiles`), and uses the same `forEachRichTextImage`.
20693
+ *
20694
+ * **A field is renamed when the key it names is the old key**, and "names" is
20695
+ * `galleryKeyOf` in core: the field's `path` when that is a key, and otherwise
20696
+ * the local path inside it when it is a ref. That is how a field is read
20697
+ * (`fillFromGallery`) and how it is validated, so it is exactly the set of
20698
+ * fields the rename would break. The second shape is real: an image uploaded
20699
+ * THROUGH an `s.image(remoteGallery)` field in the Studio stores the ref in
20700
+ * the field and keys the entry by the local path inside it, so uploading that
20701
+ * entry here has to move the field to the entry's new key as well. A ref that
20702
+ * is itself a key names its own entry, and is left alone.
20703
+ *
20704
+ * Nothing here imports `fixHandlers.ts` at runtime: that module calls this one.
20705
+ */
20706
+
20707
+ /** What of a `Service` the project-wide walk needs. */
20708
+
20709
+ /** A value that names an entry of a collection by its key. */
20710
+
20711
+ function isRecord$2(value) {
20712
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20713
+ }
20714
+
20715
+ /** Whether a serialized schema is backed by `collection` anywhere in it. */
20716
+ function isBackedBy(schema, collection) {
20717
+ if (Array.isArray(schema)) {
20718
+ return schema.some(item => isBackedBy(item, collection));
20719
+ }
20720
+ if (!isRecord$2(schema)) {
20721
+ return false;
20722
+ }
20723
+ if (schema.referencedModule === collection) {
20724
+ return true;
20725
+ }
20726
+ return Object.values(schema).some(value => isBackedBy(value, collection));
20727
+ }
20728
+
20729
+ /**
20730
+ * The references already found, per service and collection.
20731
+ *
20732
+ * `val validate --fix` renames a key once per local entry, so a gallery of N
20733
+ * local entries would otherwise walk the project N times — validating every
20734
+ * module that uses the gallery each time. Caching per `Service` is exact, not
20735
+ * an approximation: a `Service` answers `get` from the snapshot it was created
20736
+ * with (`patch` writes to disk and changes nothing it returns), so the walk
20737
+ * would find the same references every time. A new `Service` — the language
20738
+ * server makes one after every edit — starts with nothing cached.
20739
+ */
20740
+ const referencesByService = new WeakMap();
20741
+
20742
+ /**
20743
+ * Every value in the project that names an entry of `collection`, as the
20744
+ * service's snapshot has it. Found once per service; see
20745
+ * {@link referencesByService}.
20746
+ */
20747
+ function collectionReferencesOf(service, collection) {
20748
+ let byCollection = referencesByService.get(service);
20749
+ if (!byCollection) {
20750
+ byCollection = new Map();
20751
+ referencesByService.set(service, byCollection);
20752
+ }
20753
+ let references = byCollection.get(collection);
20754
+ if (!references) {
20755
+ references = findCollectionReferences(service, collection);
20756
+ // A walk that failed is not an answer; the next caller tries again.
20757
+ references.catch(() => byCollection.delete(collection));
20758
+ byCollection.set(collection, references);
20759
+ }
20760
+ return references;
20761
+ }
20762
+
20763
+ /**
20764
+ * The walk itself.
20765
+ *
20766
+ * Validated reads for the modules backed by the collection, because only
20767
+ * those come back with their `.jsonValues()` entries loaded — an unvalidated
20768
+ * read hands back each entry as a marker, and a field inside one would be
20769
+ * invisible. Every other module is read once, unvalidated, to find out it is
20770
+ * not one of them.
20771
+ */
20772
+ async function findCollectionReferences(service, collection) {
20773
+ const found = [];
20774
+ for (const moduleFilePath of service.getModuleFilePaths()) {
20775
+ const shallow = await service.get(moduleFilePath, "", {
20776
+ validate: false
20777
+ });
20778
+ if (!isBackedBy(shallow.schema, collection)) {
20779
+ continue;
20780
+ }
20781
+ const loaded = await service.get(moduleFilePath, "", {
20782
+ validate: true
20783
+ });
20784
+ if (loaded.source === undefined || !loaded.schema) {
20785
+ continue;
20786
+ }
20787
+ const add = (sourcePath, value) => {
20788
+ if (isRecord$2(value) && typeof value.path === "string") {
20789
+ found.push({
20790
+ moduleFilePath,
20791
+ sourcePath,
20792
+ path: value.path
20793
+ });
20794
+ }
20795
+ };
20796
+ internal.traverseSchemaSource(loaded.source, loaded.schema, moduleFilePath, ({
20797
+ source,
20798
+ schema,
20799
+ path: sourcePath
20800
+ }) => {
20801
+ if (schema.type === "image" || schema.type === "file" || schema.type === "video") {
20802
+ if (schema.referencedModule === collection) {
20803
+ add(sourcePath, source);
20804
+ }
20805
+ } else if (schema.type === "richtext") {
20806
+ var _richTextImageSchema;
20807
+ if (((_richTextImageSchema = internal.richTextImageSchema(schema)) === null || _richTextImageSchema === void 0 ? void 0 : _richTextImageSchema.referencedModule) === collection) {
20808
+ internal.forEachRichTextImage(sourcePath, source, add);
20809
+ }
20810
+ }
20811
+ });
20812
+ }
20813
+ return found;
20814
+ }
20815
+
20816
+ /**
20817
+ * The patch path of `sourcePath`. Not `sourceToPatchPath`: a collection's keys
20818
+ * are file paths, and their dots do not survive its `split(".")`.
20819
+ */
20820
+ function patchPathOf$1(sourcePath) {
20821
+ const [, modulePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
20822
+ return core.Internal.splitModulePath(modulePath);
20823
+ }
20824
+
20825
+ /**
20826
+ * The patches that point every value naming a key in `renamed` at its new key.
20827
+ *
20828
+ * `renamed` is keyed by the collection's keys BEFORE the rename, and the
20829
+ * collection is read as it is now — so call this before its own patch lands.
20830
+ *
20831
+ * One `add` on each value's `path`, so the alt text, hotspot, poster and
20832
+ * captions beside it are never rewritten.
20833
+ */
20834
+ async function collectionReferencePatches(service, collection, renamed) {
20835
+ const collectionSource = (await service.get(collection, "", {
20836
+ validate: false
20837
+ })).source;
20838
+ const keys = new Set(isRecord$2(collectionSource) ? Object.keys(collectionSource) : []);
20839
+ const byModule = new Map();
20840
+ for (const reference of await collectionReferencesOf(service, collection)) {
20841
+ const key = core.Internal.media.galleryKeyOf(reference.path, candidate => keys.has(candidate));
20842
+ if (key === null || !Object.prototype.hasOwnProperty.call(renamed, key)) {
20843
+ continue;
20844
+ }
20845
+ const patch = byModule.get(reference.moduleFilePath) ?? [];
20846
+ patch.push({
20847
+ op: "add",
20848
+ path: patchPathOf$1(reference.sourcePath).concat("path"),
20849
+ value: renamed[key]
20850
+ });
20851
+ byModule.set(reference.moduleFilePath, patch);
20852
+ }
20853
+ return [...byModule].map(([moduleFilePath, patch]) => ({
20854
+ moduleFilePath,
20855
+ patch
20856
+ }));
20857
+ }
20858
+
20673
20859
  /**
20674
20860
  * What is out of step between a gallery's entries and its directory.
20675
20861
  *
@@ -20778,7 +20964,7 @@ function filesInDirectory({
20778
20964
  }
20779
20965
 
20780
20966
  /**
20781
- * The fixes of an `s.videoset()` — the video twin of an image gallery.
20967
+ * The fixes of an `s.videodir()` — the video twin of an image gallery.
20782
20968
  *
20783
20969
  * A set is a record keyed by each video's file path (an HLS stream by its
20784
20970
  * master playlist), so most of this is the gallery fixes again with the key as
@@ -21041,34 +21227,14 @@ async function filesNamedByVideoFields(service, options) {
21041
21227
  /**
21042
21228
  * The patches that point every `s.video(set)` field at a set entry's new key.
21043
21229
  *
21044
- * One `add` on each field's `path`, so the description, poster, times and
21045
- * captions beside it are never rewritten. A field of another set, or of no
21046
- * set, is left alone even when its path is the same string: it names its own
21047
- * file.
21230
+ * The same walk an image or file collection's rename uses
21231
+ * (`collectionReferencePatches`): one `add` on each field's `path`, so the
21232
+ * description, poster, times and captions beside it are never rewritten, and
21233
+ * a field of another set, or of no set, is left alone even when its path is
21234
+ * the same string — it names its own file.
21048
21235
  */
21049
21236
  async function videosetReferencePatches(service, setModuleFilePath, renamed) {
21050
- const byModule = new Map();
21051
- for (const field of await videoFieldsOf(service)) {
21052
- if (field.schema.referencedModule !== setModuleFilePath) {
21053
- continue;
21054
- }
21055
- const value = field.source;
21056
- const current = isRecord$1(value) ? value.path : undefined;
21057
- if (typeof current !== "string" || !(current in renamed)) {
21058
- continue;
21059
- }
21060
- const patch = byModule.get(field.moduleFilePath) ?? [];
21061
- patch.push({
21062
- op: "add",
21063
- path: patchPathOf(field.sourcePath).concat("path"),
21064
- value: renamed[current]
21065
- });
21066
- byModule.set(field.moduleFilePath, patch);
21067
- }
21068
- return [...byModule].map(([moduleFilePath, patch]) => ({
21069
- moduleFilePath,
21070
- patch
21071
- }));
21237
+ return collectionReferencePatches(service, setModuleFilePath, renamed);
21072
21238
  }
21073
21239
 
21074
21240
  /** The record at `sourcePath` and its entries, or why it could not be read. */
@@ -22652,13 +22818,13 @@ async function handleRemoteFileUpload(ctx) {
22652
22818
  return uploadRemoteFileCore(ctx, fileRefProp, fileSourceMetadata, resolveRemoteFileSchema);
22653
22819
  }
22654
22820
 
22655
- // Gallery (s.imageset({ ... }).remote() / s.fileset({ ... }).remote()) upload.
22821
+ // Gallery (s.imagedir(dir, { ... }).remote() / s.filedir(dir, { ... }).remote()) upload.
22656
22822
  // Unlike a single image/file field, a gallery entry is keyed by its local file
22657
22823
  // path and the value is bare metadata (no FileSource), so we derive the file
22658
22824
  // ref from the key and synthesize the image/file schema from the record's
22659
22825
  // media options.
22660
22826
  async function handleRemoteGalleryFileUpload(ctx) {
22661
- var _ctx$validationError$;
22827
+ var _ctx$validationError$, _ctx$remoteFiles$ctx$;
22662
22828
  if (!ctx.fix) {
22663
22829
  return {
22664
22830
  success: false,
@@ -22696,7 +22862,7 @@ async function handleRemoteGalleryFileUpload(ctx) {
22696
22862
  // `accept`/`dir` come from the RECORD that holds the entry, resolved
22697
22863
  // from the entry's PARENT path - not from the module root, which is only the
22698
22864
  // record when the gallery is the whole module. A nested gallery
22699
- // (s.object({ gallery: s.imageset(...) })) would otherwise synthesize a schema
22865
+ // (s.object({ gallery: s.imagedir(...) })) would otherwise synthesize a schema
22700
22866
  // with no options and bake a validation hash into the remote ref that can
22701
22867
  // never validate, so a mismatch fails fast instead of uploading.
22702
22868
  const [, parentModulePath] = core.Internal.splitModuleFilePathAndModulePath(core.Internal.parentOfSourcePath(ctx.sourcePath));
@@ -22731,7 +22897,24 @@ async function handleRemoteGalleryFileUpload(ctx) {
22731
22897
  } : {})
22732
22898
  }
22733
22899
  };
22734
- return uploadRemoteFileCore(ctx, fileRef, metadata, schema);
22900
+ const uploaded = await uploadRemoteFileCore(ctx, fileRef, metadata, schema);
22901
+ const ref = (_ctx$remoteFiles$ctx$ = ctx.remoteFiles[ctx.sourcePath]) === null || _ctx$remoteFiles$ctx$ === void 0 ? void 0 : _ctx$remoteFiles$ctx$.ref;
22902
+ if (!uploaded.success || !uploaded.shouldApplyPatch || ref === undefined) {
22903
+ return uploaded;
22904
+ }
22905
+ // `createFixPatch` renames the entry's key to the ref. Every field picked
22906
+ // from the gallery that holds the old key — and every rich text image from
22907
+ // it — would then name an image the gallery no longer has, so they are
22908
+ // pointed at the ref in the same run, as `videos:upload-remote` does.
22909
+ const otherModulePatches = await collectionReferencePatches(ctx.service, ctx.moduleFilePath, {
22910
+ [fileRef]: ref
22911
+ });
22912
+ return {
22913
+ ...uploaded,
22914
+ ...(otherModulePatches.length > 0 ? {
22915
+ otherModulePatches
22916
+ } : {})
22917
+ };
22735
22918
  }
22736
22919
  async function handleRemoteFileDownload(ctx) {
22737
22920
  if (ctx.fix) {
@@ -22750,13 +22933,47 @@ async function handleRemoteFileDownload(ctx) {
22750
22933
  };
22751
22934
  }
22752
22935
  }
22936
+
22937
+ /**
22938
+ * `image:check-remote` / `file:check-remote`: core's "Remote image was not
22939
+ * checked." — emitted for EVERY remote value of an `s.image().remote()` /
22940
+ * `s.file().remote()`, wrong or not, because core cannot read the bytes or the
22941
+ * ref's validation hash. It is a request to look, not a finding, so there is
22942
+ * nothing to report here: `createFixPatch` does the looking (`checkRemoteRef`)
22943
+ * and returns an error only for a ref that is actually wrong, with a fix where
22944
+ * the ref only needs rewriting.
22945
+ *
22946
+ * So these stay a hand-off. Reporting core's message here instead would put an
22947
+ * error on every remote image in a project, which is what the language server
22948
+ * learned to stop doing (`mediaChecks.ts`).
22949
+ */
22753
22950
  async function handleRemoteFileCheck() {
22754
- // Skip - no action needed
22755
22951
  return {
22756
22952
  success: true,
22757
22953
  shouldApplyPatch: true
22758
22954
  };
22759
22955
  }
22956
+
22957
+ /**
22958
+ * `images:check-remote` / `files:check-remote`: a gallery key that is wrong as
22959
+ * a key — a remote URL in a gallery that is not `.remote()`, a ref that does
22960
+ * not parse, one outside the gallery's directory (`validateMediaKey` in core).
22961
+ *
22962
+ * Unlike the singular codes, core emits these only when something IS wrong,
22963
+ * and its message says what. There is nothing to look up and nothing to move,
22964
+ * so it is reported as it is, for a person: only they know whether the key or
22965
+ * the gallery's options are the mistake. It used to share the singular codes'
22966
+ * handler, which hands off to `createFixPatch` — and that has no branch for a
22967
+ * gallery key, so the error vanished and the module was reported valid.
22968
+ *
22969
+ * The same answer as `videos:check-remote`.
22970
+ */
22971
+ async function handleGalleryCheckRemote(ctx) {
22972
+ return {
22973
+ success: false,
22974
+ errorMessage: ctx.validationError.message
22975
+ };
22976
+ }
22760
22977
  async function handleUniqueFolderCheck(ctx) {
22761
22978
  const value = ctx.validationError.value;
22762
22979
  if (!value || typeof value.dir !== "string") {
@@ -22951,9 +23168,9 @@ const currentFixHandlers = {
22951
23168
  "videos:check-unique-folder": handleUniqueFolderCheck,
22952
23169
  "videos:check-all-files": handleVideosetCheckAllFiles,
22953
23170
  "image:check-remote": handleRemoteFileCheck,
22954
- "images:check-remote": handleRemoteFileCheck,
23171
+ "images:check-remote": handleGalleryCheckRemote,
22955
23172
  "file:check-remote": handleRemoteFileCheck,
22956
- "files:check-remote": handleRemoteFileCheck,
23173
+ "files:check-remote": handleGalleryCheckRemote,
22957
23174
  "images:check-unique-folder": handleUniqueFolderCheck,
22958
23175
  "files:check-unique-folder": handleUniqueFolderCheck,
22959
23176
  "images:check-all-files": handleCheckAllFiles,