riverbankcms 0.21.0 → 0.22.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/dist/acceptance/matrix.mjs +3 -3
- package/dist/cli/index.mjs +2771 -1288
- package/dist/cli/init-docs/content/getting-started.md +17 -1
- package/dist/{entryCommands-DSQZqznu.mjs → entryCommands-Bu-z9kxb.mjs} +1810 -1615
- package/dist/{eventUpsertInputSchema-zCTx0fdF.mjs → eventUpsertInputSchema-xHmuEKwM.mjs} +8 -2
- package/dist/load-config-BRd0EUsF.mjs +4 -0
- package/dist/{load-config-B68gtN45.mjs → load-config-pAPKpeE0.mjs} +3 -3
- package/dist/{plan-CkMANumv.mjs → plan-DUdJbgTV.mjs} +8 -3
- package/dist/scaffold/index.mjs +2 -2
- package/dist/{sdk-event-upsert-CrOuJrxo.mjs → sdk-event-upsert-B3gflo1K.mjs} +803 -166
- package/dist/{src-BENi4Q5k.mjs → src-RfhyMOpX.mjs} +266 -77
- package/dist/testing.mjs +4 -4
- package/package.json +2 -2
- package/dist/load-config-ghkXBKim.mjs +0 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as CliSdkPeerError, t as loadConfigFile } from "./load-config-
|
|
2
|
+
import { $t as pageDesignIntentSchemaVersion, A as getSiteCommandMetadata, B as eventBookingOfferIntentSchema, Dn as getDirectImageUrlForSiteScopedPath, E as parseAndPlanCommandBatchForTypes, Gn as getExtensionForMimeType, Hn as slugifyMediaIdentifier, J as createCommandRef, Jn as normalizeAmbiguousStoragePathForSite, Kn as getMimeType, O as evaluateSiteCommandBatchExposure, P as entryIdentifierForCommand, Qt as pageDesignBoundaryTreatmentSchema, Rn as buildMediaIdentifierFromFilename, T as createDryRunReport, Vn as resolveMediaIdentifier, Wt as buttonStyleSelectionSchema, Xn as FILE_SIZE_LIMITS, Y as createContentEntryRef, Zn as MAX_MANAGEMENT_MEDIA_UPLOAD_BYTES, at as slugPart, en as footerPreferencesSchema, fr as asPageFlowPresetId, k as evaluateSiteCommandExposure, lr as asAppearancePresetId, m as isInternalResolvedLinkValue, n as sdkCourseBookingSettingsSchema, nt as createSiteCommandSiteRef, pr as asSiteStyleId, r as sdkEventOccurrenceInputSchema, s as RESOLVED_INTERNAL_LINK_ENTITY_TYPES, w as stableStringify, wt as themeSchema, x as resolvePageUpsertSeo, y as eventVisibilitySchema } from "./sdk-event-upsert-B3gflo1K.mjs";
|
|
3
|
+
import { Hn as ok, In as unsafeAsStaffMemberId, M as parseFooterCompositionV1, Qt as assertNever, S as parseManagementKey, Sn as unsafeAsEventSeriesId, Tn as unsafeAsOccurrenceId, Vn as err, a as canonicalizeConfiguredBookingVerticalConfig, b as createSecret, c as resolveSiteVerticalBookingConfig, fn as unsafeAsAppointmentResourceId, i as areConfiguredBookingVerticalConfigsEqual, l as DEFAULT_EVENT_TYPE_PRESETS, ln as parseSiteId, m as publicReadFreshnessSeconds, o as parseVerticalId, s as parseVerticalPresetId, sn as parsePageId, t as resolveEventCatalogue, u as EVENT_PRESET_VALUES, x as exposeSecret, xn as unsafeAsEventOfferingId } from "./src-RfhyMOpX.mjs";
|
|
4
|
+
import { n as CliSdkPeerError, t as loadConfigFile } from "./load-config-pAPKpeE0.mjs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import * as fs$1 from "fs/promises";
|
|
@@ -414,6 +414,12 @@ const newsletterUsageLocationSchemas = [z.object({
|
|
|
414
414
|
campaignName: z.string()
|
|
415
415
|
})];
|
|
416
416
|
const mediaUsageLocationSchema = z.discriminatedUnion("kind", [
|
|
417
|
+
z.object({
|
|
418
|
+
kind: z.literal("appointmentService"),
|
|
419
|
+
siteId: identifierSchema,
|
|
420
|
+
serviceId: identifierSchema,
|
|
421
|
+
serviceTitle: z.string()
|
|
422
|
+
}),
|
|
417
423
|
...newsletterUsageLocationSchemas,
|
|
418
424
|
z.object({
|
|
419
425
|
kind: z.literal("pageBlock"),
|
|
@@ -661,6 +667,8 @@ const sdkPullTruncationSchema = z.union([
|
|
|
661
667
|
})
|
|
662
668
|
]);
|
|
663
669
|
const sdkPullPageSchema = z.object({
|
|
670
|
+
seoTitle: z.string().nullable().optional(),
|
|
671
|
+
seoDescription: z.string().nullable().optional(),
|
|
664
672
|
id: sdkIdentifierSchema$1,
|
|
665
673
|
identifier: sdkIdentifierSchema$1,
|
|
666
674
|
title: z.string(),
|
|
@@ -1839,6 +1847,34 @@ function createHttpClient(config) {
|
|
|
1839
1847
|
};
|
|
1840
1848
|
}
|
|
1841
1849
|
|
|
1850
|
+
//#endregion
|
|
1851
|
+
//#region ../management-client/src/appointmentServices.ts
|
|
1852
|
+
function createAppointmentServiceOperations(http) {
|
|
1853
|
+
return {
|
|
1854
|
+
async list() {
|
|
1855
|
+
const result = await http.get("/appointment-services");
|
|
1856
|
+
return result.services;
|
|
1857
|
+
},
|
|
1858
|
+
async get(serviceId) {
|
|
1859
|
+
try {
|
|
1860
|
+
const result = await http.get(`/appointment-services/${encodeURIComponent(serviceId)}`);
|
|
1861
|
+
return result.service;
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
if (is404Error(error)) return null;
|
|
1864
|
+
throw error;
|
|
1865
|
+
}
|
|
1866
|
+
},
|
|
1867
|
+
async create(input) {
|
|
1868
|
+
const result = await http.post("/appointment-services", input, "write");
|
|
1869
|
+
return result.service;
|
|
1870
|
+
},
|
|
1871
|
+
async update(serviceId, input) {
|
|
1872
|
+
const result = await http.patch(`/appointment-services/${encodeURIComponent(serviceId)}`, input);
|
|
1873
|
+
return result.service;
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1842
1878
|
//#endregion
|
|
1843
1879
|
//#region ../management-client/src/entries.ts
|
|
1844
1880
|
/**
|
|
@@ -1907,6 +1943,8 @@ function mapPulledPage(page) {
|
|
|
1907
1943
|
id: page.id,
|
|
1908
1944
|
identifier: page.identifier,
|
|
1909
1945
|
title: page.title,
|
|
1946
|
+
...page.seoTitle !== undefined ? { seoTitle: page.seoTitle } : {},
|
|
1947
|
+
...page.seoDescription !== undefined ? { seoDescription: page.seoDescription } : {},
|
|
1910
1948
|
path: page.path,
|
|
1911
1949
|
status: page.status,
|
|
1912
1950
|
hasUnpublishedChanges: page.hasUnpublishedChanges,
|
|
@@ -2617,6 +2655,7 @@ function createManagementClient(config) {
|
|
|
2617
2655
|
}
|
|
2618
2656
|
const http = createHttpClient(config);
|
|
2619
2657
|
return {
|
|
2658
|
+
appointmentServices: createAppointmentServiceOperations(http),
|
|
2620
2659
|
entries: createEntryOperations(http),
|
|
2621
2660
|
pages: createPageOperations(http),
|
|
2622
2661
|
blocks: createBlockOperations(http),
|
|
@@ -4434,6 +4473,31 @@ function parsePublicProductCategorySelector(value) {
|
|
|
4434
4473
|
return undefined;
|
|
4435
4474
|
}
|
|
4436
4475
|
|
|
4476
|
+
//#endregion
|
|
4477
|
+
//#region ../api/src/apiEndpointTypes.ts
|
|
4478
|
+
function allowedDemoMutationPolicy() {
|
|
4479
|
+
return {
|
|
4480
|
+
classification: "allowed",
|
|
4481
|
+
publicMessage: ""
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4484
|
+
function blockedDemoMutationPolicy(classification, publicMessage) {
|
|
4485
|
+
if (publicMessage.trim() === "") {
|
|
4486
|
+
throw new Error(`Demo mutation policy ${classification} requires publicMessage.`);
|
|
4487
|
+
}
|
|
4488
|
+
return {
|
|
4489
|
+
classification,
|
|
4490
|
+
publicMessage
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
//#endregion
|
|
4495
|
+
//#region ../api/src/demoMutationPolicies.ts
|
|
4496
|
+
const DEMO_MUTATION_BLOCKED_HIDDEN = blockedDemoMutationPolicy("blocked-hidden", "This action is not available in demo sites.");
|
|
4497
|
+
const DEMO_MUTATION_BLOCKED_DISABLED = blockedDemoMutationPolicy("blocked-disabled", "This setting is disabled in demo sites.");
|
|
4498
|
+
const DEMO_MUTATION_INTERNAL_ONLY = blockedDemoMutationPolicy("internal-only", "This internal operation is not available in demo sites.");
|
|
4499
|
+
const DEMO_MUTATION_ALLOWED = allowedDemoMutationPolicy();
|
|
4500
|
+
|
|
4437
4501
|
//#endregion
|
|
4438
4502
|
//#region ../api/src/defineEndpointDefinitions.ts
|
|
4439
4503
|
/**
|
|
@@ -4444,6 +4508,50 @@ function defineEndpointDefinitions(definitions) {
|
|
|
4444
4508
|
return definitions;
|
|
4445
4509
|
}
|
|
4446
4510
|
|
|
4511
|
+
//#endregion
|
|
4512
|
+
//#region ../api/src/bookingAttentionEndpointDefinitions.ts
|
|
4513
|
+
const BOOKING_ATTENTION_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
4514
|
+
dismissBookingAttention: {
|
|
4515
|
+
demoMutation: DEMO_MUTATION_ALLOWED,
|
|
4516
|
+
path: "/sites/{siteId}/bookings/attention/dismiss",
|
|
4517
|
+
method: "POST",
|
|
4518
|
+
tags: ["site-{siteId}"],
|
|
4519
|
+
auth: "user",
|
|
4520
|
+
responseKind: "json",
|
|
4521
|
+
errors: [
|
|
4522
|
+
"validation:invalid_input",
|
|
4523
|
+
"auth:forbidden",
|
|
4524
|
+
"server:internal_error"
|
|
4525
|
+
]
|
|
4526
|
+
},
|
|
4527
|
+
resendWaitlistInvite: {
|
|
4528
|
+
path: "/sites/{siteId}/bookings/waitlist-entries/{entryId}/invite",
|
|
4529
|
+
method: "POST",
|
|
4530
|
+
tags: ["site-{siteId}"],
|
|
4531
|
+
auth: "user",
|
|
4532
|
+
responseKind: "json",
|
|
4533
|
+
errors: [
|
|
4534
|
+
"validation:invalid_input",
|
|
4535
|
+
"resource:not_found",
|
|
4536
|
+
"resource:conflict",
|
|
4537
|
+
"server:internal_error"
|
|
4538
|
+
]
|
|
4539
|
+
},
|
|
4540
|
+
resendAppointmentPaymentRequest: {
|
|
4541
|
+
demoMutation: DEMO_MUTATION_BLOCKED_DISABLED,
|
|
4542
|
+
path: "/sites/{siteId}/bookings/appointment-payment-requests/{requestId}/resend",
|
|
4543
|
+
method: "POST",
|
|
4544
|
+
tags: ["site-{siteId}"],
|
|
4545
|
+
auth: "user",
|
|
4546
|
+
responseKind: "json",
|
|
4547
|
+
errors: [
|
|
4548
|
+
"validation:invalid_input",
|
|
4549
|
+
"auth:forbidden",
|
|
4550
|
+
"server:internal_error"
|
|
4551
|
+
]
|
|
4552
|
+
}
|
|
4553
|
+
});
|
|
4554
|
+
|
|
4447
4555
|
//#endregion
|
|
4448
4556
|
//#region ../api/src/previewAuthEndpointDefinitions.ts
|
|
4449
4557
|
const PREVIEW_AUTH_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
@@ -4470,31 +4578,6 @@ const PREVIEW_AUTH_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
4470
4578
|
}
|
|
4471
4579
|
});
|
|
4472
4580
|
|
|
4473
|
-
//#endregion
|
|
4474
|
-
//#region ../api/src/apiEndpointTypes.ts
|
|
4475
|
-
function allowedDemoMutationPolicy() {
|
|
4476
|
-
return {
|
|
4477
|
-
classification: "allowed",
|
|
4478
|
-
publicMessage: ""
|
|
4479
|
-
};
|
|
4480
|
-
}
|
|
4481
|
-
function blockedDemoMutationPolicy(classification, publicMessage) {
|
|
4482
|
-
if (publicMessage.trim() === "") {
|
|
4483
|
-
throw new Error(`Demo mutation policy ${classification} requires publicMessage.`);
|
|
4484
|
-
}
|
|
4485
|
-
return {
|
|
4486
|
-
classification,
|
|
4487
|
-
publicMessage
|
|
4488
|
-
};
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
|
-
//#endregion
|
|
4492
|
-
//#region ../api/src/demoMutationPolicies.ts
|
|
4493
|
-
const DEMO_MUTATION_BLOCKED_HIDDEN = blockedDemoMutationPolicy("blocked-hidden", "This action is not available in demo sites.");
|
|
4494
|
-
const DEMO_MUTATION_BLOCKED_DISABLED = blockedDemoMutationPolicy("blocked-disabled", "This setting is disabled in demo sites.");
|
|
4495
|
-
const DEMO_MUTATION_INTERNAL_ONLY = blockedDemoMutationPolicy("internal-only", "This internal operation is not available in demo sites.");
|
|
4496
|
-
const DEMO_MUTATION_ALLOWED = allowedDemoMutationPolicy();
|
|
4497
|
-
|
|
4498
4581
|
//#endregion
|
|
4499
4582
|
//#region ../api/src/siteLifecycleEndpoints.ts
|
|
4500
4583
|
const SITE_LIFECYCLE_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
@@ -4827,6 +4910,42 @@ const SDK_ENTRY_READ_ENDPOINTS = {
|
|
|
4827
4910
|
}
|
|
4828
4911
|
};
|
|
4829
4912
|
|
|
4913
|
+
//#endregion
|
|
4914
|
+
//#region ../api/src/appointmentRequestEndpointDefinitions.ts
|
|
4915
|
+
const APPOINTMENT_REQUEST_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
4916
|
+
approveAppointmentRequest: {
|
|
4917
|
+
path: "/sites/{siteId}/bookings/appointments/{appointmentId}/approve",
|
|
4918
|
+
method: "POST",
|
|
4919
|
+
auth: "user",
|
|
4920
|
+
responseKind: "json",
|
|
4921
|
+
errors: [
|
|
4922
|
+
"validation:invalid_input",
|
|
4923
|
+
"resource:not_found",
|
|
4924
|
+
"auth:forbidden",
|
|
4925
|
+
"server:internal_error"
|
|
4926
|
+
]
|
|
4927
|
+
},
|
|
4928
|
+
declineAppointmentRequest: {
|
|
4929
|
+
path: "/sites/{siteId}/bookings/appointments/{appointmentId}/decline",
|
|
4930
|
+
method: "POST",
|
|
4931
|
+
auth: "user",
|
|
4932
|
+
responseKind: "json",
|
|
4933
|
+
errors: [
|
|
4934
|
+
"validation:invalid_input",
|
|
4935
|
+
"resource:not_found",
|
|
4936
|
+
"auth:forbidden",
|
|
4937
|
+
"server:internal_error"
|
|
4938
|
+
]
|
|
4939
|
+
},
|
|
4940
|
+
listWaitingAppointmentRequests: {
|
|
4941
|
+
path: "/sites/{siteId}/bookings/appointments/requests",
|
|
4942
|
+
method: "GET",
|
|
4943
|
+
auth: "user",
|
|
4944
|
+
responseKind: "json",
|
|
4945
|
+
errors: ["auth:forbidden", "server:internal_error"]
|
|
4946
|
+
}
|
|
4947
|
+
});
|
|
4948
|
+
|
|
4830
4949
|
//#endregion
|
|
4831
4950
|
//#region ../api/src/appointmentServiceCategoryEndpoints.ts
|
|
4832
4951
|
const APPOINTMENT_SERVICE_CATEGORY_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
@@ -4911,6 +5030,7 @@ const APPOINTMENT_SERVICE_CATEGORY_ENDPOINT_DEFINITIONS = defineEndpointDefiniti
|
|
|
4911
5030
|
//#region ../api/src/appointmentEndpointDefinitions.ts
|
|
4912
5031
|
const APPOINTMENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
4913
5032
|
...APPOINTMENT_SERVICE_CATEGORY_ENDPOINT_DEFINITIONS,
|
|
5033
|
+
...APPOINTMENT_REQUEST_ENDPOINT_DEFINITIONS,
|
|
4914
5034
|
listAppointmentResources: {
|
|
4915
5035
|
path: "/sites/{siteId}/bookings/resources",
|
|
4916
5036
|
method: "GET",
|
|
@@ -6276,6 +6396,47 @@ const SDK_EVENT_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
6276
6396
|
}
|
|
6277
6397
|
});
|
|
6278
6398
|
|
|
6399
|
+
//#endregion
|
|
6400
|
+
//#region ../api/src/sdkAppointmentServiceEndpointDefinitions.ts
|
|
6401
|
+
const SDK_APPOINTMENT_SERVICE_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
6402
|
+
sdkListAppointmentServices: {
|
|
6403
|
+
path: "/sdk/{siteId}/appointment-services",
|
|
6404
|
+
method: "GET",
|
|
6405
|
+
auth: "service",
|
|
6406
|
+
responseKind: "json",
|
|
6407
|
+
errors: ["server:internal_error"]
|
|
6408
|
+
},
|
|
6409
|
+
sdkCreateAppointmentService: {
|
|
6410
|
+
path: "/sdk/{siteId}/appointment-services",
|
|
6411
|
+
method: "POST",
|
|
6412
|
+
auth: "service",
|
|
6413
|
+
responseKind: "json",
|
|
6414
|
+
errors: [
|
|
6415
|
+
"validation:invalid_input",
|
|
6416
|
+
"resource:not_found",
|
|
6417
|
+
"server:internal_error"
|
|
6418
|
+
]
|
|
6419
|
+
},
|
|
6420
|
+
sdkGetAppointmentService: {
|
|
6421
|
+
path: "/sdk/{siteId}/appointment-services/{serviceId}",
|
|
6422
|
+
method: "GET",
|
|
6423
|
+
auth: "service",
|
|
6424
|
+
responseKind: "json",
|
|
6425
|
+
errors: ["resource:not_found", "server:internal_error"]
|
|
6426
|
+
},
|
|
6427
|
+
sdkUpdateAppointmentService: {
|
|
6428
|
+
path: "/sdk/{siteId}/appointment-services/{serviceId}",
|
|
6429
|
+
method: "PATCH",
|
|
6430
|
+
auth: "service",
|
|
6431
|
+
responseKind: "json",
|
|
6432
|
+
errors: [
|
|
6433
|
+
"validation:invalid_input",
|
|
6434
|
+
"resource:not_found",
|
|
6435
|
+
"server:internal_error"
|
|
6436
|
+
]
|
|
6437
|
+
}
|
|
6438
|
+
});
|
|
6439
|
+
|
|
6279
6440
|
//#endregion
|
|
6280
6441
|
//#region ../api/src/sdkPageDesignEndpointDefinitions.ts
|
|
6281
6442
|
/**
|
|
@@ -6883,6 +7044,14 @@ const GIFTING_ENDPOINT_DEFINITIONS = defineEndpointDefinitions({
|
|
|
6883
7044
|
auth: "public",
|
|
6884
7045
|
responseKind: "json"
|
|
6885
7046
|
},
|
|
7047
|
+
previewGiftCardClaimPublic: {
|
|
7048
|
+
path: "/public/sites/{siteId}/gifting/claim",
|
|
7049
|
+
method: "GET",
|
|
7050
|
+
tags: [TAG_TEMPLATES.publicGifting],
|
|
7051
|
+
errors: [],
|
|
7052
|
+
auth: "public",
|
|
7053
|
+
responseKind: "json"
|
|
7054
|
+
},
|
|
6886
7055
|
claimGiftCardPublic: {
|
|
6887
7056
|
path: "/public/sites/{siteId}/gifting/claim",
|
|
6888
7057
|
method: "POST",
|
|
@@ -10631,6 +10800,14 @@ const ENDPOINT_DEFINITIONS = {
|
|
|
10631
10800
|
auth: "public",
|
|
10632
10801
|
responseKind: "text"
|
|
10633
10802
|
},
|
|
10803
|
+
getPublicAppointmentCalendarIcs: {
|
|
10804
|
+
path: "/public/sites/{siteId}/bookings/appointments/{token}/appointment.ics",
|
|
10805
|
+
method: "GET",
|
|
10806
|
+
tags: ["public-appointment-calendar-{siteId}"],
|
|
10807
|
+
auth: "public",
|
|
10808
|
+
responseKind: "text",
|
|
10809
|
+
errors: ["resource:not_found", "server:internal_error"]
|
|
10810
|
+
},
|
|
10634
10811
|
getPublicBookingCalendarFeedIcs: {
|
|
10635
10812
|
path: "/public/sites/{siteId}/bookings/calendar-feeds/{token}/feed.ics",
|
|
10636
10813
|
method: "GET",
|
|
@@ -10967,19 +11144,7 @@ const ENDPOINT_DEFINITIONS = {
|
|
|
10967
11144
|
responseKind: "json",
|
|
10968
11145
|
errors: ["validation:invalid_input", "server:internal_error"]
|
|
10969
11146
|
},
|
|
10970
|
-
|
|
10971
|
-
path: "/sites/{siteId}/bookings/waitlist-entries/{entryId}/invite",
|
|
10972
|
-
method: "POST",
|
|
10973
|
-
tags: ["site-{siteId}"],
|
|
10974
|
-
auth: "user",
|
|
10975
|
-
responseKind: "json",
|
|
10976
|
-
errors: [
|
|
10977
|
-
"validation:invalid_input",
|
|
10978
|
-
"resource:not_found",
|
|
10979
|
-
"resource:conflict",
|
|
10980
|
-
"server:internal_error"
|
|
10981
|
-
]
|
|
10982
|
-
},
|
|
11147
|
+
...BOOKING_ATTENTION_ENDPOINT_DEFINITIONS,
|
|
10983
11148
|
cancelWaitlistEntry: {
|
|
10984
11149
|
path: "/sites/{siteId}/bookings/waitlist-entries/{entryId}/cancel",
|
|
10985
11150
|
method: "POST",
|
|
@@ -12319,6 +12484,7 @@ const ENDPOINT_DEFINITIONS = {
|
|
|
12319
12484
|
auth: "service",
|
|
12320
12485
|
responseKind: "json"
|
|
12321
12486
|
},
|
|
12487
|
+
...SDK_APPOINTMENT_SERVICE_ENDPOINT_DEFINITIONS,
|
|
12322
12488
|
sdkUpsertEventCategory: {
|
|
12323
12489
|
path: "/sdk/{siteId}/event-categories",
|
|
12324
12490
|
method: "POST",
|
|
@@ -13834,1652 +14000,1647 @@ function createBearerAPIClient(token, baseUrl) {
|
|
|
13834
14000
|
}
|
|
13835
14001
|
|
|
13836
14002
|
//#endregion
|
|
13837
|
-
//#region src/
|
|
13838
|
-
|
|
13839
|
-
|
|
14003
|
+
//#region ../api/src/appointmentServiceVisibility.ts
|
|
14004
|
+
const APPOINTMENT_SERVICE_VISIBILITY_VALUES = ["public", "unlisted"];
|
|
14005
|
+
function isAppointmentServiceVisibility(value) {
|
|
14006
|
+
return value === "public" || value === "unlisted";
|
|
13840
14007
|
}
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
14008
|
+
|
|
14009
|
+
//#endregion
|
|
14010
|
+
//#region ../api/src/appointmentServiceLocation.ts
|
|
14011
|
+
const APPOINTMENT_SERVICE_LOCATION_TYPE_VALUES = [
|
|
14012
|
+
"virtual",
|
|
14013
|
+
"in_person",
|
|
14014
|
+
"hybrid"
|
|
14015
|
+
];
|
|
14016
|
+
function isAppointmentServiceLocationType(value) {
|
|
14017
|
+
return value === "virtual" || value === "in_person" || value === "hybrid";
|
|
13845
14018
|
}
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
13853
|
-
|
|
13854
|
-
|
|
13855
|
-
|
|
13856
|
-
|
|
13857
|
-
|
|
13858
|
-
|
|
13859
|
-
})),
|
|
13860
|
-
...sync.settings.error ? [{
|
|
13861
|
-
area: "settings",
|
|
13862
|
-
message: sync.settings.error
|
|
13863
|
-
}] : [],
|
|
13864
|
-
...!isConfigSync(sync) ? sync.pages.errors.map((message) => ({
|
|
13865
|
-
area: "pages",
|
|
13866
|
-
message
|
|
13867
|
-
})) : [],
|
|
13868
|
-
...!isConfigSync(sync) ? sync.entries.errors.map((message) => ({
|
|
13869
|
-
area: "entries",
|
|
13870
|
-
message
|
|
13871
|
-
})) : [],
|
|
13872
|
-
...!isConfigSync(sync) ? sync.navigationMenus.errors.map((message) => ({
|
|
13873
|
-
area: "navigationMenus",
|
|
13874
|
-
message
|
|
13875
|
-
})) : []
|
|
13876
|
-
];
|
|
13877
|
-
}
|
|
13878
|
-
function collectPushConfigSyncWarnings(sync) {
|
|
13879
|
-
return [
|
|
13880
|
-
...sync.contentTypes.warnings.map((message) => ({
|
|
13881
|
-
area: "contentTypes",
|
|
13882
|
-
message
|
|
13883
|
-
})),
|
|
13884
|
-
...sync.customBlocks.warnings.map((message) => ({
|
|
13885
|
-
area: "customBlocks",
|
|
13886
|
-
message
|
|
13887
|
-
})),
|
|
13888
|
-
...sync.templateBlocks.warnings.map((message) => ({
|
|
13889
|
-
area: "templateBlocks",
|
|
13890
|
-
message
|
|
13891
|
-
})),
|
|
13892
|
-
...isConfigSync(sync) ? sync.ignoredDocumentSections.map((section) => ({
|
|
13893
|
-
area: "ignoredDocuments",
|
|
13894
|
-
message: formatIgnoredDocumentSyncSectionWarning(section)
|
|
13895
|
-
})) : [
|
|
13896
|
-
...sync.pages.warnings.map((message) => ({
|
|
13897
|
-
area: "pages",
|
|
13898
|
-
message
|
|
13899
|
-
})),
|
|
13900
|
-
...sync.entries.warnings.map((message) => ({
|
|
13901
|
-
area: "entries",
|
|
13902
|
-
message
|
|
13903
|
-
})),
|
|
13904
|
-
...sync.navigationMenus.warnings.map((message) => ({
|
|
13905
|
-
area: "navigationMenus",
|
|
13906
|
-
message
|
|
13907
|
-
}))
|
|
13908
|
-
]
|
|
13909
|
-
];
|
|
13910
|
-
}
|
|
13911
|
-
function groupPushConfigSyncWarningsForHuman(warnings) {
|
|
13912
|
-
const ignoredDocumentCount = warnings.filter((warning) => warning.area === "ignoredDocuments").length;
|
|
13913
|
-
return [...warnings.filter((warning) => warning.area !== "ignoredDocuments"), ...ignoredDocumentCount > 0 ? [{
|
|
13914
|
-
area: "ignoredDocuments",
|
|
13915
|
-
message: `${ignoredDocumentCount} document content section${ignoredDocumentCount === 1 ? "" : "s"} ignored by push-config. Use \`riverbankcms push\` for pages, entries, and navigation content.`
|
|
13916
|
-
}] : []];
|
|
13917
|
-
}
|
|
13918
|
-
function buildPreviewSummary(sync) {
|
|
13919
|
-
return { wouldConsider: {
|
|
13920
|
-
contentTypes: sync.contentTypes.skipped,
|
|
13921
|
-
customBlocks: sync.customBlocks.skipped,
|
|
13922
|
-
templateBlocks: sync.templateBlocks.skipped,
|
|
13923
|
-
settings: sync.settings.updated,
|
|
13924
|
-
...isConfigSync(sync) ? { footerBlocks: sync.footerBlocks.skipped } : {},
|
|
13925
|
-
...isConfigSync(sync) ? { ignoredDocumentSections: sync.ignoredDocumentSections } : {}
|
|
13926
|
-
} };
|
|
14019
|
+
|
|
14020
|
+
//#endregion
|
|
14021
|
+
//#region ../api/src/bookingOperations.ts
|
|
14022
|
+
const APPOINTMENT_RESERVATION_STATUS_VALUES = [
|
|
14023
|
+
"active",
|
|
14024
|
+
"paused",
|
|
14025
|
+
"ended"
|
|
14026
|
+
];
|
|
14027
|
+
|
|
14028
|
+
//#endregion
|
|
14029
|
+
//#region ../api/src/utils/isRecord.ts
|
|
14030
|
+
function isRecord$5(value) {
|
|
14031
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13927
14032
|
}
|
|
13928
14033
|
|
|
13929
14034
|
//#endregion
|
|
13930
|
-
//#region src/
|
|
14035
|
+
//#region ../api/src/utils/error-propagation.ts
|
|
13931
14036
|
/**
|
|
13932
|
-
*
|
|
13933
|
-
*
|
|
13934
|
-
*
|
|
14037
|
+
* Extracts an error message from an API error response body.
|
|
14038
|
+
* Checks for our standard API error format: { error: { message: "..." } }
|
|
14039
|
+
* Also handles legacy formats with `error` or `message` as strings.
|
|
13935
14040
|
*/
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
}
|
|
13940
|
-
function isRecord$6(value) {
|
|
13941
|
-
return typeof value === "object" && value !== null;
|
|
13942
|
-
}
|
|
13943
|
-
function parseErrorDetails(value) {
|
|
13944
|
-
if (!value) return {};
|
|
13945
|
-
if (typeof value === "string") {
|
|
13946
|
-
return { message: value };
|
|
13947
|
-
}
|
|
13948
|
-
if (!isRecord$6(value)) return {};
|
|
13949
|
-
const message = typeof value.message === "string" ? value.message : undefined;
|
|
13950
|
-
const fieldErrorsRaw = value.fieldErrors;
|
|
13951
|
-
const fieldErrors = Array.isArray(fieldErrorsRaw) ? fieldErrorsRaw.filter(isRecord$6).map((d) => ({
|
|
13952
|
-
field: typeof d.field === "string" ? d.field : "",
|
|
13953
|
-
message: typeof d.message === "string" ? d.message : ""
|
|
13954
|
-
})).filter((d) => d.field.length > 0 && d.message.length > 0) : undefined;
|
|
13955
|
-
return {
|
|
13956
|
-
message,
|
|
13957
|
-
fieldErrors
|
|
13958
|
-
};
|
|
13959
|
-
}
|
|
13960
|
-
function formatFieldErrors(fieldErrors) {
|
|
13961
|
-
if (!fieldErrors || fieldErrors.length === 0) return "";
|
|
13962
|
-
return ":\n" + fieldErrors.map((d) => ` - ${d.field}: ${d.message}`).join("\n");
|
|
13963
|
-
}
|
|
13964
|
-
function getDashboardErrorMessage(responseJson, fallback) {
|
|
13965
|
-
if (!responseJson) return fallback;
|
|
13966
|
-
if (isRecord$6(responseJson) && "error" in responseJson) {
|
|
13967
|
-
const parsed = parseErrorDetails(responseJson.error);
|
|
13968
|
-
const msg = parsed.message ?? fallback;
|
|
13969
|
-
return msg + formatFieldErrors(parsed.fieldErrors);
|
|
14041
|
+
function extractApiErrorMessage(body) {
|
|
14042
|
+
if (!isRecord$5(body)) {
|
|
14043
|
+
return null;
|
|
13970
14044
|
}
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
* Push config to dashboard API
|
|
13975
|
-
*/
|
|
13976
|
-
async function pushToDashboard(dashboardUrl, siteId, apiKey, config, options) {
|
|
13977
|
-
const normalizedDashboardUrl = dashboardUrl.replace(/\/+$/, "");
|
|
13978
|
-
const baseUrl = `${normalizedDashboardUrl}/api`;
|
|
13979
|
-
const path$3 = `/sites/${siteId}/sdk-config`;
|
|
13980
|
-
const pushUrl = `${baseUrl}${path$3}`;
|
|
13981
|
-
const requestInfo = {
|
|
13982
|
-
method: "POST",
|
|
13983
|
-
intent: "write",
|
|
13984
|
-
path: path$3,
|
|
13985
|
-
url: pushUrl,
|
|
13986
|
-
baseUrl,
|
|
13987
|
-
dashboardUrl: normalizedDashboardUrl,
|
|
13988
|
-
siteId
|
|
13989
|
-
};
|
|
13990
|
-
const client = createBearerAPIClient(exposeSecret(apiKey), baseUrl);
|
|
13991
|
-
const timeoutMs = currentRequestTimeoutMs() ?? 3e4;
|
|
13992
|
-
try {
|
|
13993
|
-
return unwrapResponse(await client({
|
|
13994
|
-
endpoint: "pushSdkConfig",
|
|
13995
|
-
params: { siteId },
|
|
13996
|
-
body: {
|
|
13997
|
-
config,
|
|
13998
|
-
forceContentTypes: options?.forceContentTypes ?? false,
|
|
13999
|
-
mode: options?.dryRun ? "preview" : "apply",
|
|
14000
|
-
expectedMailingListProviderScope: options?.expectedMailingListProviderScope ?? undefined
|
|
14001
|
-
},
|
|
14002
|
-
options: { signal: AbortSignal.timeout(timeoutMs) }
|
|
14003
|
-
}));
|
|
14004
|
-
} catch (error) {
|
|
14005
|
-
if (error instanceof ApiEnvelopeError) {
|
|
14006
|
-
const details = {
|
|
14007
|
-
success: false,
|
|
14008
|
-
error: {
|
|
14009
|
-
code: error.code,
|
|
14010
|
-
message: error.message,
|
|
14011
|
-
fieldErrors: error.fieldErrors
|
|
14012
|
-
}
|
|
14013
|
-
};
|
|
14014
|
-
const errorMessage = getDashboardErrorMessage(details, error.message);
|
|
14015
|
-
throw new ManagementApiError(errorMessage, "sdk:http-error", details, error.status, requestInfo);
|
|
14016
|
-
}
|
|
14017
|
-
if (error instanceof ApiRequestError) {
|
|
14018
|
-
const errorMessage = getDashboardErrorMessage(error.body, `Dashboard returned ${error.status}`);
|
|
14019
|
-
throw new ManagementApiError(errorMessage, "sdk:http-error", error.body, error.status, requestInfo);
|
|
14045
|
+
if (isRecord$5(body.error)) {
|
|
14046
|
+
if (typeof body.error.message === "string" && body.error.message.trim().length > 0) {
|
|
14047
|
+
return body.error.message;
|
|
14020
14048
|
}
|
|
14021
|
-
throw managementTransportError(error, timeoutMs, requestInfo);
|
|
14022
|
-
}
|
|
14023
|
-
}
|
|
14024
|
-
function validatePushConfig(rawConfig) {
|
|
14025
|
-
const parsed = riverbankSiteConfigSchema.safeParse(rawConfig);
|
|
14026
|
-
if (!parsed.success) {
|
|
14027
|
-
const issues = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
14028
|
-
throw new Error(`Invalid config: ${issues}`);
|
|
14029
14049
|
}
|
|
14030
|
-
|
|
14031
|
-
|
|
14032
|
-
throw new Error(`Field ID conflicts detected in blockFieldExtensions: ${conflicts.map((conflict) => conflict.message).join("; ")}`);
|
|
14050
|
+
if (typeof body.error === "string" && body.error.trim().length > 0) {
|
|
14051
|
+
return body.error;
|
|
14033
14052
|
}
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
async function loadValidatedPushConfig(target, configPath) {
|
|
14037
|
-
return validatePushConfig(await withRiverbankEnv(target, () => loadConfigFile(configPath)));
|
|
14038
|
-
}
|
|
14039
|
-
function analyzePushConfigResponse(response, dryRun) {
|
|
14040
|
-
const resultMode = response.mode ?? "apply";
|
|
14041
|
-
if (dryRun && resultMode !== "preview") {
|
|
14042
|
-
return {
|
|
14043
|
-
success: false,
|
|
14044
|
-
kind: "unsafe-preview",
|
|
14045
|
-
receivedMode: response.mode
|
|
14046
|
-
};
|
|
14053
|
+
if (typeof body.message === "string" && body.message.trim().length > 0) {
|
|
14054
|
+
return body.message;
|
|
14047
14055
|
}
|
|
14048
|
-
|
|
14049
|
-
if (!sync) return {
|
|
14050
|
-
success: true,
|
|
14051
|
-
resultMode,
|
|
14052
|
-
sync: null,
|
|
14053
|
-
warnings: []
|
|
14054
|
-
};
|
|
14055
|
-
const errors = collectPushConfigSyncErrors(sync);
|
|
14056
|
-
if (errors.length > 0 || sync.success === false) return {
|
|
14057
|
-
success: false,
|
|
14058
|
-
kind: "sync-errors",
|
|
14059
|
-
sync,
|
|
14060
|
-
errors
|
|
14061
|
-
};
|
|
14062
|
-
return {
|
|
14063
|
-
success: true,
|
|
14064
|
-
resultMode,
|
|
14065
|
-
sync,
|
|
14066
|
-
warnings: collectPushConfigSyncWarnings(sync)
|
|
14067
|
-
};
|
|
14056
|
+
return null;
|
|
14068
14057
|
}
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
}
|
|
14094
|
-
return { success: false };
|
|
14095
|
-
}
|
|
14096
|
-
if (mode === "human" && analysis.warnings.length > 0) {
|
|
14097
|
-
output.warn("SDK config sync reported warnings", { warnings: groupPushConfigSyncWarningsForHuman(analysis.warnings) });
|
|
14058
|
+
/**
|
|
14059
|
+
* Normalizes various error types and throws with an appropriate message.
|
|
14060
|
+
*
|
|
14061
|
+
* - For ApiRequestError: extracts message from body.error or body.message
|
|
14062
|
+
* - For Error instances: re-throws as-is
|
|
14063
|
+
* - For unknown errors: wraps with fallback message
|
|
14064
|
+
*
|
|
14065
|
+
* @param error - The error to normalize
|
|
14066
|
+
* @param fallback - Fallback message if no specific message can be extracted
|
|
14067
|
+
* @throws {Error} Always throws - this function never returns
|
|
14068
|
+
*/
|
|
14069
|
+
function normalizeApiError(error, fallback) {
|
|
14070
|
+
if (error instanceof ApiRequestError) {
|
|
14071
|
+
const message = extractApiErrorMessage(error.body);
|
|
14072
|
+
throw new ApiRequestError(message ?? fallback, {
|
|
14073
|
+
endpoint: error.endpoint,
|
|
14074
|
+
status: error.status,
|
|
14075
|
+
method: error.method,
|
|
14076
|
+
auth: error.auth,
|
|
14077
|
+
requestId: error.requestId,
|
|
14078
|
+
body: error.body,
|
|
14079
|
+
cause: error,
|
|
14080
|
+
errorCode: error.errorCode,
|
|
14081
|
+
retryAfterMs: error.retryAfterMs
|
|
14082
|
+
});
|
|
14098
14083
|
}
|
|
14099
|
-
if (
|
|
14100
|
-
|
|
14084
|
+
if (error instanceof Error) {
|
|
14085
|
+
throw error;
|
|
14101
14086
|
}
|
|
14102
|
-
|
|
14103
|
-
output.json({
|
|
14104
|
-
success: true,
|
|
14105
|
-
...analysis.resultMode === "preview" ? {
|
|
14106
|
-
dryRun: true,
|
|
14107
|
-
mode: analysis.resultMode
|
|
14108
|
-
} : {},
|
|
14109
|
-
sync: analysis.sync
|
|
14110
|
-
});
|
|
14111
|
-
} else {
|
|
14112
|
-
output.success(analysis.resultMode === "preview" ? "Config preview completed. No changes were applied." : "Config pushed successfully!");
|
|
14113
|
-
}
|
|
14114
|
-
return { success: true };
|
|
14115
|
-
}
|
|
14116
|
-
/** Main push-config action, retained for `push --with-config`. */
|
|
14117
|
-
async function pushConfigAction(output, options) {
|
|
14118
|
-
const target = options.target;
|
|
14119
|
-
try {
|
|
14120
|
-
if (!options.isJsonOutput) {
|
|
14121
|
-
output.info("push-config target", {
|
|
14122
|
-
env: target,
|
|
14123
|
-
dashboardUrl: options.dashboard,
|
|
14124
|
-
configPath: options.config ?? DEFAULT_CONFIG_PATH
|
|
14125
|
-
});
|
|
14126
|
-
output.info("Validating config...");
|
|
14127
|
-
}
|
|
14128
|
-
const config = await loadValidatedPushConfig(target, options.config);
|
|
14129
|
-
const apiKey = resolveManagementApiKey(output, options.apiKey, target);
|
|
14130
|
-
const response = await pushToDashboard(options.dashboard, config.siteId, apiKey, config, {
|
|
14131
|
-
forceContentTypes: options.forceContentTypes ?? false,
|
|
14132
|
-
dryRun: options.dryRun ?? false
|
|
14133
|
-
});
|
|
14134
|
-
return renderPushConfigResponse(output, response, options.isJsonOutput ? "json" : "human", options.dryRun ?? false);
|
|
14135
|
-
} catch (error) {
|
|
14136
|
-
handleCommandError(error, output, {
|
|
14137
|
-
target,
|
|
14138
|
-
dashboardUrl: options.dashboard
|
|
14139
|
-
});
|
|
14140
|
-
return { success: false };
|
|
14141
|
-
}
|
|
14142
|
-
}
|
|
14143
|
-
function resolveDashboardUrlValue(cliOption, target) {
|
|
14144
|
-
const envVar = getEnvVarName("DASHBOARD_URL", target);
|
|
14145
|
-
const url = cliOption || process.env[envVar];
|
|
14146
|
-
if (!url) {
|
|
14147
|
-
return {
|
|
14148
|
-
ok: false,
|
|
14149
|
-
error: {
|
|
14150
|
-
message: "Dashboard URL is required",
|
|
14151
|
-
details: { suggestion: `Provide --dashboard <url> or set ${envVar} environment variable.` }
|
|
14152
|
-
}
|
|
14153
|
-
};
|
|
14154
|
-
}
|
|
14155
|
-
return {
|
|
14156
|
-
ok: true,
|
|
14157
|
-
value: url
|
|
14158
|
-
};
|
|
14159
|
-
}
|
|
14160
|
-
/**
|
|
14161
|
-
* Resolve management API key from CLI option or environment variable.
|
|
14162
|
-
*/
|
|
14163
|
-
function resolveManagementApiKeyValue(cliOption, target) {
|
|
14164
|
-
const envVar = getEnvVarName("MGMT_API_KEY", target);
|
|
14165
|
-
const apiKey = cliOption || process.env[envVar];
|
|
14166
|
-
if (!apiKey) {
|
|
14167
|
-
return {
|
|
14168
|
-
ok: false,
|
|
14169
|
-
error: {
|
|
14170
|
-
message: "Management API key is required",
|
|
14171
|
-
details: { suggestion: `Provide --api-key <key> or set ${envVar} environment variable.` }
|
|
14172
|
-
}
|
|
14173
|
-
};
|
|
14174
|
-
}
|
|
14175
|
-
const parsedApiKey = parseManagementKey(apiKey);
|
|
14176
|
-
if (!parsedApiKey.ok) {
|
|
14177
|
-
return {
|
|
14178
|
-
ok: false,
|
|
14179
|
-
error: {
|
|
14180
|
-
message: "Invalid management API key format",
|
|
14181
|
-
details: {
|
|
14182
|
-
expected: "Key starting with bld_mgmt_sk_",
|
|
14183
|
-
suggestion: `Check your ${envVar} environment variable.`
|
|
14184
|
-
}
|
|
14185
|
-
}
|
|
14186
|
-
};
|
|
14187
|
-
}
|
|
14188
|
-
return {
|
|
14189
|
-
ok: true,
|
|
14190
|
-
value: parsedApiKey.value
|
|
14191
|
-
};
|
|
14087
|
+
throw new Error(fallback);
|
|
14192
14088
|
}
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14089
|
+
|
|
14090
|
+
//#endregion
|
|
14091
|
+
//#region ../api/src/bookingActivity.ts
|
|
14092
|
+
const BOOKING_ACTIVITY_CATEGORY_VALUES = [
|
|
14093
|
+
"booking",
|
|
14094
|
+
"purchase",
|
|
14095
|
+
"cancellation",
|
|
14096
|
+
"refund",
|
|
14097
|
+
"transfer",
|
|
14098
|
+
"failure"
|
|
14099
|
+
];
|
|
14100
|
+
const BOOKING_ACTIVITY_TYPE_VALUES = [
|
|
14101
|
+
"appointment_booked",
|
|
14102
|
+
"appointment_requested",
|
|
14103
|
+
"appointment_moved",
|
|
14104
|
+
"waitlist_joined",
|
|
14105
|
+
"balance_paid",
|
|
14106
|
+
"appointment_cancelled",
|
|
14107
|
+
"booking_closure_created",
|
|
14108
|
+
"booking_closure_removed",
|
|
14109
|
+
"event_registration",
|
|
14110
|
+
"event_registration_cancelled",
|
|
14111
|
+
"event_series_registration",
|
|
14112
|
+
"event_series_registration_cancelled",
|
|
14113
|
+
"pass_purchased",
|
|
14114
|
+
"membership_started",
|
|
14115
|
+
"membership_paused",
|
|
14116
|
+
"membership_cancelled",
|
|
14117
|
+
"event_refund",
|
|
14118
|
+
"pass_refund",
|
|
14119
|
+
"membership_refund",
|
|
14120
|
+
"event_series_registration_refund",
|
|
14121
|
+
"ticket_transfer",
|
|
14122
|
+
"payment_failed"
|
|
14123
|
+
];
|
|
14124
|
+
|
|
14125
|
+
//#endregion
|
|
14126
|
+
//#region ../api/src/bookingRecords.ts
|
|
14127
|
+
const CUSTOMER_PASS_STATUS_VALUES = [
|
|
14128
|
+
"active",
|
|
14129
|
+
"exhausted",
|
|
14130
|
+
"expired",
|
|
14131
|
+
"refunded"
|
|
14132
|
+
];
|
|
14133
|
+
const CUSTOMER_MEMBERSHIP_STATUS_VALUES = [
|
|
14134
|
+
"active",
|
|
14135
|
+
"trialing",
|
|
14136
|
+
"past_due",
|
|
14137
|
+
"paused",
|
|
14138
|
+
"canceled"
|
|
14139
|
+
];
|
|
14140
|
+
const BOOKING_LEDGER_KIND_VALUES = [
|
|
14141
|
+
"appointment",
|
|
14142
|
+
"event_registration",
|
|
14143
|
+
"event_series_registration"
|
|
14144
|
+
];
|
|
14145
|
+
const BOOKING_LEDGER_DATE_BASIS_VALUES = [
|
|
14146
|
+
"created_at",
|
|
14147
|
+
"scheduled_at",
|
|
14148
|
+
"cancelled_at"
|
|
14149
|
+
];
|
|
14150
|
+
const BOOKING_LEDGER_PERIOD_PRESET_VALUES = [
|
|
14151
|
+
"7d",
|
|
14152
|
+
"30d",
|
|
14153
|
+
"90d",
|
|
14154
|
+
"365d",
|
|
14155
|
+
"all"
|
|
14156
|
+
];
|
|
14157
|
+
const BOOKING_LEDGER_CANCELLATION_MODE_VALUES = [
|
|
14158
|
+
"exclude_cancelled",
|
|
14159
|
+
"include_cancelled",
|
|
14160
|
+
"cancelled_only"
|
|
14161
|
+
];
|
|
14162
|
+
const BOOKING_QUESTION_ANSWER_EXPORT_SCOPES = {
|
|
14163
|
+
site: "site",
|
|
14164
|
+
appointments: "appointments",
|
|
14165
|
+
eventSeries: "event_series"
|
|
14166
|
+
};
|
|
14167
|
+
const BOOKING_QUESTION_ANSWER_EXPORT_SCOPE_VALUES = [
|
|
14168
|
+
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.site,
|
|
14169
|
+
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.appointments,
|
|
14170
|
+
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.eventSeries
|
|
14171
|
+
];
|
|
14172
|
+
|
|
14173
|
+
//#endregion
|
|
14174
|
+
//#region ../api/src/bookingPeople.ts
|
|
14175
|
+
const PEOPLE_LIST_SORT_VALUES = [
|
|
14176
|
+
"name",
|
|
14177
|
+
"passes",
|
|
14178
|
+
"memberships",
|
|
14179
|
+
"lastBooking",
|
|
14180
|
+
"added"
|
|
14181
|
+
];
|
|
14182
|
+
const PEOPLE_LIST_FILTER_VALUES = [
|
|
14183
|
+
"everyone",
|
|
14184
|
+
"hasBought",
|
|
14185
|
+
"hasActivePass",
|
|
14186
|
+
"hasMembership",
|
|
14187
|
+
"guests",
|
|
14188
|
+
"nothingBought"
|
|
14189
|
+
];
|
|
14190
|
+
/** What can be attached to a person, in the order the dialog lists it. */
|
|
14191
|
+
const PERSON_DELETION_BLOCKER_CATEGORY_VALUES = [
|
|
14192
|
+
"bookings",
|
|
14193
|
+
"guest_places",
|
|
14194
|
+
"waitlist_places",
|
|
14195
|
+
"payments",
|
|
14196
|
+
"orders",
|
|
14197
|
+
"passes",
|
|
14198
|
+
"memberships",
|
|
14199
|
+
"credit",
|
|
14200
|
+
"digital_products",
|
|
14201
|
+
"discounts",
|
|
14202
|
+
"gifts",
|
|
14203
|
+
"notes",
|
|
14204
|
+
"forms",
|
|
14205
|
+
"terms_agreements",
|
|
14206
|
+
"uploads",
|
|
14207
|
+
"contact_corrections"
|
|
14208
|
+
];
|
|
14209
|
+
|
|
14210
|
+
//#endregion
|
|
14211
|
+
//#region ../api/src/wholeSeriesRuns.ts
|
|
14212
|
+
const DELETE_EVENT_OFFERING_CONFLICT_REASONS = [
|
|
14213
|
+
"has_registrations",
|
|
14214
|
+
"has_payments",
|
|
14215
|
+
"published_page",
|
|
14216
|
+
"inconsistent_ownership"
|
|
14217
|
+
];
|
|
14218
|
+
function isDeleteEventOfferingConflictReason(value) {
|
|
14219
|
+
return DELETE_EVENT_OFFERING_CONFLICT_REASONS.some((reason) => reason === value);
|
|
14197
14220
|
}
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14221
|
+
|
|
14222
|
+
//#endregion
|
|
14223
|
+
//#region ../api/src/bookings.ts
|
|
14224
|
+
const BOOKING_QUESTION_REQUIREMENT_SCOPES = ["before_booking_booking_level", "after_booking_due_by_participant"];
|
|
14225
|
+
function isAfterBookingQuestionRequirementScope(scope) {
|
|
14226
|
+
return scope === "after_booking_due_by_participant";
|
|
14202
14227
|
}
|
|
14203
|
-
|
|
14204
|
-
return
|
|
14205
|
-
target,
|
|
14206
|
-
dashboard: unwrapPushConfigTargetValue(resolveDashboardUrlValue(options.dashboard, target)),
|
|
14207
|
-
apiKey: unwrapPushConfigTargetValue(resolveManagementApiKeyValue(options.apiKey, target))
|
|
14208
|
-
};
|
|
14228
|
+
function isBeforeBookingQuestionRequirementScope(scope) {
|
|
14229
|
+
return scope === "before_booking_booking_level";
|
|
14209
14230
|
}
|
|
14210
|
-
async function
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14231
|
+
async function getBookingSettingsRequest(apiClient, siteId, options) {
|
|
14232
|
+
try {
|
|
14233
|
+
const response = unwrapResponse(await apiClient({
|
|
14234
|
+
endpoint: "getBookingSettings",
|
|
14235
|
+
params: { siteId },
|
|
14236
|
+
options
|
|
14237
|
+
}));
|
|
14238
|
+
return response.settings;
|
|
14239
|
+
} catch (error) {
|
|
14240
|
+
normalizeApiError(error, "Failed to load booking settings");
|
|
14218
14241
|
}
|
|
14219
|
-
return {
|
|
14220
|
-
config,
|
|
14221
|
-
preview,
|
|
14222
|
-
forceContentTypes: options.forceContentTypes ?? false
|
|
14223
|
-
};
|
|
14224
14242
|
}
|
|
14225
|
-
|
|
14226
|
-
* Re-preview a prepared config value without reloading the local config file.
|
|
14227
|
-
* The caller uses this immediately before applying a multi-target plan so a
|
|
14228
|
-
* dashboard-side schema change is classified as stale before any mutation.
|
|
14229
|
-
*/
|
|
14230
|
-
async function revalidatePushConfigPlan(context, plan) {
|
|
14243
|
+
async function updateBookingSettingsRequest(apiClient, siteId, payload, options) {
|
|
14231
14244
|
try {
|
|
14232
|
-
const
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
};
|
|
14245
|
+
const response = unwrapResponse(await apiClient({
|
|
14246
|
+
endpoint: "updateBookingSettings",
|
|
14247
|
+
params: { siteId },
|
|
14248
|
+
body: payload,
|
|
14249
|
+
options
|
|
14250
|
+
}));
|
|
14251
|
+
return response.settings;
|
|
14240
14252
|
} catch (error) {
|
|
14241
|
-
|
|
14242
|
-
ok: false,
|
|
14243
|
-
error: error instanceof Error ? error : new Error(String(error))
|
|
14244
|
-
};
|
|
14253
|
+
normalizeApiError(error, "Failed to save booking settings");
|
|
14245
14254
|
}
|
|
14246
14255
|
}
|
|
14247
|
-
async function
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
}
|
|
14258
|
-
function isPushConfigApplySuccess(result) {
|
|
14259
|
-
return result.action.success;
|
|
14260
|
-
}
|
|
14261
|
-
function renderPushConfigJsonResult(output, execution, dryRun) {
|
|
14262
|
-
output.json({
|
|
14263
|
-
success: execution.succeeded,
|
|
14264
|
-
dryRun,
|
|
14265
|
-
environments: mapEnvironmentExecutionOutcomesJson(execution, ({ result }) => ({
|
|
14266
|
-
response: result.response,
|
|
14267
|
-
success: true
|
|
14268
|
-
}), ({ result, message }) => ({
|
|
14269
|
-
response: result.response,
|
|
14270
|
-
success: false,
|
|
14271
|
-
error: message
|
|
14272
|
-
}))
|
|
14273
|
-
});
|
|
14274
|
-
}
|
|
14275
|
-
function getProductionPushConfigCommandDeps() {
|
|
14276
|
-
return {
|
|
14277
|
-
getOutputContext,
|
|
14278
|
-
planEnvironmentExecution,
|
|
14279
|
-
executeEnvironmentPlan,
|
|
14280
|
-
confirmAction,
|
|
14281
|
-
loadTarget: loadPushConfigTarget,
|
|
14282
|
-
prepareTarget: preparePushConfigTarget,
|
|
14283
|
-
revalidatePlan: revalidatePushConfigPlan,
|
|
14284
|
-
applyPlan: applyPushConfigPlan
|
|
14285
|
-
};
|
|
14286
|
-
}
|
|
14287
|
-
/**
|
|
14288
|
-
* push-config command definition
|
|
14289
|
-
*/
|
|
14290
|
-
const createPushConfigCommand = (deps = getProductionPushConfigCommandDeps()) => new Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", `Path to config file (default: ${DEFAULT_CONFIG_PATH})`).option("--force-content-types", "Force overwrite content type schema even if modified in dashboard after last SDK sync").option("--dry-run", "Preview SDK config changes without updating the dashboard").option("--yes", "Confirm applying config changes across all selected environments").addHelpText("after", `
|
|
14291
|
-
Description:
|
|
14292
|
-
Syncs your local riverbank.config.ts schema/config to the CMS dashboard, including:
|
|
14293
|
-
- Custom blocks
|
|
14294
|
-
- Block field extensions
|
|
14295
|
-
- Block field options
|
|
14296
|
-
- Content type definitions and templates
|
|
14297
|
-
- SDK-managed site settings and footer blocks
|
|
14298
|
-
|
|
14299
|
-
Pages, content entries, and navigation menus are content documents.
|
|
14300
|
-
Sync them with "riverbankcms push pages", "riverbankcms push entries",
|
|
14301
|
-
or "riverbankcms push navigation".
|
|
14302
|
-
|
|
14303
|
-
Examples:
|
|
14304
|
-
$ pnpm exec riverbankcms push-config
|
|
14305
|
-
$ pnpm exec riverbankcms push-config --dry-run
|
|
14306
|
-
$ pnpm exec riverbankcms push-config --env=remote
|
|
14307
|
-
$ pnpm exec riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
|
|
14308
|
-
$ pnpm exec riverbankcms push-config --config ./src/riverbank.config.ts
|
|
14309
|
-
`).action(async (options, command) => {
|
|
14310
|
-
const { output, selection, globalOpts } = deps.getOutputContext(command);
|
|
14311
|
-
const isJsonOutput = globalOpts.json ?? false;
|
|
14312
|
-
const authorization = requireEnvironmentWriteAuthorization({
|
|
14313
|
-
...options,
|
|
14314
|
-
json: isJsonOutput,
|
|
14315
|
-
interactive: process.stdin.isTTY === true
|
|
14316
|
-
}, output);
|
|
14317
|
-
if (!authorization) return;
|
|
14318
|
-
const planned = deps.planEnvironmentExecution({
|
|
14319
|
-
command: "push-config",
|
|
14320
|
-
selection: "both",
|
|
14321
|
-
failure: "abort",
|
|
14322
|
-
confirmation: "command",
|
|
14323
|
-
revalidation: "required"
|
|
14324
|
-
}, selection);
|
|
14325
|
-
if (!planned.ok) return output.error(planned.error.message, { suggestion: planned.error.suggestion });
|
|
14326
|
-
const execution = await deps.executeEnvironmentPlan(planned.value, {
|
|
14327
|
-
loadTarget: (target) => deps.loadTarget(target, options),
|
|
14328
|
-
preflight: async (_target, loaded) => deps.prepareTarget(loaded.context, {
|
|
14329
|
-
...options,
|
|
14330
|
-
dashboard: loaded.context.dashboard
|
|
14331
|
-
}),
|
|
14332
|
-
confirm: async ({ ready }) => {
|
|
14333
|
-
if (authorization.kind === "preauthorized") return true;
|
|
14334
|
-
for (const { target, context } of ready) {
|
|
14335
|
-
const sync = getPushConfigSync(context.preview);
|
|
14336
|
-
output.info(`${target} config preview`, sync ? buildPreviewSummary(sync) : { changes: "No sync summary returned" });
|
|
14337
|
-
}
|
|
14338
|
-
return deps.confirmAction(`Apply SDK config to ${ready.length} environment${ready.length === 1 ? "" : "s"} (${ready.map(({ target }) => target).join(" then ")})?`, { yes: options.yes }, false);
|
|
14339
|
-
},
|
|
14340
|
-
revalidate: async (_target, loaded, prepared) => options.dryRun ? { ok: true } : deps.revalidatePlan(loaded.context, prepared.context),
|
|
14341
|
-
apply: async (target, loaded, prepared) => {
|
|
14342
|
-
if (planned.value.targets.length > 1) output.info(`\n--- Pushing to ${target} environment ---`);
|
|
14343
|
-
return deps.applyPlan(output, loaded.context, prepared.context, {
|
|
14344
|
-
...options,
|
|
14345
|
-
dashboard: loaded.context.dashboard
|
|
14346
|
-
}, isJsonOutput);
|
|
14347
|
-
},
|
|
14348
|
-
isSuccess: isPushConfigApplySuccess,
|
|
14349
|
-
formatFailure: formatPushConfigActionFailure
|
|
14350
|
-
}, output);
|
|
14351
|
-
if (isJsonOutput) renderPushConfigJsonResult(output, execution, options.dryRun ?? false);
|
|
14352
|
-
});
|
|
14353
|
-
|
|
14354
|
-
//#endregion
|
|
14355
|
-
//#region ../api/src/utils/isRecord.ts
|
|
14356
|
-
function isRecord$5(value) {
|
|
14357
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14358
|
-
}
|
|
14359
|
-
|
|
14360
|
-
//#endregion
|
|
14361
|
-
//#region ../api/src/utils/error-propagation.ts
|
|
14362
|
-
/**
|
|
14363
|
-
* Extracts an error message from an API error response body.
|
|
14364
|
-
* Checks for our standard API error format: { error: { message: "..." } }
|
|
14365
|
-
* Also handles legacy formats with `error` or `message` as strings.
|
|
14366
|
-
*/
|
|
14367
|
-
function extractApiErrorMessage(body) {
|
|
14368
|
-
if (!isRecord$5(body)) {
|
|
14369
|
-
return null;
|
|
14370
|
-
}
|
|
14371
|
-
if (isRecord$5(body.error)) {
|
|
14372
|
-
if (typeof body.error.message === "string" && body.error.message.trim().length > 0) {
|
|
14373
|
-
return body.error.message;
|
|
14374
|
-
}
|
|
14256
|
+
async function createAppointmentRequest(apiClient, siteId, payload, options) {
|
|
14257
|
+
try {
|
|
14258
|
+
return unwrapResponse(await apiClient({
|
|
14259
|
+
endpoint: "createAppointment",
|
|
14260
|
+
params: { siteId },
|
|
14261
|
+
body: payload,
|
|
14262
|
+
options
|
|
14263
|
+
}));
|
|
14264
|
+
} catch (error) {
|
|
14265
|
+
normalizeApiError(error, "Failed to create appointment");
|
|
14375
14266
|
}
|
|
14376
|
-
|
|
14377
|
-
|
|
14267
|
+
}
|
|
14268
|
+
async function deleteEventOfferingRequest(apiClient, siteId, eventOfferingId, options) {
|
|
14269
|
+
try {
|
|
14270
|
+
return unwrapResponse(await apiClient({
|
|
14271
|
+
endpoint: "deleteEventOffering",
|
|
14272
|
+
params: {
|
|
14273
|
+
siteId,
|
|
14274
|
+
eventOfferingId
|
|
14275
|
+
},
|
|
14276
|
+
options
|
|
14277
|
+
}));
|
|
14278
|
+
} catch (error) {
|
|
14279
|
+
normalizeApiError(error, "Failed to delete event offering");
|
|
14378
14280
|
}
|
|
14379
|
-
|
|
14380
|
-
|
|
14281
|
+
}
|
|
14282
|
+
async function addEventSeriesRegistrationRequest(apiClient, siteId, eventSeriesId, payload, options) {
|
|
14283
|
+
try {
|
|
14284
|
+
return unwrapResponse(await apiClient({
|
|
14285
|
+
endpoint: "addEventSeriesRegistration",
|
|
14286
|
+
params: {
|
|
14287
|
+
siteId,
|
|
14288
|
+
seriesId: eventSeriesId
|
|
14289
|
+
},
|
|
14290
|
+
body: payload,
|
|
14291
|
+
options
|
|
14292
|
+
}));
|
|
14293
|
+
} catch (error) {
|
|
14294
|
+
normalizeApiError(error, "Failed to add Event run registration");
|
|
14381
14295
|
}
|
|
14382
|
-
return null;
|
|
14383
14296
|
}
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
throw new ApiRequestError(message ?? fallback, {
|
|
14399
|
-
endpoint: error.endpoint,
|
|
14400
|
-
status: error.status,
|
|
14401
|
-
method: error.method,
|
|
14402
|
-
auth: error.auth,
|
|
14403
|
-
requestId: error.requestId,
|
|
14404
|
-
body: error.body,
|
|
14405
|
-
cause: error,
|
|
14406
|
-
errorCode: error.errorCode,
|
|
14407
|
-
retryAfterMs: error.retryAfterMs
|
|
14408
|
-
});
|
|
14297
|
+
async function updateEventSeriesRegistrationRequest(apiClient, siteId, eventSeriesId, registrationId, payload, options) {
|
|
14298
|
+
try {
|
|
14299
|
+
return unwrapResponse(await apiClient({
|
|
14300
|
+
endpoint: "updateEventSeriesRegistration",
|
|
14301
|
+
params: {
|
|
14302
|
+
siteId,
|
|
14303
|
+
seriesId: eventSeriesId,
|
|
14304
|
+
registrationId
|
|
14305
|
+
},
|
|
14306
|
+
body: payload,
|
|
14307
|
+
options
|
|
14308
|
+
}));
|
|
14309
|
+
} catch (error) {
|
|
14310
|
+
normalizeApiError(error, "Failed to update Event run registration");
|
|
14409
14311
|
}
|
|
14410
|
-
|
|
14411
|
-
|
|
14312
|
+
}
|
|
14313
|
+
async function addEventOccurrenceAttendeeRequest(apiClient, siteId, seriesId, occurrenceId, payload, options) {
|
|
14314
|
+
try {
|
|
14315
|
+
return unwrapResponse(await apiClient({
|
|
14316
|
+
endpoint: "addEventOccurrenceAttendee",
|
|
14317
|
+
params: {
|
|
14318
|
+
siteId,
|
|
14319
|
+
seriesId,
|
|
14320
|
+
occurrenceId
|
|
14321
|
+
},
|
|
14322
|
+
body: payload,
|
|
14323
|
+
options
|
|
14324
|
+
}));
|
|
14325
|
+
} catch (error) {
|
|
14326
|
+
normalizeApiError(error, "Failed to add attendee");
|
|
14412
14327
|
}
|
|
14413
|
-
throw new Error(fallback);
|
|
14414
14328
|
}
|
|
14415
14329
|
|
|
14416
14330
|
//#endregion
|
|
14417
|
-
//#region
|
|
14418
|
-
function
|
|
14419
|
-
return
|
|
14331
|
+
//#region src/push-config-sync.ts
|
|
14332
|
+
function isConfigSync(sync) {
|
|
14333
|
+
return "ignoredDocumentSections" in sync;
|
|
14420
14334
|
}
|
|
14421
|
-
function
|
|
14422
|
-
|
|
14335
|
+
function getPushConfigSync(result) {
|
|
14336
|
+
if (result.configSync) return result.configSync;
|
|
14337
|
+
if (result.sync) return result.sync;
|
|
14338
|
+
return null;
|
|
14339
|
+
}
|
|
14340
|
+
function collectPushConfigSyncErrors(sync) {
|
|
14341
|
+
return [
|
|
14342
|
+
...sync.contentTypes.errors.map((message) => ({
|
|
14343
|
+
area: "contentTypes",
|
|
14344
|
+
message
|
|
14345
|
+
})),
|
|
14346
|
+
...sync.customBlocks.errors.map((message) => ({
|
|
14347
|
+
area: "customBlocks",
|
|
14348
|
+
message
|
|
14349
|
+
})),
|
|
14350
|
+
...sync.templateBlocks.errors.map((message) => ({
|
|
14351
|
+
area: "templateBlocks",
|
|
14352
|
+
message
|
|
14353
|
+
})),
|
|
14354
|
+
...sync.settings.error ? [{
|
|
14355
|
+
area: "settings",
|
|
14356
|
+
message: sync.settings.error
|
|
14357
|
+
}] : [],
|
|
14358
|
+
...!isConfigSync(sync) ? sync.pages.errors.map((message) => ({
|
|
14359
|
+
area: "pages",
|
|
14360
|
+
message
|
|
14361
|
+
})) : [],
|
|
14362
|
+
...!isConfigSync(sync) ? sync.entries.errors.map((message) => ({
|
|
14363
|
+
area: "entries",
|
|
14364
|
+
message
|
|
14365
|
+
})) : [],
|
|
14366
|
+
...!isConfigSync(sync) ? sync.navigationMenus.errors.map((message) => ({
|
|
14367
|
+
area: "navigationMenus",
|
|
14368
|
+
message
|
|
14369
|
+
})) : []
|
|
14370
|
+
];
|
|
14371
|
+
}
|
|
14372
|
+
function collectPushConfigSyncWarnings(sync) {
|
|
14373
|
+
return [
|
|
14374
|
+
...sync.contentTypes.warnings.map((message) => ({
|
|
14375
|
+
area: "contentTypes",
|
|
14376
|
+
message
|
|
14377
|
+
})),
|
|
14378
|
+
...sync.customBlocks.warnings.map((message) => ({
|
|
14379
|
+
area: "customBlocks",
|
|
14380
|
+
message
|
|
14381
|
+
})),
|
|
14382
|
+
...sync.templateBlocks.warnings.map((message) => ({
|
|
14383
|
+
area: "templateBlocks",
|
|
14384
|
+
message
|
|
14385
|
+
})),
|
|
14386
|
+
...isConfigSync(sync) ? sync.ignoredDocumentSections.map((section) => ({
|
|
14387
|
+
area: "ignoredDocuments",
|
|
14388
|
+
message: formatIgnoredDocumentSyncSectionWarning(section)
|
|
14389
|
+
})) : [
|
|
14390
|
+
...sync.pages.warnings.map((message) => ({
|
|
14391
|
+
area: "pages",
|
|
14392
|
+
message
|
|
14393
|
+
})),
|
|
14394
|
+
...sync.entries.warnings.map((message) => ({
|
|
14395
|
+
area: "entries",
|
|
14396
|
+
message
|
|
14397
|
+
})),
|
|
14398
|
+
...sync.navigationMenus.warnings.map((message) => ({
|
|
14399
|
+
area: "navigationMenus",
|
|
14400
|
+
message
|
|
14401
|
+
}))
|
|
14402
|
+
]
|
|
14403
|
+
];
|
|
14404
|
+
}
|
|
14405
|
+
function groupPushConfigSyncWarningsForHuman(warnings) {
|
|
14406
|
+
const ignoredDocumentCount = warnings.filter((warning) => warning.area === "ignoredDocuments").length;
|
|
14407
|
+
return [...warnings.filter((warning) => warning.area !== "ignoredDocuments"), ...ignoredDocumentCount > 0 ? [{
|
|
14408
|
+
area: "ignoredDocuments",
|
|
14409
|
+
message: `${ignoredDocumentCount} document content section${ignoredDocumentCount === 1 ? "" : "s"} ignored by push-config. Use \`riverbankcms push\` for pages, entries, and navigation content.`
|
|
14410
|
+
}] : []];
|
|
14411
|
+
}
|
|
14412
|
+
function buildPreviewSummary(sync) {
|
|
14413
|
+
return { wouldConsider: {
|
|
14414
|
+
contentTypes: sync.contentTypes.skipped,
|
|
14415
|
+
customBlocks: sync.customBlocks.skipped,
|
|
14416
|
+
templateBlocks: sync.templateBlocks.skipped,
|
|
14417
|
+
settings: sync.settings.updated,
|
|
14418
|
+
...isConfigSync(sync) ? { footerBlocks: sync.footerBlocks.skipped } : {},
|
|
14419
|
+
...isConfigSync(sync) ? { ignoredDocumentSections: sync.ignoredDocumentSections } : {}
|
|
14420
|
+
} };
|
|
14423
14421
|
}
|
|
14422
|
+
|
|
14423
|
+
//#endregion
|
|
14424
|
+
//#region src/push-config.ts
|
|
14424
14425
|
/**
|
|
14425
|
-
*
|
|
14426
|
-
*
|
|
14427
|
-
*
|
|
14428
|
-
* - Portal links must be root-level.
|
|
14429
|
-
* - Portal links cannot have children.
|
|
14426
|
+
* push-config CLI command
|
|
14427
|
+
*
|
|
14428
|
+
* Pushes SDK site configuration from a riverbank.config.ts file to the dashboard API.
|
|
14430
14429
|
*/
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14430
|
+
const DEFAULT_CONFIG_PATH = "./riverbank.config.ts";
|
|
14431
|
+
function formatPushConfigActionFailure() {
|
|
14432
|
+
return "push-config failed";
|
|
14433
|
+
}
|
|
14434
|
+
function isRecord$6(value) {
|
|
14435
|
+
return typeof value === "object" && value !== null;
|
|
14436
|
+
}
|
|
14437
|
+
function parseErrorDetails(value) {
|
|
14438
|
+
if (!value) return {};
|
|
14439
|
+
if (typeof value === "string") {
|
|
14440
|
+
return { message: value };
|
|
14441
|
+
}
|
|
14442
|
+
if (!isRecord$6(value)) return {};
|
|
14443
|
+
const message = typeof value.message === "string" ? value.message : undefined;
|
|
14444
|
+
const fieldErrorsRaw = value.fieldErrors;
|
|
14445
|
+
const fieldErrors = Array.isArray(fieldErrorsRaw) ? fieldErrorsRaw.filter(isRecord$6).map((d) => ({
|
|
14446
|
+
field: typeof d.field === "string" ? d.field : "",
|
|
14447
|
+
message: typeof d.message === "string" ? d.message : ""
|
|
14448
|
+
})).filter((d) => d.field.length > 0 && d.message.length > 0) : undefined;
|
|
14449
|
+
return {
|
|
14450
|
+
message,
|
|
14451
|
+
fieldErrors
|
|
14436
14452
|
};
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
if (hasChildren(item)) {
|
|
14449
|
-
const childItems = (item.children ?? []).filter(isNavigationMenuValidationItem);
|
|
14450
|
-
const child = collectPortalLinkStats(childItems, depth + 1);
|
|
14451
|
-
result.count += child.count;
|
|
14452
|
-
result.nestedLabels.push(...child.nestedLabels);
|
|
14453
|
-
result.withChildrenLabels.push(...child.withChildrenLabels);
|
|
14454
|
-
}
|
|
14453
|
+
}
|
|
14454
|
+
function formatFieldErrors(fieldErrors) {
|
|
14455
|
+
if (!fieldErrors || fieldErrors.length === 0) return "";
|
|
14456
|
+
return ":\n" + fieldErrors.map((d) => ` - ${d.field}: ${d.message}`).join("\n");
|
|
14457
|
+
}
|
|
14458
|
+
function getDashboardErrorMessage(responseJson, fallback) {
|
|
14459
|
+
if (!responseJson) return fallback;
|
|
14460
|
+
if (isRecord$6(responseJson) && "error" in responseJson) {
|
|
14461
|
+
const parsed = parseErrorDetails(responseJson.error);
|
|
14462
|
+
const msg = parsed.message ?? fallback;
|
|
14463
|
+
return msg + formatFieldErrors(parsed.fieldErrors);
|
|
14455
14464
|
}
|
|
14456
|
-
return
|
|
14465
|
+
return fallback;
|
|
14457
14466
|
}
|
|
14458
14467
|
/**
|
|
14459
|
-
*
|
|
14460
|
-
* Rules:
|
|
14461
|
-
* - Cart links must be root-level.
|
|
14462
|
-
* - Cart links cannot have children.
|
|
14463
|
-
* - Cart links cannot be CTA items.
|
|
14464
|
-
* - Cart links using `icon-with-label` must provide visible text.
|
|
14468
|
+
* Push config to dashboard API
|
|
14465
14469
|
*/
|
|
14466
|
-
function
|
|
14467
|
-
const
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14470
|
+
async function pushToDashboard(dashboardUrl, siteId, apiKey, config, options) {
|
|
14471
|
+
const normalizedDashboardUrl = dashboardUrl.replace(/\/+$/, "");
|
|
14472
|
+
const baseUrl = `${normalizedDashboardUrl}/api`;
|
|
14473
|
+
const path$3 = `/sites/${siteId}/sdk-config`;
|
|
14474
|
+
const pushUrl = `${baseUrl}${path$3}`;
|
|
14475
|
+
const requestInfo = {
|
|
14476
|
+
method: "POST",
|
|
14477
|
+
intent: "write",
|
|
14478
|
+
path: path$3,
|
|
14479
|
+
url: pushUrl,
|
|
14480
|
+
baseUrl,
|
|
14481
|
+
dashboardUrl: normalizedDashboardUrl,
|
|
14482
|
+
siteId
|
|
14473
14483
|
};
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
}
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
|
|
14484
|
+
const client = createBearerAPIClient(exposeSecret(apiKey), baseUrl);
|
|
14485
|
+
const timeoutMs = currentRequestTimeoutMs() ?? 3e4;
|
|
14486
|
+
try {
|
|
14487
|
+
return unwrapResponse(await client({
|
|
14488
|
+
endpoint: "pushSdkConfig",
|
|
14489
|
+
params: { siteId },
|
|
14490
|
+
body: {
|
|
14491
|
+
config,
|
|
14492
|
+
forceContentTypes: options?.forceContentTypes ?? false,
|
|
14493
|
+
mode: options?.dryRun ? "preview" : "apply",
|
|
14494
|
+
expectedMailingListProviderScope: options?.expectedMailingListProviderScope ?? undefined
|
|
14495
|
+
},
|
|
14496
|
+
options: { signal: AbortSignal.timeout(timeoutMs) }
|
|
14497
|
+
}));
|
|
14498
|
+
} catch (error) {
|
|
14499
|
+
if (error instanceof ApiEnvelopeError) {
|
|
14500
|
+
const details = {
|
|
14501
|
+
success: false,
|
|
14502
|
+
error: {
|
|
14503
|
+
code: error.code,
|
|
14504
|
+
message: error.message,
|
|
14505
|
+
fieldErrors: error.fieldErrors
|
|
14506
|
+
}
|
|
14507
|
+
};
|
|
14508
|
+
const errorMessage = getDashboardErrorMessage(details, error.message);
|
|
14509
|
+
throw new ManagementApiError(errorMessage, "sdk:http-error", details, error.status, requestInfo);
|
|
14490
14510
|
}
|
|
14491
|
-
if (
|
|
14492
|
-
const
|
|
14493
|
-
|
|
14494
|
-
result.count += child.count;
|
|
14495
|
-
result.nestedLabels.push(...child.nestedLabels);
|
|
14496
|
-
result.withChildrenLabels.push(...child.withChildrenLabels);
|
|
14497
|
-
result.ctaLabels.push(...child.ctaLabels);
|
|
14498
|
-
result.missingVisibleTextLabels.push(...child.missingVisibleTextLabels);
|
|
14511
|
+
if (error instanceof ApiRequestError) {
|
|
14512
|
+
const errorMessage = getDashboardErrorMessage(error.body, `Dashboard returned ${error.status}`);
|
|
14513
|
+
throw new ManagementApiError(errorMessage, "sdk:http-error", error.body, error.status, requestInfo);
|
|
14499
14514
|
}
|
|
14515
|
+
throw managementTransportError(error, timeoutMs, requestInfo);
|
|
14500
14516
|
}
|
|
14501
|
-
return result;
|
|
14502
|
-
}
|
|
14503
|
-
function isNavigationMenuValidationItem(value) {
|
|
14504
|
-
if (!isRecord$5(value)) return false;
|
|
14505
|
-
return typeof value.label === "string";
|
|
14506
14517
|
}
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
const
|
|
14511
|
-
|
|
14512
|
-
* The booking page's link to the customer portal, in the header and the
|
|
14513
|
-
* footer alike. It is a portal link, so renderers apply the portal link rules
|
|
14514
|
-
* (including the portal-enabled gate); the label stays the same signed in.
|
|
14515
|
-
*/
|
|
14516
|
-
const YOUR_BOOKINGS_PORTAL_LINK = {
|
|
14517
|
-
kind: "portal",
|
|
14518
|
-
labels: {
|
|
14519
|
-
loggedOut: YOUR_BOOKINGS_LABEL,
|
|
14520
|
-
loggedIn: YOUR_BOOKINGS_LABEL
|
|
14518
|
+
function validatePushConfig(rawConfig) {
|
|
14519
|
+
const parsed = riverbankSiteConfigSchema.safeParse(rawConfig);
|
|
14520
|
+
if (!parsed.success) {
|
|
14521
|
+
const issues = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
14522
|
+
throw new Error(`Invalid config: ${issues}`);
|
|
14521
14523
|
}
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
//#region ../api/src/navigation/linkResolver.ts
|
|
14526
|
-
/**
|
|
14527
|
-
* Type guard for internal links.
|
|
14528
|
-
* Checks if a link payload is an internal link with entityId and entityType.
|
|
14529
|
-
*/
|
|
14530
|
-
function isInternalLink(link$1) {
|
|
14531
|
-
if (!link$1 || typeof link$1 !== "object") {
|
|
14532
|
-
return false;
|
|
14524
|
+
const conflicts = validateFieldIdConflicts(parsed.data.blockFieldExtensions);
|
|
14525
|
+
if (conflicts.length > 0) {
|
|
14526
|
+
throw new Error(`Field ID conflicts detected in blockFieldExtensions: ${conflicts.map((conflict) => conflict.message).join("; ")}`);
|
|
14533
14527
|
}
|
|
14534
|
-
return
|
|
14535
|
-
}
|
|
14536
|
-
/**
|
|
14537
|
-
* Narrow internal link to page type.
|
|
14538
|
-
*/
|
|
14539
|
-
function isPageLink(link$1) {
|
|
14540
|
-
return link$1.entityType === "page";
|
|
14528
|
+
return parsed.data;
|
|
14541
14529
|
}
|
|
14542
|
-
|
|
14543
|
-
|
|
14544
|
-
*/
|
|
14545
|
-
function isEntryLink(link$1) {
|
|
14546
|
-
return link$1.entityType === "content";
|
|
14530
|
+
async function loadValidatedPushConfig(target, configPath) {
|
|
14531
|
+
return validatePushConfig(await withRiverbankEnv(target, () => loadConfigFile(configPath)));
|
|
14547
14532
|
}
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
* Supports identifier-based page/entry links and validates them against routable
|
|
14552
|
-
* lookup maps before persisting.
|
|
14553
|
-
*
|
|
14554
|
-
* @param link - The navigation link input from SDK config or API
|
|
14555
|
-
* @param options - Resolution options (isDropdown flag)
|
|
14556
|
-
* @param pageMap - Map of page identifier -> PageNavLinkInfo
|
|
14557
|
-
* @param entryMap - Map of `contentType:identifier` -> EntryNavLinkInfo
|
|
14558
|
-
* @returns Resolved link with urlType, url payload, and any error
|
|
14559
|
-
*/
|
|
14560
|
-
function resolveNavigationLinkInput(link$1, options, pageMap, entryMap) {
|
|
14561
|
-
if (options.isDropdown) {
|
|
14562
|
-
return {
|
|
14563
|
-
urlType: "dropdown",
|
|
14564
|
-
url: null,
|
|
14565
|
-
error: null
|
|
14566
|
-
};
|
|
14567
|
-
}
|
|
14568
|
-
if (!link$1) {
|
|
14533
|
+
function analyzePushConfigResponse(response, dryRun) {
|
|
14534
|
+
const resultMode = response.mode ?? "apply";
|
|
14535
|
+
if (dryRun && resultMode !== "preview") {
|
|
14569
14536
|
return {
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14537
|
+
success: false,
|
|
14538
|
+
kind: "unsafe-preview",
|
|
14539
|
+
receivedMode: response.mode
|
|
14573
14540
|
};
|
|
14574
14541
|
}
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14542
|
+
const sync = getPushConfigSync(response);
|
|
14543
|
+
if (!sync) return {
|
|
14544
|
+
success: true,
|
|
14545
|
+
resultMode,
|
|
14546
|
+
sync: null,
|
|
14547
|
+
warnings: []
|
|
14548
|
+
};
|
|
14549
|
+
const errors = collectPushConfigSyncErrors(sync);
|
|
14550
|
+
if (errors.length > 0 || sync.success === false) return {
|
|
14551
|
+
success: false,
|
|
14552
|
+
kind: "sync-errors",
|
|
14553
|
+
sync,
|
|
14554
|
+
errors
|
|
14555
|
+
};
|
|
14556
|
+
return {
|
|
14557
|
+
success: true,
|
|
14558
|
+
resultMode,
|
|
14559
|
+
sync,
|
|
14560
|
+
warnings: collectPushConfigSyncWarnings(sync)
|
|
14561
|
+
};
|
|
14562
|
+
}
|
|
14563
|
+
function renderPushConfigResponse(output, response, mode, dryRun) {
|
|
14564
|
+
const analysis = analyzePushConfigResponse(response, dryRun);
|
|
14565
|
+
if (!analysis.success) {
|
|
14566
|
+
if (analysis.kind === "unsafe-preview") {
|
|
14567
|
+
output.fail("Config preview was not confirmed by the dashboard; no safe dry-run result is available.", {
|
|
14568
|
+
expectedMode: "preview",
|
|
14569
|
+
receivedMode: analysis.receivedMode ?? null,
|
|
14570
|
+
suggestion: "Update the dashboard API before relying on push-config --dry-run."
|
|
14571
|
+
});
|
|
14572
|
+
return { success: false };
|
|
14573
|
+
}
|
|
14574
|
+
if (mode === "json") {
|
|
14575
|
+
output.markFailed();
|
|
14576
|
+
output.json({
|
|
14577
|
+
success: false,
|
|
14578
|
+
sync: analysis.sync,
|
|
14579
|
+
errors: analysis.errors
|
|
14580
|
+
});
|
|
14581
|
+
} else {
|
|
14582
|
+
output.markFailed();
|
|
14583
|
+
output.fail("Config pushed, but SDK config sync reported errors", {
|
|
14584
|
+
errors: analysis.errors,
|
|
14585
|
+
summary: analysis.sync
|
|
14586
|
+
});
|
|
14587
|
+
}
|
|
14588
|
+
return { success: false };
|
|
14589
|
+
}
|
|
14590
|
+
if (mode === "human" && analysis.warnings.length > 0) {
|
|
14591
|
+
output.warn("SDK config sync reported warnings", { warnings: groupPushConfigSyncWarningsForHuman(analysis.warnings) });
|
|
14592
|
+
}
|
|
14593
|
+
if (mode === "human" && analysis.sync) {
|
|
14594
|
+
output.info(analysis.resultMode === "preview" ? "SDK config preview summary" : "SDK config sync summary", analysis.resultMode === "preview" ? buildPreviewSummary(analysis.sync) : analysis.sync);
|
|
14595
|
+
}
|
|
14596
|
+
if (mode === "json") {
|
|
14597
|
+
output.json({
|
|
14598
|
+
success: true,
|
|
14599
|
+
...analysis.resultMode === "preview" ? {
|
|
14600
|
+
dryRun: true,
|
|
14601
|
+
mode: analysis.resultMode
|
|
14602
|
+
} : {},
|
|
14603
|
+
sync: analysis.sync
|
|
14604
|
+
});
|
|
14605
|
+
} else {
|
|
14606
|
+
output.success(analysis.resultMode === "preview" ? "Config preview completed. No changes were applied." : "Config pushed successfully!");
|
|
14617
14607
|
}
|
|
14608
|
+
return { success: true };
|
|
14618
14609
|
}
|
|
14619
|
-
/**
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14610
|
+
/** Main push-config action, retained for `push --with-config`. */
|
|
14611
|
+
async function pushConfigAction(output, options) {
|
|
14612
|
+
const target = options.target;
|
|
14613
|
+
try {
|
|
14614
|
+
if (!options.isJsonOutput) {
|
|
14615
|
+
output.info("push-config target", {
|
|
14616
|
+
env: target,
|
|
14617
|
+
dashboardUrl: options.dashboard,
|
|
14618
|
+
configPath: options.config ?? DEFAULT_CONFIG_PATH
|
|
14619
|
+
});
|
|
14620
|
+
output.info("Validating config...");
|
|
14621
|
+
}
|
|
14622
|
+
const config = await loadValidatedPushConfig(target, options.config);
|
|
14623
|
+
const apiKey = resolveManagementApiKey(output, options.apiKey, target);
|
|
14624
|
+
const response = await pushToDashboard(options.dashboard, config.siteId, apiKey, config, {
|
|
14625
|
+
forceContentTypes: options.forceContentTypes ?? false,
|
|
14626
|
+
dryRun: options.dryRun ?? false
|
|
14627
|
+
});
|
|
14628
|
+
return renderPushConfigResponse(output, response, options.isJsonOutput ? "json" : "human", options.dryRun ?? false);
|
|
14629
|
+
} catch (error) {
|
|
14630
|
+
handleCommandError(error, output, {
|
|
14631
|
+
target,
|
|
14632
|
+
dashboardUrl: options.dashboard
|
|
14633
|
+
});
|
|
14634
|
+
return { success: false };
|
|
14635
|
+
}
|
|
14636
|
+
}
|
|
14637
|
+
function resolveDashboardUrlValue(cliOption, target) {
|
|
14638
|
+
const envVar = getEnvVarName("DASHBOARD_URL", target);
|
|
14639
|
+
const url = cliOption || process.env[envVar];
|
|
14640
|
+
if (!url) {
|
|
14625
14641
|
return {
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14642
|
+
ok: false,
|
|
14643
|
+
error: {
|
|
14644
|
+
message: "Dashboard URL is required",
|
|
14645
|
+
details: { suggestion: `Provide --dashboard <url> or set ${envVar} environment variable.` }
|
|
14646
|
+
}
|
|
14629
14647
|
};
|
|
14630
14648
|
}
|
|
14631
14649
|
return {
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
kind: "page",
|
|
14635
|
-
identifier
|
|
14636
|
-
},
|
|
14637
|
-
error: null
|
|
14650
|
+
ok: true,
|
|
14651
|
+
value: url
|
|
14638
14652
|
};
|
|
14639
14653
|
}
|
|
14640
14654
|
/**
|
|
14641
|
-
* Resolve
|
|
14655
|
+
* Resolve management API key from CLI option or environment variable.
|
|
14642
14656
|
*/
|
|
14643
|
-
function
|
|
14644
|
-
const
|
|
14645
|
-
|
|
14657
|
+
function resolveManagementApiKeyValue(cliOption, target) {
|
|
14658
|
+
const envVar = getEnvVarName("MGMT_API_KEY", target);
|
|
14659
|
+
const apiKey = cliOption || process.env[envVar];
|
|
14660
|
+
if (!apiKey) {
|
|
14646
14661
|
return {
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14662
|
+
ok: false,
|
|
14663
|
+
error: {
|
|
14664
|
+
message: "Management API key is required",
|
|
14665
|
+
details: { suggestion: `Provide --api-key <key> or set ${envVar} environment variable.` }
|
|
14666
|
+
}
|
|
14650
14667
|
};
|
|
14651
14668
|
}
|
|
14652
|
-
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
}
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
//#region ../api/src/navigation/boundaries.ts
|
|
14665
|
-
function isNavigationDropdownSentinel(link$1) {
|
|
14666
|
-
return link$1?.kind === "dropdown";
|
|
14667
|
-
}
|
|
14668
|
-
function normalizeNavigationConfigItemForCommand(item) {
|
|
14669
|
-
const commandItem = { label: item.label };
|
|
14670
|
-
if (item.isCta !== undefined) {
|
|
14671
|
-
commandItem.isCta = item.isCta;
|
|
14672
|
-
}
|
|
14673
|
-
const commandLink = normalizeNavigationAuthoringLinkForCommand(item.link);
|
|
14674
|
-
if (commandLink) {
|
|
14675
|
-
commandItem.link = commandLink;
|
|
14676
|
-
}
|
|
14677
|
-
if (item.children && item.children.length > 0) {
|
|
14678
|
-
commandItem.children = item.children.map(normalizeNavigationConfigItemForCommand);
|
|
14669
|
+
const parsedApiKey = parseManagementKey(apiKey);
|
|
14670
|
+
if (!parsedApiKey.ok) {
|
|
14671
|
+
return {
|
|
14672
|
+
ok: false,
|
|
14673
|
+
error: {
|
|
14674
|
+
message: "Invalid management API key format",
|
|
14675
|
+
details: {
|
|
14676
|
+
expected: "Key starting with bld_mgmt_sk_",
|
|
14677
|
+
suggestion: `Check your ${envVar} environment variable.`
|
|
14678
|
+
}
|
|
14679
|
+
}
|
|
14680
|
+
};
|
|
14679
14681
|
}
|
|
14680
|
-
return commandItem;
|
|
14681
|
-
}
|
|
14682
|
-
function navigationCommandItemToManagementInput(item) {
|
|
14683
|
-
const link$1 = normalizeNavigationAuthoringLinkForCommand(item.link);
|
|
14684
14682
|
return {
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
...item.isCta !== undefined ? { isCta: item.isCta } : {},
|
|
14688
|
-
...item.children && item.children.length > 0 ? { children: item.children.map(navigationCommandItemToManagementInput) } : {}
|
|
14689
|
-
};
|
|
14690
|
-
}
|
|
14691
|
-
function normalizeNavigationAuthoringLinkForCommand(link$1) {
|
|
14692
|
-
if (!link$1 || isNavigationDropdownSentinel(link$1)) return undefined;
|
|
14693
|
-
return navigationAuthoringLinkToCommandLink(link$1);
|
|
14694
|
-
}
|
|
14695
|
-
function navigationApiItemToSdkLocalItem(item) {
|
|
14696
|
-
const link$1 = navigationReadLinkToLocalLink(item);
|
|
14697
|
-
const localItem = {
|
|
14698
|
-
label: item.label,
|
|
14699
|
-
...item.isCta ? { isCta: true } : {},
|
|
14700
|
-
...link$1 ? { link: link$1 } : {},
|
|
14701
|
-
...item.children && item.children.length > 0 ? { children: item.children.map(navigationApiItemToSdkLocalItem) } : {}
|
|
14683
|
+
ok: true,
|
|
14684
|
+
value: parsedApiKey.value
|
|
14702
14685
|
};
|
|
14703
|
-
return localItem;
|
|
14704
|
-
}
|
|
14705
|
-
function navigationAuthoringLinkToCommandLink(link$1) {
|
|
14706
|
-
switch (link$1.kind) {
|
|
14707
|
-
case "page": return {
|
|
14708
|
-
kind: "page",
|
|
14709
|
-
identifier: link$1.identifier
|
|
14710
|
-
};
|
|
14711
|
-
case "entry": return {
|
|
14712
|
-
kind: "entry",
|
|
14713
|
-
contentType: link$1.contentType,
|
|
14714
|
-
identifier: link$1.identifier
|
|
14715
|
-
};
|
|
14716
|
-
case "external": return {
|
|
14717
|
-
kind: "external",
|
|
14718
|
-
href: link$1.href
|
|
14719
|
-
};
|
|
14720
|
-
case "url": return {
|
|
14721
|
-
kind: "url",
|
|
14722
|
-
href: link$1.href
|
|
14723
|
-
};
|
|
14724
|
-
case "portal": return {
|
|
14725
|
-
kind: "portal",
|
|
14726
|
-
...link$1.path ? { path: link$1.path } : {},
|
|
14727
|
-
...portalLabelsForCommand(link$1.labels)
|
|
14728
|
-
};
|
|
14729
|
-
case "cart": return {
|
|
14730
|
-
kind: "cart",
|
|
14731
|
-
badge: link$1.badge,
|
|
14732
|
-
label: link$1.label
|
|
14733
|
-
};
|
|
14734
|
-
default: return assertNever(link$1);
|
|
14735
|
-
}
|
|
14736
14686
|
}
|
|
14737
|
-
function
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
...typeof labels.loggedIn === "string" ? { loggedIn: labels.loggedIn } : {}
|
|
14742
|
-
};
|
|
14743
|
-
return Object.keys(commandLabels).length > 0 ? { labels: commandLabels } : {};
|
|
14687
|
+
function resolveManagementApiKey(output, cliOption, target) {
|
|
14688
|
+
const result = resolveManagementApiKeyValue(cliOption, target);
|
|
14689
|
+
if (!result.ok) output.error(result.error.message, result.error.details);
|
|
14690
|
+
return result.value;
|
|
14744
14691
|
}
|
|
14745
|
-
function
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
case "page": return {
|
|
14750
|
-
kind: "page",
|
|
14751
|
-
identifier: link$1.identifier
|
|
14752
|
-
};
|
|
14753
|
-
case "entry": return {
|
|
14754
|
-
kind: "entry",
|
|
14755
|
-
contentType: link$1.contentType,
|
|
14756
|
-
identifier: link$1.identifier
|
|
14757
|
-
};
|
|
14758
|
-
case "internal": return internalNavigationLinkToLocalLink(item);
|
|
14759
|
-
case "external": return {
|
|
14760
|
-
kind: "external",
|
|
14761
|
-
href: link$1.href
|
|
14762
|
-
};
|
|
14763
|
-
case "url": return {
|
|
14764
|
-
kind: "url",
|
|
14765
|
-
href: link$1.href
|
|
14766
|
-
};
|
|
14767
|
-
case "portal": return {
|
|
14768
|
-
kind: "portal",
|
|
14769
|
-
...link$1.path ? { path: link$1.path } : {},
|
|
14770
|
-
...link$1.labels ? { labels: link$1.labels } : {}
|
|
14771
|
-
};
|
|
14772
|
-
case "cart": return {
|
|
14773
|
-
kind: "cart",
|
|
14774
|
-
badge: link$1.badge,
|
|
14775
|
-
label: link$1.label
|
|
14776
|
-
};
|
|
14777
|
-
default: return assertNever(link$1);
|
|
14778
|
-
}
|
|
14692
|
+
function unwrapPushConfigTargetValue(result) {
|
|
14693
|
+
if (result.ok) return result.value;
|
|
14694
|
+
const suggestion = result.error.details.suggestion;
|
|
14695
|
+
throw new Error(`${result.error.message}${typeof suggestion === "string" ? `. ${suggestion}` : ""}`);
|
|
14779
14696
|
}
|
|
14780
|
-
function
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
};
|
|
14796
|
-
}
|
|
14697
|
+
async function loadPushConfigTarget(target, options) {
|
|
14698
|
+
return {
|
|
14699
|
+
target,
|
|
14700
|
+
dashboard: unwrapPushConfigTargetValue(resolveDashboardUrlValue(options.dashboard, target)),
|
|
14701
|
+
apiKey: unwrapPushConfigTargetValue(resolveManagementApiKeyValue(options.apiKey, target))
|
|
14702
|
+
};
|
|
14703
|
+
}
|
|
14704
|
+
async function preparePushConfigTarget(context, options) {
|
|
14705
|
+
const config = await loadValidatedPushConfig(context.target, options.config);
|
|
14706
|
+
const preview = await pushToDashboard(context.dashboard, config.siteId, context.apiKey, config, {
|
|
14707
|
+
forceContentTypes: options.forceContentTypes ?? false,
|
|
14708
|
+
dryRun: true
|
|
14709
|
+
});
|
|
14710
|
+
if (!analyzePushConfigResponse(preview, true).success) {
|
|
14711
|
+
throw new Error(`Config preview failed for ${context.target} environment.`);
|
|
14797
14712
|
}
|
|
14798
|
-
|
|
14713
|
+
return {
|
|
14714
|
+
config,
|
|
14715
|
+
preview,
|
|
14716
|
+
forceContentTypes: options.forceContentTypes ?? false
|
|
14717
|
+
};
|
|
14718
|
+
}
|
|
14719
|
+
/**
|
|
14720
|
+
* Re-preview a prepared config value without reloading the local config file.
|
|
14721
|
+
* The caller uses this immediately before applying a multi-target plan so a
|
|
14722
|
+
* dashboard-side schema change is classified as stale before any mutation.
|
|
14723
|
+
*/
|
|
14724
|
+
async function revalidatePushConfigPlan(context, plan) {
|
|
14725
|
+
try {
|
|
14726
|
+
const current = await pushToDashboard(context.dashboard, plan.config.siteId, context.apiKey, plan.config, {
|
|
14727
|
+
forceContentTypes: plan.forceContentTypes,
|
|
14728
|
+
dryRun: true
|
|
14729
|
+
});
|
|
14730
|
+
return stableStringify(current) === stableStringify(plan.preview) ? { ok: true } : {
|
|
14731
|
+
ok: false,
|
|
14732
|
+
error: new Error("SDK config changed after preflight; the push plan is stale.")
|
|
14733
|
+
};
|
|
14734
|
+
} catch (error) {
|
|
14799
14735
|
return {
|
|
14800
|
-
|
|
14801
|
-
|
|
14736
|
+
ok: false,
|
|
14737
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
14802
14738
|
};
|
|
14803
14739
|
}
|
|
14804
|
-
return undefined;
|
|
14805
14740
|
}
|
|
14741
|
+
async function applyPushConfigPlan(output, context, plan, options, isJsonOutput) {
|
|
14742
|
+
const response = options.dryRun ? plan.preview : await pushToDashboard(context.dashboard, plan.config.siteId, context.apiKey, plan.config, {
|
|
14743
|
+
forceContentTypes: plan.forceContentTypes,
|
|
14744
|
+
dryRun: false,
|
|
14745
|
+
expectedMailingListProviderScope: plan.preview.mailingListProviderScope
|
|
14746
|
+
});
|
|
14747
|
+
return {
|
|
14748
|
+
response,
|
|
14749
|
+
action: isJsonOutput ? { success: analyzePushConfigResponse(response, options.dryRun ?? false).success } : renderPushConfigResponse(output, response, "human", options.dryRun ?? false)
|
|
14750
|
+
};
|
|
14751
|
+
}
|
|
14752
|
+
function isPushConfigApplySuccess(result) {
|
|
14753
|
+
return result.action.success;
|
|
14754
|
+
}
|
|
14755
|
+
function renderPushConfigJsonResult(output, execution, dryRun) {
|
|
14756
|
+
output.json({
|
|
14757
|
+
success: execution.succeeded,
|
|
14758
|
+
dryRun,
|
|
14759
|
+
environments: mapEnvironmentExecutionOutcomesJson(execution, ({ result }) => ({
|
|
14760
|
+
response: result.response,
|
|
14761
|
+
success: true
|
|
14762
|
+
}), ({ result, message }) => ({
|
|
14763
|
+
response: result.response,
|
|
14764
|
+
success: false,
|
|
14765
|
+
error: message
|
|
14766
|
+
}))
|
|
14767
|
+
});
|
|
14768
|
+
}
|
|
14769
|
+
function getProductionPushConfigCommandDeps() {
|
|
14770
|
+
return {
|
|
14771
|
+
getOutputContext,
|
|
14772
|
+
planEnvironmentExecution,
|
|
14773
|
+
executeEnvironmentPlan,
|
|
14774
|
+
confirmAction,
|
|
14775
|
+
loadTarget: loadPushConfigTarget,
|
|
14776
|
+
prepareTarget: preparePushConfigTarget,
|
|
14777
|
+
revalidatePlan: revalidatePushConfigPlan,
|
|
14778
|
+
applyPlan: applyPushConfigPlan
|
|
14779
|
+
};
|
|
14780
|
+
}
|
|
14781
|
+
/**
|
|
14782
|
+
* push-config command definition
|
|
14783
|
+
*/
|
|
14784
|
+
const createPushConfigCommand = (deps = getProductionPushConfigCommandDeps()) => new Command("push-config").description("Push SDK config to dashboard").option("--api-key <key>", "Management API key (or set RIVERBANK_*_MGMT_API_KEY)").option("--dashboard <url>", "Dashboard URL (or set RIVERBANK_*_DASHBOARD_URL env var)").option("--config <path>", `Path to config file (default: ${DEFAULT_CONFIG_PATH})`).option("--force-content-types", "Force overwrite content type schema even if modified in dashboard after last SDK sync").option("--dry-run", "Preview SDK config changes without updating the dashboard").option("--yes", "Confirm applying config changes across all selected environments").addHelpText("after", `
|
|
14785
|
+
Description:
|
|
14786
|
+
Syncs your local riverbank.config.ts schema/config to the CMS dashboard, including:
|
|
14787
|
+
- Custom blocks
|
|
14788
|
+
- Block field extensions
|
|
14789
|
+
- Block field options
|
|
14790
|
+
- Content type definitions and templates
|
|
14791
|
+
- SDK-managed site settings and footer blocks
|
|
14792
|
+
|
|
14793
|
+
Pages, content entries, and navigation menus are content documents.
|
|
14794
|
+
Sync them with "riverbankcms push pages", "riverbankcms push entries",
|
|
14795
|
+
or "riverbankcms push navigation".
|
|
14796
|
+
|
|
14797
|
+
Examples:
|
|
14798
|
+
$ pnpm exec riverbankcms push-config
|
|
14799
|
+
$ pnpm exec riverbankcms push-config --dry-run
|
|
14800
|
+
$ pnpm exec riverbankcms push-config --env=remote
|
|
14801
|
+
$ pnpm exec riverbankcms push-config --api-key bld_mgmt_sk_... --dashboard https://www.riverbankcms.com
|
|
14802
|
+
$ pnpm exec riverbankcms push-config --config ./src/riverbank.config.ts
|
|
14803
|
+
`).action(async (options, command) => {
|
|
14804
|
+
const { output, selection, globalOpts } = deps.getOutputContext(command);
|
|
14805
|
+
const isJsonOutput = globalOpts.json ?? false;
|
|
14806
|
+
const authorization = requireEnvironmentWriteAuthorization({
|
|
14807
|
+
...options,
|
|
14808
|
+
json: isJsonOutput,
|
|
14809
|
+
interactive: process.stdin.isTTY === true
|
|
14810
|
+
}, output);
|
|
14811
|
+
if (!authorization) return;
|
|
14812
|
+
const planned = deps.planEnvironmentExecution({
|
|
14813
|
+
command: "push-config",
|
|
14814
|
+
selection: "both",
|
|
14815
|
+
failure: "abort",
|
|
14816
|
+
confirmation: "command",
|
|
14817
|
+
revalidation: "required"
|
|
14818
|
+
}, selection);
|
|
14819
|
+
if (!planned.ok) return output.error(planned.error.message, { suggestion: planned.error.suggestion });
|
|
14820
|
+
const execution = await deps.executeEnvironmentPlan(planned.value, {
|
|
14821
|
+
loadTarget: (target) => deps.loadTarget(target, options),
|
|
14822
|
+
preflight: async (_target, loaded) => deps.prepareTarget(loaded.context, {
|
|
14823
|
+
...options,
|
|
14824
|
+
dashboard: loaded.context.dashboard
|
|
14825
|
+
}),
|
|
14826
|
+
confirm: async ({ ready }) => {
|
|
14827
|
+
if (authorization.kind === "preauthorized") return true;
|
|
14828
|
+
for (const { target, context } of ready) {
|
|
14829
|
+
const sync = getPushConfigSync(context.preview);
|
|
14830
|
+
output.info(`${target} config preview`, sync ? buildPreviewSummary(sync) : { changes: "No sync summary returned" });
|
|
14831
|
+
}
|
|
14832
|
+
return deps.confirmAction(`Apply SDK config to ${ready.length} environment${ready.length === 1 ? "" : "s"} (${ready.map(({ target }) => target).join(" then ")})?`, { yes: options.yes }, false);
|
|
14833
|
+
},
|
|
14834
|
+
revalidate: async (_target, loaded, prepared) => options.dryRun ? { ok: true } : deps.revalidatePlan(loaded.context, prepared.context),
|
|
14835
|
+
apply: async (target, loaded, prepared) => {
|
|
14836
|
+
if (planned.value.targets.length > 1) output.info(`\n--- Pushing to ${target} environment ---`);
|
|
14837
|
+
return deps.applyPlan(output, loaded.context, prepared.context, {
|
|
14838
|
+
...options,
|
|
14839
|
+
dashboard: loaded.context.dashboard
|
|
14840
|
+
}, isJsonOutput);
|
|
14841
|
+
},
|
|
14842
|
+
isSuccess: isPushConfigApplySuccess,
|
|
14843
|
+
formatFailure: formatPushConfigActionFailure
|
|
14844
|
+
}, output);
|
|
14845
|
+
if (isJsonOutput) renderPushConfigJsonResult(output, execution, options.dryRun ?? false);
|
|
14846
|
+
});
|
|
14806
14847
|
|
|
14807
14848
|
//#endregion
|
|
14808
|
-
//#region ../api/src/
|
|
14849
|
+
//#region ../api/src/navigationMenuValidation.ts
|
|
14850
|
+
function hasChildren(item) {
|
|
14851
|
+
return Array.isArray(item.children) && item.children.length > 0;
|
|
14852
|
+
}
|
|
14853
|
+
function isCartLinkMissingVisibleLabel(link$1) {
|
|
14854
|
+
return link$1?.kind === "cart" && link$1.label?.kind === "icon-with-label" && (typeof link$1.label.label !== "string" || link$1.label.label.trim().length === 0);
|
|
14855
|
+
}
|
|
14809
14856
|
/**
|
|
14810
|
-
*
|
|
14811
|
-
*
|
|
14857
|
+
* Recursively collects portal-link constraints for a single menu tree.
|
|
14858
|
+
* Rules:
|
|
14859
|
+
* - At most one portal link per menu.
|
|
14860
|
+
* - Portal links must be root-level.
|
|
14861
|
+
* - Portal links cannot have children.
|
|
14812
14862
|
*/
|
|
14813
|
-
function
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
|
|
14863
|
+
function collectPortalLinkStats(items, depth = 0) {
|
|
14864
|
+
const result = {
|
|
14865
|
+
count: 0,
|
|
14866
|
+
nestedLabels: [],
|
|
14867
|
+
withChildrenLabels: []
|
|
14868
|
+
};
|
|
14869
|
+
for (const item of items) {
|
|
14870
|
+
const isPortal = item.link?.kind === "portal";
|
|
14871
|
+
if (isPortal) {
|
|
14872
|
+
result.count += 1;
|
|
14873
|
+
if (depth > 0) {
|
|
14874
|
+
result.nestedLabels.push(item.label);
|
|
14821
14875
|
}
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
if (item.identifier && item.contentTypeKey) {
|
|
14825
|
-
return {
|
|
14826
|
-
kind: "entry",
|
|
14827
|
-
contentType: item.contentTypeKey,
|
|
14828
|
-
identifier: item.identifier
|
|
14829
|
-
};
|
|
14876
|
+
if (hasChildren(item)) {
|
|
14877
|
+
result.withChildrenLabels.push(item.label);
|
|
14830
14878
|
}
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
|
|
14879
|
+
}
|
|
14880
|
+
if (hasChildren(item)) {
|
|
14881
|
+
const childItems = (item.children ?? []).filter(isNavigationMenuValidationItem);
|
|
14882
|
+
const child = collectPortalLinkStats(childItems, depth + 1);
|
|
14883
|
+
result.count += child.count;
|
|
14884
|
+
result.nestedLabels.push(...child.nestedLabels);
|
|
14885
|
+
result.withChildrenLabels.push(...child.withChildrenLabels);
|
|
14886
|
+
}
|
|
14834
14887
|
}
|
|
14888
|
+
return result;
|
|
14835
14889
|
}
|
|
14836
|
-
|
|
14837
|
-
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14890
|
+
/**
|
|
14891
|
+
* Recursively collects cart-link constraints for a single menu tree.
|
|
14892
|
+
* Rules:
|
|
14893
|
+
* - Cart links must be root-level.
|
|
14894
|
+
* - Cart links cannot have children.
|
|
14895
|
+
* - Cart links cannot be CTA items.
|
|
14896
|
+
* - Cart links using `icon-with-label` must provide visible text.
|
|
14897
|
+
*/
|
|
14898
|
+
function collectCartLinkStats(items, depth = 0) {
|
|
14899
|
+
const result = {
|
|
14900
|
+
count: 0,
|
|
14901
|
+
nestedLabels: [],
|
|
14902
|
+
withChildrenLabels: [],
|
|
14903
|
+
ctaLabels: [],
|
|
14904
|
+
missingVisibleTextLabels: []
|
|
14848
14905
|
};
|
|
14906
|
+
for (const item of items) {
|
|
14907
|
+
const isCart = item.link?.kind === "cart";
|
|
14908
|
+
if (isCart) {
|
|
14909
|
+
result.count += 1;
|
|
14910
|
+
if (depth > 0) {
|
|
14911
|
+
result.nestedLabels.push(item.label);
|
|
14912
|
+
}
|
|
14913
|
+
if (hasChildren(item)) {
|
|
14914
|
+
result.withChildrenLabels.push(item.label);
|
|
14915
|
+
}
|
|
14916
|
+
if (item.isCta === true) {
|
|
14917
|
+
result.ctaLabels.push(item.label);
|
|
14918
|
+
}
|
|
14919
|
+
if (isCartLinkMissingVisibleLabel(item.link ?? null)) {
|
|
14920
|
+
result.missingVisibleTextLabels.push(item.label);
|
|
14921
|
+
}
|
|
14922
|
+
}
|
|
14923
|
+
if (hasChildren(item)) {
|
|
14924
|
+
const childItems = (item.children ?? []).filter(isNavigationMenuValidationItem);
|
|
14925
|
+
const child = collectCartLinkStats(childItems, depth + 1);
|
|
14926
|
+
result.count += child.count;
|
|
14927
|
+
result.nestedLabels.push(...child.nestedLabels);
|
|
14928
|
+
result.withChildrenLabels.push(...child.withChildrenLabels);
|
|
14929
|
+
result.ctaLabels.push(...child.ctaLabels);
|
|
14930
|
+
result.missingVisibleTextLabels.push(...child.missingVisibleTextLabels);
|
|
14931
|
+
}
|
|
14932
|
+
}
|
|
14933
|
+
return result;
|
|
14849
14934
|
}
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
const LINK_PAYLOAD_NAVIGATION_URL_TYPES = {
|
|
14854
|
-
page: "internal",
|
|
14855
|
-
entry: "internal",
|
|
14856
|
-
internal: "internal",
|
|
14857
|
-
external: "external",
|
|
14858
|
-
url: "url",
|
|
14859
|
-
portal: "portal",
|
|
14860
|
-
cart: "cart"
|
|
14861
|
-
};
|
|
14862
|
-
function linkPayloadKindToNavigationUrlType(kind) {
|
|
14863
|
-
return LINK_PAYLOAD_NAVIGATION_URL_TYPES[kind];
|
|
14864
|
-
}
|
|
14865
|
-
|
|
14866
|
-
//#endregion
|
|
14867
|
-
//#region ../api/src/accessAdmin.ts
|
|
14868
|
-
const STAFF_OPERATIONAL_CAPABILITIES = ["appointments", "event_ops"];
|
|
14869
|
-
function getDefaultStaffCapabilities(surfaces) {
|
|
14870
|
-
if (surfaces.events && !surfaces.appointments) return ["event_ops"];
|
|
14871
|
-
return ["appointments"];
|
|
14935
|
+
function isNavigationMenuValidationItem(value) {
|
|
14936
|
+
if (!isRecord$5(value)) return false;
|
|
14937
|
+
return typeof value.label === "string";
|
|
14872
14938
|
}
|
|
14873
|
-
const managedDomainAddOnPricingSnapshotSchema = z.object({
|
|
14874
|
-
formulaVersion: z.literal("managed-domain-overage-v1"),
|
|
14875
|
-
conversionRule: z.literal("usd-to-gbp-1-to-1-v1"),
|
|
14876
|
-
registrarAnnualCost: z.number().nonnegative(),
|
|
14877
|
-
registrarCurrency: z.literal("USD"),
|
|
14878
|
-
includedAnnualAllowance: z.number().nonnegative(),
|
|
14879
|
-
includedAnnualAllowanceCurrency: z.literal("USD"),
|
|
14880
|
-
monthlyAmountCents: z.number().int().nonnegative(),
|
|
14881
|
-
monthlyCurrency: z.literal("gbp")
|
|
14882
|
-
});
|
|
14883
14939
|
|
|
14884
14940
|
//#endregion
|
|
14885
|
-
//#region ../api/src/
|
|
14941
|
+
//#region ../api/src/navigation/bookingPortalLink.ts
|
|
14942
|
+
const YOUR_BOOKINGS_LABEL = "Your bookings";
|
|
14886
14943
|
/**
|
|
14887
|
-
*
|
|
14944
|
+
* The booking page's link to the customer portal, in the header and the
|
|
14945
|
+
* footer alike. It is a portal link, so renderers apply the portal link rules
|
|
14946
|
+
* (including the portal-enabled gate); the label stays the same signed in.
|
|
14888
14947
|
*/
|
|
14889
|
-
const
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14948
|
+
const YOUR_BOOKINGS_PORTAL_LINK = {
|
|
14949
|
+
kind: "portal",
|
|
14950
|
+
labels: {
|
|
14951
|
+
loggedOut: YOUR_BOOKINGS_LABEL,
|
|
14952
|
+
loggedIn: YOUR_BOOKINGS_LABEL
|
|
14953
|
+
}
|
|
14894
14954
|
};
|
|
14955
|
+
|
|
14956
|
+
//#endregion
|
|
14957
|
+
//#region ../api/src/navigation/linkResolver.ts
|
|
14895
14958
|
/**
|
|
14896
|
-
*
|
|
14959
|
+
* Type guard for internal links.
|
|
14960
|
+
* Checks if a link payload is an internal link with entityId and entityType.
|
|
14897
14961
|
*/
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
editor: 2,
|
|
14902
|
-
team_member: 3
|
|
14903
|
-
};
|
|
14904
|
-
async function getSiteMembers(apiClient, { siteId, options }) {
|
|
14905
|
-
try {
|
|
14906
|
-
return unwrapResponse(await apiClient({
|
|
14907
|
-
endpoint: "listSiteMembers",
|
|
14908
|
-
params: { siteId },
|
|
14909
|
-
options
|
|
14910
|
-
}));
|
|
14911
|
-
} catch (error) {
|
|
14912
|
-
return normalizeApiError(error, "Failed to load site members");
|
|
14962
|
+
function isInternalLink(link$1) {
|
|
14963
|
+
if (!link$1 || typeof link$1 !== "object") {
|
|
14964
|
+
return false;
|
|
14913
14965
|
}
|
|
14966
|
+
return link$1.kind === "internal" && "entityId" in link$1 && "entityType" in link$1 && typeof link$1.entityId === "string" && typeof link$1.entityType === "string";
|
|
14914
14967
|
}
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
body,
|
|
14921
|
-
options
|
|
14922
|
-
}));
|
|
14923
|
-
} catch (error) {
|
|
14924
|
-
return normalizeApiError(error, "Failed to invite collaborator");
|
|
14925
|
-
}
|
|
14968
|
+
/**
|
|
14969
|
+
* Narrow internal link to page type.
|
|
14970
|
+
*/
|
|
14971
|
+
function isPageLink(link$1) {
|
|
14972
|
+
return link$1.entityType === "page";
|
|
14926
14973
|
}
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
siteId,
|
|
14933
|
-
memberId
|
|
14934
|
-
},
|
|
14935
|
-
body,
|
|
14936
|
-
options
|
|
14937
|
-
}));
|
|
14938
|
-
} catch (error) {
|
|
14939
|
-
return normalizeApiError(error, "Failed to update member role");
|
|
14940
|
-
}
|
|
14974
|
+
/**
|
|
14975
|
+
* Narrow internal link to content/entry type.
|
|
14976
|
+
*/
|
|
14977
|
+
function isEntryLink(link$1) {
|
|
14978
|
+
return link$1.entityType === "content";
|
|
14941
14979
|
}
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14980
|
+
/**
|
|
14981
|
+
* Resolve a navigation link input to URL type and link payload.
|
|
14982
|
+
*
|
|
14983
|
+
* Supports identifier-based page/entry links and validates them against routable
|
|
14984
|
+
* lookup maps before persisting.
|
|
14985
|
+
*
|
|
14986
|
+
* @param link - The navigation link input from SDK config or API
|
|
14987
|
+
* @param options - Resolution options (isDropdown flag)
|
|
14988
|
+
* @param pageMap - Map of page identifier -> PageNavLinkInfo
|
|
14989
|
+
* @param entryMap - Map of `contentType:identifier` -> EntryNavLinkInfo
|
|
14990
|
+
* @returns Resolved link with urlType, url payload, and any error
|
|
14991
|
+
*/
|
|
14992
|
+
function resolveNavigationLinkInput(link$1, options, pageMap, entryMap) {
|
|
14993
|
+
if (options.isDropdown) {
|
|
14994
|
+
return {
|
|
14995
|
+
urlType: "dropdown",
|
|
14996
|
+
url: null,
|
|
14997
|
+
error: null
|
|
14998
|
+
};
|
|
14952
14999
|
}
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
options
|
|
14960
|
-
}));
|
|
14961
|
-
} catch (error) {
|
|
14962
|
-
return normalizeApiError(error, "Failed to unlink practitioner profile");
|
|
15000
|
+
if (!link$1) {
|
|
15001
|
+
return {
|
|
15002
|
+
urlType: "url",
|
|
15003
|
+
url: null,
|
|
15004
|
+
error: "Link is required for non-dropdown items"
|
|
15005
|
+
};
|
|
14963
15006
|
}
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
return
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
15007
|
+
switch (link$1.kind) {
|
|
15008
|
+
case "page": return resolvePageLink(link$1.identifier, pageMap);
|
|
15009
|
+
case "entry": return resolveEntryLink(link$1.contentType, link$1.identifier, entryMap);
|
|
15010
|
+
case "external": return {
|
|
15011
|
+
urlType: "external",
|
|
15012
|
+
url: {
|
|
15013
|
+
kind: "external",
|
|
15014
|
+
href: link$1.href
|
|
14972
15015
|
},
|
|
14973
|
-
|
|
14974
|
-
}
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
try {
|
|
14981
|
-
return unwrapResponse(await apiClient({
|
|
14982
|
-
endpoint: "revokeSiteInvitation",
|
|
14983
|
-
params: {
|
|
14984
|
-
siteId,
|
|
14985
|
-
invitationId
|
|
15016
|
+
error: null
|
|
15017
|
+
};
|
|
15018
|
+
case "url": return {
|
|
15019
|
+
urlType: "url",
|
|
15020
|
+
url: {
|
|
15021
|
+
kind: "url",
|
|
15022
|
+
href: link$1.href
|
|
14986
15023
|
},
|
|
14987
|
-
|
|
14988
|
-
}
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
return unwrapResponse(await apiClient({
|
|
14996
|
-
endpoint: "resendSiteInvitation",
|
|
14997
|
-
params: {
|
|
14998
|
-
siteId,
|
|
14999
|
-
invitationId
|
|
15024
|
+
error: null
|
|
15025
|
+
};
|
|
15026
|
+
case "portal": return {
|
|
15027
|
+
urlType: "portal",
|
|
15028
|
+
url: {
|
|
15029
|
+
kind: "portal",
|
|
15030
|
+
...link$1.path ? { path: link$1.path } : {},
|
|
15031
|
+
...link$1.labels ? { labels: link$1.labels } : {}
|
|
15000
15032
|
},
|
|
15001
|
-
|
|
15002
|
-
}
|
|
15003
|
-
|
|
15004
|
-
|
|
15033
|
+
error: null
|
|
15034
|
+
};
|
|
15035
|
+
case "cart": return {
|
|
15036
|
+
urlType: "cart",
|
|
15037
|
+
url: {
|
|
15038
|
+
kind: "cart",
|
|
15039
|
+
badge: link$1.badge,
|
|
15040
|
+
label: link$1.label
|
|
15041
|
+
},
|
|
15042
|
+
error: null
|
|
15043
|
+
};
|
|
15044
|
+
default: return {
|
|
15045
|
+
urlType: "url",
|
|
15046
|
+
url: null,
|
|
15047
|
+
error: "Unknown link type"
|
|
15048
|
+
};
|
|
15005
15049
|
}
|
|
15006
15050
|
}
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15012
|
-
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15051
|
+
/**
|
|
15052
|
+
* Resolve a page identifier to a canonical page link payload.
|
|
15053
|
+
*/
|
|
15054
|
+
function resolvePageLink(identifier, pageMap) {
|
|
15055
|
+
const pageInfo = pageMap.get(identifier);
|
|
15056
|
+
if (!pageInfo) {
|
|
15057
|
+
return {
|
|
15058
|
+
urlType: "internal",
|
|
15059
|
+
url: null,
|
|
15060
|
+
error: `Page '${identifier}' not found`
|
|
15061
|
+
};
|
|
15017
15062
|
}
|
|
15063
|
+
return {
|
|
15064
|
+
urlType: "internal",
|
|
15065
|
+
url: {
|
|
15066
|
+
kind: "page",
|
|
15067
|
+
identifier
|
|
15068
|
+
},
|
|
15069
|
+
error: null
|
|
15070
|
+
};
|
|
15018
15071
|
}
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15072
|
+
/**
|
|
15073
|
+
* Resolve an entry identifier to a canonical entry link payload.
|
|
15074
|
+
*/
|
|
15075
|
+
function resolveEntryLink(contentType, identifier, entryMap) {
|
|
15076
|
+
const entryInfo = entryMap.get(`${contentType}:${identifier}`);
|
|
15077
|
+
if (!entryInfo) {
|
|
15078
|
+
return {
|
|
15079
|
+
urlType: "internal",
|
|
15080
|
+
url: null,
|
|
15081
|
+
error: `Entry '${contentType}:${identifier}' not found or not routable`
|
|
15082
|
+
};
|
|
15028
15083
|
}
|
|
15084
|
+
return {
|
|
15085
|
+
urlType: "internal",
|
|
15086
|
+
url: {
|
|
15087
|
+
kind: "entry",
|
|
15088
|
+
contentType,
|
|
15089
|
+
identifier
|
|
15090
|
+
},
|
|
15091
|
+
error: null
|
|
15092
|
+
};
|
|
15029
15093
|
}
|
|
15030
15094
|
|
|
15031
15095
|
//#endregion
|
|
15032
|
-
//#region ../api/src/
|
|
15033
|
-
function
|
|
15034
|
-
return
|
|
15035
|
-
}
|
|
15036
|
-
function roleAtLeastOrNone(role, minimum) {
|
|
15037
|
-
if (!role) return false;
|
|
15038
|
-
return roleAtLeast$1(role, minimum);
|
|
15039
|
-
}
|
|
15040
|
-
function appointmentResourceIdFromStaffMemberId(staffMemberId) {
|
|
15041
|
-
return unsafeAsAppointmentResourceId(staffMemberId);
|
|
15042
|
-
}
|
|
15043
|
-
function normalizeStaffCapabilities(capabilities) {
|
|
15044
|
-
if (!capabilities) return null;
|
|
15045
|
-
return STAFF_OPERATIONAL_CAPABILITIES.filter((capability) => capabilities.includes(capability));
|
|
15096
|
+
//#region ../api/src/navigation/boundaries.ts
|
|
15097
|
+
function isNavigationDropdownSentinel(link$1) {
|
|
15098
|
+
return link$1?.kind === "dropdown";
|
|
15046
15099
|
}
|
|
15047
|
-
function
|
|
15048
|
-
const
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
case null:
|
|
15052
|
-
case "owner":
|
|
15053
|
-
case "administrator":
|
|
15054
|
-
case "editor": return { kind: "none" };
|
|
15055
|
-
case "team_member":
|
|
15056
|
-
if (!staffMemberId) {
|
|
15057
|
-
return {
|
|
15058
|
-
kind: "staff_setup_required",
|
|
15059
|
-
reason: "missing_staff_member"
|
|
15060
|
-
};
|
|
15061
|
-
}
|
|
15062
|
-
if (persistedCapabilities === null) {
|
|
15063
|
-
return {
|
|
15064
|
-
kind: "appointments",
|
|
15065
|
-
staffMemberId
|
|
15066
|
-
};
|
|
15067
|
-
}
|
|
15068
|
-
if (persistedCapabilities.length === 0) {
|
|
15069
|
-
return {
|
|
15070
|
-
kind: "staff_setup_required",
|
|
15071
|
-
reason: "missing_capabilities"
|
|
15072
|
-
};
|
|
15073
|
-
}
|
|
15074
|
-
if (persistedCapabilities.includes("appointments") && persistedCapabilities.includes("event_ops")) {
|
|
15075
|
-
return {
|
|
15076
|
-
kind: "hybrid",
|
|
15077
|
-
staffMemberId
|
|
15078
|
-
};
|
|
15079
|
-
}
|
|
15080
|
-
if (persistedCapabilities.includes("event_ops")) {
|
|
15081
|
-
return {
|
|
15082
|
-
kind: "event_ops_only",
|
|
15083
|
-
staffMemberId
|
|
15084
|
-
};
|
|
15085
|
-
}
|
|
15086
|
-
return {
|
|
15087
|
-
kind: "appointments",
|
|
15088
|
-
staffMemberId
|
|
15089
|
-
};
|
|
15100
|
+
function normalizeNavigationConfigItemForCommand(item) {
|
|
15101
|
+
const commandItem = { label: item.label };
|
|
15102
|
+
if (item.isCta !== undefined) {
|
|
15103
|
+
commandItem.isCta = item.isCta;
|
|
15090
15104
|
}
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
}
|
|
15095
|
-
function isTeamMemberScope(scope) {
|
|
15096
|
-
return scope.kind === "appointments" || scope.kind === "staff_setup_required";
|
|
15097
|
-
}
|
|
15098
|
-
function hasAppointmentStaffAccess(scope) {
|
|
15099
|
-
return scope.kind === "appointments" || scope.kind === "hybrid";
|
|
15100
|
-
}
|
|
15101
|
-
function hasEventOpsAccess(scope) {
|
|
15102
|
-
return scope.kind === "event_ops_only" || scope.kind === "hybrid";
|
|
15103
|
-
}
|
|
15104
|
-
function getLinkedStaffMemberId(scope) {
|
|
15105
|
-
switch (scope.kind) {
|
|
15106
|
-
case "appointments":
|
|
15107
|
-
case "event_ops_only":
|
|
15108
|
-
case "hybrid": return scope.staffMemberId;
|
|
15109
|
-
case "none":
|
|
15110
|
-
case "staff_setup_required": return null;
|
|
15105
|
+
const commandLink = normalizeNavigationAuthoringLinkForCommand(item.link);
|
|
15106
|
+
if (commandLink) {
|
|
15107
|
+
commandItem.link = commandLink;
|
|
15111
15108
|
}
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
const staffMemberId = getLinkedStaffMemberId(scope);
|
|
15115
|
-
return staffMemberId ? appointmentResourceIdFromStaffMemberId(staffMemberId) : null;
|
|
15116
|
-
}
|
|
15117
|
-
function canAccessOwnAppointmentResource(scope, resourceId) {
|
|
15118
|
-
const ownResourceId = getOwnAppointmentResourceId(scope);
|
|
15119
|
-
if (!ownResourceId) return false;
|
|
15120
|
-
const normalizedResourceId = typeof resourceId === "string" ? unsafeAsAppointmentResourceId(resourceId) : resourceId;
|
|
15121
|
-
return ownResourceId === normalizedResourceId;
|
|
15122
|
-
}
|
|
15123
|
-
function deriveLinkedAppointmentIdentity(input) {
|
|
15124
|
-
if (!input.membershipRole || !input.linkedStaffMemberId) {
|
|
15125
|
-
return { kind: "none" };
|
|
15109
|
+
if (item.children && item.children.length > 0) {
|
|
15110
|
+
commandItem.children = item.children.map(normalizeNavigationConfigItemForCommand);
|
|
15126
15111
|
}
|
|
15127
|
-
|
|
15112
|
+
return commandItem;
|
|
15113
|
+
}
|
|
15114
|
+
function navigationCommandItemToManagementInput(item) {
|
|
15115
|
+
const link$1 = normalizeNavigationAuthoringLinkForCommand(item.link);
|
|
15128
15116
|
return {
|
|
15129
|
-
|
|
15130
|
-
|
|
15131
|
-
|
|
15117
|
+
label: item.label,
|
|
15118
|
+
...link$1 ? { link: link$1 } : {},
|
|
15119
|
+
...item.isCta !== undefined ? { isCta: item.isCta } : {},
|
|
15120
|
+
...item.children && item.children.length > 0 ? { children: item.children.map(navigationCommandItemToManagementInput) } : {}
|
|
15132
15121
|
};
|
|
15133
15122
|
}
|
|
15134
|
-
function
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
case "none": return null;
|
|
15138
|
-
}
|
|
15139
|
-
}
|
|
15140
|
-
function getIdentityStaffMemberIdValue(identity) {
|
|
15141
|
-
return getIdentityStaffMemberId(identity);
|
|
15123
|
+
function normalizeNavigationAuthoringLinkForCommand(link$1) {
|
|
15124
|
+
if (!link$1 || isNavigationDropdownSentinel(link$1)) return undefined;
|
|
15125
|
+
return navigationAuthoringLinkToCommandLink(link$1);
|
|
15142
15126
|
}
|
|
15143
|
-
function
|
|
15144
|
-
|
|
15127
|
+
function navigationApiItemToSdkLocalItem(item) {
|
|
15128
|
+
const link$1 = navigationReadLinkToLocalLink(item);
|
|
15129
|
+
const localItem = {
|
|
15130
|
+
label: item.label,
|
|
15131
|
+
...item.isCta ? { isCta: true } : {},
|
|
15132
|
+
...link$1 ? { link: link$1 } : {},
|
|
15133
|
+
...item.children && item.children.length > 0 ? { children: item.children.map(navigationApiItemToSdkLocalItem) } : {}
|
|
15134
|
+
};
|
|
15135
|
+
return localItem;
|
|
15145
15136
|
}
|
|
15146
|
-
function
|
|
15147
|
-
|
|
15137
|
+
function navigationAuthoringLinkToCommandLink(link$1) {
|
|
15138
|
+
switch (link$1.kind) {
|
|
15139
|
+
case "page": return {
|
|
15140
|
+
kind: "page",
|
|
15141
|
+
identifier: link$1.identifier
|
|
15142
|
+
};
|
|
15143
|
+
case "entry": return {
|
|
15144
|
+
kind: "entry",
|
|
15145
|
+
contentType: link$1.contentType,
|
|
15146
|
+
identifier: link$1.identifier
|
|
15147
|
+
};
|
|
15148
|
+
case "external": return {
|
|
15149
|
+
kind: "external",
|
|
15150
|
+
href: link$1.href
|
|
15151
|
+
};
|
|
15152
|
+
case "url": return {
|
|
15153
|
+
kind: "url",
|
|
15154
|
+
href: link$1.href
|
|
15155
|
+
};
|
|
15156
|
+
case "portal": return {
|
|
15157
|
+
kind: "portal",
|
|
15158
|
+
...link$1.path ? { path: link$1.path } : {},
|
|
15159
|
+
...portalLabelsForCommand(link$1.labels)
|
|
15160
|
+
};
|
|
15161
|
+
case "cart": return {
|
|
15162
|
+
kind: "cart",
|
|
15163
|
+
badge: link$1.badge,
|
|
15164
|
+
label: link$1.label
|
|
15165
|
+
};
|
|
15166
|
+
default: return assertNever(link$1);
|
|
15167
|
+
}
|
|
15148
15168
|
}
|
|
15149
|
-
function
|
|
15150
|
-
if (
|
|
15151
|
-
|
|
15169
|
+
function portalLabelsForCommand(labels) {
|
|
15170
|
+
if (!labels) return {};
|
|
15171
|
+
const commandLabels = {
|
|
15172
|
+
...typeof labels.loggedOut === "string" ? { loggedOut: labels.loggedOut } : {},
|
|
15173
|
+
...typeof labels.loggedIn === "string" ? { loggedIn: labels.loggedIn } : {}
|
|
15174
|
+
};
|
|
15175
|
+
return Object.keys(commandLabels).length > 0 ? { labels: commandLabels } : {};
|
|
15152
15176
|
}
|
|
15153
|
-
function
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15177
|
+
function navigationReadLinkToLocalLink(item) {
|
|
15178
|
+
const { link: link$1 } = item;
|
|
15179
|
+
if (!link$1) return undefined;
|
|
15180
|
+
switch (link$1.kind) {
|
|
15181
|
+
case "page": return {
|
|
15182
|
+
kind: "page",
|
|
15183
|
+
identifier: link$1.identifier
|
|
15184
|
+
};
|
|
15185
|
+
case "entry": return {
|
|
15186
|
+
kind: "entry",
|
|
15187
|
+
contentType: link$1.contentType,
|
|
15188
|
+
identifier: link$1.identifier
|
|
15189
|
+
};
|
|
15190
|
+
case "internal": return internalNavigationLinkToLocalLink(item);
|
|
15191
|
+
case "external": return {
|
|
15192
|
+
kind: "external",
|
|
15193
|
+
href: link$1.href
|
|
15194
|
+
};
|
|
15195
|
+
case "url": return {
|
|
15196
|
+
kind: "url",
|
|
15197
|
+
href: link$1.href
|
|
15198
|
+
};
|
|
15199
|
+
case "portal": return {
|
|
15200
|
+
kind: "portal",
|
|
15201
|
+
...link$1.path ? { path: link$1.path } : {},
|
|
15202
|
+
...link$1.labels ? { labels: link$1.labels } : {}
|
|
15203
|
+
};
|
|
15204
|
+
case "cart": return {
|
|
15205
|
+
kind: "cart",
|
|
15206
|
+
badge: link$1.badge,
|
|
15207
|
+
label: link$1.label
|
|
15208
|
+
};
|
|
15209
|
+
default: return assertNever(link$1);
|
|
15159
15210
|
}
|
|
15160
|
-
return { kind: "dashboard_site_home" };
|
|
15161
|
-
}
|
|
15162
|
-
function canUseAppointmentsLabel(staffScope) {
|
|
15163
|
-
return staffScope.kind === "appointments" || staffScope.kind === "hybrid" || staffScope.kind === "staff_setup_required";
|
|
15164
|
-
}
|
|
15165
|
-
function isEventOpsOnlyUser(sites) {
|
|
15166
|
-
if (sites.length === 0) return false;
|
|
15167
|
-
return sites.every((site) => deriveStaffScope(site).kind === "event_ops_only");
|
|
15168
|
-
}
|
|
15169
|
-
function deriveSiteListingNavigationPlan(site) {
|
|
15170
|
-
const staffScope = deriveStaffScope(site);
|
|
15171
|
-
const staffHomeDestination = deriveStaffSiteHomeDestination(staffScope);
|
|
15172
|
-
const isStaffScopedMembership = isStaffScoped(staffScope);
|
|
15173
|
-
return {
|
|
15174
|
-
canUseDashboard: roleAtLeastOrNone(site.membershipRole, "editor") || isStaffScopedMembership,
|
|
15175
|
-
primaryDestination: isStaffScopedMembership ? staffHomeDestination : { kind: "dashboard_site_open" },
|
|
15176
|
-
manageDestination: isStaffScopedMembership ? staffHomeDestination : { kind: "dashboard_site_home" },
|
|
15177
|
-
manageLabel: staffScope.kind === "event_ops_only" ? "Ops" : canUseAppointmentsLabel(staffScope) ? "Appointments" : "Manage"
|
|
15178
|
-
};
|
|
15179
15211
|
}
|
|
15180
|
-
function
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
|
|
15184
|
-
|
|
15185
|
-
|
|
15186
|
-
|
|
15187
|
-
|
|
15188
|
-
|
|
15212
|
+
function internalNavigationLinkToLocalLink(item) {
|
|
15213
|
+
const { link: link$1 } = item;
|
|
15214
|
+
if (!link$1 || link$1.kind !== "internal") return undefined;
|
|
15215
|
+
if (item.resolvedIdentifier) {
|
|
15216
|
+
if (link$1.entityType === "page") {
|
|
15217
|
+
return {
|
|
15218
|
+
kind: "page",
|
|
15219
|
+
identifier: item.resolvedIdentifier
|
|
15220
|
+
};
|
|
15221
|
+
}
|
|
15222
|
+
if (link$1.entityType === "content" && item.resolvedContentType) {
|
|
15223
|
+
return {
|
|
15224
|
+
kind: "entry",
|
|
15225
|
+
contentType: item.resolvedContentType,
|
|
15226
|
+
identifier: item.resolvedIdentifier
|
|
15227
|
+
};
|
|
15228
|
+
}
|
|
15229
|
+
}
|
|
15230
|
+
if (link$1.href) {
|
|
15231
|
+
return {
|
|
15232
|
+
kind: "url",
|
|
15233
|
+
href: link$1.href
|
|
15234
|
+
};
|
|
15235
|
+
}
|
|
15236
|
+
return undefined;
|
|
15189
15237
|
}
|
|
15190
15238
|
|
|
15191
15239
|
//#endregion
|
|
15192
|
-
//#region ../api/src/
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15240
|
+
//#region ../api/src/navigation/routableLink.ts
|
|
15241
|
+
/**
|
|
15242
|
+
* Prefer identifier-based links when available so links survive route/path updates.
|
|
15243
|
+
* Falls back to an internal route payload for legacy rows or incomplete data.
|
|
15244
|
+
*/
|
|
15245
|
+
function toRoutableLinkPayload(item) {
|
|
15246
|
+
switch (item.entityType) {
|
|
15247
|
+
case "page":
|
|
15248
|
+
if (item.identifier) {
|
|
15249
|
+
return {
|
|
15250
|
+
kind: "page",
|
|
15251
|
+
identifier: item.identifier
|
|
15252
|
+
};
|
|
15253
|
+
}
|
|
15254
|
+
return toResolvedInternalPayload(item);
|
|
15255
|
+
case "content":
|
|
15256
|
+
if (item.identifier && item.contentTypeKey) {
|
|
15257
|
+
return {
|
|
15258
|
+
kind: "entry",
|
|
15259
|
+
contentType: item.contentTypeKey,
|
|
15260
|
+
identifier: item.identifier
|
|
15261
|
+
};
|
|
15262
|
+
}
|
|
15263
|
+
return toResolvedInternalPayload(item);
|
|
15264
|
+
case "pageSurface": return toResolvedInternalPayload(item);
|
|
15265
|
+
default: return assertNever(item);
|
|
15266
|
+
}
|
|
15196
15267
|
}
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15268
|
+
function toResolvedInternalPayload(item) {
|
|
15269
|
+
return {
|
|
15270
|
+
kind: "internal",
|
|
15271
|
+
routeId: item.routeId,
|
|
15272
|
+
entityId: item.entityId,
|
|
15273
|
+
entityType: item.entityType,
|
|
15274
|
+
href: item.path ?? "/",
|
|
15275
|
+
title: item.title ?? item.draftTitle ?? "Untitled",
|
|
15276
|
+
typeLabel: item.typeLabel,
|
|
15277
|
+
contentTypeKey: item.contentTypeKey,
|
|
15278
|
+
contentTypeName: item.contentTypeName,
|
|
15279
|
+
updatedAt: item.updatedAt ?? null
|
|
15280
|
+
};
|
|
15207
15281
|
}
|
|
15208
15282
|
|
|
15209
15283
|
//#endregion
|
|
15210
|
-
//#region ../api/src/
|
|
15211
|
-
const
|
|
15212
|
-
"
|
|
15213
|
-
"
|
|
15214
|
-
"
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
"
|
|
15218
|
-
"
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
];
|
|
15222
|
-
|
|
15223
|
-
//#endregion
|
|
15224
|
-
//#region ../api/src/common/cancellation.ts
|
|
15225
|
-
const STAFF_CANCELLATION_CONTEXT_UI_CATEGORIES = [
|
|
15226
|
-
"customer_requested",
|
|
15227
|
-
"schedule_conflict",
|
|
15228
|
-
"duplicate_or_admin_error",
|
|
15229
|
-
"non_payment",
|
|
15230
|
-
"policy_or_eligibility",
|
|
15231
|
-
"other"
|
|
15232
|
-
];
|
|
15233
|
-
const STAFF_CANCELLATION_CONTEXT_CATEGORIES = [...STAFF_CANCELLATION_CONTEXT_UI_CATEGORIES, "legacy_client_unspecified"];
|
|
15234
|
-
|
|
15235
|
-
//#endregion
|
|
15236
|
-
//#region ../api/src/siteOperations.ts
|
|
15237
|
-
const FLEXIBLE_BALANCE_ADMIN_ACTION_SOURCES = [
|
|
15238
|
-
"order_detail",
|
|
15239
|
-
"payment_issues_queue",
|
|
15240
|
-
"appointment_detail",
|
|
15241
|
-
"event_attendee_detail",
|
|
15242
|
-
"event_series_registration_detail"
|
|
15243
|
-
];
|
|
15244
|
-
|
|
15245
|
-
//#endregion
|
|
15246
|
-
//#region ../api/src/bookingRecords.ts
|
|
15247
|
-
const CUSTOMER_PASS_STATUS_VALUES = [
|
|
15248
|
-
"active",
|
|
15249
|
-
"exhausted",
|
|
15250
|
-
"expired",
|
|
15251
|
-
"refunded"
|
|
15252
|
-
];
|
|
15253
|
-
const CUSTOMER_MEMBERSHIP_STATUS_VALUES = [
|
|
15254
|
-
"active",
|
|
15255
|
-
"trialing",
|
|
15256
|
-
"past_due",
|
|
15257
|
-
"paused",
|
|
15258
|
-
"canceled"
|
|
15259
|
-
];
|
|
15260
|
-
const BOOKING_ACTIVITY_CATEGORY_VALUES = [
|
|
15261
|
-
"booking",
|
|
15262
|
-
"purchase",
|
|
15263
|
-
"cancellation",
|
|
15264
|
-
"refund",
|
|
15265
|
-
"transfer",
|
|
15266
|
-
"failure"
|
|
15267
|
-
];
|
|
15268
|
-
const BOOKING_ACTIVITY_TYPE_VALUES = [
|
|
15269
|
-
"appointment_booked",
|
|
15270
|
-
"appointment_cancelled",
|
|
15271
|
-
"booking_closure_created",
|
|
15272
|
-
"booking_closure_removed",
|
|
15273
|
-
"event_registration",
|
|
15274
|
-
"event_registration_cancelled",
|
|
15275
|
-
"event_series_registration",
|
|
15276
|
-
"event_series_registration_cancelled",
|
|
15277
|
-
"pass_purchased",
|
|
15278
|
-
"membership_started",
|
|
15279
|
-
"membership_paused",
|
|
15280
|
-
"membership_cancelled",
|
|
15281
|
-
"event_refund",
|
|
15282
|
-
"pass_refund",
|
|
15283
|
-
"membership_refund",
|
|
15284
|
-
"event_series_registration_refund",
|
|
15285
|
-
"ticket_transfer",
|
|
15286
|
-
"payment_failed"
|
|
15287
|
-
];
|
|
15288
|
-
const BOOKING_LEDGER_KIND_VALUES = [
|
|
15289
|
-
"appointment",
|
|
15290
|
-
"event_registration",
|
|
15291
|
-
"event_series_registration"
|
|
15292
|
-
];
|
|
15293
|
-
const BOOKING_LEDGER_DATE_BASIS_VALUES = [
|
|
15294
|
-
"created_at",
|
|
15295
|
-
"scheduled_at",
|
|
15296
|
-
"cancelled_at"
|
|
15297
|
-
];
|
|
15298
|
-
const BOOKING_LEDGER_PERIOD_PRESET_VALUES = [
|
|
15299
|
-
"7d",
|
|
15300
|
-
"30d",
|
|
15301
|
-
"90d",
|
|
15302
|
-
"365d",
|
|
15303
|
-
"all"
|
|
15304
|
-
];
|
|
15305
|
-
const BOOKING_LEDGER_CANCELLATION_MODE_VALUES = [
|
|
15306
|
-
"exclude_cancelled",
|
|
15307
|
-
"include_cancelled",
|
|
15308
|
-
"cancelled_only"
|
|
15309
|
-
];
|
|
15310
|
-
const BOOKING_QUESTION_ANSWER_EXPORT_SCOPES = {
|
|
15311
|
-
site: "site",
|
|
15312
|
-
appointments: "appointments",
|
|
15313
|
-
eventSeries: "event_series"
|
|
15314
|
-
};
|
|
15315
|
-
const BOOKING_QUESTION_ANSWER_EXPORT_SCOPE_VALUES = [
|
|
15316
|
-
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.site,
|
|
15317
|
-
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.appointments,
|
|
15318
|
-
BOOKING_QUESTION_ANSWER_EXPORT_SCOPES.eventSeries
|
|
15319
|
-
];
|
|
15320
|
-
|
|
15321
|
-
//#endregion
|
|
15322
|
-
//#region ../api/src/bookingPeople.ts
|
|
15323
|
-
const PEOPLE_LIST_SORT_VALUES = [
|
|
15324
|
-
"name",
|
|
15325
|
-
"passes",
|
|
15326
|
-
"memberships",
|
|
15327
|
-
"lastBooking",
|
|
15328
|
-
"added"
|
|
15329
|
-
];
|
|
15330
|
-
const PEOPLE_LIST_FILTER_VALUES = [
|
|
15331
|
-
"everyone",
|
|
15332
|
-
"hasBought",
|
|
15333
|
-
"hasActivePass",
|
|
15334
|
-
"hasMembership",
|
|
15335
|
-
"guests",
|
|
15336
|
-
"nothingBought"
|
|
15337
|
-
];
|
|
15338
|
-
/** What can be attached to a person, in the order the dialog lists it. */
|
|
15339
|
-
const PERSON_DELETION_BLOCKER_CATEGORY_VALUES = [
|
|
15340
|
-
"bookings",
|
|
15341
|
-
"guest_places",
|
|
15342
|
-
"waitlist_places",
|
|
15343
|
-
"payments",
|
|
15344
|
-
"orders",
|
|
15345
|
-
"passes",
|
|
15346
|
-
"memberships",
|
|
15347
|
-
"credit",
|
|
15348
|
-
"digital_products",
|
|
15349
|
-
"discounts",
|
|
15350
|
-
"gifts",
|
|
15351
|
-
"notes",
|
|
15352
|
-
"forms",
|
|
15353
|
-
"terms_agreements",
|
|
15354
|
-
"uploads",
|
|
15355
|
-
"contact_corrections"
|
|
15356
|
-
];
|
|
15284
|
+
//#region ../api/src/navigation/linkUrlType.ts
|
|
15285
|
+
const LINK_PAYLOAD_NAVIGATION_URL_TYPES = {
|
|
15286
|
+
page: "internal",
|
|
15287
|
+
entry: "internal",
|
|
15288
|
+
internal: "internal",
|
|
15289
|
+
external: "external",
|
|
15290
|
+
url: "url",
|
|
15291
|
+
portal: "portal",
|
|
15292
|
+
cart: "cart"
|
|
15293
|
+
};
|
|
15294
|
+
function linkPayloadKindToNavigationUrlType(kind) {
|
|
15295
|
+
return LINK_PAYLOAD_NAVIGATION_URL_TYPES[kind];
|
|
15296
|
+
}
|
|
15357
15297
|
|
|
15358
15298
|
//#endregion
|
|
15359
|
-
//#region ../api/src/
|
|
15360
|
-
const
|
|
15361
|
-
|
|
15362
|
-
"
|
|
15363
|
-
"
|
|
15364
|
-
"inconsistent_ownership"
|
|
15365
|
-
];
|
|
15366
|
-
function isDeleteEventOfferingConflictReason(value) {
|
|
15367
|
-
return DELETE_EVENT_OFFERING_CONFLICT_REASONS.some((reason) => reason === value);
|
|
15299
|
+
//#region ../api/src/accessAdmin.ts
|
|
15300
|
+
const STAFF_OPERATIONAL_CAPABILITIES = ["appointments", "event_ops"];
|
|
15301
|
+
function getDefaultStaffCapabilities(surfaces) {
|
|
15302
|
+
if (surfaces.events && !surfaces.appointments) return ["event_ops"];
|
|
15303
|
+
return ["appointments"];
|
|
15368
15304
|
}
|
|
15305
|
+
const managedDomainAddOnPricingSnapshotSchema = z.object({
|
|
15306
|
+
formulaVersion: z.literal("managed-domain-overage-v1"),
|
|
15307
|
+
conversionRule: z.literal("usd-to-gbp-1-to-1-v1"),
|
|
15308
|
+
registrarAnnualCost: z.number().nonnegative(),
|
|
15309
|
+
registrarCurrency: z.literal("USD"),
|
|
15310
|
+
includedAnnualAllowance: z.number().nonnegative(),
|
|
15311
|
+
includedAnnualAllowanceCurrency: z.literal("USD"),
|
|
15312
|
+
monthlyAmountCents: z.number().int().nonnegative(),
|
|
15313
|
+
monthlyCurrency: z.literal("gbp")
|
|
15314
|
+
});
|
|
15369
15315
|
|
|
15370
15316
|
//#endregion
|
|
15371
|
-
//#region ../api/src/
|
|
15372
|
-
|
|
15373
|
-
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15317
|
+
//#region ../api/src/siteMembers.ts
|
|
15318
|
+
/**
|
|
15319
|
+
* Higher number means more permissions.
|
|
15320
|
+
*/
|
|
15321
|
+
const SITE_ROLE_PERMISSION_LEVEL = {
|
|
15322
|
+
team_member: 0,
|
|
15323
|
+
editor: 1,
|
|
15324
|
+
administrator: 2,
|
|
15325
|
+
owner: 3
|
|
15326
|
+
};
|
|
15327
|
+
/**
|
|
15328
|
+
* Lower number sorts earlier in UI role pickers.
|
|
15329
|
+
*/
|
|
15330
|
+
const SITE_ROLE_DISPLAY_ORDER = {
|
|
15331
|
+
owner: 0,
|
|
15332
|
+
administrator: 1,
|
|
15333
|
+
editor: 2,
|
|
15334
|
+
team_member: 3
|
|
15335
|
+
};
|
|
15336
|
+
async function getSiteMembers(apiClient, { siteId, options }) {
|
|
15337
|
+
try {
|
|
15338
|
+
return unwrapResponse(await apiClient({
|
|
15339
|
+
endpoint: "listSiteMembers",
|
|
15340
|
+
params: { siteId },
|
|
15341
|
+
options
|
|
15342
|
+
}));
|
|
15343
|
+
} catch (error) {
|
|
15344
|
+
return normalizeApiError(error, "Failed to load site members");
|
|
15345
|
+
}
|
|
15381
15346
|
}
|
|
15382
|
-
function
|
|
15383
|
-
|
|
15347
|
+
async function inviteSiteMember(apiClient, { siteId, body, options }) {
|
|
15348
|
+
try {
|
|
15349
|
+
return unwrapResponse(await apiClient({
|
|
15350
|
+
endpoint: "inviteSiteMember",
|
|
15351
|
+
params: { siteId },
|
|
15352
|
+
body,
|
|
15353
|
+
options
|
|
15354
|
+
}));
|
|
15355
|
+
} catch (error) {
|
|
15356
|
+
return normalizeApiError(error, "Failed to invite collaborator");
|
|
15357
|
+
}
|
|
15384
15358
|
}
|
|
15385
|
-
async function
|
|
15359
|
+
async function updateSiteMemberRole(apiClient, { siteId, memberId, body, options }) {
|
|
15386
15360
|
try {
|
|
15387
|
-
|
|
15388
|
-
endpoint: "
|
|
15361
|
+
return unwrapResponse(await apiClient({
|
|
15362
|
+
endpoint: "updateSiteMemberRole",
|
|
15363
|
+
params: {
|
|
15364
|
+
siteId,
|
|
15365
|
+
memberId
|
|
15366
|
+
},
|
|
15367
|
+
body,
|
|
15368
|
+
options
|
|
15369
|
+
}));
|
|
15370
|
+
} catch (error) {
|
|
15371
|
+
return normalizeApiError(error, "Failed to update member role");
|
|
15372
|
+
}
|
|
15373
|
+
}
|
|
15374
|
+
async function linkCurrentUserPractitioner(apiClient, { siteId, body, options }) {
|
|
15375
|
+
try {
|
|
15376
|
+
return unwrapResponse(await apiClient({
|
|
15377
|
+
endpoint: "linkCurrentUserPractitioner",
|
|
15389
15378
|
params: { siteId },
|
|
15379
|
+
body,
|
|
15390
15380
|
options
|
|
15391
15381
|
}));
|
|
15392
|
-
return response.settings;
|
|
15393
15382
|
} catch (error) {
|
|
15394
|
-
normalizeApiError(error, "Failed to
|
|
15383
|
+
return normalizeApiError(error, "Failed to link practitioner profile");
|
|
15384
|
+
}
|
|
15385
|
+
}
|
|
15386
|
+
async function unlinkCurrentUserPractitioner(apiClient, { siteId, options }) {
|
|
15387
|
+
try {
|
|
15388
|
+
return unwrapResponse(await apiClient({
|
|
15389
|
+
endpoint: "unlinkCurrentUserPractitioner",
|
|
15390
|
+
params: { siteId },
|
|
15391
|
+
options
|
|
15392
|
+
}));
|
|
15393
|
+
} catch (error) {
|
|
15394
|
+
return normalizeApiError(error, "Failed to unlink practitioner profile");
|
|
15395
|
+
}
|
|
15396
|
+
}
|
|
15397
|
+
async function removeSiteMember(apiClient, { siteId, memberId, options }) {
|
|
15398
|
+
try {
|
|
15399
|
+
return unwrapResponse(await apiClient({
|
|
15400
|
+
endpoint: "removeSiteMember",
|
|
15401
|
+
params: {
|
|
15402
|
+
siteId,
|
|
15403
|
+
memberId
|
|
15404
|
+
},
|
|
15405
|
+
options
|
|
15406
|
+
}));
|
|
15407
|
+
} catch (error) {
|
|
15408
|
+
return normalizeApiError(error, "Failed to remove collaborator");
|
|
15409
|
+
}
|
|
15410
|
+
}
|
|
15411
|
+
async function revokeSiteInvitation(apiClient, { siteId, invitationId, options }) {
|
|
15412
|
+
try {
|
|
15413
|
+
return unwrapResponse(await apiClient({
|
|
15414
|
+
endpoint: "revokeSiteInvitation",
|
|
15415
|
+
params: {
|
|
15416
|
+
siteId,
|
|
15417
|
+
invitationId
|
|
15418
|
+
},
|
|
15419
|
+
options
|
|
15420
|
+
}));
|
|
15421
|
+
} catch (error) {
|
|
15422
|
+
return normalizeApiError(error, "Failed to revoke invitation");
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
async function resendSiteInvitation(apiClient, { siteId, invitationId, options }) {
|
|
15426
|
+
try {
|
|
15427
|
+
return unwrapResponse(await apiClient({
|
|
15428
|
+
endpoint: "resendSiteInvitation",
|
|
15429
|
+
params: {
|
|
15430
|
+
siteId,
|
|
15431
|
+
invitationId
|
|
15432
|
+
},
|
|
15433
|
+
options
|
|
15434
|
+
}));
|
|
15435
|
+
} catch (error) {
|
|
15436
|
+
return normalizeApiError(error, "Failed to resend invitation");
|
|
15437
|
+
}
|
|
15438
|
+
}
|
|
15439
|
+
async function transferSiteOwnership(apiClient, { siteId, body, options }) {
|
|
15440
|
+
try {
|
|
15441
|
+
return unwrapResponse(await apiClient({
|
|
15442
|
+
endpoint: "transferSiteOwnership",
|
|
15443
|
+
params: { siteId },
|
|
15444
|
+
body,
|
|
15445
|
+
options
|
|
15446
|
+
}));
|
|
15447
|
+
} catch (error) {
|
|
15448
|
+
return normalizeApiError(error, "Failed to transfer ownership");
|
|
15449
|
+
}
|
|
15450
|
+
}
|
|
15451
|
+
async function acceptSiteInvitation(apiClient, { body, options }) {
|
|
15452
|
+
try {
|
|
15453
|
+
return unwrapResponse(await apiClient({
|
|
15454
|
+
endpoint: "acceptSiteInvitation",
|
|
15455
|
+
body,
|
|
15456
|
+
options
|
|
15457
|
+
}));
|
|
15458
|
+
} catch (error) {
|
|
15459
|
+
return normalizeApiError(error, "Failed to accept invitation");
|
|
15460
|
+
}
|
|
15461
|
+
}
|
|
15462
|
+
|
|
15463
|
+
//#endregion
|
|
15464
|
+
//#region ../api/src/staffAccess.ts
|
|
15465
|
+
function roleAtLeast$1(role, minimum) {
|
|
15466
|
+
return SITE_ROLE_PERMISSION_LEVEL[role] >= SITE_ROLE_PERMISSION_LEVEL[minimum];
|
|
15467
|
+
}
|
|
15468
|
+
function roleAtLeastOrNone(role, minimum) {
|
|
15469
|
+
if (!role) return false;
|
|
15470
|
+
return roleAtLeast$1(role, minimum);
|
|
15471
|
+
}
|
|
15472
|
+
function appointmentResourceIdFromStaffMemberId(staffMemberId) {
|
|
15473
|
+
return unsafeAsAppointmentResourceId(staffMemberId);
|
|
15474
|
+
}
|
|
15475
|
+
function normalizeStaffCapabilities(capabilities) {
|
|
15476
|
+
if (!capabilities) return null;
|
|
15477
|
+
return STAFF_OPERATIONAL_CAPABILITIES.filter((capability) => capabilities.includes(capability));
|
|
15478
|
+
}
|
|
15479
|
+
function deriveStaffScope(input) {
|
|
15480
|
+
const staffMemberId = input.linkedStaffMemberId ? unsafeAsStaffMemberId(input.linkedStaffMemberId) : null;
|
|
15481
|
+
const persistedCapabilities = normalizeStaffCapabilities(input.staffCapabilities);
|
|
15482
|
+
switch (input.membershipRole) {
|
|
15483
|
+
case null:
|
|
15484
|
+
case "owner":
|
|
15485
|
+
case "administrator":
|
|
15486
|
+
case "editor": return { kind: "none" };
|
|
15487
|
+
case "team_member":
|
|
15488
|
+
if (!staffMemberId) {
|
|
15489
|
+
return {
|
|
15490
|
+
kind: "staff_setup_required",
|
|
15491
|
+
reason: "missing_staff_member"
|
|
15492
|
+
};
|
|
15493
|
+
}
|
|
15494
|
+
if (persistedCapabilities === null) {
|
|
15495
|
+
return {
|
|
15496
|
+
kind: "appointments",
|
|
15497
|
+
staffMemberId
|
|
15498
|
+
};
|
|
15499
|
+
}
|
|
15500
|
+
if (persistedCapabilities.length === 0) {
|
|
15501
|
+
return {
|
|
15502
|
+
kind: "staff_setup_required",
|
|
15503
|
+
reason: "missing_capabilities"
|
|
15504
|
+
};
|
|
15505
|
+
}
|
|
15506
|
+
if (persistedCapabilities.includes("appointments") && persistedCapabilities.includes("event_ops")) {
|
|
15507
|
+
return {
|
|
15508
|
+
kind: "hybrid",
|
|
15509
|
+
staffMemberId
|
|
15510
|
+
};
|
|
15511
|
+
}
|
|
15512
|
+
if (persistedCapabilities.includes("event_ops")) {
|
|
15513
|
+
return {
|
|
15514
|
+
kind: "event_ops_only",
|
|
15515
|
+
staffMemberId
|
|
15516
|
+
};
|
|
15517
|
+
}
|
|
15518
|
+
return {
|
|
15519
|
+
kind: "appointments",
|
|
15520
|
+
staffMemberId
|
|
15521
|
+
};
|
|
15522
|
+
}
|
|
15523
|
+
}
|
|
15524
|
+
function isStaffScoped(scope) {
|
|
15525
|
+
return scope.kind !== "none";
|
|
15526
|
+
}
|
|
15527
|
+
function isTeamMemberScope(scope) {
|
|
15528
|
+
return scope.kind === "appointments" || scope.kind === "staff_setup_required";
|
|
15529
|
+
}
|
|
15530
|
+
function hasAppointmentStaffAccess(scope) {
|
|
15531
|
+
return scope.kind === "appointments" || scope.kind === "hybrid";
|
|
15532
|
+
}
|
|
15533
|
+
function hasEventOpsAccess(scope) {
|
|
15534
|
+
return scope.kind === "event_ops_only" || scope.kind === "hybrid";
|
|
15535
|
+
}
|
|
15536
|
+
function getLinkedStaffMemberId(scope) {
|
|
15537
|
+
switch (scope.kind) {
|
|
15538
|
+
case "appointments":
|
|
15539
|
+
case "event_ops_only":
|
|
15540
|
+
case "hybrid": return scope.staffMemberId;
|
|
15541
|
+
case "none":
|
|
15542
|
+
case "staff_setup_required": return null;
|
|
15395
15543
|
}
|
|
15396
15544
|
}
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
endpoint: "updateBookingSettings",
|
|
15401
|
-
params: { siteId },
|
|
15402
|
-
body: payload,
|
|
15403
|
-
options
|
|
15404
|
-
}));
|
|
15405
|
-
return response.settings;
|
|
15406
|
-
} catch (error) {
|
|
15407
|
-
normalizeApiError(error, "Failed to save booking settings");
|
|
15408
|
-
}
|
|
15545
|
+
function getOwnAppointmentResourceId(scope) {
|
|
15546
|
+
const staffMemberId = getLinkedStaffMemberId(scope);
|
|
15547
|
+
return staffMemberId ? appointmentResourceIdFromStaffMemberId(staffMemberId) : null;
|
|
15409
15548
|
}
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
|
|
15414
|
-
|
|
15415
|
-
body: payload,
|
|
15416
|
-
options
|
|
15417
|
-
}));
|
|
15418
|
-
} catch (error) {
|
|
15419
|
-
normalizeApiError(error, "Failed to create appointment");
|
|
15420
|
-
}
|
|
15549
|
+
function canAccessOwnAppointmentResource(scope, resourceId) {
|
|
15550
|
+
const ownResourceId = getOwnAppointmentResourceId(scope);
|
|
15551
|
+
if (!ownResourceId) return false;
|
|
15552
|
+
const normalizedResourceId = typeof resourceId === "string" ? unsafeAsAppointmentResourceId(resourceId) : resourceId;
|
|
15553
|
+
return ownResourceId === normalizedResourceId;
|
|
15421
15554
|
}
|
|
15422
|
-
|
|
15423
|
-
|
|
15424
|
-
return
|
|
15425
|
-
endpoint: "deleteEventOffering",
|
|
15426
|
-
params: {
|
|
15427
|
-
siteId,
|
|
15428
|
-
eventOfferingId
|
|
15429
|
-
},
|
|
15430
|
-
options
|
|
15431
|
-
}));
|
|
15432
|
-
} catch (error) {
|
|
15433
|
-
normalizeApiError(error, "Failed to delete event offering");
|
|
15555
|
+
function deriveLinkedAppointmentIdentity(input) {
|
|
15556
|
+
if (!input.membershipRole || !input.linkedStaffMemberId) {
|
|
15557
|
+
return { kind: "none" };
|
|
15434
15558
|
}
|
|
15559
|
+
const staffMemberId = unsafeAsStaffMemberId(input.linkedStaffMemberId);
|
|
15560
|
+
return {
|
|
15561
|
+
kind: "linked",
|
|
15562
|
+
staffMemberId,
|
|
15563
|
+
resourceId: appointmentResourceIdFromStaffMemberId(staffMemberId)
|
|
15564
|
+
};
|
|
15435
15565
|
}
|
|
15436
|
-
|
|
15437
|
-
|
|
15438
|
-
return
|
|
15439
|
-
|
|
15440
|
-
params: {
|
|
15441
|
-
siteId,
|
|
15442
|
-
seriesId: eventSeriesId
|
|
15443
|
-
},
|
|
15444
|
-
body: payload,
|
|
15445
|
-
options
|
|
15446
|
-
}));
|
|
15447
|
-
} catch (error) {
|
|
15448
|
-
normalizeApiError(error, "Failed to add Event run registration");
|
|
15566
|
+
function getIdentityStaffMemberId(identity) {
|
|
15567
|
+
switch (identity.kind) {
|
|
15568
|
+
case "linked": return identity.staffMemberId;
|
|
15569
|
+
case "none": return null;
|
|
15449
15570
|
}
|
|
15450
15571
|
}
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
return unwrapResponse(await apiClient({
|
|
15454
|
-
endpoint: "updateEventSeriesRegistration",
|
|
15455
|
-
params: {
|
|
15456
|
-
siteId,
|
|
15457
|
-
seriesId: eventSeriesId,
|
|
15458
|
-
registrationId
|
|
15459
|
-
},
|
|
15460
|
-
body: payload,
|
|
15461
|
-
options
|
|
15462
|
-
}));
|
|
15463
|
-
} catch (error) {
|
|
15464
|
-
normalizeApiError(error, "Failed to update Event run registration");
|
|
15465
|
-
}
|
|
15572
|
+
function getIdentityStaffMemberIdValue(identity) {
|
|
15573
|
+
return getIdentityStaffMemberId(identity);
|
|
15466
15574
|
}
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15575
|
+
function getIdentityAppointmentResourceId(identity) {
|
|
15576
|
+
return identity.kind === "linked" ? identity.resourceId : null;
|
|
15577
|
+
}
|
|
15578
|
+
function getIdentityAppointmentResourceIdValue(identity) {
|
|
15579
|
+
return getIdentityAppointmentResourceId(identity);
|
|
15580
|
+
}
|
|
15581
|
+
function isLinkedAppointmentResource(identity, resourceId) {
|
|
15582
|
+
if (identity.kind !== "linked") return false;
|
|
15583
|
+
return identity.resourceId === resourceId;
|
|
15584
|
+
}
|
|
15585
|
+
function deriveStaffSiteHomeDestination(staffScope) {
|
|
15586
|
+
if (hasAppointmentStaffAccess(staffScope)) {
|
|
15587
|
+
return { kind: "staff_appointments" };
|
|
15588
|
+
}
|
|
15589
|
+
if (hasEventOpsAccess(staffScope)) {
|
|
15590
|
+
return { kind: "ops_site_events" };
|
|
15481
15591
|
}
|
|
15592
|
+
return { kind: "dashboard_site_home" };
|
|
15593
|
+
}
|
|
15594
|
+
function canUseAppointmentsLabel(staffScope) {
|
|
15595
|
+
return staffScope.kind === "appointments" || staffScope.kind === "hybrid" || staffScope.kind === "staff_setup_required";
|
|
15596
|
+
}
|
|
15597
|
+
function isEventOpsOnlyUser(sites) {
|
|
15598
|
+
if (sites.length === 0) return false;
|
|
15599
|
+
return sites.every((site) => deriveStaffScope(site).kind === "event_ops_only");
|
|
15600
|
+
}
|
|
15601
|
+
function deriveSiteListingNavigationPlan(site) {
|
|
15602
|
+
const staffScope = deriveStaffScope(site);
|
|
15603
|
+
const staffHomeDestination = deriveStaffSiteHomeDestination(staffScope);
|
|
15604
|
+
const isStaffScopedMembership = isStaffScoped(staffScope);
|
|
15605
|
+
return {
|
|
15606
|
+
canUseDashboard: roleAtLeastOrNone(site.membershipRole, "editor") || isStaffScopedMembership,
|
|
15607
|
+
primaryDestination: isStaffScopedMembership ? staffHomeDestination : { kind: "dashboard_site_open" },
|
|
15608
|
+
manageDestination: isStaffScopedMembership ? staffHomeDestination : { kind: "dashboard_site_home" },
|
|
15609
|
+
manageLabel: staffScope.kind === "event_ops_only" ? "Ops" : canUseAppointmentsLabel(staffScope) ? "Appointments" : "Manage"
|
|
15610
|
+
};
|
|
15611
|
+
}
|
|
15612
|
+
function shouldRedirectStaffScopeToOps(staffScope) {
|
|
15613
|
+
return staffScope.kind === "event_ops_only";
|
|
15482
15614
|
}
|
|
15615
|
+
function deriveOpsLandingDestination(sites) {
|
|
15616
|
+
const onlySite = sites.length === 1 ? sites[0] : undefined;
|
|
15617
|
+
return onlySite?.id ? {
|
|
15618
|
+
kind: "ops_site_events",
|
|
15619
|
+
siteId: onlySite.id
|
|
15620
|
+
} : { kind: "ops_home" };
|
|
15621
|
+
}
|
|
15622
|
+
|
|
15623
|
+
//#endregion
|
|
15624
|
+
//#region ../api/src/common/cancellation.ts
|
|
15625
|
+
const STAFF_CANCELLATION_CONTEXT_UI_CATEGORIES = [
|
|
15626
|
+
"customer_requested",
|
|
15627
|
+
"schedule_conflict",
|
|
15628
|
+
"duplicate_or_admin_error",
|
|
15629
|
+
"non_payment",
|
|
15630
|
+
"policy_or_eligibility",
|
|
15631
|
+
"other"
|
|
15632
|
+
];
|
|
15633
|
+
const STAFF_CANCELLATION_CONTEXT_CATEGORIES = [...STAFF_CANCELLATION_CONTEXT_UI_CATEGORIES, "legacy_client_unspecified"];
|
|
15634
|
+
|
|
15635
|
+
//#endregion
|
|
15636
|
+
//#region ../api/src/siteOperations.ts
|
|
15637
|
+
const FLEXIBLE_BALANCE_ADMIN_ACTION_SOURCES = [
|
|
15638
|
+
"order_detail",
|
|
15639
|
+
"payment_issues_queue",
|
|
15640
|
+
"appointment_detail",
|
|
15641
|
+
"event_attendee_detail",
|
|
15642
|
+
"event_series_registration_detail"
|
|
15643
|
+
];
|
|
15483
15644
|
|
|
15484
15645
|
//#endregion
|
|
15485
15646
|
//#region ../api/src/navigation/visibility.ts
|
|
@@ -16291,19 +16452,8 @@ function buildDashboardBookingsNavPlan(modules, ctx, catalogue) {
|
|
|
16291
16452
|
if (policy.kind === "staff_scoped") {
|
|
16292
16453
|
return buildStaffScopedBookingsNavPlan(policy);
|
|
16293
16454
|
}
|
|
16294
|
-
const groups = [{
|
|
16295
|
-
label: "Overview",
|
|
16296
|
-
items: [{
|
|
16297
|
-
label: "Dashboard",
|
|
16298
|
-
destination: { kind: "bookings_dashboard" },
|
|
16299
|
-
keywords: [
|
|
16300
|
-
"overview",
|
|
16301
|
-
"summary",
|
|
16302
|
-
"activity"
|
|
16303
|
-
]
|
|
16304
|
-
}]
|
|
16305
|
-
}];
|
|
16306
16455
|
const hasEnabledModules = modules.appointments || modules.events;
|
|
16456
|
+
const groups = [buildOverviewNavGroup(hasEnabledModules)];
|
|
16307
16457
|
groups.push({
|
|
16308
16458
|
label: "Events",
|
|
16309
16459
|
items: modules.events ? [
|
|
@@ -16520,6 +16670,32 @@ function buildDashboardBookingsNavPlan(modules, ctx, catalogue) {
|
|
|
16520
16670
|
}
|
|
16521
16671
|
return groups;
|
|
16522
16672
|
}
|
|
16673
|
+
/** At a glance: the bookings Overview, and the Timetable once anything can be booked. */
|
|
16674
|
+
function buildOverviewNavGroup(hasEnabledModules) {
|
|
16675
|
+
return {
|
|
16676
|
+
label: "At a glance",
|
|
16677
|
+
items: [{
|
|
16678
|
+
label: "Overview",
|
|
16679
|
+
destination: { kind: "bookings_dashboard" },
|
|
16680
|
+
keywords: [
|
|
16681
|
+
"overview",
|
|
16682
|
+
"summary",
|
|
16683
|
+
"activity"
|
|
16684
|
+
]
|
|
16685
|
+
}, ...hasEnabledModules ? [{
|
|
16686
|
+
label: "Timetable",
|
|
16687
|
+
destination: { kind: "timetable" },
|
|
16688
|
+
keywords: [
|
|
16689
|
+
"schedule",
|
|
16690
|
+
"calendar",
|
|
16691
|
+
"week",
|
|
16692
|
+
"diary",
|
|
16693
|
+
"what's on",
|
|
16694
|
+
"clashes"
|
|
16695
|
+
]
|
|
16696
|
+
}] : []]
|
|
16697
|
+
};
|
|
16698
|
+
}
|
|
16523
16699
|
function buildStaffScopedBookingsNavPlan(policy) {
|
|
16524
16700
|
if (policy.appointments.kind !== "calendar") return [];
|
|
16525
16701
|
const appointmentItems = [{
|
|
@@ -17000,6 +17176,8 @@ function convertPulledPagesToLocal(remotePages) {
|
|
|
17000
17176
|
return remotePages.map((page) => ({
|
|
17001
17177
|
identifier: page.identifier,
|
|
17002
17178
|
title: page.title,
|
|
17179
|
+
...page.seoTitle != null ? { seoTitle: page.seoTitle } : {},
|
|
17180
|
+
...page.seoDescription != null ? { seoDescription: page.seoDescription } : {},
|
|
17003
17181
|
path: page.path,
|
|
17004
17182
|
status: page.status,
|
|
17005
17183
|
hasUnpublishedChanges: page.hasUnpublishedChanges,
|
|
@@ -20736,6 +20914,18 @@ function calculateEntryDiffs(localEntries, remoteEntries, options) {
|
|
|
20736
20914
|
};
|
|
20737
20915
|
}
|
|
20738
20916
|
|
|
20917
|
+
//#endregion
|
|
20918
|
+
//#region src/site-commands/pageMetadata.ts
|
|
20919
|
+
/** The supported metadata sent by a CLI page upsert; empty SEO is omitted. */
|
|
20920
|
+
function pageMetadataForUpsert(page) {
|
|
20921
|
+
return {
|
|
20922
|
+
title: page.title,
|
|
20923
|
+
path: page.path,
|
|
20924
|
+
...page.seoTitle ? { seoTitle: page.seoTitle } : {},
|
|
20925
|
+
...page.seoDescription ? { seoDescription: page.seoDescription } : {}
|
|
20926
|
+
};
|
|
20927
|
+
}
|
|
20928
|
+
|
|
20739
20929
|
//#endregion
|
|
20740
20930
|
//#region src/sync/pageDiff.ts
|
|
20741
20931
|
function createPageSummary() {
|
|
@@ -20784,7 +20974,9 @@ function recordBlockDiff(summary, diff) {
|
|
|
20784
20974
|
case "unchanged":
|
|
20785
20975
|
summary.unchanged++;
|
|
20786
20976
|
break;
|
|
20787
|
-
case "reorder":
|
|
20977
|
+
case "reorder":
|
|
20978
|
+
summary.update++;
|
|
20979
|
+
break;
|
|
20788
20980
|
}
|
|
20789
20981
|
}
|
|
20790
20982
|
function createBlockDiffs(blocks, pageIdentifier, summary) {
|
|
@@ -20827,64 +21019,47 @@ function classifyBlock(localBlock, remoteBlock, pageIdentifier) {
|
|
|
20827
21019
|
};
|
|
20828
21020
|
}
|
|
20829
21021
|
function buildRemoteBlockLookup(remoteBlocks) {
|
|
20830
|
-
|
|
20831
|
-
for (const block of remoteBlocks) lookup.set(block.identifier, block);
|
|
20832
|
-
return lookup;
|
|
21022
|
+
return new Map(remoteBlocks.map((block) => [block.identifier, block]));
|
|
20833
21023
|
}
|
|
20834
|
-
function
|
|
20835
|
-
|
|
20836
|
-
for (const localBlock of localBlocks) {
|
|
20837
|
-
const diff = classifyBlock(localBlock, remoteLookup.get(localBlock.identifier), pageIdentifier);
|
|
20838
|
-
diffs.push(diff);
|
|
20839
|
-
recordBlockDiff(summary, diff);
|
|
20840
|
-
}
|
|
20841
|
-
return diffs;
|
|
21024
|
+
function localBlockDiffs(localBlocks, remoteLookup, pageIdentifier) {
|
|
21025
|
+
return localBlocks.map((block) => classifyBlock(block, remoteLookup.get(block.identifier), pageIdentifier));
|
|
20842
21026
|
}
|
|
20843
|
-
function
|
|
20844
|
-
|
|
20845
|
-
|
|
20846
|
-
|
|
20847
|
-
|
|
20848
|
-
|
|
20849
|
-
pageIdentifier
|
|
20850
|
-
};
|
|
20851
|
-
diffs.push(diff);
|
|
20852
|
-
recordBlockDiff(summary, diff);
|
|
20853
|
-
}
|
|
21027
|
+
function deletedBlockDiffs(remoteBlocks, localBlockIdentifiers, pageIdentifier) {
|
|
21028
|
+
return remoteBlocks.filter((block) => !localBlockIdentifiers.has(block.identifier)).map((block) => ({
|
|
21029
|
+
type: "delete",
|
|
21030
|
+
identifier: block.identifier,
|
|
21031
|
+
pageIdentifier
|
|
21032
|
+
}));
|
|
20854
21033
|
}
|
|
20855
|
-
function
|
|
20856
|
-
const
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
20862
|
-
});
|
|
20863
|
-
return;
|
|
20864
|
-
}
|
|
21034
|
+
function blockReorderDiff(localBlocks, remoteBlocks, remoteLookup, localBlockIdentifiers, pageIdentifier, diffs) {
|
|
21035
|
+
const reorder = {
|
|
21036
|
+
type: "reorder",
|
|
21037
|
+
identifier: pageIdentifier,
|
|
21038
|
+
pageIdentifier
|
|
21039
|
+
};
|
|
21040
|
+
if (diffs.some((diff) => diff.type === "create") && remoteBlocks.length > 0) return reorder;
|
|
20865
21041
|
const localOrder = localBlocks.filter((block) => remoteLookup.has(block.identifier)).map((block) => block.identifier);
|
|
20866
21042
|
const remoteOrder = remoteBlocks.filter((block) => localBlockIdentifiers.has(block.identifier)).sort((a, b) => a.position - b.position).map((block) => block.identifier);
|
|
20867
|
-
|
|
20868
|
-
diffs.push({
|
|
20869
|
-
type: "reorder",
|
|
20870
|
-
identifier: pageIdentifier,
|
|
20871
|
-
pageIdentifier
|
|
20872
|
-
});
|
|
20873
|
-
}
|
|
21043
|
+
return localOrder.length > 1 && !equal(localOrder, remoteOrder) ? reorder : undefined;
|
|
20874
21044
|
}
|
|
20875
21045
|
function calculateBlockDiffs(localBlocks, remoteBlocks, pageIdentifier, summary) {
|
|
20876
21046
|
const remoteLookup = buildRemoteBlockLookup(remoteBlocks);
|
|
20877
21047
|
const localBlockIdentifiers = new Set(localBlocks.map((block) => block.identifier));
|
|
20878
|
-
const
|
|
20879
|
-
|
|
20880
|
-
|
|
21048
|
+
const contentDiffs = [...localBlockDiffs(localBlocks, remoteLookup, pageIdentifier), ...deletedBlockDiffs(remoteBlocks, localBlockIdentifiers, pageIdentifier)];
|
|
21049
|
+
const reorder = blockReorderDiff(localBlocks, remoteBlocks, remoteLookup, localBlockIdentifiers, pageIdentifier, contentDiffs);
|
|
21050
|
+
const diffs = [...contentDiffs, ...reorder ? [reorder] : []];
|
|
21051
|
+
diffs.forEach((diff) => recordBlockDiff(summary, diff));
|
|
20881
21052
|
return diffs;
|
|
20882
21053
|
}
|
|
20883
21054
|
function changedPageFields(localPage, remotePage) {
|
|
20884
|
-
const
|
|
20885
|
-
|
|
20886
|
-
|
|
20887
|
-
|
|
21055
|
+
const localSeo = resolvePageUpsertSeo(pageMetadataForUpsert(localPage));
|
|
21056
|
+
const remoteSeo = resolvePageUpsertSeo(remotePage);
|
|
21057
|
+
return [
|
|
21058
|
+
...localPage.title !== remotePage.title ? ["title"] : [],
|
|
21059
|
+
...localPage.path !== remotePage.path ? ["path"] : [],
|
|
21060
|
+
...localSeo.seoTitle !== remoteSeo.seoTitle ? ["seoTitle"] : [],
|
|
21061
|
+
...localSeo.seoDescription !== remoteSeo.seoDescription ? ["seoDescription"] : []
|
|
21062
|
+
];
|
|
20888
21063
|
}
|
|
20889
21064
|
function classifyPage(localPage, remotePage, options, blockSummary) {
|
|
20890
21065
|
if (!remotePage) {
|
|
@@ -20907,20 +21082,12 @@ function classifyPage(localPage, remotePage, options, blockSummary) {
|
|
|
20907
21082
|
}
|
|
20908
21083
|
const blockDiffs = calculateBlockDiffs(localPage.blocks ?? [], remotePage.blocks ?? [], localPage.identifier, blockSummary);
|
|
20909
21084
|
const changes = changedPageFields(localPage, remotePage);
|
|
20910
|
-
const hasBlockChanges = blockDiffs.some((diff) => diff.type !== "unchanged");
|
|
20911
|
-
if (changes.length > 0 || hasBlockChanges) {
|
|
20912
|
-
return {
|
|
20913
|
-
type: "update",
|
|
20914
|
-
identifier: localPage.identifier,
|
|
20915
|
-
changes: changes.length > 0 ? changes : undefined,
|
|
20916
|
-
blocks: blockDiffs,
|
|
20917
|
-
remoteBlockIdentifiers: (remotePage.blocks ?? []).map((block) => block.identifier),
|
|
20918
|
-
statusChange
|
|
20919
|
-
};
|
|
20920
|
-
}
|
|
20921
21085
|
return {
|
|
20922
|
-
type: "unchanged",
|
|
21086
|
+
type: changes.length > 0 ? "update" : "unchanged",
|
|
20923
21087
|
identifier: localPage.identifier,
|
|
21088
|
+
changes: changes.length > 0 ? changes : undefined,
|
|
21089
|
+
blocks: blockDiffs,
|
|
21090
|
+
remoteBlockIdentifiers: (remotePage.blocks ?? []).map((block) => block.identifier),
|
|
20924
21091
|
statusChange
|
|
20925
21092
|
};
|
|
20926
21093
|
}
|
|
@@ -20942,6 +21109,10 @@ function calculatePageDiffs(localPages, remotePages, options) {
|
|
|
20942
21109
|
blockSummary
|
|
20943
21110
|
};
|
|
20944
21111
|
}
|
|
21112
|
+
/** A page may have block operations without changing its metadata. */
|
|
21113
|
+
function hasPageOperations(diff) {
|
|
21114
|
+
return diff.type !== "unchanged" || Boolean(diff.statusChange) || (diff.blocks ?? []).some((block) => block.type !== "unchanged");
|
|
21115
|
+
}
|
|
20945
21116
|
|
|
20946
21117
|
//#endregion
|
|
20947
21118
|
//#region src/sync/navigationDiffMatching.ts
|
|
@@ -21111,7 +21282,10 @@ function buildBlockChanges(pageDiff, localPages, remotePages, mode) {
|
|
|
21111
21282
|
function buildPageChanges(diff, lookups, mode) {
|
|
21112
21283
|
const changes = [];
|
|
21113
21284
|
for (const pageDiff of diff.pages) {
|
|
21114
|
-
if (pageDiff.type === "unchanged" && !pageDiff.statusChange)
|
|
21285
|
+
if (pageDiff.type === "unchanged" && !pageDiff.statusChange) {
|
|
21286
|
+
changes.push(...buildBlockChanges(pageDiff, lookups.localPages, lookups.remotePages, mode));
|
|
21287
|
+
continue;
|
|
21288
|
+
}
|
|
21115
21289
|
const change = {
|
|
21116
21290
|
type: "page",
|
|
21117
21291
|
identifier: pageDiff.identifier,
|
|
@@ -21341,11 +21515,13 @@ function calculatePullBlockDiffs(remoteBlocks, localBlocks, pageIdentifier, summ
|
|
|
21341
21515
|
return diff;
|
|
21342
21516
|
});
|
|
21343
21517
|
}
|
|
21344
|
-
function changedPullPageFields(
|
|
21345
|
-
|
|
21346
|
-
|
|
21347
|
-
|
|
21348
|
-
|
|
21518
|
+
function changedPullPageFields(localPage, remotePage) {
|
|
21519
|
+
return [
|
|
21520
|
+
...localPage.title !== remotePage.title ? ["title"] : [],
|
|
21521
|
+
...localPage.path !== remotePage.path ? ["path"] : [],
|
|
21522
|
+
...localPage.seoTitle !== (remotePage.seoTitle ?? undefined) ? ["seoTitle"] : [],
|
|
21523
|
+
...localPage.seoDescription !== (remotePage.seoDescription ?? undefined) ? ["seoDescription"] : []
|
|
21524
|
+
];
|
|
21349
21525
|
}
|
|
21350
21526
|
function calculatePullPageDiffs(remotePages, local) {
|
|
21351
21527
|
const diffs = [];
|
|
@@ -21365,7 +21541,7 @@ function calculatePullPageDiffs(remotePages, local) {
|
|
|
21365
21541
|
continue;
|
|
21366
21542
|
}
|
|
21367
21543
|
const blockDiffs = calculatePullBlockDiffs(remotePage.blocks ?? [], localPage.blocks ?? [], remotePage.identifier, blockSummary);
|
|
21368
|
-
const changes = changedPullPageFields(
|
|
21544
|
+
const changes = changedPullPageFields(localPage, remotePage);
|
|
21369
21545
|
const diff = changes.length > 0 || blockDiffs.some((block) => block.type !== "unchanged") ? {
|
|
21370
21546
|
type: "update",
|
|
21371
21547
|
identifier: remotePage.identifier,
|
|
@@ -21621,11 +21797,17 @@ function formatPageDetails(diff, localPages, remotePages) {
|
|
|
21621
21797
|
const localPage = localPages.get(diff.identifier);
|
|
21622
21798
|
const remotePage = remotePages.get(diff.identifier);
|
|
21623
21799
|
if (!localPage || !remotePage) return [];
|
|
21624
|
-
const
|
|
21625
|
-
|
|
21626
|
-
|
|
21627
|
-
|
|
21628
|
-
|
|
21800
|
+
const localFields = {
|
|
21801
|
+
title: localPage.title,
|
|
21802
|
+
path: localPage.path,
|
|
21803
|
+
...resolvePageUpsertSeo(pageMetadataForUpsert(localPage))
|
|
21804
|
+
};
|
|
21805
|
+
const remoteFields = {
|
|
21806
|
+
title: remotePage.title,
|
|
21807
|
+
path: remotePage.path,
|
|
21808
|
+
...resolvePageUpsertSeo(remotePage)
|
|
21809
|
+
};
|
|
21810
|
+
return findChangedFieldsWithValues(localFields, remoteFields).map((change) => ` ${formatFieldChange(change)}`);
|
|
21629
21811
|
}
|
|
21630
21812
|
function buildIdentifierLookup(items) {
|
|
21631
21813
|
const lookup = new Map();
|
|
@@ -21642,6 +21824,11 @@ function formatHumanDiff(diff, local, remote) {
|
|
|
21642
21824
|
const localPages = buildIdentifierLookup(local.pages);
|
|
21643
21825
|
const remotePages = buildIdentifierLookup(remote.pages);
|
|
21644
21826
|
appendOperationSections$1(lines, "Pages", diff.pages, CREATE_UPDATE_OPERATIONS, (page) => page.identifier, (page) => formatPageDetails(page, localPages, remotePages));
|
|
21827
|
+
appendOperationSections$1(lines, "Blocks", diff.pages.flatMap((page) => page.blocks ?? []), [...ENTRY_OPERATIONS, {
|
|
21828
|
+
type: "reorder",
|
|
21829
|
+
label: "reordered",
|
|
21830
|
+
marker: "~"
|
|
21831
|
+
}], (block) => `${block.pageIdentifier}/${block.identifier}`);
|
|
21645
21832
|
appendOperationSections$1(lines, "Navigation", diff.navigation, CREATE_UPDATE_OPERATIONS, (navigation) => navigation.type === "update" && navigation.changes ? `${navigation.location} [${navigation.changes.join(", ")}]` : navigation.location);
|
|
21646
21833
|
if (diff.settings) {
|
|
21647
21834
|
lines.push("\nSettings (updated):");
|
|
@@ -21861,13 +22048,21 @@ function appendSingleUpdateSection(lines, title, detail) {
|
|
|
21861
22048
|
lines.push(`\n${title}:`);
|
|
21862
22049
|
lines.push(` ~ ${detail}`);
|
|
21863
22050
|
}
|
|
22051
|
+
function appendPageDetailSections(lines, pages) {
|
|
22052
|
+
appendOperationSections(lines, "Pages", pages, CREATE_UPDATE_DETAIL_OPERATIONS, (page) => formatIdentifierDetail(page.identifier, page.changes));
|
|
22053
|
+
appendOperationSections(lines, "Blocks", pages.flatMap((page) => page.blocks ?? []), [...CREATE_UPDATE_DELETE_DETAIL_OPERATIONS, {
|
|
22054
|
+
type: "reorder",
|
|
22055
|
+
label: "reordered",
|
|
22056
|
+
marker: "~"
|
|
22057
|
+
}], (block) => formatIdentifierDetail(`${block.pageIdentifier}/${block.identifier}`, block.changes));
|
|
22058
|
+
}
|
|
21864
22059
|
/** Format detailed diff for display. */
|
|
21865
22060
|
function formatDiffDetail(diff) {
|
|
21866
22061
|
const lines = [];
|
|
21867
22062
|
for (const [contentType, entryDiffs] of diff.entries) {
|
|
21868
22063
|
appendOperationSections(lines, contentType, entryDiffs, CREATE_UPDATE_DELETE_DETAIL_OPERATIONS, (entry) => formatIdentifierDetail(entry.identifier, entry.changes));
|
|
21869
22064
|
}
|
|
21870
|
-
|
|
22065
|
+
appendPageDetailSections(lines, diff.pages);
|
|
21871
22066
|
appendOperationSections(lines, "Navigation", diff.navigation, CREATE_UPDATE_DETAIL_OPERATIONS, (navigation) => navigation.location);
|
|
21872
22067
|
if (diff.settings) {
|
|
21873
22068
|
appendSingleUpdateSection(lines, "Settings (updated)", `[${diff.settings.changes.join(", ")}]`);
|
|
@@ -23107,4 +23302,4 @@ function optionalTitle(data) {
|
|
|
23107
23302
|
}
|
|
23108
23303
|
|
|
23109
23304
|
//#endregion
|
|
23110
|
-
export {
|
|
23305
|
+
export { normalizeMediaValueToIdentifierOnly as $, bindRequestTimeout as $t, formatDiffDetail as A, transportSuggestion as An, navigationIdentity as At, buildFieldChanges as B, sdkEventScheduleSchema as Bn, preparePushConfigTarget as Bt, reportFromOutcomes as C, parseEnvironmentSelection as Cn, fileExists$1 as Ct, normalizeRemoteHomepageReference as D, withRiverbankEnv as Dn, writeJsonFileAtomic as Dt, createEmptySyncResult as E, requireEnvironmentWriteAuthorization as En, writeJsonFile$1 as Et, calculatePullDiff as F, isLocalConfigured as Fn, navigationCommandItemToManagementInput as Ft, syncLocalMediaForPull as G, APPOINTMENT_SERVICE_VISIBILITY_VALUES as Gt, formatFieldPath as H, linkPayloadSchema as Hn, extractApiErrorMessage as Ht, buildJsonDiff as I, isRemoteConfigured as In, normalizeNavigationConfigItemForCommand as It, admitBinaryWriteDestination as J, ApiEnvelopeError as Jt, applyLocalMediaPush as K, ApiRequestError as Kt, hasPageOperations as L, loadSdkEnvironment as Ln, applyPushConfigPlan as Lt, hasPendingChanges as M, createOutput as Mn, convertPulledFooterToLocal as Mt, findChangedFieldsWithValues as N, getEnvPrefix as Nn, convertPulledNavigationToLocal as Nt, calculateDiff as O, ErrorCodes as On, isMediaValue as Ot, formatHumanDiff as P, getEnvironmentDebugInfo as Pn, convertPulledPagesToLocal as Pt, normalizeMediaIdentifierInput as Q, mapEnvironmentExecutionOutcomesJson as Qt, pageMetadataForUpsert as R, createManagementClient as Rn, createPushConfigCommand as Rt, planCliCommandBatchForBindings as S, executeEnvironmentPlan as Sn, ensureDir$1 as St, executeResidualSettingsSyncPlan as T, planEnvironmentWriteAuthorization as Tn, readJsonFile as Tt, revalidateLocalMediaPushPlan as U, portablePageDesignSchema as Un, normalizeApiError as Ut, formatFieldChange as V, sdkSettingsUpdateSchema as Vn, revalidatePushConfigPlan as Vt, syncLocalMediaForPush as W, sdkConfiguredBookingVerticalConfigSchema as Wn, isRecord$5 as Wt, writeBinaryFileAtomic as X, deriveCmsApiUrlFromDashboardUrl as Xt, readStableBinaryFile as Y, unwrapResponse as Yt, sha256 as Z, API_ENDPOINTS as Zt, commandExecutionToReport as _, runWithInvocation as _n, metadataFilePath as _t, executeEntryCommandPlanAsReport as a, createOutputForCommand as an, readEntries as at, executeCompiledCliCommandPlanAsReport as b, withErrorHandling as bn, writeMetadataFile as bt, titleFromEntryData as c, getOutputContext as cn, readEventsMeta as ct, unsafeAsCliEntryIdentifier as d, managementClientConfigFor as dn, readNavigationMeta as dt, confirmAction as en, walkLocalContentMedia as et, unsafeAsCliFormSlug as f, mapEntryForOutput as fn, readPagesMeta as ft, commandCompileFailureToReport as g, resolveCommandPath as gn, ensureMetadataDir as gt, mutationCounterForEffect as h, parsePaginationOptions as hn, writeMediaManifest as ht, entrySiteCommandTypes as i, createListCommand as in, findLegacyEntryFiles as it, formatDiffSummary as j, CliExit as jn, navigationIdentityMap as jt, calculateFooterDiff as k, formatError as kn, duplicateNavigationIdentityMessage as kt, unsafeAsCliBlockIdentifier as l, getOutputOnlyContext as ln, readFooterMeta as lt, commandReportToSyncResult as m, parseJsonData as mn, readVenuesMeta as mt, entryCommandCompileErrorToReport as n, createCommandContext as nn, downloadMedia as nt, executeEntryCommandPlanAsSyncResult as o, currentInvocationEnvelope as on, readAllMeta as ot, unsafeAsCliPageIdentifier as p, parseJsonArray as pn, readSettingsMeta as pt, prepareLocalMediaForPush as q, createBearerAPIClient as qt, entryCommandReportToSyncResult as r, createGetCommand as rn, extractMediaIdentifiers as rt, syncResultForEntryCommandCompileError as s, formatDateShort as sn, readEventCategoriesMeta as st, compileEntryPushCommands as t, createClientForEnv as tn, buildStorageUrl as tt, unsafeAsCliContentTypeKey as u, handleCommandError as un, readFormsMeta as ut, commandReportHasFailures as v, withCommandContext as vn, metadataTargetFromEnvironment as vt, mergeManySyncResults as w, planEnvironmentExecution as wn, listFiles as wt, formatManagementError as x, withOutputOnlyErrorHandling as xn, writeMetadataRecordMap as xt, executeCliCommandPlan as y, withConfirmation as yn, readMetadataFile as yt, calculateEntryDiffs as z, ManagementApiError as zn, loadPushConfigTarget as zt };
|