@valbuild/shared 0.146.0 → 0.146.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.146.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#871](https://github.com/valbuild/val/pull/871) [`e4913e1`](https://github.com/valbuild/val/commit/e4913e1f1178e09987a7c03fd540909ab4b499b0) Thanks [@freekh](https://github.com/freekh)! - An image, file or video picked from a gallery is checked against the gallery as it is now. Picking one uploaded since the site was built (still unpublished, saved in a proposal, or published without a rebuild) used to fail with "The gallery does not have an image at …" and refuse the save. In a proposal, Save now reads "Fix N" and goes to the validation errors instead of being refused by the server, as Publish does on the site.
8
+
9
+ - [#867](https://github.com/valbuild/val/pull/867) [`04f81ba`](https://github.com/valbuild/val/commit/04f81baeee4a98054c0673ca9fdc34bc5cadacb9) Thanks [@freekh](https://github.com/freekh)! - When a publish breaks a page on a site with proposals, its changes are no longer
10
+ held back on the site: they move into a proposal of their own, with any later
11
+ changes to the same pages, so everyone else keeps publishing. The Studio says
12
+ so — "Publishing broke the site" — and offers **Open the proposal**, where the
13
+ changes can be fixed and published. Sites without proposals hold the changes
14
+ back as before.
15
+
16
+ - [#867](https://github.com/valbuild/val/pull/867) [`04f81ba`](https://github.com/valbuild/val/commit/04f81baeee4a98054c0673ca9fdc34bc5cadacb9) Thanks [@freekh](https://github.com/freekh)! - Publish only some of your changes: tick them in Review and press **Publish
17
+ selected**. On a site with proposals, Val moves them into a proposal of their
18
+ own and publishes it straight away; everything else stays unpublished, as it
19
+ was.
20
+
21
+ Some changes cannot be published apart — two edits to the same list, say — so
22
+ the confirm says what comes with your selection and whose it is before anything
23
+ moves. If the site is published while yours is being prepared, nothing is
24
+ published and you are asked to press it again. If publishing stops after the
25
+ changes have moved, they are safe in their proposal, and Review offers to open
26
+ it as well as to try again.
27
+
28
+ - Updated dependencies [[`e4913e1`](https://github.com/valbuild/val/commit/e4913e1f1178e09987a7c03fd540909ab4b499b0)]:
29
+ - @valbuild/core@0.146.2
30
+
3
31
  ## 0.146.0
4
32
 
5
33
  ### Minor Changes
@@ -1147,6 +1147,63 @@ export declare const Api: {
1147
1147
  }, z.core.$strip>]>;
1148
1148
  };
1149
1149
  };
1150
+ /**
1151
+ * Publish selected (`docs/plans/remove-patch-groups.md`, Phase 4), its first
1152
+ * half, on the site's own server: the changes picked in Review, already
1153
+ * closed into whole patch sets (`selectionClosure`), moved into a proposal
1154
+ * of kind `publish` and saved there -- prepared over the site's head, which
1155
+ * the proposal is based on, so a publish landing in between is refused
1156
+ * rather than reverted. The second half is that proposal's Publish, pressed
1157
+ * as any proposal's is (`publishProposal`); this server answers its checks
1158
+ * and press with what the merge moves, as the proposal's own address would.
1159
+ */
1160
+ "/publish-selected": {
1161
+ POST: {
1162
+ req: {
1163
+ body: z.ZodObject<{
1164
+ patchIds: z.ZodArray<z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>>;
1165
+ displayName: z.ZodString;
1166
+ requestId: z.ZodString;
1167
+ }, z.core.$strip>;
1168
+ cookies: {
1169
+ val_session: z.ZodOptional<z.ZodString>;
1170
+ };
1171
+ };
1172
+ res: z.ZodUnion<readonly [z.ZodObject<{
1173
+ status: z.ZodLiteral<401>;
1174
+ json: z.ZodObject<{
1175
+ message: z.ZodString;
1176
+ }, z.core.$strip>;
1177
+ }, z.core.$strip>, z.ZodObject<{
1178
+ status: z.ZodLiteral<400>;
1179
+ json: z.ZodObject<{
1180
+ message: z.ZodString;
1181
+ }, z.core.$strip>;
1182
+ }, z.core.$strip>, z.ZodObject<{
1183
+ status: z.ZodLiteral<409>;
1184
+ json: z.ZodObject<{
1185
+ message: z.ZodString;
1186
+ headMoved: z.ZodOptional<z.ZodLiteral<true>>;
1187
+ }, z.core.$strip>;
1188
+ }, z.core.$strip>, z.ZodObject<{
1189
+ status: z.ZodLiteral<500>;
1190
+ json: z.ZodObject<{
1191
+ message: z.ZodString;
1192
+ }, z.core.$strip>;
1193
+ }, z.core.$strip>, z.ZodObject<{
1194
+ status: z.ZodLiteral<502>;
1195
+ json: z.ZodObject<{
1196
+ message: z.ZodString;
1197
+ proposal: z.ZodString;
1198
+ }, z.core.$strip>;
1199
+ }, z.core.$strip>, z.ZodObject<{
1200
+ status: z.ZodLiteral<200>;
1201
+ json: z.ZodObject<{
1202
+ proposal: z.ZodString;
1203
+ }, z.core.$strip>;
1204
+ }, z.core.$strip>]>;
1205
+ };
1206
+ };
1150
1207
  "/save": {
1151
1208
  POST: {
1152
1209
  req: {
@@ -38,5 +38,6 @@ export * from "./theme/accentRamp.js";
38
38
  export * from "./publish/contentApi.js";
39
39
  export * from "./publish/protocol.js";
40
40
  export * from "./patchSets/PatchSets.js";
41
+ export * from "./patchSets/selectionClosure.js";
41
42
  export * from "./patchSets/schemaTypesOfPath.js";
42
43
  export * from "./proposalMerge/trees.js";
@@ -0,0 +1,23 @@
1
+ import type { PatchId } from "@valbuild/core";
2
+ import type { SerializedPatchSet } from "./PatchSets.js";
3
+ /**
4
+ * Patch sets that share a change become one: a change is never split.
5
+ *
6
+ * Shared by the two places that move changes between branches a whole set at
7
+ * a time -- the merge moving the site's conflicting pending changes
8
+ * (`changesToMove` in `@valbuild/server`) and Publish selected
9
+ * ({@link selectionClosure}) -- so that they agree on what "a whole set" is.
10
+ */
11
+ export declare function joinSharedChanges(sets: PatchId[][]): Set<PatchId>[];
12
+ /**
13
+ * What publishing `selected` publishes: every change in every patch set a
14
+ * selected change is in, sets that share a change joined
15
+ * (valbuild/val `docs/plans/remove-patch-groups.md`, Phase 4, rule 2).
16
+ *
17
+ * A patch set is changes that do not apply without each other, so a selection
18
+ * of part of one cannot be published on its own. What it pulls in is someone
19
+ * else's as often as not; the caller says whose, before anything moves.
20
+ *
21
+ * In the order of `patchIds` -- the chain's -- with nothing that is not in it.
22
+ */
23
+ export declare function selectionClosure(patchSets: SerializedPatchSet, selected: Iterable<PatchId>, patchIds: readonly PatchId[]): PatchId[];
@@ -431,6 +431,16 @@ export type PublishRequestStatus = {
431
431
  actions: ("try-again" | "discard" | "re-run-build")[];
432
432
  /** The job that failed: what Discard is pressed on. */
433
433
  job: string;
434
+ /**
435
+ * Where its changes went, on a project with proposals: off the site,
436
+ * into a proposal of their own, where they are fixed and published.
437
+ * `actions` is then empty -- that proposal's Publish is the Try again.
438
+ */
439
+ movedTo?: {
440
+ proposal: string;
441
+ displayName: string;
442
+ changes: number;
443
+ };
434
444
  } | {
435
445
  kind: "cancelled";
436
446
  } | {
@@ -59,8 +59,9 @@ export declare const SCHEMA_SOURCE_FIXES: ValidationFix[];
59
59
  /** Whether an error is a marker only the whole project can answer. */
60
60
  export declare function isSchemaSourceFixError(error: ValidationError): boolean;
61
61
  /**
62
- * The record a `keyof:check-keys` error resolves against, or null for any other
63
- * error — including a malformed marker, which
62
+ * The record a `keyof:check-keys` error resolves against -- or a gallery
63
+ * marker's (`image:check-gallery` and the rest): its gallery module -- or null
64
+ * for any other error, including a malformed marker, which
64
65
  * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
65
66
  * about.
66
67
  *
@@ -1618,7 +1618,7 @@ var ValConfig = zod.z.object({
1618
1618
  gitCommit: zod.z.string().optional(),
1619
1619
  gitBranch: zod.z.string().optional()
1620
1620
  });
1621
- var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1621
+ var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("image:check-gallery"), zod.z.literal("file:check-gallery"), zod.z.literal("video:check-gallery"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1622
1622
  var ValidationError = zod.z.object({
1623
1623
  message: zod.z.string(),
1624
1624
  value: zod.z.unknown().optional(),
@@ -2871,6 +2871,66 @@ var Api = {
2871
2871
  })])
2872
2872
  }
2873
2873
  },
2874
+ /**
2875
+ * Publish selected (`docs/plans/remove-patch-groups.md`, Phase 4), its first
2876
+ * half, on the site's own server: the changes picked in Review, already
2877
+ * closed into whole patch sets (`selectionClosure`), moved into a proposal
2878
+ * of kind `publish` and saved there -- prepared over the site's head, which
2879
+ * the proposal is based on, so a publish landing in between is refused
2880
+ * rather than reverted. The second half is that proposal's Publish, pressed
2881
+ * as any proposal's is (`publishProposal`); this server answers its checks
2882
+ * and press with what the merge moves, as the proposal's own address would.
2883
+ */
2884
+ "/publish-selected": {
2885
+ POST: {
2886
+ req: {
2887
+ body: zod.z.object({
2888
+ /** The changes to publish, whole patch sets, in any order: a move keeps the chain's. */
2889
+ patchIds: zod.z.array(PatchId).min(1),
2890
+ /** What the merged proposal is listed as. */
2891
+ displayName: zod.z.string().min(1).max(200),
2892
+ /** The press's own id: the same press sent again is the same proposal. */
2893
+ requestId: zod.z.string().regex(/^[A-Za-z0-9_-]{1,100}$/)
2894
+ }),
2895
+ cookies: {
2896
+ val_session: zod.z.string().optional()
2897
+ }
2898
+ },
2899
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2900
+ status: zod.z.literal(400),
2901
+ json: GenericError
2902
+ }),
2903
+ /**
2904
+ * Refused: the site moved (`headMoved`: pressing again prepares over
2905
+ * the new head), a change went, or this deployment cannot.
2906
+ */
2907
+ zod.z.object({
2908
+ status: zod.z.literal(409),
2909
+ json: GenericError.extend({
2910
+ headMoved: zod.z.literal(true).optional()
2911
+ })
2912
+ }), zod.z.object({
2913
+ status: zod.z.literal(500),
2914
+ json: GenericError
2915
+ }),
2916
+ /**
2917
+ * The proposal was made and its changes moved into it, and the save
2918
+ * there failed: they are safe in it, which `proposal` names.
2919
+ */
2920
+ zod.z.object({
2921
+ status: zod.z.literal(502),
2922
+ json: GenericError.extend({
2923
+ proposal: zod.z.string()
2924
+ })
2925
+ }), zod.z.object({
2926
+ status: zod.z.literal(200),
2927
+ json: zod.z.object({
2928
+ /** The proposal to publish next. */
2929
+ proposal: zod.z.string()
2930
+ })
2931
+ })])
2932
+ }
2933
+ },
2874
2934
  "/save": {
2875
2935
  POST: {
2876
2936
  req: {
@@ -4790,7 +4850,7 @@ function declaredLocales(snapshot) {
4790
4850
  * these has to resolve all of them, and the two that leaked did so by adding a
4791
4851
  * fix here and not to a condition somewhere else.
4792
4852
  */
4793
- var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "router:check-route", "locale:check-locale", "record:fill-keys"];
4853
+ var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "image:check-gallery", "file:check-gallery", "video:check-gallery", "router:check-route", "locale:check-locale", "record:fill-keys"];
4794
4854
 
4795
4855
  /** Whether an error is a marker only the whole project can answer. */
4796
4856
  function isSchemaSourceFixError(error) {
@@ -4801,8 +4861,9 @@ function isSchemaSourceFixError(error) {
4801
4861
  }
4802
4862
 
4803
4863
  /**
4804
- * The record a `keyof:check-keys` error resolves against, or null for any other
4805
- * error — including a malformed marker, which
4864
+ * The record a `keyof:check-keys` error resolves against -- or a gallery
4865
+ * marker's (`image:check-gallery` and the rest): its gallery module -- or null
4866
+ * for any other error, including a malformed marker, which
4806
4867
  * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
4807
4868
  * about.
4808
4869
  *
@@ -4812,6 +4873,12 @@ function isSchemaSourceFixError(error) {
4812
4873
  */
4813
4874
  function keyOfRecordPath(error) {
4814
4875
  var _error$fixes2;
4876
+ // A gallery-backed field's marker reads its gallery the same way: the
4877
+ // record whose keys say whether the field's path is there.
4878
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4879
+ if (gallery !== null) {
4880
+ return gallery.gallery;
4881
+ }
4815
4882
  if (!((_error$fixes2 = error.fixes) !== null && _error$fixes2 !== void 0 ? _error$fixes2 : []).includes("keyof:check-keys")) {
4816
4883
  return null;
4817
4884
  }
@@ -4833,6 +4900,37 @@ function keyOfRecordPath(error) {
4833
4900
  function resolveSchemaSourceFixForError(error, snapshot) {
4834
4901
  var _error$fixes3;
4835
4902
  var fixes = (_error$fixes3 = error.fixes) !== null && _error$fixes3 !== void 0 ? _error$fixes3 : [];
4903
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4904
+ if (gallery !== null) {
4905
+ // The gallery as it is now -- with what a pending change, a save in a
4906
+ // proposal or a content-only publish added -- not as the schema was built.
4907
+ var entries = getKeyOfRecordAt(gallery.gallery, snapshot).source;
4908
+ var has = getSitemapTree._typeof(entries) === "object" && entries !== null && !Array.isArray(entries) && core.Internal.media.galleryKeyOf(gallery.path, function (key) {
4909
+ return key in entries;
4910
+ }) !== null;
4911
+ if (has) {
4912
+ return {
4913
+ status: "resolved"
4914
+ };
4915
+ }
4916
+ return {
4917
+ status: "remaining",
4918
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4919
+ value: gallery.source,
4920
+ fixes: undefined
4921
+ })
4922
+ };
4923
+ }
4924
+ if (fixes.includes("image:check-gallery") || fixes.includes("file:check-gallery") || fixes.includes("video:check-gallery")) {
4925
+ return {
4926
+ status: "remaining",
4927
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4928
+ message: "".concat(error.message, " The gallery could not be read from this error. ").concat(TYPE_ERROR_MESSAGE),
4929
+ typeError: true,
4930
+ fixes: undefined
4931
+ })
4932
+ };
4933
+ }
4836
4934
  if (fixes.includes("keyof:check-keys")) {
4837
4935
  if (!error.value) {
4838
4936
  return {
@@ -6124,6 +6222,9 @@ function isSkippableFixCode(fix) {
6124
6222
  // also took them off the publish gate, so a broken key published unseen.
6125
6223
  return false;
6126
6224
  case "keyof:check-keys":
6225
+ case "image:check-gallery":
6226
+ case "file:check-gallery":
6227
+ case "video:check-gallery":
6127
6228
  case "router:check-route":
6128
6229
  case "locale:check-locale":
6129
6230
  case "record:fill-keys":
@@ -7757,7 +7858,12 @@ var requestStatus = zod.z.union([zod.z.object({
7757
7858
  kind: zod.z.literal("failed"),
7758
7859
  message: zod.z.string(),
7759
7860
  actions: zod.z.array(zod.z["enum"](["try-again", "discard", "re-run-build"])),
7760
- job: zod.z.string()
7861
+ job: zod.z.string(),
7862
+ movedTo: zod.z.object({
7863
+ proposal: zod.z.string(),
7864
+ displayName: zod.z.string(),
7865
+ changes: zod.z.number()
7866
+ }).optional()
7761
7867
  }), zod.z.object({
7762
7868
  kind: zod.z.literal("cancelled")
7763
7869
  }), zod.z.object({
@@ -8276,6 +8382,126 @@ var PatchSets = /*#__PURE__*/function () {
8276
8382
  }]);
8277
8383
  }();
8278
8384
 
8385
+ /**
8386
+ * Patch sets that share a change become one: a change is never split.
8387
+ *
8388
+ * Shared by the two places that move changes between branches a whole set at
8389
+ * a time -- the merge moving the site's conflicting pending changes
8390
+ * (`changesToMove` in `@valbuild/server`) and Publish selected
8391
+ * ({@link selectionClosure}) -- so that they agree on what "a whole set" is.
8392
+ */
8393
+ function joinSharedChanges(sets) {
8394
+ var groups = [];
8395
+ var _iterator = getSitemapTree._createForOfIteratorHelper(sets),
8396
+ _step;
8397
+ try {
8398
+ var _loop = function _loop() {
8399
+ var set = _step.value;
8400
+ var joined = new Set(set);
8401
+ var _iterator2 = getSitemapTree._createForOfIteratorHelper(groups.filter(function (g) {
8402
+ return set.some(function (id) {
8403
+ return g.has(id);
8404
+ });
8405
+ })),
8406
+ _step2;
8407
+ try {
8408
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
8409
+ var group = _step2.value;
8410
+ var _iterator3 = getSitemapTree._createForOfIteratorHelper(group),
8411
+ _step3;
8412
+ try {
8413
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
8414
+ var id = _step3.value;
8415
+ joined.add(id);
8416
+ }
8417
+ } catch (err) {
8418
+ _iterator3.e(err);
8419
+ } finally {
8420
+ _iterator3.f();
8421
+ }
8422
+ groups.splice(groups.indexOf(group), 1);
8423
+ }
8424
+ } catch (err) {
8425
+ _iterator2.e(err);
8426
+ } finally {
8427
+ _iterator2.f();
8428
+ }
8429
+ groups.push(joined);
8430
+ };
8431
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8432
+ _loop();
8433
+ }
8434
+ } catch (err) {
8435
+ _iterator.e(err);
8436
+ } finally {
8437
+ _iterator.f();
8438
+ }
8439
+ return groups;
8440
+ }
8441
+
8442
+ /**
8443
+ * What publishing `selected` publishes: every change in every patch set a
8444
+ * selected change is in, sets that share a change joined
8445
+ * (valbuild/val `docs/plans/remove-patch-groups.md`, Phase 4, rule 2).
8446
+ *
8447
+ * A patch set is changes that do not apply without each other, so a selection
8448
+ * of part of one cannot be published on its own. What it pulls in is someone
8449
+ * else's as often as not; the caller says whose, before anything moves.
8450
+ *
8451
+ * In the order of `patchIds` -- the chain's -- with nothing that is not in it.
8452
+ */
8453
+ function selectionClosure(patchSets, selected, patchIds) {
8454
+ var wanted = new Set(selected);
8455
+ var closed = new Set();
8456
+ var _iterator4 = getSitemapTree._createForOfIteratorHelper(joinSharedChanges(patchSets.map(function (set) {
8457
+ return set.patches.map(function (patch) {
8458
+ return patch.patchId;
8459
+ });
8460
+ }))),
8461
+ _step4;
8462
+ try {
8463
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
8464
+ var group = _step4.value;
8465
+ if (_toConsumableArray(group).some(function (id) {
8466
+ return wanted.has(id);
8467
+ })) {
8468
+ var _iterator6 = getSitemapTree._createForOfIteratorHelper(group),
8469
+ _step6;
8470
+ try {
8471
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
8472
+ var id = _step6.value;
8473
+ closed.add(id);
8474
+ }
8475
+ } catch (err) {
8476
+ _iterator6.e(err);
8477
+ } finally {
8478
+ _iterator6.f();
8479
+ }
8480
+ }
8481
+ }
8482
+ // A selected change no patch set knows of is its own set.
8483
+ } catch (err) {
8484
+ _iterator4.e(err);
8485
+ } finally {
8486
+ _iterator4.f();
8487
+ }
8488
+ var _iterator5 = getSitemapTree._createForOfIteratorHelper(wanted),
8489
+ _step5;
8490
+ try {
8491
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
8492
+ var _id = _step5.value;
8493
+ closed.add(_id);
8494
+ }
8495
+ } catch (err) {
8496
+ _iterator5.e(err);
8497
+ } finally {
8498
+ _iterator5.f();
8499
+ }
8500
+ return patchIds.filter(function (id) {
8501
+ return closed.has(id);
8502
+ });
8503
+ }
8504
+
8279
8505
  /**
8280
8506
  * Update from the site, for a proposal with code (valbuild/home
8281
8507
  * `docs/hosted-publishing.md` step 8.2, slice 1b): what content hands over,
@@ -8486,6 +8712,7 @@ exports.isMimeTypeAccepted = isMimeTypeAccepted;
8486
8712
  exports.isRemoteSchema = isRemoteSchema;
8487
8713
  exports.isSchemaSourceFixError = isSchemaSourceFixError;
8488
8714
  exports.isSkippedSource = isSkippedSource;
8715
+ exports.joinSharedChanges = joinSharedChanges;
8489
8716
  exports.keyOfRecordPath = keyOfRecordPath;
8490
8717
  exports.levenshtein = levenshtein;
8491
8718
  exports.localeAt = localeAt;
@@ -8523,6 +8750,7 @@ exports.richTextToRemirror = richTextToRemirror;
8523
8750
  exports.safeParsePatch = safeParsePatch;
8524
8751
  exports.schemaErrorsOf = schemaErrorsOf;
8525
8752
  exports.schemaTypesOfPath = schemaTypesOfPath;
8753
+ exports.selectionClosure = selectionClosure;
8526
8754
  exports.sideFiles = sideFiles;
8527
8755
  exports.sideSource = sideSource;
8528
8756
  exports.sourcePathOfChild = sourcePathOfChild;
@@ -1618,7 +1618,7 @@ var ValConfig = zod.z.object({
1618
1618
  gitCommit: zod.z.string().optional(),
1619
1619
  gitBranch: zod.z.string().optional()
1620
1620
  });
1621
- var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1621
+ var ValidationFixZ = zod.z.union([zod.z.literal("image:add-metadata"), zod.z.literal("image:check-metadata"), zod.z.literal("image:check-remote"), zod.z.literal("image:upload-remote"), zod.z.literal("image:download-remote"), zod.z.literal("images:check-remote"), zod.z.literal("images:upload-remote"), zod.z.literal("file:add-metadata"), zod.z.literal("video:add-metadata"), zod.z.literal("video:upload-remote"), zod.z.literal("video:download-remote"), zod.z.literal("videos:add-metadata"), zod.z.literal("videos:check-remote"), zod.z.literal("videos:upload-remote"), zod.z.literal("file:check-metadata"), zod.z.literal("file:check-remote"), zod.z.literal("file:upload-remote"), zod.z.literal("file:download-remote"), zod.z.literal("files:check-remote"), zod.z.literal("files:upload-remote"), zod.z.literal("keyof:check-keys"), zod.z.literal("image:check-gallery"), zod.z.literal("file:check-gallery"), zod.z.literal("video:check-gallery"), zod.z.literal("router:check-route"), zod.z.literal("locale:check-locale"), zod.z.literal("record:fill-keys"), zod.z.literal("images:check-unique-folder"), zod.z.literal("files:check-unique-folder"), zod.z.literal("images:check-all-files"), zod.z.literal("files:check-all-files"), zod.z.literal("videos:check-unique-folder"), zod.z.literal("videos:check-all-files"), zod.z.literal("jsonValues:extract-entry"), zod.z.literal("view:check-module")]);
1622
1622
  var ValidationError = zod.z.object({
1623
1623
  message: zod.z.string(),
1624
1624
  value: zod.z.unknown().optional(),
@@ -2871,6 +2871,66 @@ var Api = {
2871
2871
  })])
2872
2872
  }
2873
2873
  },
2874
+ /**
2875
+ * Publish selected (`docs/plans/remove-patch-groups.md`, Phase 4), its first
2876
+ * half, on the site's own server: the changes picked in Review, already
2877
+ * closed into whole patch sets (`selectionClosure`), moved into a proposal
2878
+ * of kind `publish` and saved there -- prepared over the site's head, which
2879
+ * the proposal is based on, so a publish landing in between is refused
2880
+ * rather than reverted. The second half is that proposal's Publish, pressed
2881
+ * as any proposal's is (`publishProposal`); this server answers its checks
2882
+ * and press with what the merge moves, as the proposal's own address would.
2883
+ */
2884
+ "/publish-selected": {
2885
+ POST: {
2886
+ req: {
2887
+ body: zod.z.object({
2888
+ /** The changes to publish, whole patch sets, in any order: a move keeps the chain's. */
2889
+ patchIds: zod.z.array(PatchId).min(1),
2890
+ /** What the merged proposal is listed as. */
2891
+ displayName: zod.z.string().min(1).max(200),
2892
+ /** The press's own id: the same press sent again is the same proposal. */
2893
+ requestId: zod.z.string().regex(/^[A-Za-z0-9_-]{1,100}$/)
2894
+ }),
2895
+ cookies: {
2896
+ val_session: zod.z.string().optional()
2897
+ }
2898
+ },
2899
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2900
+ status: zod.z.literal(400),
2901
+ json: GenericError
2902
+ }),
2903
+ /**
2904
+ * Refused: the site moved (`headMoved`: pressing again prepares over
2905
+ * the new head), a change went, or this deployment cannot.
2906
+ */
2907
+ zod.z.object({
2908
+ status: zod.z.literal(409),
2909
+ json: GenericError.extend({
2910
+ headMoved: zod.z.literal(true).optional()
2911
+ })
2912
+ }), zod.z.object({
2913
+ status: zod.z.literal(500),
2914
+ json: GenericError
2915
+ }),
2916
+ /**
2917
+ * The proposal was made and its changes moved into it, and the save
2918
+ * there failed: they are safe in it, which `proposal` names.
2919
+ */
2920
+ zod.z.object({
2921
+ status: zod.z.literal(502),
2922
+ json: GenericError.extend({
2923
+ proposal: zod.z.string()
2924
+ })
2925
+ }), zod.z.object({
2926
+ status: zod.z.literal(200),
2927
+ json: zod.z.object({
2928
+ /** The proposal to publish next. */
2929
+ proposal: zod.z.string()
2930
+ })
2931
+ })])
2932
+ }
2933
+ },
2874
2934
  "/save": {
2875
2935
  POST: {
2876
2936
  req: {
@@ -4790,7 +4850,7 @@ function declaredLocales(snapshot) {
4790
4850
  * these has to resolve all of them, and the two that leaked did so by adding a
4791
4851
  * fix here and not to a condition somewhere else.
4792
4852
  */
4793
- var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "router:check-route", "locale:check-locale", "record:fill-keys"];
4853
+ var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "image:check-gallery", "file:check-gallery", "video:check-gallery", "router:check-route", "locale:check-locale", "record:fill-keys"];
4794
4854
 
4795
4855
  /** Whether an error is a marker only the whole project can answer. */
4796
4856
  function isSchemaSourceFixError(error) {
@@ -4801,8 +4861,9 @@ function isSchemaSourceFixError(error) {
4801
4861
  }
4802
4862
 
4803
4863
  /**
4804
- * The record a `keyof:check-keys` error resolves against, or null for any other
4805
- * error — including a malformed marker, which
4864
+ * The record a `keyof:check-keys` error resolves against -- or a gallery
4865
+ * marker's (`image:check-gallery` and the rest): its gallery module -- or null
4866
+ * for any other error, including a malformed marker, which
4806
4867
  * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
4807
4868
  * about.
4808
4869
  *
@@ -4812,6 +4873,12 @@ function isSchemaSourceFixError(error) {
4812
4873
  */
4813
4874
  function keyOfRecordPath(error) {
4814
4875
  var _error$fixes2;
4876
+ // A gallery-backed field's marker reads its gallery the same way: the
4877
+ // record whose keys say whether the field's path is there.
4878
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4879
+ if (gallery !== null) {
4880
+ return gallery.gallery;
4881
+ }
4815
4882
  if (!((_error$fixes2 = error.fixes) !== null && _error$fixes2 !== void 0 ? _error$fixes2 : []).includes("keyof:check-keys")) {
4816
4883
  return null;
4817
4884
  }
@@ -4833,6 +4900,37 @@ function keyOfRecordPath(error) {
4833
4900
  function resolveSchemaSourceFixForError(error, snapshot) {
4834
4901
  var _error$fixes3;
4835
4902
  var fixes = (_error$fixes3 = error.fixes) !== null && _error$fixes3 !== void 0 ? _error$fixes3 : [];
4903
+ var gallery = core.Internal.media.galleryMarkerOf(error);
4904
+ if (gallery !== null) {
4905
+ // The gallery as it is now -- with what a pending change, a save in a
4906
+ // proposal or a content-only publish added -- not as the schema was built.
4907
+ var entries = getKeyOfRecordAt(gallery.gallery, snapshot).source;
4908
+ var has = getSitemapTree._typeof(entries) === "object" && entries !== null && !Array.isArray(entries) && core.Internal.media.galleryKeyOf(gallery.path, function (key) {
4909
+ return key in entries;
4910
+ }) !== null;
4911
+ if (has) {
4912
+ return {
4913
+ status: "resolved"
4914
+ };
4915
+ }
4916
+ return {
4917
+ status: "remaining",
4918
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4919
+ value: gallery.source,
4920
+ fixes: undefined
4921
+ })
4922
+ };
4923
+ }
4924
+ if (fixes.includes("image:check-gallery") || fixes.includes("file:check-gallery") || fixes.includes("video:check-gallery")) {
4925
+ return {
4926
+ status: "remaining",
4927
+ error: getSitemapTree._objectSpread2(getSitemapTree._objectSpread2({}, error), {}, {
4928
+ message: "".concat(error.message, " The gallery could not be read from this error. ").concat(TYPE_ERROR_MESSAGE),
4929
+ typeError: true,
4930
+ fixes: undefined
4931
+ })
4932
+ };
4933
+ }
4836
4934
  if (fixes.includes("keyof:check-keys")) {
4837
4935
  if (!error.value) {
4838
4936
  return {
@@ -6124,6 +6222,9 @@ function isSkippableFixCode(fix) {
6124
6222
  // also took them off the publish gate, so a broken key published unseen.
6125
6223
  return false;
6126
6224
  case "keyof:check-keys":
6225
+ case "image:check-gallery":
6226
+ case "file:check-gallery":
6227
+ case "video:check-gallery":
6127
6228
  case "router:check-route":
6128
6229
  case "locale:check-locale":
6129
6230
  case "record:fill-keys":
@@ -7757,7 +7858,12 @@ var requestStatus = zod.z.union([zod.z.object({
7757
7858
  kind: zod.z.literal("failed"),
7758
7859
  message: zod.z.string(),
7759
7860
  actions: zod.z.array(zod.z["enum"](["try-again", "discard", "re-run-build"])),
7760
- job: zod.z.string()
7861
+ job: zod.z.string(),
7862
+ movedTo: zod.z.object({
7863
+ proposal: zod.z.string(),
7864
+ displayName: zod.z.string(),
7865
+ changes: zod.z.number()
7866
+ }).optional()
7761
7867
  }), zod.z.object({
7762
7868
  kind: zod.z.literal("cancelled")
7763
7869
  }), zod.z.object({
@@ -8276,6 +8382,126 @@ var PatchSets = /*#__PURE__*/function () {
8276
8382
  }]);
8277
8383
  }();
8278
8384
 
8385
+ /**
8386
+ * Patch sets that share a change become one: a change is never split.
8387
+ *
8388
+ * Shared by the two places that move changes between branches a whole set at
8389
+ * a time -- the merge moving the site's conflicting pending changes
8390
+ * (`changesToMove` in `@valbuild/server`) and Publish selected
8391
+ * ({@link selectionClosure}) -- so that they agree on what "a whole set" is.
8392
+ */
8393
+ function joinSharedChanges(sets) {
8394
+ var groups = [];
8395
+ var _iterator = getSitemapTree._createForOfIteratorHelper(sets),
8396
+ _step;
8397
+ try {
8398
+ var _loop = function _loop() {
8399
+ var set = _step.value;
8400
+ var joined = new Set(set);
8401
+ var _iterator2 = getSitemapTree._createForOfIteratorHelper(groups.filter(function (g) {
8402
+ return set.some(function (id) {
8403
+ return g.has(id);
8404
+ });
8405
+ })),
8406
+ _step2;
8407
+ try {
8408
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
8409
+ var group = _step2.value;
8410
+ var _iterator3 = getSitemapTree._createForOfIteratorHelper(group),
8411
+ _step3;
8412
+ try {
8413
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
8414
+ var id = _step3.value;
8415
+ joined.add(id);
8416
+ }
8417
+ } catch (err) {
8418
+ _iterator3.e(err);
8419
+ } finally {
8420
+ _iterator3.f();
8421
+ }
8422
+ groups.splice(groups.indexOf(group), 1);
8423
+ }
8424
+ } catch (err) {
8425
+ _iterator2.e(err);
8426
+ } finally {
8427
+ _iterator2.f();
8428
+ }
8429
+ groups.push(joined);
8430
+ };
8431
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8432
+ _loop();
8433
+ }
8434
+ } catch (err) {
8435
+ _iterator.e(err);
8436
+ } finally {
8437
+ _iterator.f();
8438
+ }
8439
+ return groups;
8440
+ }
8441
+
8442
+ /**
8443
+ * What publishing `selected` publishes: every change in every patch set a
8444
+ * selected change is in, sets that share a change joined
8445
+ * (valbuild/val `docs/plans/remove-patch-groups.md`, Phase 4, rule 2).
8446
+ *
8447
+ * A patch set is changes that do not apply without each other, so a selection
8448
+ * of part of one cannot be published on its own. What it pulls in is someone
8449
+ * else's as often as not; the caller says whose, before anything moves.
8450
+ *
8451
+ * In the order of `patchIds` -- the chain's -- with nothing that is not in it.
8452
+ */
8453
+ function selectionClosure(patchSets, selected, patchIds) {
8454
+ var wanted = new Set(selected);
8455
+ var closed = new Set();
8456
+ var _iterator4 = getSitemapTree._createForOfIteratorHelper(joinSharedChanges(patchSets.map(function (set) {
8457
+ return set.patches.map(function (patch) {
8458
+ return patch.patchId;
8459
+ });
8460
+ }))),
8461
+ _step4;
8462
+ try {
8463
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
8464
+ var group = _step4.value;
8465
+ if (_toConsumableArray(group).some(function (id) {
8466
+ return wanted.has(id);
8467
+ })) {
8468
+ var _iterator6 = getSitemapTree._createForOfIteratorHelper(group),
8469
+ _step6;
8470
+ try {
8471
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
8472
+ var id = _step6.value;
8473
+ closed.add(id);
8474
+ }
8475
+ } catch (err) {
8476
+ _iterator6.e(err);
8477
+ } finally {
8478
+ _iterator6.f();
8479
+ }
8480
+ }
8481
+ }
8482
+ // A selected change no patch set knows of is its own set.
8483
+ } catch (err) {
8484
+ _iterator4.e(err);
8485
+ } finally {
8486
+ _iterator4.f();
8487
+ }
8488
+ var _iterator5 = getSitemapTree._createForOfIteratorHelper(wanted),
8489
+ _step5;
8490
+ try {
8491
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
8492
+ var _id = _step5.value;
8493
+ closed.add(_id);
8494
+ }
8495
+ } catch (err) {
8496
+ _iterator5.e(err);
8497
+ } finally {
8498
+ _iterator5.f();
8499
+ }
8500
+ return patchIds.filter(function (id) {
8501
+ return closed.has(id);
8502
+ });
8503
+ }
8504
+
8279
8505
  /**
8280
8506
  * Update from the site, for a proposal with code (valbuild/home
8281
8507
  * `docs/hosted-publishing.md` step 8.2, slice 1b): what content hands over,
@@ -8486,6 +8712,7 @@ exports.isMimeTypeAccepted = isMimeTypeAccepted;
8486
8712
  exports.isRemoteSchema = isRemoteSchema;
8487
8713
  exports.isSchemaSourceFixError = isSchemaSourceFixError;
8488
8714
  exports.isSkippedSource = isSkippedSource;
8715
+ exports.joinSharedChanges = joinSharedChanges;
8489
8716
  exports.keyOfRecordPath = keyOfRecordPath;
8490
8717
  exports.levenshtein = levenshtein;
8491
8718
  exports.localeAt = localeAt;
@@ -8523,6 +8750,7 @@ exports.richTextToRemirror = richTextToRemirror;
8523
8750
  exports.safeParsePatch = safeParsePatch;
8524
8751
  exports.schemaErrorsOf = schemaErrorsOf;
8525
8752
  exports.schemaTypesOfPath = schemaTypesOfPath;
8753
+ exports.selectionClosure = selectionClosure;
8526
8754
  exports.sideFiles = sideFiles;
8527
8755
  exports.sideSource = sideSource;
8528
8756
  exports.sourcePathOfChild = sourcePathOfChild;
@@ -1611,7 +1611,7 @@ var ValConfig = z.object({
1611
1611
  gitCommit: z.string().optional(),
1612
1612
  gitBranch: z.string().optional()
1613
1613
  });
1614
- var ValidationFixZ = z.union([z.literal("image:add-metadata"), z.literal("image:check-metadata"), z.literal("image:check-remote"), z.literal("image:upload-remote"), z.literal("image:download-remote"), z.literal("images:check-remote"), z.literal("images:upload-remote"), z.literal("file:add-metadata"), z.literal("video:add-metadata"), z.literal("video:upload-remote"), z.literal("video:download-remote"), z.literal("videos:add-metadata"), z.literal("videos:check-remote"), z.literal("videos:upload-remote"), z.literal("file:check-metadata"), z.literal("file:check-remote"), z.literal("file:upload-remote"), z.literal("file:download-remote"), z.literal("files:check-remote"), z.literal("files:upload-remote"), z.literal("keyof:check-keys"), z.literal("router:check-route"), z.literal("locale:check-locale"), z.literal("record:fill-keys"), z.literal("images:check-unique-folder"), z.literal("files:check-unique-folder"), z.literal("images:check-all-files"), z.literal("files:check-all-files"), z.literal("videos:check-unique-folder"), z.literal("videos:check-all-files"), z.literal("jsonValues:extract-entry"), z.literal("view:check-module")]);
1614
+ var ValidationFixZ = z.union([z.literal("image:add-metadata"), z.literal("image:check-metadata"), z.literal("image:check-remote"), z.literal("image:upload-remote"), z.literal("image:download-remote"), z.literal("images:check-remote"), z.literal("images:upload-remote"), z.literal("file:add-metadata"), z.literal("video:add-metadata"), z.literal("video:upload-remote"), z.literal("video:download-remote"), z.literal("videos:add-metadata"), z.literal("videos:check-remote"), z.literal("videos:upload-remote"), z.literal("file:check-metadata"), z.literal("file:check-remote"), z.literal("file:upload-remote"), z.literal("file:download-remote"), z.literal("files:check-remote"), z.literal("files:upload-remote"), z.literal("keyof:check-keys"), z.literal("image:check-gallery"), z.literal("file:check-gallery"), z.literal("video:check-gallery"), z.literal("router:check-route"), z.literal("locale:check-locale"), z.literal("record:fill-keys"), z.literal("images:check-unique-folder"), z.literal("files:check-unique-folder"), z.literal("images:check-all-files"), z.literal("files:check-all-files"), z.literal("videos:check-unique-folder"), z.literal("videos:check-all-files"), z.literal("jsonValues:extract-entry"), z.literal("view:check-module")]);
1615
1615
  var ValidationError = z.object({
1616
1616
  message: z.string(),
1617
1617
  value: z.unknown().optional(),
@@ -2864,6 +2864,66 @@ var Api = {
2864
2864
  })])
2865
2865
  }
2866
2866
  },
2867
+ /**
2868
+ * Publish selected (`docs/plans/remove-patch-groups.md`, Phase 4), its first
2869
+ * half, on the site's own server: the changes picked in Review, already
2870
+ * closed into whole patch sets (`selectionClosure`), moved into a proposal
2871
+ * of kind `publish` and saved there -- prepared over the site's head, which
2872
+ * the proposal is based on, so a publish landing in between is refused
2873
+ * rather than reverted. The second half is that proposal's Publish, pressed
2874
+ * as any proposal's is (`publishProposal`); this server answers its checks
2875
+ * and press with what the merge moves, as the proposal's own address would.
2876
+ */
2877
+ "/publish-selected": {
2878
+ POST: {
2879
+ req: {
2880
+ body: z.object({
2881
+ /** The changes to publish, whole patch sets, in any order: a move keeps the chain's. */
2882
+ patchIds: z.array(PatchId).min(1),
2883
+ /** What the merged proposal is listed as. */
2884
+ displayName: z.string().min(1).max(200),
2885
+ /** The press's own id: the same press sent again is the same proposal. */
2886
+ requestId: z.string().regex(/^[A-Za-z0-9_-]{1,100}$/)
2887
+ }),
2888
+ cookies: {
2889
+ val_session: z.string().optional()
2890
+ }
2891
+ },
2892
+ res: z.union([unauthorizedResponse, z.object({
2893
+ status: z.literal(400),
2894
+ json: GenericError
2895
+ }),
2896
+ /**
2897
+ * Refused: the site moved (`headMoved`: pressing again prepares over
2898
+ * the new head), a change went, or this deployment cannot.
2899
+ */
2900
+ z.object({
2901
+ status: z.literal(409),
2902
+ json: GenericError.extend({
2903
+ headMoved: z.literal(true).optional()
2904
+ })
2905
+ }), z.object({
2906
+ status: z.literal(500),
2907
+ json: GenericError
2908
+ }),
2909
+ /**
2910
+ * The proposal was made and its changes moved into it, and the save
2911
+ * there failed: they are safe in it, which `proposal` names.
2912
+ */
2913
+ z.object({
2914
+ status: z.literal(502),
2915
+ json: GenericError.extend({
2916
+ proposal: z.string()
2917
+ })
2918
+ }), z.object({
2919
+ status: z.literal(200),
2920
+ json: z.object({
2921
+ /** The proposal to publish next. */
2922
+ proposal: z.string()
2923
+ })
2924
+ })])
2925
+ }
2926
+ },
2867
2927
  "/save": {
2868
2928
  POST: {
2869
2929
  req: {
@@ -4783,7 +4843,7 @@ function declaredLocales(snapshot) {
4783
4843
  * these has to resolve all of them, and the two that leaked did so by adding a
4784
4844
  * fix here and not to a condition somewhere else.
4785
4845
  */
4786
- var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "router:check-route", "locale:check-locale", "record:fill-keys"];
4846
+ var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "image:check-gallery", "file:check-gallery", "video:check-gallery", "router:check-route", "locale:check-locale", "record:fill-keys"];
4787
4847
 
4788
4848
  /** Whether an error is a marker only the whole project can answer. */
4789
4849
  function isSchemaSourceFixError(error) {
@@ -4794,8 +4854,9 @@ function isSchemaSourceFixError(error) {
4794
4854
  }
4795
4855
 
4796
4856
  /**
4797
- * The record a `keyof:check-keys` error resolves against, or null for any other
4798
- * error — including a malformed marker, which
4857
+ * The record a `keyof:check-keys` error resolves against -- or a gallery
4858
+ * marker's (`image:check-gallery` and the rest): its gallery module -- or null
4859
+ * for any other error, including a malformed marker, which
4799
4860
  * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
4800
4861
  * about.
4801
4862
  *
@@ -4805,6 +4866,12 @@ function isSchemaSourceFixError(error) {
4805
4866
  */
4806
4867
  function keyOfRecordPath(error) {
4807
4868
  var _error$fixes2;
4869
+ // A gallery-backed field's marker reads its gallery the same way: the
4870
+ // record whose keys say whether the field's path is there.
4871
+ var gallery = Internal.media.galleryMarkerOf(error);
4872
+ if (gallery !== null) {
4873
+ return gallery.gallery;
4874
+ }
4808
4875
  if (!((_error$fixes2 = error.fixes) !== null && _error$fixes2 !== void 0 ? _error$fixes2 : []).includes("keyof:check-keys")) {
4809
4876
  return null;
4810
4877
  }
@@ -4826,6 +4893,37 @@ function keyOfRecordPath(error) {
4826
4893
  function resolveSchemaSourceFixForError(error, snapshot) {
4827
4894
  var _error$fixes3;
4828
4895
  var fixes = (_error$fixes3 = error.fixes) !== null && _error$fixes3 !== void 0 ? _error$fixes3 : [];
4896
+ var gallery = Internal.media.galleryMarkerOf(error);
4897
+ if (gallery !== null) {
4898
+ // The gallery as it is now -- with what a pending change, a save in a
4899
+ // proposal or a content-only publish added -- not as the schema was built.
4900
+ var entries = getKeyOfRecordAt(gallery.gallery, snapshot).source;
4901
+ var has = _typeof(entries) === "object" && entries !== null && !Array.isArray(entries) && Internal.media.galleryKeyOf(gallery.path, function (key) {
4902
+ return key in entries;
4903
+ }) !== null;
4904
+ if (has) {
4905
+ return {
4906
+ status: "resolved"
4907
+ };
4908
+ }
4909
+ return {
4910
+ status: "remaining",
4911
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4912
+ value: gallery.source,
4913
+ fixes: undefined
4914
+ })
4915
+ };
4916
+ }
4917
+ if (fixes.includes("image:check-gallery") || fixes.includes("file:check-gallery") || fixes.includes("video:check-gallery")) {
4918
+ return {
4919
+ status: "remaining",
4920
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4921
+ message: "".concat(error.message, " The gallery could not be read from this error. ").concat(TYPE_ERROR_MESSAGE),
4922
+ typeError: true,
4923
+ fixes: undefined
4924
+ })
4925
+ };
4926
+ }
4829
4927
  if (fixes.includes("keyof:check-keys")) {
4830
4928
  if (!error.value) {
4831
4929
  return {
@@ -6117,6 +6215,9 @@ function isSkippableFixCode(fix) {
6117
6215
  // also took them off the publish gate, so a broken key published unseen.
6118
6216
  return false;
6119
6217
  case "keyof:check-keys":
6218
+ case "image:check-gallery":
6219
+ case "file:check-gallery":
6220
+ case "video:check-gallery":
6120
6221
  case "router:check-route":
6121
6222
  case "locale:check-locale":
6122
6223
  case "record:fill-keys":
@@ -7750,7 +7851,12 @@ var requestStatus = z.union([z.object({
7750
7851
  kind: z.literal("failed"),
7751
7852
  message: z.string(),
7752
7853
  actions: z.array(z["enum"](["try-again", "discard", "re-run-build"])),
7753
- job: z.string()
7854
+ job: z.string(),
7855
+ movedTo: z.object({
7856
+ proposal: z.string(),
7857
+ displayName: z.string(),
7858
+ changes: z.number()
7859
+ }).optional()
7754
7860
  }), z.object({
7755
7861
  kind: z.literal("cancelled")
7756
7862
  }), z.object({
@@ -8269,6 +8375,126 @@ var PatchSets = /*#__PURE__*/function () {
8269
8375
  }]);
8270
8376
  }();
8271
8377
 
8378
+ /**
8379
+ * Patch sets that share a change become one: a change is never split.
8380
+ *
8381
+ * Shared by the two places that move changes between branches a whole set at
8382
+ * a time -- the merge moving the site's conflicting pending changes
8383
+ * (`changesToMove` in `@valbuild/server`) and Publish selected
8384
+ * ({@link selectionClosure}) -- so that they agree on what "a whole set" is.
8385
+ */
8386
+ function joinSharedChanges(sets) {
8387
+ var groups = [];
8388
+ var _iterator = _createForOfIteratorHelper(sets),
8389
+ _step;
8390
+ try {
8391
+ var _loop = function _loop() {
8392
+ var set = _step.value;
8393
+ var joined = new Set(set);
8394
+ var _iterator2 = _createForOfIteratorHelper(groups.filter(function (g) {
8395
+ return set.some(function (id) {
8396
+ return g.has(id);
8397
+ });
8398
+ })),
8399
+ _step2;
8400
+ try {
8401
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
8402
+ var group = _step2.value;
8403
+ var _iterator3 = _createForOfIteratorHelper(group),
8404
+ _step3;
8405
+ try {
8406
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
8407
+ var id = _step3.value;
8408
+ joined.add(id);
8409
+ }
8410
+ } catch (err) {
8411
+ _iterator3.e(err);
8412
+ } finally {
8413
+ _iterator3.f();
8414
+ }
8415
+ groups.splice(groups.indexOf(group), 1);
8416
+ }
8417
+ } catch (err) {
8418
+ _iterator2.e(err);
8419
+ } finally {
8420
+ _iterator2.f();
8421
+ }
8422
+ groups.push(joined);
8423
+ };
8424
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
8425
+ _loop();
8426
+ }
8427
+ } catch (err) {
8428
+ _iterator.e(err);
8429
+ } finally {
8430
+ _iterator.f();
8431
+ }
8432
+ return groups;
8433
+ }
8434
+
8435
+ /**
8436
+ * What publishing `selected` publishes: every change in every patch set a
8437
+ * selected change is in, sets that share a change joined
8438
+ * (valbuild/val `docs/plans/remove-patch-groups.md`, Phase 4, rule 2).
8439
+ *
8440
+ * A patch set is changes that do not apply without each other, so a selection
8441
+ * of part of one cannot be published on its own. What it pulls in is someone
8442
+ * else's as often as not; the caller says whose, before anything moves.
8443
+ *
8444
+ * In the order of `patchIds` -- the chain's -- with nothing that is not in it.
8445
+ */
8446
+ function selectionClosure(patchSets, selected, patchIds) {
8447
+ var wanted = new Set(selected);
8448
+ var closed = new Set();
8449
+ var _iterator4 = _createForOfIteratorHelper(joinSharedChanges(patchSets.map(function (set) {
8450
+ return set.patches.map(function (patch) {
8451
+ return patch.patchId;
8452
+ });
8453
+ }))),
8454
+ _step4;
8455
+ try {
8456
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
8457
+ var group = _step4.value;
8458
+ if (_toConsumableArray(group).some(function (id) {
8459
+ return wanted.has(id);
8460
+ })) {
8461
+ var _iterator6 = _createForOfIteratorHelper(group),
8462
+ _step6;
8463
+ try {
8464
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
8465
+ var id = _step6.value;
8466
+ closed.add(id);
8467
+ }
8468
+ } catch (err) {
8469
+ _iterator6.e(err);
8470
+ } finally {
8471
+ _iterator6.f();
8472
+ }
8473
+ }
8474
+ }
8475
+ // A selected change no patch set knows of is its own set.
8476
+ } catch (err) {
8477
+ _iterator4.e(err);
8478
+ } finally {
8479
+ _iterator4.f();
8480
+ }
8481
+ var _iterator5 = _createForOfIteratorHelper(wanted),
8482
+ _step5;
8483
+ try {
8484
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
8485
+ var _id = _step5.value;
8486
+ closed.add(_id);
8487
+ }
8488
+ } catch (err) {
8489
+ _iterator5.e(err);
8490
+ } finally {
8491
+ _iterator5.f();
8492
+ }
8493
+ return patchIds.filter(function (id) {
8494
+ return closed.has(id);
8495
+ });
8496
+ }
8497
+
8272
8498
  /**
8273
8499
  * Update from the site, for a proposal with code (valbuild/home
8274
8500
  * `docs/hosted-publishing.md` step 8.2, slice 1b): what content hands over,
@@ -8344,4 +8570,4 @@ function schemaErrorsOf(schema, moduleFilePath, source) {
8344
8570
  return errors;
8345
8571
  }
8346
8572
 
8347
- export { AA_LARGE, AA_TEXT, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, PublishProtocolError, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isInsidePatchSetPath, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseCancel, parseDeclare, parseDiscard, parseJob, parseNewestCiRun, parsePress, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseRenew, parseRequestStatus, parseStatus, parseUpdateTarget, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, schemaErrorsOf, schemaTypesOfPath, sideFiles, sideSource, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
8573
+ export { AA_LARGE, AA_TEXT, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, PublishProtocolError, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isInsidePatchSetPath, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseCancel, parseDeclare, parseDiscard, parseJob, parseNewestCiRun, parsePress, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseRenew, parseRequestStatus, parseStatus, parseUpdateTarget, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, schemaErrorsOf, schemaTypesOfPath, selectionClosure, sideFiles, sideSource, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/shared",
3
- "version": "0.146.0",
3
+ "version": "0.146.2",
4
4
  "private": false,
5
5
  "description": "Val shared types and utilities",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "flexsearch": "^0.8.212",
38
38
  "zod": "^4.4.3",
39
39
  "zod-validation-error": "^5.0.0",
40
- "@valbuild/core": "0.145.0"
40
+ "@valbuild/core": "0.146.2"
41
41
  },
42
42
  "files": [
43
43
  "CHANGELOG.md",