@valbuild/shared 0.136.8 → 0.137.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,61 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.137.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#676](https://github.com/valbuild/val/pull/676) [`48ac9f0`](https://github.com/valbuild/val/commit/48ac9f06dd59a5b4ca60232b1ef198cf42f23aa6) Thanks [@freekh](https://github.com/freekh)! - A link-check endpoint, so the Studio can find out whether an external page is
8
+ still there.
9
+
10
+ `POST /external-urls/check` opens up to twenty URLs and reports what answered:
11
+ the final status after redirects, where it ended up, and how long it took. The
12
+ Studio's Check button in the external pages dialog is the only caller, and it
13
+ cannot do this itself — a cross-origin `fetch` from a browser cannot read a
14
+ response status without CORS headers no ordinary site sends.
15
+
16
+ It is an endpoint that makes outbound requests to addresses your content
17
+ supplies, so it is built as one:
18
+
19
+ - It is behind the same session check as every other authenticated route —
20
+ which, in fs mode, means the same anonymous access every other route allows
21
+ on a developer's own machine. It is not a new way in; it is the same door.
22
+ - It refuses to connect to anything that is not a public internet address —
23
+ loopback, link-local, the private ranges, the carrier-grade NAT range, and
24
+ the cloud metadata service that hands out instance credentials. The check is
25
+ on the RESOLVED address rather than the hostname, so a name that resolves to
26
+ `127.0.0.1` is refused, and it runs again at every redirect hop.
27
+ - It answers with one generic message for anything it refused, so the endpoint
28
+ cannot be used to find out what a hostname resolves to from inside your
29
+ network. The reason is logged on the server.
30
+ - `HEAD` first, `GET` only where a site answers 405 or 501 to it, and no
31
+ connection reuse between targets.
32
+
33
+ - [#746](https://github.com/valbuild/val/pull/746) [`e2bfc0e`](https://github.com/valbuild/val/commit/e2bfc0e40383fe25d3ec0473d1032f6264a8939f) Thanks [@freekh](https://github.com/freekh)! - A site hosted on Val (a managed project, with no repository of its own) can now be updated from the Studio. **Settings → Updates** says whether a newer version of the software the site runs on is available, lists the packages that move and their versions, and offers **Update site**.
34
+
35
+ Updating rebuilds the site in the browser on the new versions and publishes it through the ordinary publish, so it is checked before it goes live: if the updated site does not render, nothing changes and the site keeps running the old versions. Unpublished changes are kept and are **not** published by an update. After it goes live, reload the Studio to use the new one.
36
+
37
+ In Safari, which cannot build the site in the Studio's own page, Update site opens a tab that builds and publishes it, the same way a publish does there, and the Studio follows its progress.
38
+
39
+ A Studio that was opened before the site was updated no longer publishes: pressing Publish asks you to reload first, and nothing is saved until you do. Your unpublished changes are kept. Without this, a Studio left open from before an update would have built the site with the previous version of itself.
40
+
41
+ The versions come from the template the project was made from, as the platform last built it. A project whose code lives in a repository is not offered this; its dependencies are updated there.
42
+
43
+ For hosts: `@valbuild/server` forwards `GET` and `POST /api/val/publish-api/update-target` to the content service, beside `/build-target` and `/project-source`.
44
+
45
+ ### Patch Changes
46
+
47
+ - [#748](https://github.com/valbuild/val/pull/748) [`beda04b`](https://github.com/valbuild/val/commit/beda04bd326cbab18ed103e8d43917b16f43bd8c) Thanks [@freekh](https://github.com/freekh)! - A save is no longer refused once because an older answer from the server arrived after a newer one. The content service now reports a version of its list of changes, which goes up with every save, discard and publish. The Studio ignores any answer older than one it already has, including the answer to its own last save, discard or publish, so a discarded change can no longer briefly come back, and a published one can no longer briefly show as unpublished.
48
+ - Updated dependencies [[`43b58f2`](https://github.com/valbuild/val/commit/43b58f2962840883badf91950da2c51b89d1c7cb)]:
49
+ - @valbuild/core@0.137.0
50
+
51
+ ## 0.136.10
52
+
53
+ ### Patch Changes
54
+
55
+ - [#743](https://github.com/valbuild/val/pull/743) [`fef29b0`](https://github.com/valbuild/val/commit/fef29b0d9e826e4e7e63a3ae61553137a3f9d280) Thanks [@freekh](https://github.com/freekh)! - Saving no longer gets stuck on "Changes cannot be saved: something else keeps changing them first" when another editor has an unpublished change and later work has been published.
56
+
57
+ A new change is now written on top of the latest change the content service has registered, which it reports alongside the list of pending changes, instead of the last change this deployment was shown. Since changes can be published independently, those two can differ for good: an unpublished change can sit before published ones that the running deployment already contains. The Studio and the MCP tools both name the reported head, and fall back to the old behaviour against a content service that does not report one.
58
+
3
59
  ## 0.136.8
4
60
 
5
61
  ### Patch Changes
@@ -439,6 +439,8 @@ export declare const Api: {
439
439
  patches: z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>;
440
440
  appliedPatches: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>>;
441
441
  headCommitSha: z.ZodOptional<z.ZodString>;
442
+ headPatchId: z.ZodOptional<z.ZodNullable<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>>;
443
+ headVersion: z.ZodOptional<z.ZodNumber>;
442
444
  commits: z.ZodArray<z.ZodObject<{
443
445
  commitSha: z.ZodString;
444
446
  clientCommitSha: z.ZodNullable<z.ZodString>;
@@ -566,6 +568,55 @@ export declare const Api: {
566
568
  }, z.core.$strip>]>;
567
569
  };
568
570
  };
571
+ /**
572
+ * Open a batch of external URLs and report what answered.
573
+ *
574
+ * A batch rather than one URL because the client checks a list, and a POST
575
+ * rather than a GET because the list is the input and does not belong in a
576
+ * URL or a cache.
577
+ *
578
+ * The server makes outbound requests to addresses this body supplies, which
579
+ * is SSRF by construction - see `linkCheck/addressGuard.ts` for the deny
580
+ * list that makes it safe, and `docs/plans/external-page-link-checks.md` for
581
+ * why it is a deny list on the RESOLVED address.
582
+ */
583
+ "/external-urls/check": {
584
+ POST: {
585
+ req: {
586
+ body: z.ZodObject<{
587
+ urls: z.ZodArray<z.ZodString>;
588
+ }, z.core.$strip>;
589
+ cookies: {
590
+ val_session: z.ZodOptional<z.ZodString>;
591
+ };
592
+ };
593
+ res: z.ZodUnion<readonly [z.ZodObject<{
594
+ status: z.ZodLiteral<401>;
595
+ json: z.ZodObject<{
596
+ message: z.ZodString;
597
+ }, z.core.$strip>;
598
+ }, z.core.$strip>, z.ZodObject<{
599
+ status: z.ZodLiteral<200>;
600
+ json: z.ZodObject<{
601
+ results: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
602
+ kind: z.ZodLiteral<"answered">;
603
+ code: z.ZodNumber;
604
+ finalUrl: z.ZodString;
605
+ ms: z.ZodNumber;
606
+ }, z.core.$strip>, z.ZodObject<{
607
+ kind: z.ZodLiteral<"unreachable">;
608
+ message: z.ZodString;
609
+ }, z.core.$strip>, z.ZodObject<{
610
+ kind: z.ZodLiteral<"timeout">;
611
+ ms: z.ZodNumber;
612
+ }, z.core.$strip>, z.ZodObject<{
613
+ kind: z.ZodLiteral<"skipped">;
614
+ message: z.ZodString;
615
+ }, z.core.$strip>]>>;
616
+ }, z.core.$strip>;
617
+ }, z.core.$strip>]>;
618
+ };
619
+ };
569
620
  "/direct-file-upload-settings": {
570
621
  POST: {
571
622
  req: {
@@ -607,6 +658,7 @@ export declare const Api: {
607
658
  };
608
659
  body: z.ZodOptional<z.ZodObject<{
609
660
  unstagePatchIds: z.ZodOptional<z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>>;
661
+ reportHeadVersion: z.ZodOptional<z.ZodLiteral<true>>;
610
662
  }, z.core.$strip>>;
611
663
  cookies: {
612
664
  val_session: z.ZodOptional<z.ZodString>;
@@ -631,7 +683,10 @@ export declare const Api: {
631
683
  }, z.core.$strip>;
632
684
  }, z.core.$strip>, z.ZodObject<{
633
685
  status: z.ZodLiteral<200>;
634
- json: z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>;
686
+ json: z.ZodUnion<readonly [z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>, z.ZodObject<{
687
+ deleted: z.ZodArray<z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>>;
688
+ headVersion: z.ZodOptional<z.ZodNumber>;
689
+ }, z.core.$strip>]>;
635
690
  }, z.core.$strip>]>;
636
691
  };
637
692
  PUT: {
@@ -691,6 +746,7 @@ export declare const Api: {
691
746
  patchId: z.ZodString & z.ZodType<import("@valbuild/core").PatchId, string, z.core.$ZodTypeInternals<import("@valbuild/core").PatchId, string>>;
692
747
  }, z.core.$strip>]>;
693
748
  patchGroupId: z.ZodOptional<z.ZodString>;
749
+ headVersion: z.ZodOptional<z.ZodNumber>;
694
750
  }, z.core.$strip>;
695
751
  }, z.core.$strip>]>;
