@valbuild/shared 0.147.0 → 0.148.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,85 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.148.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - Remote files are now addressed on the host the project's settings name. For a
8
+ project hosted by Val, that is the project's own address, and its remote images
9
+ and files are served from there rather than from `remote.val.build`. Every other
10
+ project is unchanged.
11
+
12
+ - The Studio, `val validate --fix` and the MCP image tool all take the host from
13
+ the project's settings, falling back to `remote.val.build` as before.
14
+ - Re-reading a remote file whose recorded size or type is out of date keeps it
15
+ on the host it was on.
16
+ - When a remote file's host cannot be reached, tooling downloads it from the
17
+ project's permanent `<id>.valstart.dev` address instead, and keeps it only if
18
+ its bytes match the file the address names.
19
+
20
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - Remote files served from a project's own site (addresses like
21
+ `https://www.acme.no/_val/file/…`) are now checked for whether that address
22
+ still serves the project. When a custom domain has been removed or moved to
23
+ another project, the image or file is reported, and **Fix** on the Studio's
24
+ Validate page — or `val validate --fix` — points it at the project's permanent
25
+ `<id>.valstart.dev` address. Nothing is uploaded or downloaded: only the address
26
+ changes.
27
+
28
+ Remote file addresses with a `/_val` prefix after the host are now recognised.
29
+
30
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - Fix existing remote images in rich text being saved as `/publichttps://…`.
31
+
32
+ Editing a rich text field could rewrite every remote image already in it into a
33
+ broken path when the field was not set up to upload remote images itself — for
34
+ example before the project's remote file settings had loaded, or when the
35
+ field's image schema is not `.remote()`. A published remote image is now kept
36
+ as it is, whatever host it is on.
37
+
38
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - **Check everything** on the Studio's Validate page now checks media directories
39
+ (`s.imagedir()`, `s.filedir()`, `s.fontdir()` and `s.videodir()`) by the files
40
+ they list:
41
+
42
+ - An entry whose file is gone has a **Fix** that removes it from the directory.
43
+ - An image or file entry whose recorded size or type does not match its file has
44
+ a **Fix** that corrects it from the file, keeping its alt text.
45
+ - A video entry whose file is gone is reported, and is removed by hand: its
46
+ posters and captions may still be in use.
47
+ - A remote entry is checked with its host, like any remote file.
48
+
49
+ `val validate --fix` fixes media directories exactly as before.
50
+
51
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - **Check everything** on the Studio's Validate page now checks remote images and
52
+ files too.
53
+
54
+ - A remote image or file whose address no longer matches what is recorded about
55
+ it (its size, type or the schema's options changed since it was uploaded) has
56
+ a **Fix**, which re-reads the file and corrects it, as `val validate --fix`
57
+ does.
58
+ - A remote host that does not answer is shown once, at the top of the page, with
59
+ how many files it holds and a **Try again**, rather than as an error on every
60
+ image.
61
+ - A file its host no longer has is shown at the image or file that uses it.
62
+
63
+ Images and files in your unpublished changes are not asked about until they are
64
+ published; the check makes sure the change still has them.
65
+
66
+ - [#818](https://github.com/valbuild/val/pull/818) [`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1) Thanks [@freekh](https://github.com/freekh)! - Two new Studio API routes for the full content check, not yet used by the
67
+ Studio's UI:
68
+
69
+ - `POST /api/val/validate` checks one module — its content against its schema,
70
+ and its local images and files against their bytes — on the content the
71
+ Studio is showing.
72
+ - `POST /api/val/validate/fix` returns the patch that fixes one of those errors,
73
+ for the Studio to add as a pending change. It builds only the fixes that need
74
+ no disk or remote host: image, file and video metadata, and a view pointing at
75
+ the wrong module.
76
+
77
+ Also fixed: an API route whose name started with another route's name could be
78
+ answered by the other route.
79
+
80
+ - Updated dependencies [[`562d032`](https://github.com/valbuild/val/commit/562d03280c3fcf97a80562e8b017dd2125ac4ad1)]:
81
+ - @valbuild/core@0.148.0
82
+
3
83
  ## 0.147.0
4
84
 
5
85
  ### Minor Changes
@@ -328,6 +328,7 @@ export declare const Api: {
328
328
  remoteFileBuckets: z.ZodArray<z.ZodObject<{
329
329
  bucket: z.ZodString;
330
330
  }, z.core.$strip>>;
331
+ remoteHost: z.ZodOptional<z.ZodString>;
331
332
  }, z.core.$strip>;
332
333
  }, z.core.$strip>, z.ZodObject<{
333
334
  status: z.ZodLiteral<400>;
@@ -1024,6 +1025,153 @@ export declare const Api: {
1024
1025
  }, z.core.$strip>]>;
1025
1026
  };
1026
1027
  };
1028
+ /**
1029
+ * The Studio's full check, one module per request: its sources against its
1030
+ * schema and its local files against their bytes, on the content the caller
1031
+ * is looking at. See `docs/plans/studio-validate.md`.
1032
+ */
1033
+ "/validate": {
1034
+ POST: {
1035
+ req: {
1036
+ body: z.ZodObject<{
1037
+ moduleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
1038
+ patchIds: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>>>;
1039
+ }, z.core.$strip>;
1040
+ cookies: {
1041
+ val_session: z.ZodOptional<z.ZodString>;
1042
+ };
1043
+ };
1044
+ res: z.ZodUnion<readonly [z.ZodObject<{
1045
+ status: z.ZodLiteral<401>;
1046
+ json: z.ZodObject<{
1047
+ message: z.ZodString;
1048
+ }, z.core.$strip>;
1049
+ }, z.core.$strip>, z.ZodObject<{
1050
+ status: z.ZodLiteral<404>;
1051
+ json: z.ZodObject<{
1052
+ message: z.ZodString;
1053
+ }, z.core.$strip>;
1054
+ }, z.core.$strip>, z.ZodObject<{
1055
+ status: z.ZodLiteral<500>;
1056
+ json: z.ZodObject<{
1057
+ message: z.ZodString;
1058
+ }, z.core.$strip>;
1059
+ }, z.core.$strip>, z.ZodObject<{
1060
+ status: z.ZodLiteral<501>;
1061
+ json: z.ZodObject<{
1062
+ message: z.ZodString;
1063
+ }, z.core.$strip>;
1064
+ }, z.core.$strip>, z.ZodObject<{
1065
+ status: z.ZodLiteral<200>;
1066
+ json: z.ZodObject<{
1067
+ moduleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
1068
+ errors: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
1069
+ message: z.ZodString;
1070
+ value: z.ZodOptional<z.ZodUnknown>;
1071
+ fatal: z.ZodOptional<z.ZodBoolean>;
1072
+ fixes: z.ZodOptional<z.ZodArray<z.ZodType<ValidationFix, unknown, z.core.$ZodTypeInternals<ValidationFix, unknown>>>>;
1073
+ }, z.core.$strip>>>;
1074
+ invalidSource: z.ZodOptional<z.ZodString>;
1075
+ remoteHosts: z.ZodOptional<z.ZodArray<z.ZodObject<{
1076
+ host: z.ZodString;
1077
+ status: z.ZodUnion<readonly [z.ZodLiteral<"missing">, z.ZodLiteral<"unreachable">]>;
1078
+ details: z.ZodString;
1079
+ sourcePaths: z.ZodArray<z.ZodType<SourcePathT, unknown, z.core.$ZodTypeInternals<SourcePathT, unknown>>>;
1080
+ }, z.core.$strip>>>;
1081
+ }, z.core.$strip>;
1082
+ }, z.core.$strip>]>;
1083
+ };
1084
+ };
1085
+ /**
1086
+ * The patch that fixes one error the full check reported. The server finds
1087
+ * the error again rather than trusting the caller's copy, and builds only the
1088
+ * fixes `STUDIO_FIXES` lists. The patch is returned, not applied: the Studio
1089
+ * adds it as a pending change like any edit.
1090
+ */
1091
+ "/validate/fix": {
1092
+ POST: {
1093
+ req: {
1094
+ body: z.ZodObject<{
1095
+ moduleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
1096
+ sourcePath: z.ZodType<SourcePathT, unknown, z.core.$ZodTypeInternals<SourcePathT, unknown>>;
1097
+ fix: z.ZodType<ValidationFix, unknown, z.core.$ZodTypeInternals<ValidationFix, unknown>>;
1098
+ patchIds: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<PatchIdT, string, z.core.$ZodTypeInternals<PatchIdT, string>>>>;
1099
+ }, z.core.$strip>;
1100
+ cookies: {
1101
+ val_session: z.ZodOptional<z.ZodString>;
1102
+ };
1103
+ };
1104
+ res: z.ZodUnion<readonly [z.ZodObject<{
1105
+ status: z.ZodLiteral<401>;
1106
+ json: z.ZodObject<{
1107
+ message: z.ZodString;
1108
+ }, z.core.$strip>;
1109
+ }, z.core.$strip>, z.ZodObject<{
1110
+ status: z.ZodLiteral<404>;
1111
+ json: z.ZodObject<{
1112
+ message: z.ZodString;
1113
+ }, z.core.$strip>;
1114
+ }, z.core.$strip>, z.ZodObject<{
1115
+ status: z.ZodLiteral<400>;
1116
+ json: z.ZodObject<{
1117
+ message: z.ZodString;
1118
+ }, z.core.$strip>;
1119
+ }, z.core.$strip>, z.ZodObject<{
1120
+ status: z.ZodLiteral<500>;
1121
+ json: z.ZodObject<{
1122
+ message: z.ZodString;
1123
+ }, z.core.$strip>;
1124
+ }, z.core.$strip>, z.ZodObject<{
1125
+ status: z.ZodLiteral<501>;
1126
+ json: z.ZodObject<{
1127
+ message: z.ZodString;
1128
+ }, z.core.$strip>;
1129
+ }, z.core.$strip>, z.ZodObject<{
1130
+ status: z.ZodLiteral<200>;
1131
+ json: z.ZodObject<{
1132
+ patch: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1133
+ op: z.ZodLiteral<"add">;
1134
+ path: z.ZodArray<z.ZodString>;
1135
+ value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
1136
+ }, z.core.$strict>, z.ZodObject<{
1137
+ op: z.ZodLiteral<"remove">;
1138
+ path: z.ZodTuple<[z.ZodString], z.ZodString>;
1139
+ }, z.core.$strict>, z.ZodObject<{
1140
+ op: z.ZodLiteral<"replace">;
1141
+ path: z.ZodArray<z.ZodString>;
1142
+ value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
1143
+ }, z.core.$strict>, z.ZodObject<{
1144
+ op: z.ZodLiteral<"move">;
1145
+ from: z.ZodTuple<[z.ZodString], z.ZodString>;
1146
+ path: z.ZodArray<z.ZodString>;
1147
+ }, z.core.$strict>, z.ZodObject<{
1148
+ op: z.ZodLiteral<"copy">;
1149
+ from: z.ZodArray<z.ZodString>;
1150
+ path: z.ZodArray<z.ZodString>;
1151
+ }, z.core.$strict>, z.ZodObject<{
1152
+ op: z.ZodLiteral<"test">;
1153
+ path: z.ZodArray<z.ZodString>;
1154
+ value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
1155
+ }, z.core.$strict>, z.ZodObject<{
1156
+ op: z.ZodLiteral<"file">;
1157
+ path: z.ZodArray<z.ZodString>;
1158
+ filePath: z.ZodString;
1159
+ value: z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>;
1160
+ remote: z.ZodBoolean;
1161
+ nestedFilePath: z.ZodOptional<z.ZodArray<z.ZodString>>;
1162
+ metadata: z.ZodOptional<z.ZodType<JSONValueT, unknown, z.core.$ZodTypeInternals<JSONValueT, unknown>>>;
1163
+ }, z.core.$strict>], "op">>;
1164
+ remainingErrors: z.ZodArray<z.ZodObject<{
1165
+ message: z.ZodString;
1166
+ value: z.ZodOptional<z.ZodUnknown>;
1167
+ fatal: z.ZodOptional<z.ZodBoolean>;
1168
+ fixes: z.ZodOptional<z.ZodArray<z.ZodType<ValidationFix, unknown, z.core.$ZodTypeInternals<ValidationFix, unknown>>>>;
1169
+ sourcePath: z.ZodOptional<z.ZodString>;
1170
+ }, z.core.$strip>>;
1171
+ }, z.core.$strip>;
1172
+ }, z.core.$strip>]>;
1173
+ };
1174
+ };
1027
1175
  "/publish-job-prepare": {
1028
1176
  POST: {
1029
1177
  req: {
@@ -30,6 +30,7 @@ export * from "./aiTools/aiImageToolPatches.js";
30
30
  export * from "./aiTools/aiSourceToolPatches.js";
31
31
  export * from "./validation/partitionValidationErrors.js";
32
32
  export * from "./validation/blockingValidationErrors.js";
33
+ export * from "./validation/studioFixes.js";
33
34
  export * from "./media/encodeImageDecisions.js";
34
35
  export * from "./search/searchIndex.js";
35
36
  export * from "./search/traverseSchemaSource.js";
@@ -26,3 +26,10 @@ export declare function partitionValidationErrors(validationErrors: Record<Sourc
26
26
  surfaced: Record<SourcePath, ValidationError[]>;
27
27
  skipped: Record<SourcePath, ValidationError[]>;
28
28
  };
29
+ /**
30
+ * Whether the Studio leaves this error to the server, which repairs it on
31
+ * save, rather than showing it and holding a publish back for it. One rule
32
+ * for every gate: `val publish` asks the same question, so the CLI does not
33
+ * refuse what the Studio would publish.
34
+ */
35
+ export declare function isRepairedOnSave(error: ValidationError): boolean;
@@ -0,0 +1,14 @@
1
+ import type { ValidationFix } from "@valbuild/core";
2
+ /**
3
+ * The fixes the Studio's full check offers a **Fix** button for.
4
+ *
5
+ * Each is one `createFixPatch` can build without a disk: it reads a file's
6
+ * bytes through `FixFiles`, or needs no bytes at all. The server refuses any
7
+ * other code on `/validate/fix`, and the Studio shows those errors with the
8
+ * command to run instead of a button — so a code is added here when its fix
9
+ * can run in http mode and on the platform, not before. See
10
+ * `docs/plans/studio-validate.md`, "Order of work".
11
+ */
12
+ export declare const STUDIO_FIXES: readonly ["image:add-metadata", "image:check-metadata", "file:add-metadata", "file:check-metadata", "video:add-metadata", "videos:add-metadata", "view:check-module", "image:check-remote", "file:check-remote", "images:check-all-files", "files:check-all-files", "remote:rewrite-host"];
13
+ export type StudioFix = (typeof STUDIO_FIXES)[number];
14
+ export declare function isStudioFix(fix: ValidationFix): fix is StudioFix;
@@ -273,7 +273,7 @@ function convertImageNode(path, node, files, configDirectory, remoteOptions) {
273
273
  } else {
274
274
  console.error("Patched image URL does not start with /api/val/files: " + url);
275
275
  }
276
- } else if (remoteOptions && noParamsUrl.startsWith(remoteOptions.remoteHost)) ; else if (!noParamsUrl.startsWith("/public")) {
276
+ } else if (core.Internal.remote.splitRemoteRef(noParamsUrl).status === "success") ; else if (!noParamsUrl.startsWith("/public")) {
277
277
  noParamsUrl = "/public".concat(noParamsUrl);
278
278
  } else {
279
279
  console.error("Unpatched image URL starts with /public: " + url);
@@ -1054,6 +1054,10 @@ var PatchBlock = zod.z.object({
1054
1054
  parentRef: ParentRef$1
1055
1055
  });
1056
1056
 
1057
+ var SourcePath = zod.z.string().transform(function (path) {
1058
+ return path;
1059
+ });
1060
+
1057
1061
  function _objectWithoutPropertiesLoose(r, e) {
1058
1062
  if (null == r) return {};
1059
1063
  var t = {};
@@ -1076,10 +1080,6 @@ function _objectWithoutProperties(e, t) {
1076
1080
  return i;
1077
1081
  }
1078
1082
 
1079
- var SourcePath = zod.z.string().transform(function (path) {
1080
- return path;
1081
- });
1082
-
1083
1083
  var ModuleFilePath$1 = zod.z.string().transform(function (path) {
1084
1084
  return path;
1085
1085
  });
@@ -1621,7 +1621,7 @@ var ValConfig = zod.z.object({
1621
1621
  gitCommit: zod.z.string().optional(),
1622
1622
  gitBranch: zod.z.string().optional()
1623
1623
  });
1624
- 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("router:check-locale-param"), 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")]);
1624
+ 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("router:check-locale-param"), 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"), zod.z.literal("external:upload"), zod.z.literal("remote:rewrite-host")]);
1625
1625
  var ValidationError = zod.z.object({
1626
1626
  message: zod.z.string(),
1627
1627
  value: zod.z.unknown().optional(),
@@ -2023,7 +2023,13 @@ var Api = {
2023
2023
  coreVersion: zod.z.string(),
2024
2024
  remoteFileBuckets: zod.z.array(zod.z.object({
2025
2025
  bucket: zod.z.string()
2026
- }))
2026
+ })),
2027
+ /**
2028
+ * The host new refs name, from the project's settings: its own
2029
+ * address for a Hosted project whose files are in R2. Optional,
2030
+ * for a content service that predates it.
2031
+ */
2032
+ remoteHost: zod.z.string().optional()
2027
2033
  })
2028
2034
  }), zod.z.object({
2029
2035
  status: zod.z.literal(400),
@@ -2769,6 +2775,105 @@ var Api = {
2769
2775
  })])
