riverbankcms 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -749,32 +749,27 @@ riverbankcms block list home --json
749
749
 
750
750
  ## Navigation Commands
751
751
 
752
- Manage navigation menus.
752
+ Every site has three fixed locations: `main`, `footer`, and `legal`. Each owns its items. Locations cannot be renamed, reassigned, shared or deleted.
753
753
 
754
754
  ### navigation upsert
755
755
 
756
- Create or update a navigation menu.
756
+ Replace the items in one location. An empty array clears its links and keeps the location.
757
757
 
758
758
  ```bash
759
- # With inline JSON
760
- riverbankcms navigation upsert <menu-name> --data '[{"label": "Home", "url": "/"}]'
761
-
762
- # With JSON file
763
- riverbankcms navigation upsert main --file ./main-nav.json
759
+ riverbankcms navigation upsert main --data '[{"label":"Home","link":{"kind":"page","identifier":"home"}}]'
760
+ riverbankcms navigation upsert footer --file ./footer-links.json
761
+ riverbankcms navigation upsert legal --data '[]'
764
762
  ```
765
763
 
766
- **Example menu structure:**
764
+ Example item array:
767
765
 
768
766
  ```json
769
767
  [
770
- { "label": "Home", "url": "/" },
771
- { "label": "About", "url": "/about" },
768
+ { "label": "Home", "link": { "kind": "page", "identifier": "home" } },
772
769
  {
773
770
  "label": "Products",
774
- "url": "/products",
775
771
  "children": [
776
- { "label": "Category A", "url": "/products/a" },
777
- { "label": "Category B", "url": "/products/b" }
772
+ { "label": "Category A", "link": { "kind": "url", "href": "/products/a" } }
778
773
  ]
779
774
  }
780
775
  ]
@@ -782,17 +777,17 @@ riverbankcms navigation upsert main --file ./main-nav.json
782
777
 
783
778
  ### navigation get
784
779
 
785
- Retrieve a navigation menu.
780
+ Read one fixed location.
786
781
 
787
782
  ```bash
788
- riverbankcms navigation get <menu-name>
789
783
  riverbankcms navigation get main
790
- riverbankcms navigation get main --json
784
+ riverbankcms navigation get footer --json
785
+ riverbankcms navigation get legal
791
786
  ```
792
787
 
793
788
  ### navigation list
794
789
 
795
- List all navigation menus.
790
+ List all three locations, including those with no items.
796
791
 
797
792
  ```bash
798
793
  riverbankcms navigation list
@@ -65,7 +65,8 @@ See `block-types.md` for the full list.
65
65
 
66
66
  Menu structures for site navigation.
67
67
 
68
- - Named menus (e.g., "main", "footer")
68
+ - Three independent fixed locations: `main`, `footer`, and `legal`
69
+ - Empty item lists clear a location; omitted locations are left unchanged
69
70
  - Hierarchical items with labels, URLs, and children
70
71
 
71
72
  **Local file structure:**