696
752
  };
@@ -1130,6 +1186,7 @@ export declare const Api: {
1130
1186
  moduleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.core.$ZodTypeInternals<ModuleFilePath, string>>;
1131
1187
  message: z.ZodString;
1132
1188
  }, z.core.$strip>>>;
1189
+ headVersion: z.ZodOptional<z.ZodNumber>;
1133
1190
  }, z.core.$strip>;
1134
1191
  }, z.core.$strip>, z.ZodObject<{
1135
1192
  status: z.ZodLiteral<403>;
@@ -0,0 +1,24 @@
1
+ import type { PatchId } from "@valbuild/core";
2
+ /**
3
+ * The patch a new write names as its parent: the HEAD of the chain, or `null`
4
+ * when the chain is empty and the write goes on the base.
5
+ *
6
+ * The head the server REPORTS (`headPatchId`) when it reports one, and not the
7
+ * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
8
+ * it — leaves out patches the running deployment already contains, and since
9
+ * patch groups a publish can ship later patches while an earlier one stays
10
+ * pending (another author's change that nets to nothing can never be published
11
+ * at all). The last listed id is then behind the head for good, and the content
12
+ * service accepts a write only on the head, so every save naming it is refused
13
+ * — and a re-sync returns the same list. What fixed it in the Studio before
14
+ * this existed was discarding the other author's change.
15
+ *
16
+ * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
17
+ * that reports no head (`fs` mode, which ignores the parent anyway, or a
18
+ * content service that predates the field), and only then does this fall back
19
+ * to the last listed id.
20
+ *
21
+ * One rule for both writers, the Studio's `PatchSync` and the MCP's
22
+ * `deriveParentRef`, so they cannot come to name different parents.
23
+ */
24
+ export declare function chainHeadOf(reportedHead: PatchId | null | undefined, listed: readonly PatchId[]): PatchId | null;
@@ -6,6 +6,7 @@ export * from "./ValUrls.js";
6
6
  export * from "./ApiRoutes.js";
7
7
  export * from "./schema/compatibility.js";
8
8
  export * from "./newestCommitSha.js";
9
+ export * from "./chainHead.js";
9
10
  export * from "./zod/Patch.js";
10
11
  export * from "./sessionStorage.js";
11
12
  export * from "./SharedValConfig.js";
@@ -115,6 +115,84 @@ declare const projectSourceResponse: z.ZodObject<{
115
115
  publicPaths: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
116
116
  }, z.core.$strip>;
117
117
  export type ProjectSourceResponse = z.infer<typeof projectSourceResponse>;
118
+ /**
119
+ * Whether a managed project can be updated onto its template's current seed,
120
+ * and -- when it is asked with `POST`, the press -- what to build against.
121
+ *
122
+ * An update rebuilds the site as it is on newer dependencies. The loader
123
+ * decides whether there is one; content adds whether the project is managed at
124
+ * all, and the branch head the rebuild is wired at, because an update
125
+ * publishes no content of its own.
126
+ *
127
+ * `target` is a `/build-target` answer whose project half is the SEED's
128
+ * dependency layer, already copied into the project so a publish can name it.
129
+ * Only a `POST` carries it.
130
+ */
131
+ declare const dependencyChange: z.ZodObject<{
132
+ name: z.ZodString;
133
+ section: z.ZodEnum<{
134
+ dependencies: "dependencies";
135
+ devDependencies: "devDependencies";
136
+ }>;
137
+ from: z.ZodNullable<z.ZodString>;
138
+ to: z.ZodNullable<z.ZodString>;
139
+ }, z.core.$strip>;
140
+ declare const updateTargetResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
141
+ status: z.ZodLiteral<"current">;
142
+ template: z.ZodString;
143
+ layerRev: z.ZodString;
144
+ }, z.core.$strip>, z.ZodObject<{
145
+ status: z.ZodLiteral<"available">;
146
+ template: z.ZodString;
147
+ changes: z.ZodArray<z.ZodObject<{
148
+ name: z.ZodString;
149
+ section: z.ZodEnum<{
150
+ dependencies: "dependencies";
151
+ devDependencies: "devDependencies";
152
+ }>;
153
+ from: z.ZodNullable<z.ZodString>;
154
+ to: z.ZodNullable<z.ZodString>;
155
+ }, z.core.$strip>>;
156
+ layerRev: z.ZodObject<{
157
+ from: z.ZodNullable<z.ZodString>;
158
+ to: z.ZodString;
159
+ }, z.core.$strip>;
160
+ packageJson: z.ZodString;
161
+ commit: z.ZodNullable<z.ZodString>;
162
+ branch: z.ZodString;
163
+ target: z.ZodOptional<z.ZodObject<{
164
+ base: z.ZodObject<{
165
+ rev: z.ZodString;
166
+ shellRev: z.ZodString;
167
+ rscShellRev: z.ZodString;
168
+ modules: z.ZodRecord<z.ZodString, z.ZodString>;
169
+ rscModules: z.ZodRecord<z.ZodString, z.ZodString>;
170
+ paths: z.ZodObject<{
171
+ baseFromProject: z.ZodString;
172
+ projectVendorDir: z.ZodString;
173
+ rscVendorBase: z.ZodString;
174
+ rscRuntimeSpecifier: z.ZodString;
175
+ rscRuntimePath: z.ZodString;
176
+ flightServer: z.ZodString;
177
+ serverFnBase: z.ZodString;
178
+ }, z.core.$strip>;
179
+ }, z.core.$strip>;
180
+ project: z.ZodObject<{
181
+ rev: z.ZodNullable<z.ZodString>;
182
+ rsc: z.ZodBoolean;
183
+ modules: z.ZodRecord<z.ZodString, z.ZodString>;
184
+ css: z.ZodRecord<z.ZodString, z.ZodString>;
185
+ workerOnly: z.ZodArray<z.ZodString>;
186
+ }, z.core.$strip>;
187
+ }, z.core.$strip>>;
188
+ }, z.core.$strip>, z.ZodObject<{
189
+ status: z.ZodLiteral<"unavailable">;
190
+ reason: z.ZodString;
191
+ message: z.ZodString;
192
+ }, z.core.$strip>], "status">;
193
+ export type UpdateTargetResponse = z.infer<typeof updateTargetResponse>;
194
+ export type DependencyChange = z.infer<typeof dependencyChange>;
195
+ export declare const parseUpdateTarget: (body: unknown) => UpdateTargetResponse;
118
196
  export declare const parseBuildTarget: (body: unknown) => BuildTargetResponse;