2770
2776
  }
2771
2777
  },
2778
+ /**
2779
+ * The Studio's full check, one module per request: its sources against its
2780
+ * schema and its local files against their bytes, on the content the caller
2781
+ * is looking at. See `docs/plans/studio-validate.md`.
2782
+ */
2783
+ "/validate": {
2784
+ POST: {
2785
+ req: {
2786
+ body: zod.z.object({
2787
+ moduleFilePath: ModuleFilePath,
2788
+ /**
2789
+ * The patches the caller is showing, applied over base. Omitted
2790
+ * means every pending patch, as `/sources/~`; empty means base.
2791
+ */
2792
+ patchIds: zod.z.array(PatchId).optional()
2793
+ }),
2794
+ cookies: {
2795
+ val_session: zod.z.string().optional()
2796
+ }
2797
+ },
2798
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2799
+ status: zod.z.literal(500),
2800
+ json: zod.z.object({
2801
+ message: zod.z.string()
2802
+ })
2803
+ }), /** No full check on this server (one running in a browser tab). */
2804
+ zod.z.object({
2805
+ status: zod.z.literal(501),
2806
+ json: zod.z.object({
2807
+ message: zod.z.string()
2808
+ })
2809
+ }), zod.z.object({
2810
+ status: zod.z.literal(200),
2811
+ json: zod.z.object({
2812
+ moduleFilePath: ModuleFilePath,
2813
+ errors: zod.z.record(zod.z.string(), zod.z.array(ValidationError)),
2814
+ /** Set when the module's source could not be read at all. */
2815
+ invalidSource: zod.z.string().optional(),
2816
+ /**
2817
+ * Hosts that did not serve this module's remote files: one entry
2818
+ * per host, however many files it holds. Optional so a server
2819
+ * from before remote checks still parses.
2820
+ */
2821
+ remoteHosts: zod.z.array(zod.z.object({
2822
+ host: zod.z.string(),
2823
+ status: zod.z.union([zod.z.literal("missing"), zod.z.literal("unreachable")]),
2824
+ details: zod.z.string(),
2825
+ sourcePaths: zod.z.array(SourcePath)
2826
+ })).optional()
2827
+ })
2828
+ })])
2829
+ }
2830
+ },
2831
+ /**
2832
+ * The patch that fixes one error the full check reported. The server finds
2833
+ * the error again rather than trusting the caller's copy, and builds only the
2834
+ * fixes `STUDIO_FIXES` lists. The patch is returned, not applied: the Studio
2835
+ * adds it as a pending change like any edit.
2836
+ */
2837
+ "/validate/fix": {
2838
+ POST: {
2839
+ req: {
2840
+ body: zod.z.object({
2841
+ moduleFilePath: ModuleFilePath,
2842
+ sourcePath: SourcePath,
2843
+ fix: ValidationFixZ,
2844
+ patchIds: zod.z.array(PatchId).optional()
2845
+ }),
2846
+ cookies: {
2847
+ val_session: zod.z.string().optional()
2848
+ }
2849
+ },
2850
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2851
+ status: zod.z.literal(400),
2852
+ json: zod.z.object({
2853
+ message: zod.z.string()
2854
+ })
2855
+ }), zod.z.object({
2856
+ status: zod.z.literal(500),
2857
+ json: zod.z.object({
2858
+ message: zod.z.string()
2859
+ })
2860
+ }), /** No full check on this server (one running in a browser tab). */
2861
+ zod.z.object({
2862
+ status: zod.z.literal(501),
2863
+ json: zod.z.object({
2864
+ message: zod.z.string()
2865
+ })
2866
+ }), zod.z.object({
2867
+ status: zod.z.literal(200),
2868
+ json: zod.z.object({
2869
+ patch: Patch,
2870
+ remainingErrors: zod.z.array(ValidationError.extend({
2871
+ sourcePath: zod.z.string().optional()
2872
+ }))
2873
+ })
2874
+ })])
2875
+ }
2876
+ },
2772
2877
  "/publish-job-prepare": {
2773
2878
  POST: {
2774
2879
  req: {
@@ -6312,6 +6417,16 @@ function partitionValidationErrors(validationErrors) {
6312
6417
  skipped: skipped
6313
6418
  };
6314
6419
  }
6420
+
6421
+ /**
6422
+ * Whether the Studio leaves this error to the server, which repairs it on
6423
+ * save, rather than showing it and holding a publish back for it. One rule
6424
+ * for every gate: `val publish` asks the same question, so the CLI does not
6425
+ * refuse what the Studio would publish.
6426
+ */
6427
+ function isRepairedOnSave(error) {
6428
+ return isSkippedFix(error);
6429
+ }
6315
6430
  function isSkippedFix(error) {
6316
6431
  var _error$fixes;
6317
6432
  var fixes = (_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : [];
@@ -6381,6 +6496,12 @@ function isSkippableFixCode(fix) {
6381
6496
  // under `val external upload` and never under a blanket `--fix`. Surfacing
6382
6497
  // it is the whole point: someone has to decide.
6383
6498
  return false;
6499
+ case "remote:rewrite-host":
6500
+ // Reported only by the server's full check (the Studio's Validate page,
6501
+ // `val validate`), which asks the ref's host -- never by a schema, so
6502
+ // the Studio's own validator never holds one. Were it to, a person has
6503
+ // to see it: the host is gone, and nothing on save repoints the ref.
6504
+ return false;
6384
6505
  default:
6385
6506
  {
6386
6507
  return false;
@@ -6420,6 +6541,33 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
6420
6541
  return partitionValidationErrors(resolved).surfaced;
6421
6542
  }
6422
6543
 
6544
+ /**
6545
+ * The fixes the Studio's full check offers a **Fix** button for.
6546
+ *
6547
+ * Each is one `createFixPatch` can build without a disk: it reads a file's
6548
+ * bytes through `FixFiles`, or needs no bytes at all. The server refuses any
6549
+ * other code on `/validate/fix`, and the Studio shows those errors with the
6550
+ * command to run instead of a button — so a code is added here when its fix
6551
+ * can run in http mode and on the platform, not before. See
6552
+ * `docs/plans/studio-validate.md`, "Order of work".
6553
+ */
6554
+ var STUDIO_FIXES = ["image:add-metadata", "image:check-metadata", "file:add-metadata", "file:check-metadata", "video:add-metadata", "videos:add-metadata", "view:check-module",
6555
+ // A stale remote ref: re-read through `FixFiles.readRemoteFile`, rewritten
6556
+ // on the host it was already on.
6557
+ "image:check-remote", "file:check-remote",
6558
+ // One entry of an `s.imagedir()` / `s.filedir()`: removed when its file is
6559
+ // gone, rewritten from its bytes when they disagree. Built for that entry
6560
+ // alone, never the whole directory (`fixForStudio`).
6561
+ "images:check-all-files", "files:check-all-files",
6562
+ // A platform ref on a host that no longer serves its project, pointed at
6563
+ // the project's own `valstart.dev` address. Derived from the ref alone.
6564
+ "remote:rewrite-host"];
6565
+ function isStudioFix(fix) {
6566
+ return STUDIO_FIXES.some(function (studioFix) {
6567
+ return studioFix === fix;
6568
+ });
6569
+ }
6570
+
6423
6571
  /**
6424
6572
  * Every decision `encode` makes, with none of the pixels.
6425
6573
  *
@@ -8838,6 +8986,7 @@ exports.RemirrorTextMark = RemirrorTextMark;
8838
8986
  exports.RestoreVerdict = RestoreVerdict;
8839
8987
  exports.RichTextOptions = RichTextOptions;
8840
8988
  exports.SCHEMA_SOURCE_FIXES = SCHEMA_SOURCE_FIXES;
8989
+ exports.STUDIO_FIXES = STUDIO_FIXES;
8841
8990
  exports.SerializedArraySchema = SerializedArraySchema;
8842
8991
  exports.SerializedBooleanSchema = SerializedBooleanSchema;
8843
8992
  exports.SerializedCodeSchema = SerializedCodeSchema;
@@ -8913,8 +9062,10 @@ exports.isInsidePatchSetPath = isInsidePatchSetPath;
8913
9062
  exports.isLocaleSchema = isLocaleSchema;
8914
9063
  exports.isMimeTypeAccepted = isMimeTypeAccepted;
8915
9064
  exports.isRemoteSchema = isRemoteSchema;
9065
+ exports.isRepairedOnSave = isRepairedOnSave;
8916
9066
  exports.isSchemaSourceFixError = isSchemaSourceFixError;
8917
9067
  exports.isSkippedSource = isSkippedSource;
9068
+ exports.isStudioFix = isStudioFix;
8918
9069
  exports.joinSharedChanges = joinSharedChanges;
8919
9070
  exports.keyOfRecordPath = keyOfRecordPath;
8920
9071
  exports.levenshtein = levenshtein;
@@ -273,7 +273,7 @@ function convertImageNode(path, node, files, configDirectory, remoteOptions) {
273
273
  } else {
274
274
  console.error("Patched image URL does not start with /api/val/files: " + url);
275
275
  }
276
- } else if (remoteOptions && noParamsUrl.startsWith(remoteOptions.remoteHost)) ; else if (!noParamsUrl.startsWith("/public")) {
276
+ } else if (core.Internal.remote.splitRemoteRef(noParamsUrl).status === "success") ; else if (!noParamsUrl.startsWith("/public")) {
277
277
  noParamsUrl = "/public".concat(noParamsUrl);
278
278
  } else {
279
279
  console.error("Unpatched image URL starts with /public: " + url);
@@ -1054,6 +1054,10 @@ var PatchBlock = zod.z.object({
1054
1054
  parentRef: ParentRef$1
1055
1055
  });
1056
1056
 
1057
+ var SourcePath = zod.z.string().transform(function (path) {
1058
+ return path;
1059
+ });
1060
+
1057
1061
  function _objectWithoutPropertiesLoose(r, e) {
1058
1062
  if (null == r) return {};
1059
1063
  var t = {};
@@ -1076,10 +1080,6 @@ function _objectWithoutProperties(e, t) {
1076
1080
  return i;
1077
1081
  }
1078
1082
 
1079
- var SourcePath = zod.z.string().transform(function (path) {
1080
- return path;
1081
- });
1082
-
1083
1083
  var ModuleFilePath$1 = zod.z.string().transform(function (path) {
1084
1084
  return path;
1085
1085
  });
@@ -1621,7 +1621,7 @@ var ValConfig = zod.z.object({
1621
1621
  gitCommit: zod.z.string().optional(),
1622
1622
  gitBranch: zod.z.string().optional()
1623
1623
  });
1624
- 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("router:check-locale-param"), 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")]);
1624
+ 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("router:check-locale-param"), 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"), zod.z.literal("external:upload"), zod.z.literal("remote:rewrite-host")]);
1625
1625
  var ValidationError = zod.z.object({
1626
1626
  message: zod.z.string(),
1627
1627
  value: zod.z.unknown().optional(),
@@ -2023,7 +2023,13 @@ var Api = {
2023
2023
  coreVersion: zod.z.string(),
2024
2024
  remoteFileBuckets: zod.z.array(zod.z.object({
2025
2025
  bucket: zod.z.string()
2026
- }))
2026
+ })),
2027
+ /**
2028
+ * The host new refs name, from the project's settings: its own
2029
+ * address for a Hosted project whose files are in R2. Optional,
2030
+ * for a content service that predates it.
2031
+ */
2032
+ remoteHost: zod.z.string().optional()
2027
2033
  })
2028
2034
  }), zod.z.object({
2029
2035
  status: zod.z.literal(400),
@@ -2769,6 +2775,105 @@ var Api = {
2769
2775
  })])