@@ -557,8 +558,7 @@ riverbankcms push --env=remote --yes
557
558
  {
558
559
  "menus": [
559
560
  {
560
- "identifier": "main",
561
- "name": "Main Navigation",
561
+ "location": "main",
562
562
  "items": [
563
563
  { "label": "Home", "link": { "kind": "page", "identifier": "home" } },
564
564
  {
@@ -582,8 +582,7 @@ riverbankcms push --env=remote --yes
582
582
  ]
583
583
  },
584
584
  {
585
- "identifier": "footer",
586
- "name": "Footer Navigation",
585
+ "location": "footer",
587
586
  "items": [
588
587
  { "label": "Privacy", "link": { "kind": "url", "href": "/privacy" } },
589
588
  { "label": "Terms", "link": { "kind": "url", "href": "/terms" } }
@@ -669,34 +668,45 @@ Footer links are managed via the site's footer navigation menu (see `content/nav
669
668
  {
670
669
  "identifier": "yoga-class",
671
670
  "title": "Morning Yoga Class",
672
- "slug": "yoga-class",
673
- "path": "/events/yoga-class",
674
- "status": "active",
675
- "visibility": "public",
676
671
  "preset": "class",
677
- "bookingScope": "single_occurrence",
678
- "occurrences": [
672
+ "runRefs": ["yoga-class"],
673
+ "runs": [
679
674
  {
680
- "startsAt": "2026-01-05T15:00:00.000Z",
681
- "endsAt": "2026-01-05T16:00:00.000Z",
682
- "timeZone": "America/Los_Angeles"
683
- },
684
- {
685
- "startsAt": "2026-01-12T15:00:00.000Z",
686
- "endsAt": "2026-01-12T16:00:00.000Z",
687
- "timeZone": "America/Los_Angeles"
675
+ "identifier": "yoga-class",
676
+ "eventRef": "yoga-class",
677
+ "status": "active",
678
+ "visibility": "public",
679
+ "bookingScope": "single_occurrence",
680
+ "occurrences": [
681
+ {
682
+ "startsAt": "2026-01-05T15:00:00.000Z",
683
+ "endsAt": "2026-01-05T16:00:00.000Z",
684
+ "timeZone": "America/Los_Angeles"
685
+ },
686
+ {
687
+ "startsAt": "2026-01-12T15:00:00.000Z",
688
+ "endsAt": "2026-01-12T16:00:00.000Z",
689
+ "timeZone": "America/Los_Angeles"
690
+ }
691
+ ],
692
+ "venueRef": "main-hall",
693
+ "categoryRef": "workshop",
694
+ "capacity": 30,
695
+ "bookingOffer": {
696
+ "bookingRequirement": "required",
697
+ "pricing": {
698
+ "kind": "single",
699
+ "currency": "USD",
700
+ "amount": 2500,
701
+ "acceptsPasses": true,
702
+ "acceptsMemberships": true
703
+ },
704
+ "paymentTerms": {
705
+ "allowedOptions": [{ "kind": "upfront" }]
706
+ }
707
+ }
688
708
  }
689
709
  ],
690
- "venueRef": "main-hall",
691
- "categoryRef": "workshop",
692
- "capacity": 30,
693
- "pricing": {
694
- "requiresPayment": true,
695
- "priceAmount": 2500,
696
- "priceCurrency": "usd",
697
- "acceptsPasses": true,
698
- "acceptsMemberships": true
699
- },
700
710
  "content": {
701
711
  "summary": "Start your day with energizing yoga",
702
712
  "body": "<p>Join us for a revitalizing morning yoga session.</p>"
@@ -724,15 +734,14 @@ Footer links are managed via the site's footer navigation menu (see `content/nav
724
734
 
725
735
  **Event field notes:**
726
736
 
727
- - `priceAmount` is in cents (2500 = $25.00)
737
+ - `bookingOffer.pricing.amount` and tiered option `priceAmount` values are in cents (2500 = $25.00).
728
738
  - `preset` controls the event's presentation defaults; `bookingScope` controls
729
739
  whether a registration selects one occurrence or the complete series.
730
- - `occurrences` is the complete authored schedule. Instants use canonical UTC
740
+ - Each run's `occurrences` is its complete authored schedule. Instants use canonical UTC
731
741
  ISO datetimes and every item carries the event's IANA timezone.
732
- - `venueRef` and `categoryRef` reference slugs/identifiers from venues and event-categories
742
+ - Run `venueRef` and `categoryRef` values reference identifiers from venues and event-categories.
733
743
  - `status`: "draft", "active", "completed", "cancelled", or "archived"
734
744
  - `visibility`: "private", "public", or "unlisted"
735
- - `path` sets the draft route owned by the event's native page surface. Omit it to keep the CMS-generated route.
736
745
  - Event page content is owned by the native `eventOffering` or `eventSeries` page-surface subject.
737
746
  - `blocks` writes to the event page surface's template-defined surface slots. The default event template declares `blocks.main`.
738
747
  - Use stable block `identifier` values. Re-pushing the same event updates matching slot blocks instead of creating duplicates.
@@ -30,7 +30,7 @@ import { blockOverrides } from '@/lib/block-overrides'
30
30
  import { getRiverbankClient } from '@/lib/riverbank-client'
31
31
 
32
32
  // Next.js route config values must remain statically analyzable literals.
33
- export const revalidate = 300
33
+ export const revalidate = 60
34
34
  export const dynamicParams = true
35
35
  export const generateStaticParams = createGenerateStaticParams({
36
36
  getClient: getRiverbankClient,
@@ -80,7 +80,7 @@ If you are upgrading an existing generated site, make sure your proxy imports `g
80
80
 
81
81
  Use `resolveRouteDecision` for middleware or redirect-only checks. It returns only `contentExists`, `redirect`, `notFound`, or `loadFailed`; use `resolveRouteOutcome` only when the caller needs the full content payload for rendering.
82
82
 
83
- Event registration endpoints use the `public-event-ticket-selection.v2` transport shape: pass `ticketSelections: [{ ticketTypeId, quantity }]`. Legacy top-level `ticketsCount` or `ticketTypeId` fields are rejected so stale clients fail visibly instead of silently selecting the wrong tickets.
83
+ Event registration endpoints use the `public-event-ticket-selection.v2` transport shape: pass `ticketSelections: [{ pricingOptionId, quantity }]`. Retired `ticketTypeId` fields and top-level `ticketsCount` are rejected so stale clients fail visibly instead of silently selecting the wrong pricing option.
84
84
 
85
85
  ## Add Preview Keep-Alive Route
86
86
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as EVENT_PRESET_VALUES } from "./src-DKm0bLEA.mjs";
3
- import { a as classifySdkEventPricing, i as classifySdkEventSchedule, o as classifySdkEventPaymentTerms, r as sdkEventUpsertOptionalFieldSchemas } from "./sdk-event-upsert-Dy08IXYL.mjs";
2
+ import { a as classifySdkEventBookingOffer, i as classifySdkEventSchedule, r as sdkEventUpsertOptionalFieldSchemas } from "./sdk-event-upsert-CS9tY-KW.mjs";
3
+ import { l as EVENT_PRESET_VALUES } from "./src-DT7DBZtQ.mjs";
4
4
  import { z } from "zod";
5
5
 
6
6
  //#region ../blocks/src/lib/typeGuards.ts
@@ -3298,11 +3298,13 @@ const DEFAULT_TEMPLATE_DEFINITIONS = {
3298
3298
  {
3299
3299
  blockKind: "block.hero",
3300
3300
  scope: "entry",
3301
+ content: { _sectionStyles: { background: { type: "image" } } },
3301
3302
  bindings: {
3302
3303
  eyebrow: literalBinding("Event"),
3303
3304
  headline: metaBinding("title"),
3304
3305
  headlineInline: ENTRY_TITLE_INLINE_TEXT,
3305
- subheadline: EVENT_SUBHEADLINE_FALLBACK
3306
+ subheadline: EVENT_SUBHEADLINE_FALLBACK,
3307
+ "_sectionStyles.background.image": fieldBinding("image")
3306
3308
  }
3307
3309
  },
3308
3310
  {
@@ -4733,60 +4735,53 @@ const eventScheduleInputSchema = z.object({
4733
4735
  durationMinutes: z.number().int().positive().optional(),
4734
4736
  startsAt: z.string().min(1).optional(),
4735
4737
  endsAt: z.string().min(1).optional()
4736
- }).superRefine((schedule, ctx) => {
4737
- const result = classifySdkEventSchedule(schedule, { requireCanonicalFields: true });
4738
- if (result.ok) return;
4739
- result.issues.forEach((issue) => ctx.addIssue({
4740
- code: "custom",
4741
- path: [...issue.path],
4742
- message: issue.message
4743
- }));
4744
- }).transform((schedule) => {
4738
+ }).transform((schedule, ctx) => {
4745
4739
  const result = classifySdkEventSchedule(schedule, { requireCanonicalFields: true });
4746
- if (!result.ok) throw new Error("Unreachable invalid event schedule");
4740
+ if (!result.ok) {
4741
+ result.issues.forEach((issue) => ctx.addIssue({
4742
+ code: "custom",
4743
+ path: [...issue.path],
4744
+ message: issue.message
4745
+ }));
4746
+ return z.NEVER;
4747
+ }
4747
4748
  if (result.kind === "start-end") {
4748
- if (typeof schedule.startsAt !== "string" || typeof schedule.endsAt !== "string") throw new Error("Unreachable invalid start/end event schedule");
4749
4749
  return {
4750
- startsAt: schedule.startsAt,
4751
- endsAt: schedule.endsAt
4750
+ startsAt: result.startsAtIso,
4751
+ endsAt: result.endsAtIso
4752
4752
  };
4753
4753
  }
4754
4754
  if (result.kind === "start-duration") {
4755
- if (typeof schedule.startsAt !== "string" || typeof schedule.durationMinutes !== "number") throw new Error("Unreachable invalid start/duration event schedule");
4756
4755
  return {
4757
- startsAt: schedule.startsAt,
4758
- durationMinutes: schedule.durationMinutes
4756
+ startsAt: result.startsAtIso,
4757
+ durationMinutes: (Date.parse(result.endsAtIso) - Date.parse(result.startsAtIso)) / 6e4
4759
4758
  };
4760
4759
  }
4761
4760
  return result.canonical;
4762
4761
  });
4763
4762
  function classifiedSchema(classify$1) {
4764
- return z.unknown().superRefine((input, ctx) => {
4763
+ return z.unknown().transform((input, ctx) => {
4765
4764
  const result = classify$1(input);
4766
- if (result.ok) return;
4765
+ if (result.ok) return result.value;
4767
4766
  result.issues.forEach((issue) => ctx.addIssue({
4768
4767
  code: "custom",
4769
4768
  path: issue.path.map(String),
4770
4769
  message: issue.message
4771
4770
  }));
4772
- }).transform((input) => {
4773
- const result = classify$1(input);
4774
- if (!result.ok) throw new Error("Unreachable invalid classified input");
4775
- return result.value;
4771
+ return z.NEVER;
4776
4772
  });
4777
4773
  }
4778
- const eventPricingInputSchema = classifiedSchema((input) => {
4779
- const result = classifySdkEventPricing(input);
4780
- return result.ok ? {
4781
- ok: true,
4782
- value: result.pricing
4783
- } : result;
4784
- });
4785
- const eventPaymentTermsInputSchema = classifiedSchema((input) => {
4786
- const result = classifySdkEventPaymentTerms(input);
4787
- return result.ok ? {
4774
+ const eventBookingOfferInputSchema = classifiedSchema((input) => {
4775
+ const result = classifySdkEventBookingOffer(input);
4776
+ return result.ok && result.bookingOffer ? {
4788
4777
  ok: true,
4789
- value: result.paymentTerms
4778
+ value: result.bookingOffer
4779
+ } : result.ok ? {
4780
+ ok: false,
4781
+ issues: [{
4782
+ path: [],
4783
+ message: "Event bookingOffer is required"
4784
+ }]
4790
4785
  } : result;
4791
4786
  });
4792
4787
  const eventRunInputSchema = z.object({
@@ -4798,8 +4793,7 @@ const eventRunInputSchema = z.object({
4798
4793
  bookingScope: z.enum(["single_occurrence", "whole_series"]),
4799
4794
  occurrences: sdkEventUpsertOptionalFieldSchemas.occurrences.unwrap(),
4800
4795
  schedule: eventScheduleInputSchema.optional(),
4801
- pricing: eventPricingInputSchema.optional(),
4802
- paymentTerms: eventPaymentTermsInputSchema.optional(),
4796
+ bookingOffer: eventBookingOfferInputSchema.optional(),
4803
4797
  capacity: sdkEventUpsertOptionalFieldSchemas.capacity,
4804
4798
  venueRef: z.string().nullable().optional(),
4805
4799
  categoryRef: z.string().nullable().optional(),
@@ -4807,7 +4801,7 @@ const eventRunInputSchema = z.object({
4807
4801
  settings: z.record(z.string(), z.unknown()).optional(),
4808
4802
  presentation: z.record(z.string(), z.unknown()).nullable().optional(),
4809
4803
  tags: z.array(z.string()).optional()
4810
- });
4804
+ }).strict();
4811
4805
  const eventUpsertInputSchema = z.object({
4812
4806
  identifier: z.string().min(1).optional(),
4813
4807
  title: z.string().min(1, "title is required"),
@@ -4817,7 +4811,7 @@ const eventUpsertInputSchema = z.object({
4817
4811
  preset: z.enum(EVENT_PRESET_VALUES),
4818
4812
  runRefs: z.tuple([z.string().min(1)], z.string().min(1)),
4819
4813
  runs: z.tuple([eventRunInputSchema], eventRunInputSchema)
4820
- }).superRefine((input, ctx) => input.runs.forEach((run, index) => {
4814
+ }).strict().superRefine((input, ctx) => input.runs.forEach((run, index) => {
4821
4815
  if (new Set(run.occurrences.map((item) => item.timeZone)).size <= 1) return;
4822
4816
  ctx.addIssue({
4823
4817
  code: "custom",
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./sdk-peer-BjTmH5Mt.mjs";
3
+ import { t as loadConfigFile } from "./load-config-DBGNYfxV.mjs";
4
+
5
+ export { loadConfigFile };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as assertCompatibleSdkPeer } from "./sdk-peer-sZF43pDS.mjs";
2
+ import { n as assertCompatibleSdkPeer } from "./sdk-peer-BjTmH5Mt.mjs";
3
3
  import { dirname, resolve } from "path";
4
4
  import { createJiti } from "jiti";
5
5
  import { existsSync } from "fs";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { At as ok, kt as err, nt as parseSiteId } from "./src-DKm0bLEA.mjs";
2
+ import { Rt as err, dt as parseSiteId, zt as ok } from "./src-DT7DBZtQ.mjs";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -561,7 +561,7 @@ import { dataLoaderOverrides } from '@/lib/data-loader-overrides.server';
561
561
  ${previewImports}
562
562
 
563
563
  // Next.js route config values must remain statically analyzable literals.
564
- export const revalidate = 300;
564
+ export const revalidate = 60;
565
565
  export const dynamicParams = true;
566
566
  // Route discovery falls back to the homepage when the CMS is unavailable so
567
567
  // the scaffold verification build does not require a live API. Add
@@ -2014,18 +2014,26 @@ function starterContentTemplates(config) {
2014
2014
  status: "published",
2015
2015
  blocks: []
2016
2016
  }),
2017
- jsonTemplate("content/navigation.json", { menus: [{
2018
- identifier: "main",
2019
- name: "Main",
2020
- isPrimary: true,
2021
- items: [{
2022
- label: "Home",
2023
- link: {
2024
- kind: "page",
2025
- identifier: "home"
2026
- }
2027
- }]
2028
- }] }),
2017
+ jsonTemplate("content/navigation.json", { menus: [
2018
+ {
2019
+ location: "main",
2020
+ items: [{
2021
+ label: "Home",
2022
+ link: {
2023
+ kind: "page",
2024
+ identifier: "home"
2025
+ }
2026
+ }]
2027
+ },
2028
+ {
2029
+ location: "footer",
2030
+ items: []
2031
+ },
2032
+ {
2033
+ location: "legal",
2034
+ items: []
2035
+ }
2036
+ ] }),
2029
2037
  jsonTemplate("content/settings.json", {
2030
2038
  homepageId: "home",
2031
2039
  seoDefaults: {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "../src-DKm0bLEA.mjs";
3
- import { a as prepareScaffoldExecution, d as loadGeneratedPackageRanges, i as preflightScaffoldTargets, l as deriveGeneratedPackageRanges, o as writeScaffoldFiles, r as preflightScaffoldPlan, s as deriveScaffoldLocalManifest, t as deriveScaffoldPlan, u as findPackageManifestPath } from "../plan-DPS1XAby.mjs";
2
+ import "../src-DT7DBZtQ.mjs";
3
+ import { a as prepareScaffoldExecution, d as loadGeneratedPackageRanges, i as preflightScaffoldTargets, l as deriveGeneratedPackageRanges, o as writeScaffoldFiles, r as preflightScaffoldPlan, s as deriveScaffoldLocalManifest, t as deriveScaffoldPlan, u as findPackageManifestPath } from "../plan-DgjlCWxa.mjs";
4
4
 
5
5
  export { deriveGeneratedPackageRanges, deriveScaffoldLocalManifest, deriveScaffoldPlan, findPackageManifestPath, loadGeneratedPackageRanges, preflightScaffoldPlan, preflightScaffoldTargets, prepareScaffoldExecution, writeScaffoldFiles };