119
197
  export declare const parseProjectSource: (body: unknown) => ProjectSourceResponse;
120
198
  /**
@@ -1503,6 +1503,28 @@ var GenericError = zod.z.object({
1503
1503
  message: zod.z.string()
1504
1504
  });
1505
1505
 
1506
+ /**
1507
+ * What answered when one URL was opened.
1508
+ *
1509
+ * The one place the Studio's `ExternalUrlProbeResult` and the server's
1510
+ * `ProbeResult` meet, so the two cannot drift without this failing to parse.
1511
+ */
1512
+ var ExternalUrlProbeResult = zod.z.union([zod.z.object({
1513
+ kind: zod.z.literal("answered"),
1514
+ code: zod.z.number(),
1515
+ finalUrl: zod.z.string(),
1516
+ ms: zod.z.number()
1517
+ }), zod.z.object({
1518
+ kind: zod.z.literal("unreachable"),
1519
+ message: zod.z.string()
1520
+ }), zod.z.object({
1521
+ kind: zod.z.literal("timeout"),
1522
+ ms: zod.z.number()
1523
+ }), zod.z.object({
1524
+ kind: zod.z.literal("skipped"),
1525
+ message: zod.z.string()
1526
+ })]);
1527
+
1506
1528
  /**
1507
1529
  * Why a publish cannot happen, in a form the Studio can show.
1508
1530
  *
@@ -1984,6 +2006,20 @@ var Api = {
1984
2006
  * carries back to `/save` to say which world it decided against.
1985
2007
  */
1986
2008
  headCommitSha: zod.z.string().optional(),
2009
+ /**
2010
+ * The head of the PATCH CHAIN: the last patch registered on the
2011
+ * branch, published or not. `null` when there is none. What a
2012
+ * new patch names as its parent — see `chainHeadOf`. Optional: a
2013
+ * content service that predates it sends nothing.
2014
+ */
2015
+ headPatchId: PatchId.nullable().optional(),
2016
+ /**
2017
+ * The version of the chain `headPatchId` was read at, bumped by
2018
+ * every write and delete. `/stat` has more than one caller, so
2019
+ * answers can land out of order; a client keeps the head with the
2020
+ * highest version. Optional, for the same reason `headPatchId` is.
2021
+ */
2022
+ headVersion: zod.z.number().optional(),
1987
2023
  commits: zod.z.array(ValCommit),
1988
2024
  /**
1989
2025
  * The publishes the content service knows about.
@@ -2137,6 +2173,42 @@ var Api = {
2137
2173
  })])
2138
2174
  }
2139
2175
  },
2176
+ /**
2177
+ * Open a batch of external URLs and report what answered.
2178
+ *
2179
+ * A batch rather than one URL because the client checks a list, and a POST
2180
+ * rather than a GET because the list is the input and does not belong in a
2181
+ * URL or a cache.
2182
+ *
2183
+ * The server makes outbound requests to addresses this body supplies, which
2184
+ * is SSRF by construction - see `linkCheck/addressGuard.ts` for the deny
2185
+ * list that makes it safe, and `docs/plans/external-page-link-checks.md` for
2186
+ * why it is a deny list on the RESOLVED address.
2187
+ */
2188
+ "/external-urls/check": {
2189
+ POST: {
2190
+ req: {
2191
+ body: zod.z.object({
2192
+ /*
2193
+ * Capped, and the cap is the point rather than a formality: each URL
2194
+ * is an outbound connection, and a body is not allowed to ask this
2195
+ * server to open a thousand of them. The client sends ten at a time.
2196
+ */
2197
+ urls: zod.z.array(zod.z.string()).min(1).max(20)
2198
+ }),
2199
+ cookies: {
2200
+ val_session: zod.z.string().optional()
2201
+ }
2202
+ },
2203
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2204
+ status: zod.z.literal(200),
2205
+ json: zod.z.object({
2206
+ /** Keyed by the URL exactly as it was asked about. */
2207
+ results: zod.z.record(zod.z.string(), ExternalUrlProbeResult)
2208
+ })
2209
+ })])
2210
+ }
2211
+ },
2140
2212
  "/direct-file-upload-settings": {
2141
2213
  POST: {
2142
2214
  req: {
@@ -2188,7 +2260,14 @@ var Api = {
2188
2260
  * groups, and a client that predates this is deleting correctly,
2189
2261
  * just not repairing.
2190
2262
  */
2191
- unstagePatchIds: zod.z.array(PatchId).optional()
2263
+ unstagePatchIds: zod.z.array(PatchId).optional(),
2264
+ /*
2265
+ * Answer with `{ deleted, headVersion }` rather than the bare id
2266
+ * array. Asked for rather than always sent, because the array is
2267
+ * what a Studio bundle from before this parses — and a tab left
2268
+ * open across a deploy still runs one.
2269
+ */
2270
+ reportHeadVersion: zod.z.literal(true).optional()
2192
2271
  }).optional(),
2193
2272
  cookies: {
2194
2273
  val_session: zod.z.string().optional()
@@ -2202,7 +2281,16 @@ var Api = {
2202
2281
  })
2203
2282
  }), zod.z.object({
2204
2283
  status: zod.z.literal(200),
2205
- json: zod.z.array(PatchId)
2284
+ json: zod.z.union([zod.z.array(PatchId), zod.z.object({
2285
+ deleted: zod.z.array(PatchId),
2286
+ /**
2287
+ * The chain version this delete moved the branch to. Absent when
2288
+ * it moved nothing, or the content service does not version its
2289
+ * chain. Compared with `headVersion` on `/stat`: an answer read
2290
+ * before the delete would put the deleted head back.
2291
+ */
2292
+ headVersion: zod.z.number().optional()
2293
+ })])
2206
2294
  })])