2770
2776
  }
2771
2777
  },
2778
+ /**
2779
+ * The Studio's full check, one module per request: its sources against its
2780
+ * schema and its local files against their bytes, on the content the caller
2781
+ * is looking at. See `docs/plans/studio-validate.md`.
2782
+ */
2783
+ "/validate": {
2784
+ POST: {
2785
+ req: {
2786
+ body: zod.z.object({
2787
+ moduleFilePath: ModuleFilePath,
2788
+ /**
2789
+ * The patches the caller is showing, applied over base. Omitted
2790
+ * means every pending patch, as `/sources/~`; empty means base.
2791
+ */
2792
+ patchIds: zod.z.array(PatchId).optional()
2793
+ }),
2794
+ cookies: {
2795
+ val_session: zod.z.string().optional()
2796
+ }
2797
+ },
2798
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2799
+ status: zod.z.literal(500),
2800
+ json: zod.z.object({
2801
+ message: zod.z.string()
2802
+ })
2803
+ }), /** No full check on this server (one running in a browser tab). */
2804
+ zod.z.object({
2805
+ status: zod.z.literal(501),
2806
+ json: zod.z.object({
2807
+ message: zod.z.string()
2808
+ })
2809
+ }), zod.z.object({
2810
+ status: zod.z.literal(200),
2811
+ json: zod.z.object({
2812
+ moduleFilePath: ModuleFilePath,
2813
+ errors: zod.z.record(zod.z.string(), zod.z.array(ValidationError)),
2814
+ /** Set when the module's source could not be read at all. */
2815
+ invalidSource: zod.z.string().optional(),
2816
+ /**
2817
+ * Hosts that did not serve this module's remote files: one entry
2818
+ * per host, however many files it holds. Optional so a server
2819
+ * from before remote checks still parses.
2820
+ */
2821
+ remoteHosts: zod.z.array(zod.z.object({
2822
+ host: zod.z.string(),
2823
+ status: zod.z.union([zod.z.literal("missing"), zod.z.literal("unreachable")]),
2824
+ details: zod.z.string(),
2825
+ sourcePaths: zod.z.array(SourcePath)
2826
+ })).optional()
2827
+ })
2828
+ })])
2829
+ }
2830
+ },
2831
+ /**
2832
+ * The patch that fixes one error the full check reported. The server finds
2833
+ * the error again rather than trusting the caller's copy, and builds only the
2834
+ * fixes `STUDIO_FIXES` lists. The patch is returned, not applied: the Studio
2835
+ * adds it as a pending change like any edit.
2836
+ */
2837
+ "/validate/fix": {
2838
+ POST: {
2839
+ req: {
2840
+ body: zod.z.object({
2841
+ moduleFilePath: ModuleFilePath,
2842
+ sourcePath: SourcePath,
2843
+ fix: ValidationFixZ,
2844
+ patchIds: zod.z.array(PatchId).optional()
2845
+ }),
2846
+ cookies: {
2847
+ val_session: zod.z.string().optional()
2848
+ }
2849
+ },
2850
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2851
+ status: zod.z.literal(400),
2852
+ json: zod.z.object({
2853
+ message: zod.z.string()
2854
+ })
2855
+ }), zod.z.object({
2856
+ status: zod.z.literal(500),
2857
+ json: zod.z.object({
2858
+ message: zod.z.string()
2859
+ })
2860
+ }), /** No full check on this server (one running in a browser tab). */
2861
+ zod.z.object({
2862
+ status: zod.z.literal(501),
2863
+ json: zod.z.object({
2864
+ message: zod.z.string()
2865
+ })
2866
+ }), zod.z.object({
2867
+ status: zod.z.literal(200),
2868
+ json: zod.z.object({
2869
+ patch: Patch,
2870
+ remainingErrors: zod.z.array(ValidationError.extend({
2871
+ sourcePath: zod.z.string().optional()
2872
+ }))
2873
+ })
2874
+ })])
2875
+ }
2876
+ },
2772
2877
  "/publish-job-prepare": {
2773
2878
  POST: {
2774
2879
  req: {
@@ -6312,6 +6417,16 @@ function partitionValidationErrors(validationErrors) {
6312
6417
  skipped: skipped
6313
6418
  };
6314
6419
  }
6420
+
6421
+ /**
6422
+ * Whether the Studio leaves this error to the server, which repairs it on
6423
+ * save, rather than showing it and holding a publish back for it. One rule
6424
+ * for every gate: `val publish` asks the same question, so the CLI does not
6425
+ * refuse what the Studio would publish.
6426
+ */
6427
+ function isRepairedOnSave(error) {
6428
+ return isSkippedFix(error);
6429
+ }
6315
6430
  function isSkippedFix(error) {
6316
6431
  var _error$fixes;
6317
6432
  var fixes = (_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : [];
@@ -6381,6 +6496,12 @@ function isSkippableFixCode(fix) {
6381
6496
  // under `val external upload` and never under a blanket `--fix`. Surfacing
6382
6497
  // it is the whole point: someone has to decide.
6383
6498
  return false;
6499
+ case "remote:rewrite-host":
6500
+ // Reported only by the server's full check (the Studio's Validate page,
6501
+ // `val validate`), which asks the ref's host -- never by a schema, so
6502
+ // the Studio's own validator never holds one. Were it to, a person has
6503
+ // to see it: the host is gone, and nothing on save repoints the ref.
6504
+ return false;
6384
6505
  default:
6385
6506
  {
6386
6507
  return false;
@@ -6420,6 +6541,33 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
6420
6541
  return partitionValidationErrors(resolved).surfaced;
6421
6542
  }
6422
6543
 
6544
+ /**
6545
+ * The fixes the Studio's full check offers a **Fix** button for.
6546
+ *
6547
+ * Each is one `createFixPatch` can build without a disk: it reads a file's
6548
+ * bytes through `FixFiles`, or needs no bytes at all. The server refuses any
6549
+ * other code on `/validate/fix`, and the Studio shows those errors with the
6550
+ * command to run instead of a button — so a code is added here when its fix
6551
+ * can run in http mode and on the platform, not before. See
6552
+ * `docs/plans/studio-validate.md`, "Order of work".
6553
+ */
6554
+ var STUDIO_FIXES = ["image:add-metadata", "image:check-metadata", "file:add-metadata", "file:check-metadata", "video:add-metadata", "videos:add-metadata", "view:check-module",
6555
+ // A stale remote ref: re-read through `FixFiles.readRemoteFile`, rewritten
6556
+ // on the host it was already on.
6557
+ "image:check-remote", "file:check-remote",
6558
+ // One entry of an `s.imagedir()` / `s.filedir()`: removed when its file is
6559
+ // gone, rewritten from its bytes when they disagree. Built for that entry
6560
+ // alone, never the whole directory (`fixForStudio`).
6561
+ "images:check-all-files", "files:check-all-files",
6562
+ // A platform ref on a host that no longer serves its project, pointed at
6563
+ // the project's own `valstart.dev` address. Derived from the ref alone.
6564
+ "remote:rewrite-host"];
6565
+ function isStudioFix(fix) {
6566
+ return STUDIO_FIXES.some(function (studioFix) {
6567
+ return studioFix === fix;
6568
+ });
6569
+ }
6570
+
6423
6571
  /**
6424
6572
  * Every decision `encode` makes, with none of the pixels.
6425
6573
  *
@@ -8838,6 +8986,7 @@ exports.RemirrorTextMark = RemirrorTextMark;
8838
8986
  exports.RestoreVerdict = RestoreVerdict;
8839
8987
  exports.RichTextOptions = RichTextOptions;
8840
8988
  exports.SCHEMA_SOURCE_FIXES = SCHEMA_SOURCE_FIXES;
8989
+ exports.STUDIO_FIXES = STUDIO_FIXES;
8841
8990
  exports.SerializedArraySchema = SerializedArraySchema;
8842
8991
  exports.SerializedBooleanSchema = SerializedBooleanSchema;
8843
8992
  exports.SerializedCodeSchema = SerializedCodeSchema;
@@ -8913,8 +9062,10 @@ exports.isInsidePatchSetPath = isInsidePatchSetPath;
8913
9062
  exports.isLocaleSchema = isLocaleSchema;
8914
9063
  exports.isMimeTypeAccepted = isMimeTypeAccepted;
8915
9064
  exports.isRemoteSchema = isRemoteSchema;
9065
+ exports.isRepairedOnSave = isRepairedOnSave;
8916
9066
  exports.isSchemaSourceFixError = isSchemaSourceFixError;
8917
9067
  exports.isSkippedSource = isSkippedSource;
9068
+ exports.isStudioFix = isStudioFix;
8918
9069
  exports.joinSharedChanges = joinSharedChanges;
8919
9070
  exports.keyOfRecordPath = keyOfRecordPath;
8920
9071
  exports.levenshtein = levenshtein;
@@ -266,7 +266,7 @@ function convertImageNode(path, node, files, configDirectory, remoteOptions) {
266
266
  } else {
267
267
  console.error("Patched image URL does not start with /api/val/files: " + url);
268
268
  }
269
- } else if (remoteOptions && noParamsUrl.startsWith(remoteOptions.remoteHost)) ; else if (!noParamsUrl.startsWith("/public")) {
269
+ } else if (Internal.remote.splitRemoteRef(noParamsUrl).status === "success") ; else if (!noParamsUrl.startsWith("/public")) {
270
270
  noParamsUrl = "/public".concat(noParamsUrl);
271
271
  } else {
272
272
  console.error("Unpatched image URL starts with /public: " + url);
@@ -1047,6 +1047,10 @@ var PatchBlock = z.object({
1047
1047
  parentRef: ParentRef$1
1048
1048
  });
1049
1049
 
1050
+ var SourcePath = z.string().transform(function (path) {
1051
+ return path;
1052
+ });
1053
+
1050
1054
  function _objectWithoutPropertiesLoose(r, e) {
1051
1055
  if (null == r) return {};
1052
1056
  var t = {};
@@ -1069,10 +1073,6 @@ function _objectWithoutProperties(e, t) {
1069
1073
  return i;
1070
1074
  }
1071
1075
 
1072
- var SourcePath = z.string().transform(function (path) {
1073
- return path;
1074
- });
1075
-
1076
1076
  var ModuleFilePath$1 = z.string().transform(function (path) {
1077
1077
  return path;
1078
1078
  });
@@ -1614,7 +1614,7 @@ var ValConfig = z.object({
1614
1614
  gitCommit: z.string().optional(),
1615
1615
  gitBranch: z.string().optional()
1616
1616
  });
1617
- 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("router:check-locale-param"), 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")]);
1617
+ 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("router:check-locale-param"), 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"), z.literal("external:upload"), z.literal("remote:rewrite-host")]);
1618
1618
  var ValidationError = z.object({
1619
1619
  message: z.string(),
1620
1620
  value: z.unknown().optional(),
@@ -2016,7 +2016,13 @@ var Api = {
2016
2016
  coreVersion: z.string(),
2017
2017
  remoteFileBuckets: z.array(z.object({
2018
2018
  bucket: z.string()
2019
- }))
2019
+ })),
2020
+ /**
2021
+ * The host new refs name, from the project's settings: its own
2022
+ * address for a Hosted project whose files are in R2. Optional,
2023
+ * for a content service that predates it.
2024
+ */
2025
+ remoteHost: z.string().optional()
2020
2026
  })
2021
2027
  }), z.object({
2022
2028
  status: z.literal(400),
@@ -2762,6 +2768,105 @@ var Api = {
2762
2768
  })])
