@valbuild/server 0.139.0 → 0.139.1
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 +17 -0
- package/dist/declarations/src/ValOps.d.ts +11 -0
- package/dist/declarations/src/ValOpsHttp.d.ts +19 -2
- package/dist/valbuild-server.cjs.dev.js +134 -32
- package/dist/valbuild-server.cjs.prod.js +134 -32
- package/dist/valbuild-server.esm.js +134 -32
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @valbuild/server
|
|
2
2
|
|
|
3
|
+
## 0.139.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#781](https://github.com/valbuild/val/pull/781) [`5ea70ce`](https://github.com/valbuild/val/commit/5ea70cefde3c74961cfa76b4a5f4c333b850b60a) Thanks [@freekh](https://github.com/freekh)! - Every open Studio now shows the same changes, staged the same way, as a reload would.
|
|
8
|
+
|
|
9
|
+
- Staging or unstaging a change in one browser or tab now shows up in your other open Studios right away, without a reload, and publishing from any of them ships the same set.
|
|
10
|
+
- A change you stage before you have made any edit of your own is saved straight away, instead of being kept in the tab until your first edit and lost if you reloaded first.
|
|
11
|
+
- If staging a change fails, the Studio now goes back to showing what is actually saved, instead of keeping the failed change on screen until a reload.
|
|
12
|
+
|
|
13
|
+
This needs the Val Build content service released alongside this version.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`238057d`](https://github.com/valbuild/val/commit/238057d2f17a6bdd22d0bd4316dd8e3a8843a85d), [`8f08418`](https://github.com/valbuild/val/commit/8f0841882d13c991d31dfbb03dd4873c9ab636b4), [`238057d`](https://github.com/valbuild/val/commit/238057d2f17a6bdd22d0bd4316dd8e3a8843a85d), [`db13bed`](https://github.com/valbuild/val/commit/db13bed902f9f9147feb5be07ce47fe8b5b143f9), [`5ea70ce`](https://github.com/valbuild/val/commit/5ea70cefde3c74961cfa76b4a5f4c333b850b60a)]:
|
|
16
|
+
- @valbuild/ui@0.139.1
|
|
17
|
+
- @valbuild/core@0.139.1
|
|
18
|
+
- @valbuild/shared@0.139.1
|
|
19
|
+
|
|
3
20
|
## 0.139.0
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -2,6 +2,7 @@ import { MediaSource, FileMetadata, FileSource, ImageMetadata, ModuleFilePath, P
|
|
|
2
2
|
import { result } from "@valbuild/core/fp";
|
|
3
3
|
import { JSONValue, ParentRef, Patch, PatchError } from "@valbuild/core/patch";
|
|
4
4
|
import type { HistoryError } from "./history/HistoryError.js";
|
|
5
|
+
import type { PatchGroupT } from "@valbuild/shared/internal";
|
|
5
6
|
import type { AffectedFile, StoredModuleVersion, CommitPage, CommitPatch, HistoricalCommit } from "./history/types.js";
|
|
6
7
|
import { ValSyntaxError, ValSyntaxErrorTree } from "./patch/ts/syntax.js";
|
|
7
8
|
import { ParentPatchId } from "@valbuild/core";
|
|
@@ -897,6 +898,14 @@ export type OrderedPatches = {
|
|
|
897
898
|
* reasons.
|
|
898
899
|
*/
|
|
899
900
|
headVersion?: number;
|
|
901
|
+
/**
|
|
902
|
+
* Every group on the branch and which of these patches each holds, read in
|
|
903
|
+
* the same transaction as the list and {@link headVersion}.
|
|
904
|
+
*
|
|
905
|
+
* Absent where the content service has no groups, or predates sending them
|
|
906
|
+
* here. Only `ValOpsHttp` fills it.
|
|
907
|
+
*/
|
|
908
|
+
patchGroups?: PatchGroupT[];
|
|
900
909
|
error?: GenericErrorMessage;
|
|
901
910
|
errors?: PatchReadError[];
|
|
902
911
|
unauthorized?: boolean;
|
|
@@ -912,6 +921,8 @@ export type OrderedPatchesMetadata = {
|
|
|
912
921
|
headPatchId?: OrderedPatches["headPatchId"];
|
|
913
922
|
/** See {@link OrderedPatches.headVersion}. */
|
|
914
923
|
headVersion?: OrderedPatches["headVersion"];
|
|
924
|
+
/** See {@link OrderedPatches.patchGroups}. */
|
|
925
|
+
patchGroups?: OrderedPatches["patchGroups"];
|
|
915
926
|
error?: GenericErrorMessage;
|
|
916
927
|
errors?: OrderedPatches["errors"];
|
|
917
928
|
unauthorized?: boolean;
|
|
@@ -22,6 +22,8 @@ declare const ModuleFilePath: z.ZodString & z.ZodType<ModuleFilePath, string, z.
|
|
|
22
22
|
export declare function ownBranchVersion(headVersions: Record<string, number> | undefined, branch: string | undefined): number | undefined;
|
|
23
23
|
export type PatchGroupMutationResult = {
|
|
24
24
|
patchIds: PatchId[];
|
|
25
|
+
patchGroupId: string | null;
|
|
26
|
+
headVersion?: number;
|
|
25
27
|
status?: undefined;
|
|
26
28
|
error?: undefined;
|
|
27
29
|
} | {
|
|
@@ -337,6 +339,8 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
337
339
|
headVersion?: number;
|
|
338
340
|
/** The newest commit, which is the publish head. */
|
|
339
341
|
headCommitSha?: string;
|
|
342
|
+
/** Who holds what, at `headVersion`. See {@link OrderedPatches.patchGroups}. */
|
|
343
|
+
patchGroups?: PatchGroupT[];
|
|
340
344
|
} | {
|
|
341
345
|
type: "error";
|
|
342
346
|
error: GenericErrorMessage;
|
|
@@ -374,7 +378,12 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
374
378
|
* same stamp the patch row carries — so which client wrote a row stays
|
|
375
379
|
* legible after the fact.
|
|
376
380
|
*/
|
|
377
|
-
stagePatches(
|
|
381
|
+
stagePatches(
|
|
382
|
+
/**
|
|
383
|
+
* The group to stage into, or `undefined` for the caller's open group on
|
|
384
|
+
* this branch — `~` on the content API, which creates it if there is none.
|
|
385
|
+
*/
|
|
386
|
+
patchGroupId: string | undefined,
|
|
378
387
|
/** What the user asked to stage. */
|
|
379
388
|
patchIds: PatchId[],
|
|
380
389
|
/**
|
|
@@ -410,7 +419,9 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
410
419
|
* unstages everything built on top of it within its patch sets, and that is
|
|
411
420
|
* what `withPatchIds` carries.
|
|
412
421
|
*/
|
|
413
|
-
unstagePatches(
|
|
422
|
+
unstagePatches(
|
|
423
|
+
/** See {@link stagePatches}. With no open group, there is nothing to remove. */
|
|
424
|
+
patchGroupId: string | undefined,
|
|
414
425
|
/** What the user asked to unstage. */
|
|
415
426
|
patchIds: PatchId[],
|
|
416
427
|
/** What has to go with it: everything built on top of it. */
|
|
@@ -473,6 +484,12 @@ export declare class ValOpsHttp extends ValOps {
|
|
|
473
484
|
message: string;
|
|
474
485
|
}>;
|
|
475
486
|
private fetchPatchGroups;
|
|
487
|
+
/**
|
|
488
|
+
* Which branch `~` means, where this build knows: the branch it reads
|
|
489
|
+
* `applicable/patches` on, so the group staged into is the group listed.
|
|
490
|
+
* Without one the content API uses the project's own, as it does for reads.
|
|
491
|
+
*/
|
|
492
|
+
private ownGroupBranch;
|
|
476
493
|
private mutatePatchGroup;
|
|
477
494
|
protected saveSourceFilePatch(path: ModuleFilePath, patch: PatchT, patchId: PatchId, parentRef: ParentRefT, authorId: AuthorId | null, sessionId: string | null, patchGroup?: PatchGroupMembership): Promise<SaveSourceFilePatchResult>;
|
|
478
495
|
/**
|
|
@@ -8341,8 +8341,24 @@ const GetApplicablePatches = z.z.object({
|
|
|
8341
8341
|
createdAt: z.z.string(),
|
|
8342
8342
|
applied: z.z.object({
|
|
8343
8343
|
commitSha: z.z.string()
|
|
8344
|
-
}).nullable()
|
|
8344
|
+
}).nullable(),
|
|
8345
|
+
/**
|
|
8346
|
+
* The groups this patch is a member of. Read in the SAME transaction as
|
|
8347
|
+
* the list and its `headVersion`, which is the point: see
|
|
8348
|
+
* `OrderedPatches.patchGroups`. Optional for an older content service.
|
|
8349
|
+
*/
|
|
8350
|
+
patchGroupIds: z.z.array(z.z.string()).optional()
|
|
8345
8351
|
})),
|
|
8352
|
+
/**
|
|
8353
|
+
* The groups on the branch, without their members — those are on the
|
|
8354
|
+
* patches above. Optional for a content service that predates groups.
|
|
8355
|
+
*/
|
|
8356
|
+
patchGroups: z.z.array(z.z.object({
|
|
8357
|
+
patchGroupId: z.z.string(),
|
|
8358
|
+
authorId: z.z.string().nullable(),
|
|
8359
|
+
createdAt: z.z.string(),
|
|
8360
|
+
publishedAt: z.z.string().nullable()
|
|
8361
|
+
})).optional(),
|
|
8346
8362
|
/**
|
|
8347
8363
|
* The head of the chain. See `OrderedPatches.headPatchId`. Optional because
|
|
8348
8364
|
* a content service that predates it sends nothing.
|
|
@@ -8597,8 +8613,11 @@ const PatchGroupsResponse = z.z.object({
|
|
|
8597
8613
|
patchGroups: z.z.array(internal.PatchGroup)
|
|
8598
8614
|
});
|
|
8599
8615
|
const PatchGroupMutationResponse = z.z.object({
|
|
8600
|
-
|
|
8601
|
-
|
|
8616
|
+
/** `null` for an unstage of `~` when the caller has no open group. */
|
|
8617
|
+
patchGroupId: z.z.string().nullable(),
|
|
8618
|
+
patchIds: z.z.array(PatchId),
|
|
8619
|
+
/** The chain version the change committed at. Absent from an older `home`. */
|
|
8620
|
+
headVersion: z.z.number().optional()
|
|
8602
8621
|
});
|
|
8603
8622
|
const NonceResponse = z.z.object({
|
|
8604
8623
|
nonce: z.z.string(),
|
|
@@ -8612,6 +8631,9 @@ const NonceResponse = z.z.object({
|
|
|
8612
8631
|
* in one request share an answer, and the next request asks again.
|
|
8613
8632
|
*/
|
|
8614
8633
|
const PATCH_GROUPS_CACHE_MS = 1000;
|
|
8634
|
+
|
|
8635
|
+
/** The content API's id for "the caller's open group on the branch". */
|
|
8636
|
+
const OWN_OPEN_GROUP = "~";
|
|
8615
8637
|
class ValOpsHttp extends ValOps {
|
|
8616
8638
|
authHeaders;
|
|
8617
8639
|
root;
|
|
@@ -9241,6 +9263,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9241
9263
|
...(allPatchData.headVersion !== undefined ? {
|
|
9242
9264
|
headVersion: allPatchData.headVersion
|
|
9243
9265
|
} : {}),
|
|
9266
|
+
/*
|
|
9267
|
+
* Who holds what, read with the list and its version. On every stat, so
|
|
9268
|
+
* a Studio's view of the groups is never older than its view of the
|
|
9269
|
+
* chain — which is what lets a stage made in another browser show here
|
|
9270
|
+
* without a reload. See `OrderedPatches.patchGroups`.
|
|
9271
|
+
*/
|
|
9272
|
+
...(allPatchData.patchGroups !== undefined ? {
|
|
9273
|
+
patchGroups: allPatchData.patchGroups
|
|
9274
|
+
} : {}),
|
|
9244
9275
|
/*
|
|
9245
9276
|
* The PUBLISH head, which is not `commitSha`.
|
|
9246
9277
|
*
|
|
@@ -9547,11 +9578,43 @@ class ValOpsHttp extends ValOps {
|
|
|
9547
9578
|
});
|
|
9548
9579
|
}
|
|
9549
9580
|
}
|
|
9581
|
+
/*
|
|
9582
|
+
* Membership, folded from the per-patch annotation onto the groups.
|
|
9583
|
+
*
|
|
9584
|
+
* From THIS response and not from `GET /patch-groups`, because only
|
|
9585
|
+
* this one is consistent with the list and the version beside it:
|
|
9586
|
+
* the content service reads all three in one transaction. A group
|
|
9587
|
+
* list fetched separately can be older or newer than the chain it is
|
|
9588
|
+
* shown with — and the Studio decides what an editor sees, and what
|
|
9589
|
+
* a publish ships, from the pair.
|
|
9590
|
+
*
|
|
9591
|
+
* A group holding none of the listed patches is still listed, with
|
|
9592
|
+
* no members: "groups exist and you hold nothing here" is a real
|
|
9593
|
+
* answer, and different from "this content service has no groups".
|
|
9594
|
+
*/
|
|
9595
|
+
let patchGroups;
|
|
9596
|
+
if (data.patchGroups !== undefined) {
|
|
9597
|
+
const members = new Map();
|
|
9598
|
+
for (const patch of data.patches) {
|
|
9599
|
+
for (const groupId of patch.patchGroupIds ?? []) {
|
|
9600
|
+
const list = members.get(groupId) ?? [];
|
|
9601
|
+
list.push(patch.patchId);
|
|
9602
|
+
members.set(groupId, list);
|
|
9603
|
+
}
|
|
9604
|
+
}
|
|
9605
|
+
patchGroups = data.patchGroups.map(group => ({
|
|
9606
|
+
...group,
|
|
9607
|
+
patchIds: members.get(group.patchGroupId) ?? []
|
|
9608
|
+
}));
|
|
9609
|
+
}
|
|
9550
9610
|
return {
|
|
9551
9611
|
commits,
|
|
9552
9612
|
deployments,
|
|
9553
9613
|
patches,
|
|
9554
9614
|
errors,
|
|
9615
|
+
...(patchGroups !== undefined ? {
|
|
9616
|
+
patchGroups
|
|
9617
|
+
} : {}),
|
|
9555
9618
|
...(data.headPatchId !== undefined ? {
|
|
9556
9619
|
headPatchId: data.headPatchId
|
|
9557
9620
|
} : {}),
|
|
@@ -9609,7 +9672,12 @@ class ValOpsHttp extends ValOps {
|
|
|
9609
9672
|
* same stamp the patch row carries — so which client wrote a row stays
|
|
9610
9673
|
* legible after the fact.
|
|
9611
9674
|
*/
|
|
9612
|
-
async stagePatches(
|
|
9675
|
+
async stagePatches(
|
|
9676
|
+
/**
|
|
9677
|
+
* The group to stage into, or `undefined` for the caller's open group on
|
|
9678
|
+
* this branch — `~` on the content API, which creates it if there is none.
|
|
9679
|
+
*/
|
|
9680
|
+
patchGroupId, /** What the user asked to stage. */
|
|
9613
9681
|
patchIds,
|
|
9614
9682
|
/**
|
|
9615
9683
|
* What has to come with it, because the staged patches are written on top
|
|
@@ -9641,10 +9709,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9641
9709
|
// Encoded: patchGroupId arrives in a request body, so an unencoded value
|
|
9642
9710
|
// like "../../commit" would reach a different endpoint carrying this
|
|
9643
9711
|
// project's auth headers.
|
|
9644
|
-
`patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9712
|
+
`patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9645
9713
|
patchIds,
|
|
9646
9714
|
withPatchIds,
|
|
9647
|
-
coreVersion: core.Internal.VERSION.core
|
|
9715
|
+
coreVersion: core.Internal.VERSION.core,
|
|
9716
|
+
...this.ownGroupBranch()
|
|
9648
9717
|
}, authorId);
|
|
9649
9718
|
}
|
|
9650
9719
|
|
|
@@ -9655,13 +9724,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9655
9724
|
* unstages everything built on top of it within its patch sets, and that is
|
|
9656
9725
|
* what `withPatchIds` carries.
|
|
9657
9726
|
*/
|
|
9658
|
-
async unstagePatches(
|
|
9727
|
+
async unstagePatches(/** See {@link stagePatches}. With no open group, there is nothing to remove. */
|
|
9728
|
+
patchGroupId, /** What the user asked to unstage. */
|
|
9659
9729
|
patchIds, /** What has to go with it: everything built on top of it. */
|
|
9660
9730
|
withPatchIds, /** See {@link stagePatches} — the content API's half of the ownership check. */
|
|
9661
9731
|
authorId) {
|
|
9662
|
-
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9732
|
+
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9663
9733
|
patchIds,
|
|
9664
|
-
withPatchIds
|
|
9734
|
+
withPatchIds,
|
|
9735
|
+
...this.ownGroupBranch()
|
|
9665
9736
|
}, authorId);
|
|
9666
9737
|
}
|
|
9667
9738
|
|
|
@@ -9776,6 +9847,17 @@ class ValOpsHttp extends ValOps {
|
|
|
9776
9847
|
};
|
|
9777
9848
|
}
|
|
9778
9849
|
}
|
|
9850
|
+
|
|
9851
|
+
/**
|
|
9852
|
+
* Which branch `~` means, where this build knows: the branch it reads
|
|
9853
|
+
* `applicable/patches` on, so the group staged into is the group listed.
|
|
9854
|
+
* Without one the content API uses the project's own, as it does for reads.
|
|
9855
|
+
*/
|
|
9856
|
+
ownGroupBranch() {
|
|
9857
|
+
return this.git ? {
|
|
9858
|
+
branch: this.git.branch
|
|
9859
|
+
} : {};
|
|
9860
|
+
}
|
|
9779
9861
|
async mutatePatchGroup(method, path, body,
|
|
9780
9862
|
/**
|
|
9781
9863
|
* WHO is asking. Sent as `x-val-profile-id`, which is what the content API
|
|
@@ -9812,7 +9894,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9812
9894
|
const parsed = PatchGroupMutationResponse.safeParse(await res.json());
|
|
9813
9895
|
if (parsed.success) {
|
|
9814
9896
|
return {
|
|
9815
|
-
patchIds: parsed.data.patchIds
|
|
9897
|
+
patchIds: parsed.data.patchIds,
|
|
9898
|
+
patchGroupId: parsed.data.patchGroupId,
|
|
9899
|
+
...(parsed.data.headVersion !== undefined ? {
|
|
9900
|
+
headVersion: parsed.data.headVersion
|
|
9901
|
+
} : {})
|
|
9816
9902
|
};
|
|
9817
9903
|
}
|
|
9818
9904
|
return {
|
|
@@ -13444,7 +13530,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13444
13530
|
return {
|
|
13445
13531
|
status: 200,
|
|
13446
13532
|
json: {
|
|
13447
|
-
patchGroupId,
|
|
13533
|
+
patchGroupId: patchGroupId ?? null,
|
|
13448
13534
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13449
13535
|
}
|
|
13450
13536
|
};
|
|
@@ -13457,14 +13543,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13457
13543
|
}
|
|
13458
13544
|
};
|
|
13459
13545
|
}
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13546
|
+
// A named group is checked here as well as by the content API. No name
|
|
13547
|
+
// is the caller's own open group, which the content API resolves from
|
|
13548
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13549
|
+
if (patchGroupId !== undefined) {
|
|
13550
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13551
|
+
if (refusal !== null) {
|
|
13552
|
+
return {
|
|
13553
|
+
status: refusal.status,
|
|
13554
|
+
json: {
|
|
13555
|
+
message: refusal.message
|
|
13556
|
+
}
|
|
13557
|
+
};
|
|
13558
|
+
}
|
|
13468
13559
|
}
|
|
13469
13560
|
const res = await serverOps.stagePatches(patchGroupId, patchIds, withPatchIds,
|
|
13470
13561
|
// Forwarded so the content API can refuse independently. This server
|
|
@@ -13483,8 +13574,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13483
13574
|
return {
|
|
13484
13575
|
status: 200,
|
|
13485
13576
|
json: {
|
|
13486
|
-
patchGroupId,
|
|
13487
|
-
patchIds: res.patchIds
|
|
13577
|
+
patchGroupId: res.patchGroupId,
|
|
13578
|
+
patchIds: res.patchIds,
|
|
13579
|
+
...(res.headVersion !== undefined ? {
|
|
13580
|
+
headVersion: res.headVersion
|
|
13581
|
+
} : {})
|
|
13488
13582
|
}
|
|
13489
13583
|
};
|
|
13490
13584
|
},
|
|
@@ -13507,7 +13601,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13507
13601
|
return {
|
|
13508
13602
|
status: 200,
|
|
13509
13603
|
json: {
|
|
13510
|
-
patchGroupId,
|
|
13604
|
+
patchGroupId: patchGroupId ?? null,
|
|
13511
13605
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13512
13606
|
}
|
|
13513
13607
|
};
|
|
@@ -13520,14 +13614,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13520
13614
|
}
|
|
13521
13615
|
};
|
|
13522
13616
|
}
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13617
|
+
// A named group is checked here as well as by the content API. No name
|
|
13618
|
+
// is the caller's own open group, which the content API resolves from
|
|
13619
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13620
|
+
if (patchGroupId !== undefined) {
|
|
13621
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13622
|
+
if (refusal !== null) {
|
|
13623
|
+
return {
|
|
13624
|
+
status: refusal.status,
|
|
13625
|
+
json: {
|
|
13626
|
+
message: refusal.message
|
|
13627
|
+
}
|
|
13628
|
+
};
|
|
13629
|
+
}
|
|
13531
13630
|
}
|
|
13532
13631
|
const res = await serverOps.unstagePatches(patchGroupId, patchIds, withPatchIds, auth.id);
|
|
13533
13632
|
if (res.error) {
|
|
@@ -13541,8 +13640,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13541
13640
|
return {
|
|
13542
13641
|
status: 200,
|
|
13543
13642
|
json: {
|
|
13544
|
-
patchGroupId,
|
|
13545
|
-
patchIds: res.patchIds
|
|
13643
|
+
patchGroupId: res.patchGroupId,
|
|
13644
|
+
patchIds: res.patchIds,
|
|
13645
|
+
...(res.headVersion !== undefined ? {
|
|
13646
|
+
headVersion: res.headVersion
|
|
13647
|
+
} : {})
|
|
13546
13648
|
}
|
|
13547
13649
|
};
|
|
13548
13650
|
}
|
|
@@ -8341,8 +8341,24 @@ const GetApplicablePatches = z.z.object({
|
|
|
8341
8341
|
createdAt: z.z.string(),
|
|
8342
8342
|
applied: z.z.object({
|
|
8343
8343
|
commitSha: z.z.string()
|
|
8344
|
-
}).nullable()
|
|
8344
|
+
}).nullable(),
|
|
8345
|
+
/**
|
|
8346
|
+
* The groups this patch is a member of. Read in the SAME transaction as
|
|
8347
|
+
* the list and its `headVersion`, which is the point: see
|
|
8348
|
+
* `OrderedPatches.patchGroups`. Optional for an older content service.
|
|
8349
|
+
*/
|
|
8350
|
+
patchGroupIds: z.z.array(z.z.string()).optional()
|
|
8345
8351
|
})),
|
|
8352
|
+
/**
|
|
8353
|
+
* The groups on the branch, without their members — those are on the
|
|
8354
|
+
* patches above. Optional for a content service that predates groups.
|
|
8355
|
+
*/
|
|
8356
|
+
patchGroups: z.z.array(z.z.object({
|
|
8357
|
+
patchGroupId: z.z.string(),
|
|
8358
|
+
authorId: z.z.string().nullable(),
|
|
8359
|
+
createdAt: z.z.string(),
|
|
8360
|
+
publishedAt: z.z.string().nullable()
|
|
8361
|
+
})).optional(),
|
|
8346
8362
|
/**
|
|
8347
8363
|
* The head of the chain. See `OrderedPatches.headPatchId`. Optional because
|
|
8348
8364
|
* a content service that predates it sends nothing.
|
|
@@ -8597,8 +8613,11 @@ const PatchGroupsResponse = z.z.object({
|
|
|
8597
8613
|
patchGroups: z.z.array(internal.PatchGroup)
|
|
8598
8614
|
});
|
|
8599
8615
|
const PatchGroupMutationResponse = z.z.object({
|
|
8600
|
-
|
|
8601
|
-
|
|
8616
|
+
/** `null` for an unstage of `~` when the caller has no open group. */
|
|
8617
|
+
patchGroupId: z.z.string().nullable(),
|
|
8618
|
+
patchIds: z.z.array(PatchId),
|
|
8619
|
+
/** The chain version the change committed at. Absent from an older `home`. */
|
|
8620
|
+
headVersion: z.z.number().optional()
|
|
8602
8621
|
});
|
|
8603
8622
|
const NonceResponse = z.z.object({
|
|
8604
8623
|
nonce: z.z.string(),
|
|
@@ -8612,6 +8631,9 @@ const NonceResponse = z.z.object({
|
|
|
8612
8631
|
* in one request share an answer, and the next request asks again.
|
|
8613
8632
|
*/
|
|
8614
8633
|
const PATCH_GROUPS_CACHE_MS = 1000;
|
|
8634
|
+
|
|
8635
|
+
/** The content API's id for "the caller's open group on the branch". */
|
|
8636
|
+
const OWN_OPEN_GROUP = "~";
|
|
8615
8637
|
class ValOpsHttp extends ValOps {
|
|
8616
8638
|
authHeaders;
|
|
8617
8639
|
root;
|
|
@@ -9241,6 +9263,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9241
9263
|
...(allPatchData.headVersion !== undefined ? {
|
|
9242
9264
|
headVersion: allPatchData.headVersion
|
|
9243
9265
|
} : {}),
|
|
9266
|
+
/*
|
|
9267
|
+
* Who holds what, read with the list and its version. On every stat, so
|
|
9268
|
+
* a Studio's view of the groups is never older than its view of the
|
|
9269
|
+
* chain — which is what lets a stage made in another browser show here
|
|
9270
|
+
* without a reload. See `OrderedPatches.patchGroups`.
|
|
9271
|
+
*/
|
|
9272
|
+
...(allPatchData.patchGroups !== undefined ? {
|
|
9273
|
+
patchGroups: allPatchData.patchGroups
|
|
9274
|
+
} : {}),
|
|
9244
9275
|
/*
|
|
9245
9276
|
* The PUBLISH head, which is not `commitSha`.
|
|
9246
9277
|
*
|
|
@@ -9547,11 +9578,43 @@ class ValOpsHttp extends ValOps {
|
|
|
9547
9578
|
});
|
|
9548
9579
|
}
|
|
9549
9580
|
}
|
|
9581
|
+
/*
|
|
9582
|
+
* Membership, folded from the per-patch annotation onto the groups.
|
|
9583
|
+
*
|
|
9584
|
+
* From THIS response and not from `GET /patch-groups`, because only
|
|
9585
|
+
* this one is consistent with the list and the version beside it:
|
|
9586
|
+
* the content service reads all three in one transaction. A group
|
|
9587
|
+
* list fetched separately can be older or newer than the chain it is
|
|
9588
|
+
* shown with — and the Studio decides what an editor sees, and what
|
|
9589
|
+
* a publish ships, from the pair.
|
|
9590
|
+
*
|
|
9591
|
+
* A group holding none of the listed patches is still listed, with
|
|
9592
|
+
* no members: "groups exist and you hold nothing here" is a real
|
|
9593
|
+
* answer, and different from "this content service has no groups".
|
|
9594
|
+
*/
|
|
9595
|
+
let patchGroups;
|
|
9596
|
+
if (data.patchGroups !== undefined) {
|
|
9597
|
+
const members = new Map();
|
|
9598
|
+
for (const patch of data.patches) {
|
|
9599
|
+
for (const groupId of patch.patchGroupIds ?? []) {
|
|
9600
|
+
const list = members.get(groupId) ?? [];
|
|
9601
|
+
list.push(patch.patchId);
|
|
9602
|
+
members.set(groupId, list);
|
|
9603
|
+
}
|
|
9604
|
+
}
|
|
9605
|
+
patchGroups = data.patchGroups.map(group => ({
|
|
9606
|
+
...group,
|
|
9607
|
+
patchIds: members.get(group.patchGroupId) ?? []
|
|
9608
|
+
}));
|
|
9609
|
+
}
|
|
9550
9610
|
return {
|
|
9551
9611
|
commits,
|
|
9552
9612
|
deployments,
|
|
9553
9613
|
patches,
|
|
9554
9614
|
errors,
|
|
9615
|
+
...(patchGroups !== undefined ? {
|
|
9616
|
+
patchGroups
|
|
9617
|
+
} : {}),
|
|
9555
9618
|
...(data.headPatchId !== undefined ? {
|
|
9556
9619
|
headPatchId: data.headPatchId
|
|
9557
9620
|
} : {}),
|
|
@@ -9609,7 +9672,12 @@ class ValOpsHttp extends ValOps {
|
|
|
9609
9672
|
* same stamp the patch row carries — so which client wrote a row stays
|
|
9610
9673
|
* legible after the fact.
|
|
9611
9674
|
*/
|
|
9612
|
-
async stagePatches(
|
|
9675
|
+
async stagePatches(
|
|
9676
|
+
/**
|
|
9677
|
+
* The group to stage into, or `undefined` for the caller's open group on
|
|
9678
|
+
* this branch — `~` on the content API, which creates it if there is none.
|
|
9679
|
+
*/
|
|
9680
|
+
patchGroupId, /** What the user asked to stage. */
|
|
9613
9681
|
patchIds,
|
|
9614
9682
|
/**
|
|
9615
9683
|
* What has to come with it, because the staged patches are written on top
|
|
@@ -9641,10 +9709,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9641
9709
|
// Encoded: patchGroupId arrives in a request body, so an unencoded value
|
|
9642
9710
|
// like "../../commit" would reach a different endpoint carrying this
|
|
9643
9711
|
// project's auth headers.
|
|
9644
|
-
`patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9712
|
+
`patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9645
9713
|
patchIds,
|
|
9646
9714
|
withPatchIds,
|
|
9647
|
-
coreVersion: core.Internal.VERSION.core
|
|
9715
|
+
coreVersion: core.Internal.VERSION.core,
|
|
9716
|
+
...this.ownGroupBranch()
|
|
9648
9717
|
}, authorId);
|
|
9649
9718
|
}
|
|
9650
9719
|
|
|
@@ -9655,13 +9724,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9655
9724
|
* unstages everything built on top of it within its patch sets, and that is
|
|
9656
9725
|
* what `withPatchIds` carries.
|
|
9657
9726
|
*/
|
|
9658
|
-
async unstagePatches(
|
|
9727
|
+
async unstagePatches(/** See {@link stagePatches}. With no open group, there is nothing to remove. */
|
|
9728
|
+
patchGroupId, /** What the user asked to unstage. */
|
|
9659
9729
|
patchIds, /** What has to go with it: everything built on top of it. */
|
|
9660
9730
|
withPatchIds, /** See {@link stagePatches} — the content API's half of the ownership check. */
|
|
9661
9731
|
authorId) {
|
|
9662
|
-
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9732
|
+
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9663
9733
|
patchIds,
|
|
9664
|
-
withPatchIds
|
|
9734
|
+
withPatchIds,
|
|
9735
|
+
...this.ownGroupBranch()
|
|
9665
9736
|
}, authorId);
|
|
9666
9737
|
}
|
|
9667
9738
|
|
|
@@ -9776,6 +9847,17 @@ class ValOpsHttp extends ValOps {
|
|
|
9776
9847
|
};
|
|
9777
9848
|
}
|
|
9778
9849
|
}
|
|
9850
|
+
|
|
9851
|
+
/**
|
|
9852
|
+
* Which branch `~` means, where this build knows: the branch it reads
|
|
9853
|
+
* `applicable/patches` on, so the group staged into is the group listed.
|
|
9854
|
+
* Without one the content API uses the project's own, as it does for reads.
|
|
9855
|
+
*/
|
|
9856
|
+
ownGroupBranch() {
|
|
9857
|
+
return this.git ? {
|
|
9858
|
+
branch: this.git.branch
|
|
9859
|
+
} : {};
|
|
9860
|
+
}
|
|
9779
9861
|
async mutatePatchGroup(method, path, body,
|
|
9780
9862
|
/**
|
|
9781
9863
|
* WHO is asking. Sent as `x-val-profile-id`, which is what the content API
|
|
@@ -9812,7 +9894,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9812
9894
|
const parsed = PatchGroupMutationResponse.safeParse(await res.json());
|
|
9813
9895
|
if (parsed.success) {
|
|
9814
9896
|
return {
|
|
9815
|
-
patchIds: parsed.data.patchIds
|
|
9897
|
+
patchIds: parsed.data.patchIds,
|
|
9898
|
+
patchGroupId: parsed.data.patchGroupId,
|
|
9899
|
+
...(parsed.data.headVersion !== undefined ? {
|
|
9900
|
+
headVersion: parsed.data.headVersion
|
|
9901
|
+
} : {})
|
|
9816
9902
|
};
|
|
9817
9903
|
}
|
|
9818
9904
|
return {
|
|
@@ -13444,7 +13530,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13444
13530
|
return {
|
|
13445
13531
|
status: 200,
|
|
13446
13532
|
json: {
|
|
13447
|
-
patchGroupId,
|
|
13533
|
+
patchGroupId: patchGroupId ?? null,
|
|
13448
13534
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13449
13535
|
}
|
|
13450
13536
|
};
|
|
@@ -13457,14 +13543,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13457
13543
|
}
|
|
13458
13544
|
};
|
|
13459
13545
|
}
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13546
|
+
// A named group is checked here as well as by the content API. No name
|
|
13547
|
+
// is the caller's own open group, which the content API resolves from
|
|
13548
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13549
|
+
if (patchGroupId !== undefined) {
|
|
13550
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13551
|
+
if (refusal !== null) {
|
|
13552
|
+
return {
|
|
13553
|
+
status: refusal.status,
|
|
13554
|
+
json: {
|
|
13555
|
+
message: refusal.message
|
|
13556
|
+
}
|
|
13557
|
+
};
|
|
13558
|
+
}
|
|
13468
13559
|
}
|
|
13469
13560
|
const res = await serverOps.stagePatches(patchGroupId, patchIds, withPatchIds,
|
|
13470
13561
|
// Forwarded so the content API can refuse independently. This server
|
|
@@ -13483,8 +13574,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13483
13574
|
return {
|
|
13484
13575
|
status: 200,
|
|
13485
13576
|
json: {
|
|
13486
|
-
patchGroupId,
|
|
13487
|
-
patchIds: res.patchIds
|
|
13577
|
+
patchGroupId: res.patchGroupId,
|
|
13578
|
+
patchIds: res.patchIds,
|
|
13579
|
+
...(res.headVersion !== undefined ? {
|
|
13580
|
+
headVersion: res.headVersion
|
|
13581
|
+
} : {})
|
|
13488
13582
|
}
|
|
13489
13583
|
};
|
|
13490
13584
|
},
|
|
@@ -13507,7 +13601,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13507
13601
|
return {
|
|
13508
13602
|
status: 200,
|
|
13509
13603
|
json: {
|
|
13510
|
-
patchGroupId,
|
|
13604
|
+
patchGroupId: patchGroupId ?? null,
|
|
13511
13605
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13512
13606
|
}
|
|
13513
13607
|
};
|
|
@@ -13520,14 +13614,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13520
13614
|
}
|
|
13521
13615
|
};
|
|
13522
13616
|
}
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13617
|
+
// A named group is checked here as well as by the content API. No name
|
|
13618
|
+
// is the caller's own open group, which the content API resolves from
|
|
13619
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13620
|
+
if (patchGroupId !== undefined) {
|
|
13621
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13622
|
+
if (refusal !== null) {
|
|
13623
|
+
return {
|
|
13624
|
+
status: refusal.status,
|
|
13625
|
+
json: {
|
|
13626
|
+
message: refusal.message
|
|
13627
|
+
}
|
|
13628
|
+
};
|
|
13629
|
+
}
|
|
13531
13630
|
}
|
|
13532
13631
|
const res = await serverOps.unstagePatches(patchGroupId, patchIds, withPatchIds, auth.id);
|
|
13533
13632
|
if (res.error) {
|
|
@@ -13541,8 +13640,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13541
13640
|
return {
|
|
13542
13641
|
status: 200,
|
|
13543
13642
|
json: {
|
|
13544
|
-
patchGroupId,
|
|
13545
|
-
patchIds: res.patchIds
|
|
13643
|
+
patchGroupId: res.patchGroupId,
|
|
13644
|
+
patchIds: res.patchIds,
|
|
13645
|
+
...(res.headVersion !== undefined ? {
|
|
13646
|
+
headVersion: res.headVersion
|
|
13647
|
+
} : {})
|
|
13546
13648
|
}
|
|
13547
13649
|
};
|
|
13548
13650
|
}
|
|
@@ -8304,8 +8304,24 @@ const GetApplicablePatches = z.object({
|
|
|
8304
8304
|
createdAt: z.string(),
|
|
8305
8305
|
applied: z.object({
|
|
8306
8306
|
commitSha: z.string()
|
|
8307
|
-
}).nullable()
|
|
8307
|
+
}).nullable(),
|
|
8308
|
+
/**
|
|
8309
|
+
* The groups this patch is a member of. Read in the SAME transaction as
|
|
8310
|
+
* the list and its `headVersion`, which is the point: see
|
|
8311
|
+
* `OrderedPatches.patchGroups`. Optional for an older content service.
|
|
8312
|
+
*/
|
|
8313
|
+
patchGroupIds: z.array(z.string()).optional()
|
|
8308
8314
|
})),
|
|
8315
|
+
/**
|
|
8316
|
+
* The groups on the branch, without their members — those are on the
|
|
8317
|
+
* patches above. Optional for a content service that predates groups.
|
|
8318
|
+
*/
|
|
8319
|
+
patchGroups: z.array(z.object({
|
|
8320
|
+
patchGroupId: z.string(),
|
|
8321
|
+
authorId: z.string().nullable(),
|
|
8322
|
+
createdAt: z.string(),
|
|
8323
|
+
publishedAt: z.string().nullable()
|
|
8324
|
+
})).optional(),
|
|
8309
8325
|
/**
|
|
8310
8326
|
* The head of the chain. See `OrderedPatches.headPatchId`. Optional because
|
|
8311
8327
|
* a content service that predates it sends nothing.
|
|
@@ -8560,8 +8576,11 @@ const PatchGroupsResponse = z.object({
|
|
|
8560
8576
|
patchGroups: z.array(PatchGroup)
|
|
8561
8577
|
});
|
|
8562
8578
|
const PatchGroupMutationResponse = z.object({
|
|
8563
|
-
|
|
8564
|
-
|
|
8579
|
+
/** `null` for an unstage of `~` when the caller has no open group. */
|
|
8580
|
+
patchGroupId: z.string().nullable(),
|
|
8581
|
+
patchIds: z.array(PatchId),
|
|
8582
|
+
/** The chain version the change committed at. Absent from an older `home`. */
|
|
8583
|
+
headVersion: z.number().optional()
|
|
8565
8584
|
});
|
|
8566
8585
|
const NonceResponse = z.object({
|
|
8567
8586
|
nonce: z.string(),
|
|
@@ -8575,6 +8594,9 @@ const NonceResponse = z.object({
|
|
|
8575
8594
|
* in one request share an answer, and the next request asks again.
|
|
8576
8595
|
*/
|
|
8577
8596
|
const PATCH_GROUPS_CACHE_MS = 1000;
|
|
8597
|
+
|
|
8598
|
+
/** The content API's id for "the caller's open group on the branch". */
|
|
8599
|
+
const OWN_OPEN_GROUP = "~";
|
|
8578
8600
|
class ValOpsHttp extends ValOps {
|
|
8579
8601
|
authHeaders;
|
|
8580
8602
|
root;
|
|
@@ -9204,6 +9226,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9204
9226
|
...(allPatchData.headVersion !== undefined ? {
|
|
9205
9227
|
headVersion: allPatchData.headVersion
|
|
9206
9228
|
} : {}),
|
|
9229
|
+
/*
|
|
9230
|
+
* Who holds what, read with the list and its version. On every stat, so
|
|
9231
|
+
* a Studio's view of the groups is never older than its view of the
|
|
9232
|
+
* chain — which is what lets a stage made in another browser show here
|
|
9233
|
+
* without a reload. See `OrderedPatches.patchGroups`.
|
|
9234
|
+
*/
|
|
9235
|
+
...(allPatchData.patchGroups !== undefined ? {
|
|
9236
|
+
patchGroups: allPatchData.patchGroups
|
|
9237
|
+
} : {}),
|
|
9207
9238
|
/*
|
|
9208
9239
|
* The PUBLISH head, which is not `commitSha`.
|
|
9209
9240
|
*
|
|
@@ -9510,11 +9541,43 @@ class ValOpsHttp extends ValOps {
|
|
|
9510
9541
|
});
|
|
9511
9542
|
}
|
|
9512
9543
|
}
|
|
9544
|
+
/*
|
|
9545
|
+
* Membership, folded from the per-patch annotation onto the groups.
|
|
9546
|
+
*
|
|
9547
|
+
* From THIS response and not from `GET /patch-groups`, because only
|
|
9548
|
+
* this one is consistent with the list and the version beside it:
|
|
9549
|
+
* the content service reads all three in one transaction. A group
|
|
9550
|
+
* list fetched separately can be older or newer than the chain it is
|
|
9551
|
+
* shown with — and the Studio decides what an editor sees, and what
|
|
9552
|
+
* a publish ships, from the pair.
|
|
9553
|
+
*
|
|
9554
|
+
* A group holding none of the listed patches is still listed, with
|
|
9555
|
+
* no members: "groups exist and you hold nothing here" is a real
|
|
9556
|
+
* answer, and different from "this content service has no groups".
|
|
9557
|
+
*/
|
|
9558
|
+
let patchGroups;
|
|
9559
|
+
if (data.patchGroups !== undefined) {
|
|
9560
|
+
const members = new Map();
|
|
9561
|
+
for (const patch of data.patches) {
|
|
9562
|
+
for (const groupId of patch.patchGroupIds ?? []) {
|
|
9563
|
+
const list = members.get(groupId) ?? [];
|
|
9564
|
+
list.push(patch.patchId);
|
|
9565
|
+
members.set(groupId, list);
|
|
9566
|
+
}
|
|
9567
|
+
}
|
|
9568
|
+
patchGroups = data.patchGroups.map(group => ({
|
|
9569
|
+
...group,
|
|
9570
|
+
patchIds: members.get(group.patchGroupId) ?? []
|
|
9571
|
+
}));
|
|
9572
|
+
}
|
|
9513
9573
|
return {
|
|
9514
9574
|
commits,
|
|
9515
9575
|
deployments,
|
|
9516
9576
|
patches,
|
|
9517
9577
|
errors,
|
|
9578
|
+
...(patchGroups !== undefined ? {
|
|
9579
|
+
patchGroups
|
|
9580
|
+
} : {}),
|
|
9518
9581
|
...(data.headPatchId !== undefined ? {
|
|
9519
9582
|
headPatchId: data.headPatchId
|
|
9520
9583
|
} : {}),
|
|
@@ -9572,7 +9635,12 @@ class ValOpsHttp extends ValOps {
|
|
|
9572
9635
|
* same stamp the patch row carries — so which client wrote a row stays
|
|
9573
9636
|
* legible after the fact.
|
|
9574
9637
|
*/
|
|
9575
|
-
async stagePatches(
|
|
9638
|
+
async stagePatches(
|
|
9639
|
+
/**
|
|
9640
|
+
* The group to stage into, or `undefined` for the caller's open group on
|
|
9641
|
+
* this branch — `~` on the content API, which creates it if there is none.
|
|
9642
|
+
*/
|
|
9643
|
+
patchGroupId, /** What the user asked to stage. */
|
|
9576
9644
|
patchIds,
|
|
9577
9645
|
/**
|
|
9578
9646
|
* What has to come with it, because the staged patches are written on top
|
|
@@ -9604,10 +9672,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9604
9672
|
// Encoded: patchGroupId arrives in a request body, so an unencoded value
|
|
9605
9673
|
// like "../../commit" would reach a different endpoint carrying this
|
|
9606
9674
|
// project's auth headers.
|
|
9607
|
-
`patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9675
|
+
`patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9608
9676
|
patchIds,
|
|
9609
9677
|
withPatchIds,
|
|
9610
|
-
coreVersion: Internal.VERSION.core
|
|
9678
|
+
coreVersion: Internal.VERSION.core,
|
|
9679
|
+
...this.ownGroupBranch()
|
|
9611
9680
|
}, authorId);
|
|
9612
9681
|
}
|
|
9613
9682
|
|
|
@@ -9618,13 +9687,15 @@ class ValOpsHttp extends ValOps {
|
|
|
9618
9687
|
* unstages everything built on top of it within its patch sets, and that is
|
|
9619
9688
|
* what `withPatchIds` carries.
|
|
9620
9689
|
*/
|
|
9621
|
-
async unstagePatches(
|
|
9690
|
+
async unstagePatches(/** See {@link stagePatches}. With no open group, there is nothing to remove. */
|
|
9691
|
+
patchGroupId, /** What the user asked to unstage. */
|
|
9622
9692
|
patchIds, /** What has to go with it: everything built on top of it. */
|
|
9623
9693
|
withPatchIds, /** See {@link stagePatches} — the content API's half of the ownership check. */
|
|
9624
9694
|
authorId) {
|
|
9625
|
-
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId)}/patches`, {
|
|
9695
|
+
return this.mutatePatchGroup("DELETE", `patch-groups/${encodeURIComponent(patchGroupId ?? OWN_OPEN_GROUP)}/patches`, {
|
|
9626
9696
|
patchIds,
|
|
9627
|
-
withPatchIds
|
|
9697
|
+
withPatchIds,
|
|
9698
|
+
...this.ownGroupBranch()
|
|
9628
9699
|
}, authorId);
|
|
9629
9700
|
}
|
|
9630
9701
|
|
|
@@ -9739,6 +9810,17 @@ class ValOpsHttp extends ValOps {
|
|
|
9739
9810
|
};
|
|
9740
9811
|
}
|
|
9741
9812
|
}
|
|
9813
|
+
|
|
9814
|
+
/**
|
|
9815
|
+
* Which branch `~` means, where this build knows: the branch it reads
|
|
9816
|
+
* `applicable/patches` on, so the group staged into is the group listed.
|
|
9817
|
+
* Without one the content API uses the project's own, as it does for reads.
|
|
9818
|
+
*/
|
|
9819
|
+
ownGroupBranch() {
|
|
9820
|
+
return this.git ? {
|
|
9821
|
+
branch: this.git.branch
|
|
9822
|
+
} : {};
|
|
9823
|
+
}
|
|
9742
9824
|
async mutatePatchGroup(method, path, body,
|
|
9743
9825
|
/**
|
|
9744
9826
|
* WHO is asking. Sent as `x-val-profile-id`, which is what the content API
|
|
@@ -9775,7 +9857,11 @@ class ValOpsHttp extends ValOps {
|
|
|
9775
9857
|
const parsed = PatchGroupMutationResponse.safeParse(await res.json());
|
|
9776
9858
|
if (parsed.success) {
|
|
9777
9859
|
return {
|
|
9778
|
-
patchIds: parsed.data.patchIds
|
|
9860
|
+
patchIds: parsed.data.patchIds,
|
|
9861
|
+
patchGroupId: parsed.data.patchGroupId,
|
|
9862
|
+
...(parsed.data.headVersion !== undefined ? {
|
|
9863
|
+
headVersion: parsed.data.headVersion
|
|
9864
|
+
} : {})
|
|
9779
9865
|
};
|
|
9780
9866
|
}
|
|
9781
9867
|
return {
|
|
@@ -13407,7 +13493,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13407
13493
|
return {
|
|
13408
13494
|
status: 200,
|
|
13409
13495
|
json: {
|
|
13410
|
-
patchGroupId,
|
|
13496
|
+
patchGroupId: patchGroupId ?? null,
|
|
13411
13497
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13412
13498
|
}
|
|
13413
13499
|
};
|
|
@@ -13420,14 +13506,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13420
13506
|
}
|
|
13421
13507
|
};
|
|
13422
13508
|
}
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13509
|
+
// A named group is checked here as well as by the content API. No name
|
|
13510
|
+
// is the caller's own open group, which the content API resolves from
|
|
13511
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13512
|
+
if (patchGroupId !== undefined) {
|
|
13513
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13514
|
+
if (refusal !== null) {
|
|
13515
|
+
return {
|
|
13516
|
+
status: refusal.status,
|
|
13517
|
+
json: {
|
|
13518
|
+
message: refusal.message
|
|
13519
|
+
}
|
|
13520
|
+
};
|
|
13521
|
+
}
|
|
13431
13522
|
}
|
|
13432
13523
|
const res = await serverOps.stagePatches(patchGroupId, patchIds, withPatchIds,
|
|
13433
13524
|
// Forwarded so the content API can refuse independently. This server
|
|
@@ -13446,8 +13537,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13446
13537
|
return {
|
|
13447
13538
|
status: 200,
|
|
13448
13539
|
json: {
|
|
13449
|
-
patchGroupId,
|
|
13450
|
-
patchIds: res.patchIds
|
|
13540
|
+
patchGroupId: res.patchGroupId,
|
|
13541
|
+
patchIds: res.patchIds,
|
|
13542
|
+
...(res.headVersion !== undefined ? {
|
|
13543
|
+
headVersion: res.headVersion
|
|
13544
|
+
} : {})
|
|
13451
13545
|
}
|
|
13452
13546
|
};
|
|
13453
13547
|
},
|
|
@@ -13470,7 +13564,7 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13470
13564
|
return {
|
|
13471
13565
|
status: 200,
|
|
13472
13566
|
json: {
|
|
13473
|
-
patchGroupId,
|
|
13567
|
+
patchGroupId: patchGroupId ?? null,
|
|
13474
13568
|
patchIds: [...patchIds, ...withPatchIds]
|
|
13475
13569
|
}
|
|
13476
13570
|
};
|
|
@@ -13483,14 +13577,19 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13483
13577
|
}
|
|
13484
13578
|
};
|
|
13485
13579
|
}
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13580
|
+
// A named group is checked here as well as by the content API. No name
|
|
13581
|
+
// is the caller's own open group, which the content API resolves from
|
|
13582
|
+
// the profile it is sent — there is nothing here to check it against.
|
|
13583
|
+
if (patchGroupId !== undefined) {
|
|
13584
|
+
const refusal = await refuseUnlessOwn(serverOps, patchGroupId, auth.id);
|
|
13585
|
+
if (refusal !== null) {
|
|
13586
|
+
return {
|
|
13587
|
+
status: refusal.status,
|
|
13588
|
+
json: {
|
|
13589
|
+
message: refusal.message
|
|
13590
|
+
}
|
|
13591
|
+
};
|
|
13592
|
+
}
|
|
13494
13593
|
}
|
|
13495
13594
|
const res = await serverOps.unstagePatches(patchGroupId, patchIds, withPatchIds, auth.id);
|
|
13496
13595
|
if (res.error) {
|
|
@@ -13504,8 +13603,11 @@ const ValServer = (valModules, options, callbacks) => {
|
|
|
13504
13603
|
return {
|
|
13505
13604
|
status: 200,
|
|
13506
13605
|
json: {
|
|
13507
|
-
patchGroupId,
|
|
13508
|
-
patchIds: res.patchIds
|
|
13606
|
+
patchGroupId: res.patchGroupId,
|
|
13607
|
+
patchIds: res.patchIds,
|
|
13608
|
+
...(res.headVersion !== undefined ? {
|
|
13609
|
+
headVersion: res.headVersion
|
|
13610
|
+
} : {})
|
|
13509
13611
|
}
|
|
13510
13612
|
};
|
|
13511
13613
|
}
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"./package.json": "./package.json"
|
|
17
17
|
},
|
|
18
18
|
"types": "dist/valbuild-server.cjs.d.ts",
|
|
19
|
-
"version": "0.139.
|
|
19
|
+
"version": "0.139.1",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@prettier/sync": "^0.6.1",
|
|
22
22
|
"@types/jest": "^30.0.0",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"typescript": "^6.0.3",
|
|
31
31
|
"zod": "^4.4.3",
|
|
32
32
|
"zod-validation-error": "^5.0.0",
|
|
33
|
-
"@valbuild/core": "0.139.
|
|
34
|
-
"@valbuild/
|
|
35
|
-
"@valbuild/
|
|
33
|
+
"@valbuild/core": "0.139.1",
|
|
34
|
+
"@valbuild/shared": "0.139.1",
|
|
35
|
+
"@valbuild/ui": "0.139.1"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": "^20.19.0 || >=22"
|