2207
2295
  },
2208
2296
  PUT: {
@@ -2250,7 +2338,14 @@ var Api = {
2250
2338
  json: zod.z.object({
2251
2339
  newPatchIds: zod.z.array(PatchId),
2252
2340
  parentRef: ParentRef,
2253
- patchGroupId: zod.z.string().optional()
2341
+ patchGroupId: zod.z.string().optional(),
2342
+ /**
2343
+ * The chain version the last of these patches made, comparable with
2344
+ * `headVersion` on `/stat`. A client keeps the highest version, so a
2345
+ * stat read before this write does not put its parent back behind
2346
+ * it. Absent in `fs` mode and from an older content service.
2347
+ */
2348
+ headVersion: zod.z.number().optional()
2254
2349
  })
2255
2350
  })])
2256
2351
  },
@@ -2685,7 +2780,14 @@ var Api = {
2685
2780
  patchId: PatchId,
2686
2781
  moduleFilePath: ModuleFilePath,
2687
2782
  message: zod.z.string()
2688
- })).optional()
2783
+ })).optional(),
2784
+ /**
2785
+ * The chain version this publish moved the branch to, where the
2786
+ * content service versions its chain and the publish applied
2787
+ * anything. See `headVersion` on `/stat`: a `/stat` answer read
2788
+ * before the publish would put its patches back to pending.
2789
+ */
2790
+ headVersion: zod.z.number().optional()
2689
2791
  })
2690
2792
  }),