2763
2769
  }
2764
2770
  },
2771
+ /**
2772
+ * The Studio's full check, one module per request: its sources against its
2773
+ * schema and its local files against their bytes, on the content the caller
2774
+ * is looking at. See `docs/plans/studio-validate.md`.
2775
+ */
2776
+ "/validate": {
2777
+ POST: {
2778
+ req: {
2779
+ body: z.object({
2780
+ moduleFilePath: ModuleFilePath,
2781
+ /**
2782
+ * The patches the caller is showing, applied over base. Omitted
2783
+ * means every pending patch, as `/sources/~`; empty means base.
2784
+ */
2785
+ patchIds: z.array(PatchId).optional()
2786
+ }),
2787
+ cookies: {
2788
+ val_session: z.string().optional()
2789
+ }
2790
+ },
2791
+ res: z.union([unauthorizedResponse, notFoundResponse, z.object({
2792
+ status: z.literal(500),
2793
+ json: z.object({
2794
+ message: z.string()
2795
+ })
2796
+ }), /** No full check on this server (one running in a browser tab). */
2797
+ z.object({
2798
+ status: z.literal(501),
2799
+ json: z.object({
2800
+ message: z.string()
2801
+ })
2802
+ }), z.object({
2803
+ status: z.literal(200),
2804
+ json: z.object({
2805
+ moduleFilePath: ModuleFilePath,
2806
+ errors: z.record(z.string(), z.array(ValidationError)),
2807
+ /** Set when the module's source could not be read at all. */
2808
+ invalidSource: z.string().optional(),
2809
+ /**
2810
+ * Hosts that did not serve this module's remote files: one entry
2811
+ * per host, however many files it holds. Optional so a server
2812
+ * from before remote checks still parses.
2813
+ */
2814
+ remoteHosts: z.array(z.object({
2815
+ host: z.string(),
2816
+ status: z.union([z.literal("missing"), z.literal("unreachable")]),
2817
+ details: z.string(),
2818
+ sourcePaths: z.array(SourcePath)
2819
+ })).optional()
2820
+ })
2821
+ })])
2822
+ }
2823
+ },
2824
+ /**
2825
+ * The patch that fixes one error the full check reported. The server finds
2826
+ * the error again rather than trusting the caller's copy, and builds only the
2827
+ * fixes `STUDIO_FIXES` lists. The patch is returned, not applied: the Studio
2828
+ * adds it as a pending change like any edit.
2829
+ */
2830
+ "/validate/fix": {
2831
+ POST: {
2832
+ req: {
2833
+ body: z.object({
2834
+ moduleFilePath: ModuleFilePath,
2835
+ sourcePath: SourcePath,
2836
+ fix: ValidationFixZ,
2837
+ patchIds: z.array(PatchId).optional()
2838
+ }),
2839
+ cookies: {
2840
+ val_session: z.string().optional()
2841
+ }
2842
+ },
2843
+ res: z.union([unauthorizedResponse, notFoundResponse, z.object({
2844
+ status: z.literal(400),
2845
+ json: z.object({
2846
+ message: z.string()
2847
+ })
2848
+ }), z.object({
2849
+ status: z.literal(500),
2850
+ json: z.object({
2851
+ message: z.string()
2852
+ })
2853
+ }), /** No full check on this server (one running in a browser tab). */
2854
+ z.object({
2855
+ status: z.literal(501),
2856
+ json: z.object({
2857
+ message: z.string()
2858
+ })
2859
+ }), z.object({
2860
+ status: z.literal(200),
2861
+ json: z.object({
2862
+ patch: Patch,
2863
+ remainingErrors: z.array(ValidationError.extend({
2864
+ sourcePath: z.string().optional()
2865
+ }))
2866
+ })
2867
+ })])
2868
+ }
2869
+ },
2765
2870
  "/publish-job-prepare": {
2766
2871
  POST: {
2767
2872
  req: {
@@ -6305,6 +6410,16 @@ function partitionValidationErrors(validationErrors) {
6305
6410
  skipped: skipped
6306
6411
  };
6307
6412
  }
6413
+
6414
+ /**
6415
+ * Whether the Studio leaves this error to the server, which repairs it on
6416
+ * save, rather than showing it and holding a publish back for it. One rule
6417
+ * for every gate: `val publish` asks the same question, so the CLI does not
6418
+ * refuse what the Studio would publish.
6419
+ */
6420
+ function isRepairedOnSave(error) {
6421
+ return isSkippedFix(error);
6422
+ }
6308
6423
  function isSkippedFix(error) {
6309
6424
  var _error$fixes;
6310
6425
  var fixes = (_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : [];
@@ -6374,6 +6489,12 @@ function isSkippableFixCode(fix) {
6374
6489
  // under `val external upload` and never under a blanket `--fix`. Surfacing
6375
6490
  // it is the whole point: someone has to decide.
6376
6491
  return false;
6492
+ case "remote:rewrite-host":
6493
+ // Reported only by the server's full check (the Studio's Validate page,
6494
+ // `val validate`), which asks the ref's host -- never by a schema, so
6495
+ // the Studio's own validator never holds one. Were it to, a person has
6496
+ // to see it: the host is gone, and nothing on save repoints the ref.
6497
+ return false;
6377
6498
  default:
6378
6499
  {
6379
6500
  return false;
@@ -6413,6 +6534,33 @@ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
6413
6534
  return partitionValidationErrors(resolved).surfaced;
6414
6535
  }
6415
6536
 
6537
+ /**
6538
+ * The fixes the Studio's full check offers a **Fix** button for.
6539
+ *
6540
+ * Each is one `createFixPatch` can build without a disk: it reads a file's
6541
+ * bytes through `FixFiles`, or needs no bytes at all. The server refuses any
6542
+ * other code on `/validate/fix`, and the Studio shows those errors with the
6543
+ * command to run instead of a button — so a code is added here when its fix
6544
+ * can run in http mode and on the platform, not before. See
6545
+ * `docs/plans/studio-validate.md`, "Order of work".
6546
+ */
6547
+ var STUDIO_FIXES = ["image:add-metadata", "image:check-metadata", "file:add-metadata", "file:check-metadata", "video:add-metadata", "videos:add-metadata", "view:check-module",
6548
+ // A stale remote ref: re-read through `FixFiles.readRemoteFile`, rewritten
6549
+ // on the host it was already on.
6550
+ "image:check-remote", "file:check-remote",
6551
+ // One entry of an `s.imagedir()` / `s.filedir()`: removed when its file is
6552
+ // gone, rewritten from its bytes when they disagree. Built for that entry
6553
+ // alone, never the whole directory (`fixForStudio`).
6554
+ "images:check-all-files", "files:check-all-files",
6555
+ // A platform ref on a host that no longer serves its project, pointed at
6556
+ // the project's own `valstart.dev` address. Derived from the ref alone.
6557
+ "remote:rewrite-host"];
6558
+ function isStudioFix(fix) {
6559
+ return STUDIO_FIXES.some(function (studioFix) {
6560
+ return studioFix === fix;
6561
+ });
6562
+ }
6563
+
6416
6564
  /**
6417
6565
  * Every decision `encode` makes, with none of the pixels.
6418
6566
  *
@@ -8767,4 +8915,4 @@ function schemaErrorsOf(schema, moduleFilePath, source) {
8767
8915
  return errors;
8768
8916
  }
8769
8917
 
8770
- 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, WASM_ENCODED_MIME_TYPES, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, enumLocaleTag, explainIncompatible, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, indexModule, isInsidePatchSetPath, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, 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, shouldEncodeWithWasm, sideFiles, sideSource, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
8918
+ 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, STUDIO_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, WASM_ENCODED_MIME_TYPES, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, enumLocaleTag, explainIncompatible, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, indexModule, isInsidePatchSetPath, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isRepairedOnSave, isSchemaSourceFixError, isSkippedSource, isStudioFix, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, 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, shouldEncodeWithWasm, 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.147.0",
3
+ "version": "0.148.0",
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.147.0"
40
+ "@valbuild/core": "0.148.0"
41
41
  },
42
42
  "files": [
43
43
  "CHANGELOG.md",