2691
2793
  /*
@@ -4060,6 +4162,36 @@ function newestCommitSha(commits) {
4060
4162
  return (_newest$commitSha = (_newest = newest) === null || _newest === void 0 ? void 0 : _newest.commitSha) !== null && _newest$commitSha !== void 0 ? _newest$commitSha : null;
4061
4163
  }
4062
4164
 
4165
+ /**
4166
+ * The patch a new write names as its parent: the HEAD of the chain, or `null`
4167
+ * when the chain is empty and the write goes on the base.
4168
+ *
4169
+ * The head the server REPORTS (`headPatchId`) when it reports one, and not the
4170
+ * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
4171
+ * it — leaves out patches the running deployment already contains, and since
4172
+ * patch groups a publish can ship later patches while an earlier one stays
4173
+ * pending (another author's change that nets to nothing can never be published
4174
+ * at all). The last listed id is then behind the head for good, and the content
4175
+ * service accepts a write only on the head, so every save naming it is refused
4176
+ * — and a re-sync returns the same list. What fixed it in the Studio before
4177
+ * this existed was discarding the other author's change.
4178
+ *
4179
+ * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
4180
+ * that reports no head (`fs` mode, which ignores the parent anyway, or a
4181
+ * content service that predates the field), and only then does this fall back
4182
+ * to the last listed id.
4183
+ *
4184
+ * One rule for both writers, the Studio's `PatchSync` and the MCP's
4185
+ * `deriveParentRef`, so they cannot come to name different parents.
4186
+ */
4187
+ function chainHeadOf(reportedHead, listed) {
4188
+ var _listed;
4189
+ if (reportedHead !== undefined) {
4190
+ return reportedHead;
4191
+ }
4192
+ return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4193
+ }
4194
+
4063
4195
  var SharedValConfig = zod.z.object({
4064
4196
  contentHostUrl: zod.z.string().optional(),
4065
4197
  appHostUrl: zod.z.string().optional(),
@@ -7328,6 +7460,54 @@ var projectSourceResponse = zod.z.object({
7328
7460
  */
7329
7461
  publicPaths: zod.z.array(zod.z.string()).nullable().optional()
7330
7462
  });
7463
+ /**
7464
+ * Whether a managed project can be updated onto its template's current seed,
7465
+ * and -- when it is asked with `POST`, the press -- what to build against.
7466
+ *
7467
+ * An update rebuilds the site as it is on newer dependencies. The loader
7468
+ * decides whether there is one; content adds whether the project is managed at
7469
+ * all, and the branch head the rebuild is wired at, because an update
7470
+ * publishes no content of its own.
7471
+ *
7472
+ * `target` is a `/build-target` answer whose project half is the SEED's
7473
+ * dependency layer, already copied into the project so a publish can name it.
7474
+ * Only a `POST` carries it.
7475
+ */
7476
+ var dependencyChange = zod.z.object({
7477
+ name: zod.z.string(),
7478
+ section: zod.z["enum"](["dependencies", "devDependencies"]),
7479
+ /** `null` for a dependency the template added. */
7480
+ from: zod.z.string().nullable(),
7481
+ /** `null` for a dependency the template dropped: it leaves `package.json`. */
7482
+ to: zod.z.string().nullable()
7483
+ });
7484
+ var updateTargetResponse = zod.z.discriminatedUnion("status", [zod.z.object({
7485
+ status: zod.z.literal("current"),
7486
+ template: zod.z.string(),
7487
+ layerRev: zod.z.string()
7488
+ }), zod.z.object({
7489
+ status: zod.z.literal("available"),
7490
+ template: zod.z.string(),
7491
+ /** Empty when only the dependency layer moved, and no version did. */
7492
+ changes: zod.z.array(dependencyChange),
7493
+ layerRev: zod.z.object({
7494
+ from: zod.z.string().nullable(),
7495
+ to: zod.z.string()
7496
+ }),
7497
+ /** The project's `package.json`, rewritten to the seed's versions. */
7498
+ packageJson: zod.z.string(),
7499
+ /** The branch head to build at; `null` for a project with no commits. */
7500
+ commit: zod.z.string().nullable(),
7501
+ branch: zod.z.string(),
7502
+ target: buildTargetResponse.optional()
7503
+ }), zod.z.object({
7504
+ status: zod.z.literal("unavailable"),
7505
+ reason: zod.z.string(),
7506
+ message: zod.z.string()
7507
+ })]);
7508
+ var parseUpdateTarget = function parseUpdateTarget(body) {
7509
+ return parse(updateTargetResponse, body, "/v1/update-target");
7510
+ };
7331
7511
  var parseBuildTarget = function parseBuildTarget(body) {
7332
7512
  return parse(buildTargetResponse, body, "GET /v1/build-target");
7333
7513
  };
@@ -7454,6 +7634,7 @@ exports.buildFileRefValue = buildFileRefValue;
7454
7634
  exports.buildImageGalleryPatch = buildImageGalleryPatch;
7455
7635
  exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
7456
7636
  exports.buildSearchIndex = buildSearchIndex;
7637
+ exports.chainHeadOf = chainHeadOf;
7457
7638
  exports.checkCompatibility = checkCompatibility;
7458
7639
  exports.chooseEncoded = chooseEncoded;
7459
7640
  exports.concatModulePath = concatModulePath;
@@ -7496,6 +7677,7 @@ exports.parseProjectSource = parseProjectSource;
7496
7677
  exports.parsePromote = parsePromote;
7497
7678
  exports.parsePublishToken = parsePublishToken;
7498
7679
  exports.parseStatus = parseStatus;
7680
+ exports.parseUpdateTarget = parseUpdateTarget;
7499
7681
  exports.parseVerify = parseVerify;
7500
7682
  exports.partitionValidationErrors = partitionValidationErrors;
7501
7683
  exports.performSearch = performSearch;
@@ -1503,6 +1503,28 @@ var GenericError = zod.z.object({
1503
1503
  message: zod.z.string()
1504
1504
  });
1505
1505
 
1506
+ /**
1507
+ * What answered when one URL was opened.
1508
+ *
1509
+ * The one place the Studio's `ExternalUrlProbeResult` and the server's
1510
+ * `ProbeResult` meet, so the two cannot drift without this failing to parse.
1511
+ */
1512
+ var ExternalUrlProbeResult = zod.z.union([zod.z.object({
1513
+ kind: zod.z.literal("answered"),
1514
+ code: zod.z.number(),
1515
+ finalUrl: zod.z.string(),
1516
+ ms: zod.z.number()
1517
+ }), zod.z.object({
1518
+ kind: zod.z.literal("unreachable"),
1519
+ message: zod.z.string()
1520
+ }), zod.z.object({
1521
+ kind: zod.z.literal("timeout"),
1522
+ ms: zod.z.number()
1523
+ }), zod.z.object({
1524
+ kind: zod.z.literal("skipped"),
1525
+ message: zod.z.string()
1526
+ })]);
1527
+
1506
1528
  /**
1507
1529
  * Why a publish cannot happen, in a form the Studio can show.
1508
1530
  *
@@ -1984,6 +2006,20 @@ var Api = {
1984
2006
  * carries back to `/save` to say which world it decided against.
1985
2007
  */
1986
2008
  headCommitSha: zod.z.string().optional(),
2009
+ /**
2010
+ * The head of the PATCH CHAIN: the last patch registered on the
2011
+ * branch, published or not. `null` when there is none. What a
2012
+ * new patch names as its parent — see `chainHeadOf`. Optional: a
2013
+ * content service that predates it sends nothing.
2014
+ */
2015
+ headPatchId: PatchId.nullable().optional(),
2016
+ /**
2017
+ * The version of the chain `headPatchId` was read at, bumped by
2018
+ * every write and delete. `/stat` has more than one caller, so
2019
+ * answers can land out of order; a client keeps the head with the
2020
+ * highest version. Optional, for the same reason `headPatchId` is.
2021
+ */
2022
+ headVersion: zod.z.number().optional(),
1987
2023
  commits: zod.z.array(ValCommit),
1988
2024
  /**
1989
2025
  * The publishes the content service knows about.
@@ -2137,6 +2173,42 @@ var Api = {
2137
2173
  })])
2138
2174
  }
2139
2175
  },
2176
+ /**
2177
+ * Open a batch of external URLs and report what answered.
2178
+ *
2179
+ * A batch rather than one URL because the client checks a list, and a POST
2180
+ * rather than a GET because the list is the input and does not belong in a
2181
+ * URL or a cache.
2182
+ *
2183
+ * The server makes outbound requests to addresses this body supplies, which
2184
+ * is SSRF by construction - see `linkCheck/addressGuard.ts` for the deny
2185
+ * list that makes it safe, and `docs/plans/external-page-link-checks.md` for
2186
+ * why it is a deny list on the RESOLVED address.
2187
+ */
2188
+ "/external-urls/check": {
2189
+ POST: {
2190
+ req: {
2191
+ body: zod.z.object({
2192
+ /*
2193
+ * Capped, and the cap is the point rather than a formality: each URL
2194
+ * is an outbound connection, and a body is not allowed to ask this
2195
+ * server to open a thousand of them. The client sends ten at a time.
2196
+ */
2197
+ urls: zod.z.array(zod.z.string()).min(1).max(20)
2198
+ }),
2199
+ cookies: {
2200
+ val_session: zod.z.string().optional()
2201
+ }
2202
+ },
2203
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2204
+ status: zod.z.literal(200),
2205
+ json: zod.z.object({
2206
+ /** Keyed by the URL exactly as it was asked about. */
2207
+ results: zod.z.record(zod.z.string(), ExternalUrlProbeResult)
2208
+ })
2209
+ })])
2210
+ }
2211
+ },
2140
2212
  "/direct-file-upload-settings": {
2141
2213
  POST: {
2142
2214
  req: {
@@ -2188,7 +2260,14 @@ var Api = {
2188
2260
  * groups, and a client that predates this is deleting correctly,
2189
2261
  * just not repairing.
2190
2262
  */
2191
- unstagePatchIds: zod.z.array(PatchId).optional()
2263
+ unstagePatchIds: zod.z.array(PatchId).optional(),
2264
+ /*
2265
+ * Answer with `{ deleted, headVersion }` rather than the bare id
2266
+ * array. Asked for rather than always sent, because the array is
2267
+ * what a Studio bundle from before this parses — and a tab left
2268
+ * open across a deploy still runs one.
2269
+ */
2270
+ reportHeadVersion: zod.z.literal(true).optional()
2192
2271
  }).optional(),
2193
2272
  cookies: {
2194
2273
  val_session: zod.z.string().optional()
@@ -2202,7 +2281,16 @@ var Api = {
2202
2281
  })
2203
2282
  }), zod.z.object({
2204
2283
  status: zod.z.literal(200),
2205
- json: zod.z.array(PatchId)
2284
+ json: zod.z.union([zod.z.array(PatchId), zod.z.object({
2285
+ deleted: zod.z.array(PatchId),
2286
+ /**
2287
+ * The chain version this delete moved the branch to. Absent when
2288
+ * it moved nothing, or the content service does not version its
2289
+ * chain. Compared with `headVersion` on `/stat`: an answer read
2290
+ * before the delete would put the deleted head back.
2291
+ */
2292
+ headVersion: zod.z.number().optional()
2293
+ })])
2206
2294
  })])
2207
2295
  },
2208
2296
  PUT: {
@@ -2250,7 +2338,14 @@ var Api = {
2250
2338
  json: zod.z.object({
2251
2339
  newPatchIds: zod.z.array(PatchId),
2252
2340
  parentRef: ParentRef,
2253
- patchGroupId: zod.z.string().optional()
2341
+ patchGroupId: zod.z.string().optional(),
2342
+ /**
2343
+ * The chain version the last of these patches made, comparable with
2344
+ * `headVersion` on `/stat`. A client keeps the highest version, so a
2345
+ * stat read before this write does not put its parent back behind
2346
+ * it. Absent in `fs` mode and from an older content service.
2347
+ */
2348
+ headVersion: zod.z.number().optional()
2254
2349
  })
2255
2350
  })])
2256
2351
  },
@@ -2685,7 +2780,14 @@ var Api = {
2685
2780
  patchId: PatchId,
2686
2781
  moduleFilePath: ModuleFilePath,
2687
2782
  message: zod.z.string()
2688
- })).optional()
2783
+ })).optional(),
2784
+ /**
2785
+ * The chain version this publish moved the branch to, where the
2786
+ * content service versions its chain and the publish applied
2787
+ * anything. See `headVersion` on `/stat`: a `/stat` answer read
2788
+ * before the publish would put its patches back to pending.
2789
+ */
2790
+ headVersion: zod.z.number().optional()
2689
2791
  })
2690
2792
  }),
2691
2793
  /*
@@ -4060,6 +4162,36 @@ function newestCommitSha(commits) {
4060
4162
  return (_newest$commitSha = (_newest = newest) === null || _newest === void 0 ? void 0 : _newest.commitSha) !== null && _newest$commitSha !== void 0 ? _newest$commitSha : null;
4061
4163
  }
4062
4164
 
4165
+ /**
4166
+ * The patch a new write names as its parent: the HEAD of the chain, or `null`
4167
+ * when the chain is empty and the write goes on the base.
4168
+ *
4169
+ * The head the server REPORTS (`headPatchId`) when it reports one, and not the
4170
+ * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
4171
+ * it — leaves out patches the running deployment already contains, and since
4172
+ * patch groups a publish can ship later patches while an earlier one stays
4173
+ * pending (another author's change that nets to nothing can never be published
4174
+ * at all). The last listed id is then behind the head for good, and the content
4175
+ * service accepts a write only on the head, so every save naming it is refused
4176
+ * — and a re-sync returns the same list. What fixed it in the Studio before
4177
+ * this existed was discarding the other author's change.
4178
+ *
4179
+ * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
4180
+ * that reports no head (`fs` mode, which ignores the parent anyway, or a
4181
+ * content service that predates the field), and only then does this fall back
4182
+ * to the last listed id.
4183
+ *
4184
+ * One rule for both writers, the Studio's `PatchSync` and the MCP's
4185
+ * `deriveParentRef`, so they cannot come to name different parents.
4186
+ */
4187
+ function chainHeadOf(reportedHead, listed) {
4188
+ var _listed;
4189
+ if (reportedHead !== undefined) {
4190
+ return reportedHead;
4191
+ }
4192
+ return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4193
+ }
4194
+
4063
4195
  var SharedValConfig = zod.z.object({
4064
4196
  contentHostUrl: zod.z.string().optional(),
4065
4197
  appHostUrl: zod.z.string().optional(),
@@ -7328,6 +7460,54 @@ var projectSourceResponse = zod.z.object({
7328
7460
  */
7329
7461
  publicPaths: zod.z.array(zod.z.string()).nullable().optional()
7330
7462
  });
7463
+ /**
7464
+ * Whether a managed project can be updated onto its template's current seed,
7465
+ * and -- when it is asked with `POST`, the press -- what to build against.
7466
+ *
7467
+ * An update rebuilds the site as it is on newer dependencies. The loader
7468
+ * decides whether there is one; content adds whether the project is managed at
7469
+ * all, and the branch head the rebuild is wired at, because an update
7470
+ * publishes no content of its own.
7471
+ *
7472
+ * `target` is a `/build-target` answer whose project half is the SEED's
7473
+ * dependency layer, already copied into the project so a publish can name it.
7474
+ * Only a `POST` carries it.
7475
+ */
7476
+ var dependencyChange = zod.z.object({
7477
+ name: zod.z.string(),
7478
+ section: zod.z["enum"](["dependencies", "devDependencies"]),
7479
+ /** `null` for a dependency the template added. */
7480
+ from: zod.z.string().nullable(),
7481
+ /** `null` for a dependency the template dropped: it leaves `package.json`. */
7482
+ to: zod.z.string().nullable()
7483
+ });
7484
+ var updateTargetResponse = zod.z.discriminatedUnion("status", [zod.z.object({
7485
+ status: zod.z.literal("current"),
7486
+ template: zod.z.string(),
7487
+ layerRev: zod.z.string()
7488
+ }), zod.z.object({
7489
+ status: zod.z.literal("available"),
7490
+ template: zod.z.string(),
7491
+ /** Empty when only the dependency layer moved, and no version did. */
7492
+ changes: zod.z.array(dependencyChange),
7493
+ layerRev: zod.z.object({
7494
+ from: zod.z.string().nullable(),
7495
+ to: zod.z.string()
7496
+ }),
7497
+ /** The project's `package.json`, rewritten to the seed's versions. */
7498
+ packageJson: zod.z.string(),
7499
+ /** The branch head to build at; `null` for a project with no commits. */
7500
+ commit: zod.z.string().nullable(),
7501
+ branch: zod.z.string(),
7502
+ target: buildTargetResponse.optional()
7503
+ }), zod.z.object({
7504
+ status: zod.z.literal("unavailable"),
7505
+ reason: zod.z.string(),
7506
+ message: zod.z.string()
7507
+ })]);
7508
+ var parseUpdateTarget = function parseUpdateTarget(body) {
7509
+ return parse(updateTargetResponse, body, "/v1/update-target");
7510
+ };
7331
7511
  var parseBuildTarget = function parseBuildTarget(body) {
7332
7512
  return parse(buildTargetResponse, body, "GET /v1/build-target");
7333
7513
  };
@@ -7454,6 +7634,7 @@ exports.buildFileRefValue = buildFileRefValue;
7454
7634
  exports.buildImageGalleryPatch = buildImageGalleryPatch;
7455
7635
  exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
7456
7636
  exports.buildSearchIndex = buildSearchIndex;
7637
+ exports.chainHeadOf = chainHeadOf;
7457
7638
  exports.checkCompatibility = checkCompatibility;
7458
7639
  exports.chooseEncoded = chooseEncoded;
7459
7640
  exports.concatModulePath = concatModulePath;
@@ -7496,6 +7677,7 @@ exports.parseProjectSource = parseProjectSource;
7496
7677
  exports.parsePromote = parsePromote;
7497
7678
  exports.parsePublishToken = parsePublishToken;
7498
7679
  exports.parseStatus = parseStatus;
7680
+ exports.parseUpdateTarget = parseUpdateTarget;
7499
7681
  exports.parseVerify = parseVerify;
7500
7682
  exports.partitionValidationErrors = partitionValidationErrors;
7501
7683
  exports.performSearch = performSearch;
@@ -1496,6 +1496,28 @@ var GenericError = z.object({
1496
1496
  message: z.string()
1497
1497
  });
1498
1498
 
1499
+ /**
1500
+ * What answered when one URL was opened.
1501
+ *
1502
+ * The one place the Studio's `ExternalUrlProbeResult` and the server's
1503
+ * `ProbeResult` meet, so the two cannot drift without this failing to parse.
1504
+ */
1505
+ var ExternalUrlProbeResult = z.union([z.object({
1506
+ kind: z.literal("answered"),
1507
+ code: z.number(),
1508
+ finalUrl: z.string(),
1509
+ ms: z.number()
1510
+ }), z.object({
1511
+ kind: z.literal("unreachable"),
1512
+ message: z.string()
1513
+ }), z.object({
1514
+ kind: z.literal("timeout"),
1515
+ ms: z.number()
1516
+ }), z.object({
1517
+ kind: z.literal("skipped"),
1518
+ message: z.string()
1519
+ })]);
1520
+
1499
1521
  /**
1500
1522
  * Why a publish cannot happen, in a form the Studio can show.
1501
1523
  *
@@ -1977,6 +1999,20 @@ var Api = {
1977
1999
  * carries back to `/save` to say which world it decided against.
1978
2000
  */
1979
2001
  headCommitSha: z.string().optional(),
2002
+ /**
2003
+ * The head of the PATCH CHAIN: the last patch registered on the
2004
+ * branch, published or not. `null` when there is none. What a
2005
+ * new patch names as its parent — see `chainHeadOf`. Optional: a
2006
+ * content service that predates it sends nothing.
2007
+ */
2008
+ headPatchId: PatchId.nullable().optional(),
2009
+ /**
2010
+ * The version of the chain `headPatchId` was read at, bumped by
2011
+ * every write and delete. `/stat` has more than one caller, so
2012
+ * answers can land out of order; a client keeps the head with the
2013
+ * highest version. Optional, for the same reason `headPatchId` is.
2014
+ */
2015
+ headVersion: z.number().optional(),
1980
2016
  commits: z.array(ValCommit),
1981
2017
  /**
1982
2018
  * The publishes the content service knows about.
@@ -2130,6 +2166,42 @@ var Api = {
2130
2166
  })])
2131
2167
  }
2132
2168
  },
2169
+ /**
2170
+ * Open a batch of external URLs and report what answered.
2171
+ *
2172
+ * A batch rather than one URL because the client checks a list, and a POST
2173
+ * rather than a GET because the list is the input and does not belong in a
2174
+ * URL or a cache.
2175
+ *
2176
+ * The server makes outbound requests to addresses this body supplies, which
2177
+ * is SSRF by construction - see `linkCheck/addressGuard.ts` for the deny
2178
+ * list that makes it safe, and `docs/plans/external-page-link-checks.md` for
2179
+ * why it is a deny list on the RESOLVED address.
2180
+ */
2181
+ "/external-urls/check": {
2182
+ POST: {
2183
+ req: {
2184
+ body: z.object({
2185
+ /*
2186
+ * Capped, and the cap is the point rather than a formality: each URL
2187
+ * is an outbound connection, and a body is not allowed to ask this
2188
+ * server to open a thousand of them. The client sends ten at a time.
2189
+ */
2190
+ urls: z.array(z.string()).min(1).max(20)
2191
+ }),
2192
+ cookies: {
2193
+ val_session: z.string().optional()
2194
+ }
2195
+ },
2196
+ res: z.union([unauthorizedResponse, z.object({
2197
+ status: z.literal(200),
2198
+ json: z.object({
2199
+ /** Keyed by the URL exactly as it was asked about. */
2200
+ results: z.record(z.string(), ExternalUrlProbeResult)
2201
+ })
2202
+ })])
2203
+ }
2204
+ },
2133
2205
  "/direct-file-upload-settings": {
2134
2206
  POST: {
2135
2207
  req: {
@@ -2181,7 +2253,14 @@ var Api = {
2181
2253
  * groups, and a client that predates this is deleting correctly,
2182
2254
  * just not repairing.
2183
2255
  */
2184
- unstagePatchIds: z.array(PatchId).optional()
2256
+ unstagePatchIds: z.array(PatchId).optional(),
2257
+ /*
2258
+ * Answer with `{ deleted, headVersion }` rather than the bare id
2259
+ * array. Asked for rather than always sent, because the array is
2260
+ * what a Studio bundle from before this parses — and a tab left
2261
+ * open across a deploy still runs one.
2262
+ */
2263
+ reportHeadVersion: z.literal(true).optional()
2185
2264
  }).optional(),
2186
2265
  cookies: {
2187
2266
  val_session: z.string().optional()
@@ -2195,7 +2274,16 @@ var Api = {
2195
2274
  })
2196
2275
  }), z.object({
2197
2276
  status: z.literal(200),
2198
- json: z.array(PatchId)
2277
+ json: z.union([z.array(PatchId), z.object({
2278
+ deleted: z.array(PatchId),
2279
+ /**
2280
+ * The chain version this delete moved the branch to. Absent when
2281
+ * it moved nothing, or the content service does not version its
2282
+ * chain. Compared with `headVersion` on `/stat`: an answer read
2283
+ * before the delete would put the deleted head back.
2284
+ */
2285
+ headVersion: z.number().optional()
2286
+ })])
2199
2287
  })])
2200
2288
  },
2201
2289
  PUT: {
@@ -2243,7 +2331,14 @@ var Api = {
2243
2331
  json: z.object({
2244
2332
  newPatchIds: z.array(PatchId),
2245
2333
  parentRef: ParentRef,
2246
- patchGroupId: z.string().optional()
2334
+ patchGroupId: z.string().optional(),
2335
+ /**
2336
+ * The chain version the last of these patches made, comparable with
2337
+ * `headVersion` on `/stat`. A client keeps the highest version, so a
2338
+ * stat read before this write does not put its parent back behind
2339
+ * it. Absent in `fs` mode and from an older content service.
2340
+ */
2341
+ headVersion: z.number().optional()
2247
2342
  })
2248
2343
  })])
2249
2344
  },
@@ -2678,7 +2773,14 @@ var Api = {
2678
2773
  patchId: PatchId,
2679
2774
  moduleFilePath: ModuleFilePath,
2680
2775
  message: z.string()
2681
- })).optional()
2776
+ })).optional(),
2777
+ /**
2778
+ * The chain version this publish moved the branch to, where the
2779
+ * content service versions its chain and the publish applied
2780
+ * anything. See `headVersion` on `/stat`: a `/stat` answer read
2781
+ * before the publish would put its patches back to pending.
2782
+ */
2783
+ headVersion: z.number().optional()
2682
2784
  })
2683
2785
  }),
2684
2786
  /*
@@ -4053,6 +4155,36 @@ function newestCommitSha(commits) {
4053
4155
  return (_newest$commitSha = (_newest = newest) === null || _newest === void 0 ? void 0 : _newest.commitSha) !== null && _newest$commitSha !== void 0 ? _newest$commitSha : null;
4054
4156
  }
4055
4157
 
4158
+ /**
4159
+ * The patch a new write names as its parent: the HEAD of the chain, or `null`
4160
+ * when the chain is empty and the write goes on the base.
4161
+ *
4162
+ * The head the server REPORTS (`headPatchId`) when it reports one, and not the
4163
+ * last patch it listed. The list — `/applicable/patches`, and `/stat` on top of
4164
+ * it — leaves out patches the running deployment already contains, and since
4165
+ * patch groups a publish can ship later patches while an earlier one stays
4166
+ * pending (another author's change that nets to nothing can never be published
4167
+ * at all). The last listed id is then behind the head for good, and the content
4168
+ * service accepts a write only on the head, so every save naming it is refused
4169
+ * — and a re-sync returns the same list. What fixed it in the Studio before
4170
+ * this existed was discarding the other author's change.
4171
+ *
4172
+ * `null` is a REPORTED empty chain, not "unknown". `undefined` is a server
4173
+ * that reports no head (`fs` mode, which ignores the parent anyway, or a
4174
+ * content service that predates the field), and only then does this fall back
4175
+ * to the last listed id.
4176
+ *
4177
+ * One rule for both writers, the Studio's `PatchSync` and the MCP's
4178
+ * `deriveParentRef`, so they cannot come to name different parents.
4179
+ */
4180
+ function chainHeadOf(reportedHead, listed) {
4181
+ var _listed;
4182
+ if (reportedHead !== undefined) {
4183
+ return reportedHead;
4184
+ }
4185
+ return (_listed = listed[listed.length - 1]) !== null && _listed !== void 0 ? _listed : null;
4186
+ }
4187
+
4056
4188
  var SharedValConfig = z.object({
4057
4189
  contentHostUrl: z.string().optional(),
4058
4190
  appHostUrl: z.string().optional(),
@@ -7321,6 +7453,54 @@ var projectSourceResponse = z.object({
7321
7453
  */
7322
7454
  publicPaths: z.array(z.string()).nullable().optional()
7323
7455
  });
7456
+ /**
7457
+ * Whether a managed project can be updated onto its template's current seed,
7458
+ * and -- when it is asked with `POST`, the press -- what to build against.
7459
+ *
7460
+ * An update rebuilds the site as it is on newer dependencies. The loader
7461
+ * decides whether there is one; content adds whether the project is managed at
7462
+ * all, and the branch head the rebuild is wired at, because an update
7463
+ * publishes no content of its own.
7464
+ *
7465
+ * `target` is a `/build-target` answer whose project half is the SEED's
7466
+ * dependency layer, already copied into the project so a publish can name it.
7467
+ * Only a `POST` carries it.
7468
+ */
7469
+ var dependencyChange = z.object({
7470
+ name: z.string(),
7471
+ section: z["enum"](["dependencies", "devDependencies"]),
7472
+ /** `null` for a dependency the template added. */
7473
+ from: z.string().nullable(),
7474
+ /** `null` for a dependency the template dropped: it leaves `package.json`. */
7475
+ to: z.string().nullable()
7476
+ });
7477
+ var updateTargetResponse = z.discriminatedUnion("status", [z.object({
7478
+ status: z.literal("current"),
7479
+ template: z.string(),
7480
+ layerRev: z.string()
7481
+ }), z.object({
7482
+ status: z.literal("available"),
7483
+ template: z.string(),
7484
+ /** Empty when only the dependency layer moved, and no version did. */
7485
+ changes: z.array(dependencyChange),
7486
+ layerRev: z.object({
7487
+ from: z.string().nullable(),
7488
+ to: z.string()
7489
+ }),
7490
+ /** The project's `package.json`, rewritten to the seed's versions. */
7491
+ packageJson: z.string(),
7492
+ /** The branch head to build at; `null` for a project with no commits. */
7493
+ commit: z.string().nullable(),
7494
+ branch: z.string(),
7495
+ target: buildTargetResponse.optional()
7496
+ }), z.object({
7497
+ status: z.literal("unavailable"),
7498
+ reason: z.string(),
7499
+ message: z.string()
7500
+ })]);
7501
+ var parseUpdateTarget = function parseUpdateTarget(body) {
7502
+ return parse(updateTargetResponse, body, "/v1/update-target");
7503
+ };
7324
7504
  var parseBuildTarget = function parseBuildTarget(body) {
7325
7505
  return parse(buildTargetResponse, body, "GET /v1/build-target");
7326
7506
  };
@@ -7349,4 +7529,4 @@ function parse(schema, body, call) {
7349
7529
  return parsed.data;
7350
7530
  }
7351
7531
 
7352
- 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, PatchGroup, PatchId, PatchJSON, 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, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseDeclare, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseStatus, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
7532
+ 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, PatchGroup, PatchId, PatchJSON, 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, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseDeclare, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseStatus, parseUpdateTarget, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, 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.136.8",
3
+ "version": "0.137.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.136.8"
40
+ "@valbuild/core": "0.137.0"
41
41
  },
42
42
  "files": [
43
43
  "CHANGELOG.md",