riverbankcms 0.20.0 → 0.22.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.
@@ -1,8 +1,233 @@
1
1
  #!/usr/bin/env node
2
- import { S as normalizeLinkValue, d as classifySdkEventBookingOffer, l as sdkEventUpsertOptionalFieldSchemas, n as createRichTextDoc, s as sdkCourseBookingSettingsSchema, u as classifySdkEventSchedule, x as linkSchema } from "./richTextSchema-DTvrDDF5.mjs";
3
- import { u as EVENT_PRESET_VALUES } from "./src-Dixcv4PB.mjs";
2
+ import { _ as normalizeLinkValue, a as classifySdkEventSchedule, g as linkSchema, i as sdkEventUpsertOptionalFieldSchemas, n as sdkCourseBookingSettingsSchema, o as classifySdkEventBookingOffer } from "./sdk-event-upsert-BqjzK2zT.mjs";
3
+ import { u as EVENT_PRESET_VALUES } from "./src-zzwJUef5.mjs";
4
4
  import { z } from "zod";
5
5
 
6
+ //#region ../blocks/src/lib/typeGuards.ts
7
+ /**
8
+ * Strict record guard: excludes null, primitives, and arrays.
9
+ */
10
+ function isRecord$3(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ /**
14
+ * Non-null object guard: includes arrays for behavior parity with legacy helpers.
15
+ *
16
+ * NOTE: This intentionally narrows to UnknownRecord for compatibility with existing
17
+ * callsites even though arrays pass at runtime. Follow-up cleanup should replace
18
+ * this API with a more precise predicate type and migrate consumers.
19
+ */
20
+ function isObjectRecord(value) {
21
+ return typeof value === "object" && value !== null;
22
+ }
23
+ /**
24
+ * Coerces unknown input into a strict record, returning an empty object fallback.
25
+ */
26
+ function asRecord(value) {
27
+ return isRecord$3(value) ? value : {};
28
+ }
29
+
30
+ //#endregion
31
+ //#region ../blocks/src/system/manifest/richTextSchema.ts
32
+ /**
33
+ * Shared Rich Text Schema
34
+ *
35
+ * This module provides a single source of truth for TipTap rich text validation
36
+ * across both the block-form editor and the block rendering system.
37
+ *
38
+ * Rich-text fields use one canonical root shape:
39
+ * `{ type: 'doc', content: [...] }`.
40
+ */
41
+ /**
42
+ * TipTap document node schema (recursive structure)
43
+ *
44
+ * Supports the full TipTap document structure including:
45
+ * - type: Node type (e.g., 'doc', 'paragraph', 'text')
46
+ * - content: Nested array of child nodes
47
+ * - text: Text content for text nodes
48
+ * - attrs: Node attributes (e.g., alignment, color)
49
+ * - marks: Text formatting marks (e.g., bold, italic)
50
+ */
51
+ const TipTapMarkSchema = z.object({
52
+ type: z.string().min(1),
53
+ attrs: z.record(z.string(), z.unknown()).optional()
54
+ }).strict();
55
+ const TipTapNodeSchema = z.lazy(() => z.object({
56
+ type: z.string().min(1),
57
+ content: z.array(TipTapNodeSchema).optional(),
58
+ text: z.string().optional(),
59
+ attrs: z.record(z.string(), z.unknown()).optional(),
60
+ marks: z.array(TipTapMarkSchema).optional()
61
+ }).strict().superRefine((node, ctx) => {
62
+ if (node.type === "doc") {
63
+ ctx.addIssue({
64
+ code: "custom",
65
+ path: ["type"],
66
+ message: "Document nodes are only valid at the rich-text root"
67
+ });
68
+ }
69
+ if (node.type === "text") {
70
+ if (node.text === undefined || node.text.length === 0) {
71
+ ctx.addIssue({
72
+ code: "custom",
73
+ path: ["text"],
74
+ message: "Text nodes require a non-empty text value"
75
+ });
76
+ }
77
+ if (node.content !== undefined) {
78
+ ctx.addIssue({
79
+ code: "custom",
80
+ path: ["content"],
81
+ message: "Text nodes cannot contain child nodes"
82
+ });
83
+ }
84
+ return;
85
+ }
86
+ if (node.text !== undefined) {
87
+ ctx.addIssue({
88
+ code: "custom",
89
+ path: ["text"],
90
+ message: "Only text nodes can contain a text value"
91
+ });
92
+ }
93
+ }));
94
+ /**
95
+ * Canonical schema for a TipTap rich-text document root.
96
+ *
97
+ * Stricter than {@link TipTapNodeSchema}: `type` is literally `'doc'` and
98
+ * `content` is required (possibly empty). This is the shape every producer
99
+ * (dummy content, editor output, migrations) must emit, and the shape every
100
+ * non-legacy consumer can rely on.
101
+ *
102
+ */
103
+ const RichTextDocSchema = z.object({
104
+ type: z.literal("doc"),
105
+ content: z.array(TipTapNodeSchema)
106
+ }).strict();
107
+ /**
108
+ * Type guard for {@link RichTextDoc}.
109
+ *
110
+ * Returns `true` only for the canonical unwrapped form. The shared structural
111
+ * contract lives in `@riverbankcms/core`; parity tests keep its dependency-free
112
+ * guard aligned with this detailed Zod validator.
113
+ */
114
+ function isRichTextDoc(value) {
115
+ return RichTextDocSchema.safeParse(value).success;
116
+ }
117
+ /**
118
+ * Build a canonical {@link RichTextDoc} from plain-text paragraphs.
119
+ *
120
+ * Single source of truth for constructing a TipTap doc from strings.
121
+ * Used by:
122
+ * - `@riverbankcms/dummy-content` (fixture body generators)
123
+ * - `@riverbankcms/content-model` (default template `cta-full` content)
124
+ *
125
+ * Empty strings produce a paragraph with no `content` array — matching
126
+ * the TipTap convention for empty paragraphs. The shape is exactly what
127
+ * {@link RichTextDocSchema} validates, so the type and runtime check
128
+ * never drift from the constructor.
129
+ */
130
+ function createRichTextDoc(paragraphs) {
131
+ return {
132
+ type: "doc",
133
+ content: paragraphs.map((text) => text ? {
134
+ type: "paragraph",
135
+ content: [{
136
+ type: "text",
137
+ text
138
+ }]
139
+ } : { type: "paragraph" })
140
+ };
141
+ }
142
+ function createHardBreakNode() {
143
+ return { type: "hardBreak" };
144
+ }
145
+ /**
146
+ * Parse a small markdown subset into TipTap inline nodes.
147
+ *
148
+ * This intentionally supports only bold (`**text**`) because the SDK/content
149
+ * import paths use it for lightweight default content, not full markdown.
150
+ */
151
+ function parseRichTextInlineMarkdown(text) {
152
+ const out = [];
153
+ const pushText = (value, marks) => {
154
+ if (!value) return;
155
+ out.push(marks && marks.length > 0 ? {
156
+ type: "text",
157
+ text: value,
158
+ marks
159
+ } : {
160
+ type: "text",
161
+ text: value
162
+ });
163
+ };
164
+ let rest = text;
165
+ while (rest.length > 0) {
166
+ const start = rest.indexOf("**");
167
+ if (start === -1) {
168
+ pushText(rest);
169
+ break;
170
+ }
171
+ const end = rest.indexOf("**", start + 2);
172
+ if (end === -1) {
173
+ pushText(rest);
174
+ break;
175
+ }
176
+ pushText(rest.slice(0, start));
177
+ pushText(rest.slice(start + 2, end), [{ type: "bold" }]);
178
+ rest = rest.slice(end + 2);
179
+ }
180
+ return out;
181
+ }
182
+ function parseRichTextParagraphInlineMarkdown(text) {
183
+ return text.split("\n").flatMap((line, index) => {
184
+ const lineNodes = parseRichTextInlineMarkdown(line.trim());
185
+ return index === 0 ? lineNodes : [createHardBreakNode(), ...lineNodes];
186
+ });
187
+ }
188
+ /**
189
+ * Convert a plain string with a tiny markdown subset to canonical TipTap JSON.
190
+ *
191
+ * Double newlines create semantic paragraphs. Single newlines remain inside the
192
+ * current paragraph as TipTap `hardBreak` nodes for line-oriented content such
193
+ * as addresses, opening hours, and contact details.
194
+ */
195
+ function createRichTextDocFromMarkdown(markdown) {
196
+ const normalized = markdown.replace(/\r\n/g, "\n").trim();
197
+ if (!normalized) return {
198
+ type: "doc",
199
+ content: []
200
+ };
201
+ const paragraphs = normalized.split(/\n{2,}/).map((paragraph) => paragraph.trim()).filter(Boolean);
202
+ return {
203
+ type: "doc",
204
+ content: paragraphs.map((paragraph) => {
205
+ const inline = parseRichTextParagraphInlineMarkdown(paragraph);
206
+ return inline.length > 0 ? {
207
+ type: "paragraph",
208
+ content: inline
209
+ } : { type: "paragraph" };
210
+ })
211
+ };
212
+ }
213
+ function buildRichTextSchema(options) {
214
+ let schema = RichTextDocSchema;
215
+ if (options?.required) {
216
+ const label = options.label ?? "This field";
217
+ schema = schema.refine((val) => {
218
+ const content = val.content;
219
+ if (!Array.isArray(content) || content.length === 0) return false;
220
+ const first = content[0];
221
+ if (content.length === 1 && isRecord$3(first) && first.type === "paragraph" && (!("content" in first) || first.content == null)) {
222
+ return false;
223
+ }
224
+ return true;
225
+ }, { message: `${label} is required` });
226
+ }
227
+ return schema;
228
+ }
229
+
230
+ //#endregion
6
231
  //#region ../content-model/src/title-resolution.ts
7
232
  /**
8
233
  * Content title resolution utilities.
@@ -1540,6 +1765,10 @@ function parseContentTypeSchema(input) {
1540
1765
  function isManualEntryOrderingEnabled(schema) {
1541
1766
  return schema?.ordering?.mode === "manual";
1542
1767
  }
1768
+ /** Default list order shared by management lists and public entry loaders. */
1769
+ function resolveContentEntryOrder(schema, explicitOrder) {
1770
+ return explicitOrder ?? (isManualEntryOrderingEnabled(schema) ? "manual.asc" : undefined);
1771
+ }
1543
1772
  function normalizeContentFields(fields) {
1544
1773
  return fields.map((field) => {
1545
1774
  const schemaFields = field.schema?.fields ?? undefined;
@@ -1683,6 +1912,7 @@ function applyContentTypeDefaults(schema) {
1683
1912
  return {
1684
1913
  version: schema.version ?? 1,
1685
1914
  fields: cloneFieldCollection(schema.fields ?? []),
1915
+ ordering: schema.ordering ? { ...schema.ordering } : undefined,
1686
1916
  routing: schema.routing ? { ...schema.routing } : undefined,
1687
1917
  widgets: schema.widgets ? { ...schema.widgets } : undefined,
1688
1918
  labels: schema.labels ? { ...schema.labels } : {
@@ -1896,6 +2126,7 @@ const PRODUCT_CONTENT_TYPE_SCHEMA = {
1896
2126
  };
1897
2127
  const TESTIMONIAL_CONTENT_TYPE_SCHEMA = {
1898
2128
  version: 1,
2129
+ ordering: { mode: "manual" },
1899
2130
  fields: [
1900
2131
  {
1901
2132
  id: "name",
@@ -4107,13 +4338,14 @@ const FIELD_TYPE_CLASSIFICATION = {
4107
4338
  repeater: "whole",
4108
4339
  presetOrCustom: "opaque",
4109
4340
  contentTypeSelect: "opaque",
4110
- entryPicker: "opaque",
4341
+ entryPicker: "leaf",
4111
4342
  time: "opaque"
4112
4343
  };
4113
4344
  function classify(type) {
4114
4345
  return FIELD_TYPE_CLASSIFICATION[type] ?? "unknown";
4115
4346
  }
4116
4347
  function leafToBlockFieldType(type) {
4348
+ if (type === "entryPicker") return "reference";
4117
4349
  return classify(type) === "leaf" ? type : null;
4118
4350
  }
4119
4351
  /**
@@ -4125,10 +4357,11 @@ function collectLeaves(fields, prefix, out) {
4125
4357
  for (const field of fields) {
4126
4358
  const id = prefix ? `${prefix}.${field.id}` : field.id;
4127
4359
  const cls = classify(field.type);
4128
- if (cls === "leaf") {
4360
+ const leafType = leafToBlockFieldType(field.type);
4361
+ if (leafType) {
4129
4362
  out.push({
4130
4363
  id,
4131
- type: field.type,
4364
+ type: leafType,
4132
4365
  required: Boolean(field.required)
4133
4366
  });
4134
4367
  continue;
@@ -4149,7 +4382,6 @@ function collectLeaves(fields, prefix, out) {
4149
4382
  if (tab.fields) collectLeaves(tab.fields, id, out);
4150
4383
  }
4151
4384
  }
4152
- void leafToBlockFieldType;
4153
4385
  }
4154
4386
  }
4155
4387
  function buildManifestRepeaterShape(field) {
@@ -4581,4 +4813,4 @@ const eventUpsertInputSchema = z.object({
4581
4813
  }));
4582
4814
 
4583
4815
  //#endregion
4584
- export { findUnfilledSentinels as i, buildEntryScaffold as n, validateEntryInstance as r, eventUpsertInputSchema as t };
4816
+ export { RichTextDocSchema as a, isRecord$3 as c, findUnfilledSentinels as i, buildEntryScaffold as n, isRichTextDoc as o, validateEntryInstance as r, isObjectRecord as s, eventUpsertInputSchema as t };
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { t as loadConfigFile } from "./load-config-j58KSqxl.mjs";
3
+
4
+ export { loadConfigFile };
@@ -7,8 +7,8 @@ import { existsSync as existsSync$1 } from "fs";
7
7
  import { fileURLToPath } from "url";
8
8
 
9
9
  //#region package.json
10
- var version = "0.20.0";
11
- var peerDependencies = { "@riverbankcms/sdk": ">=0.139.0 <1.0.0" };
10
+ var version = "0.22.0";
11
+ var peerDependencies = { "@riverbankcms/sdk": ">=0.157.0 <1.0.0" };
12
12
  var package_default = {
13
13
  name: "riverbankcms",
14
14
  version,
@@ -83,7 +83,7 @@ var package_default = {
83
83
  //#endregion
84
84
  //#region src/version.ts
85
85
  /** The package manifest is the source-run fallback; builds inject this value. */
86
- const CLI_VERSION = typeof "0.20.0" === "string" ? "0.20.0" : version;
86
+ const CLI_VERSION = typeof "0.22.0" === "string" ? "0.22.0" : version;
87
87
  /**
88
88
  * The CLI's SDK peer range is bundled from the CLI manifest alongside its
89
89
  * version. Runtime config loading uses this value as the single compatibility
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Nn as err, Pn as ok, tn as parseSiteId } from "./src-Dixcv4PB.mjs";
2
+ import { Bn as ok, sn as parseSiteId, zn as err } from "./src-zzwJUef5.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";
@@ -1199,10 +1199,16 @@ function globalsCssTemplate(config) {
1199
1199
  @import "@riverbankcms/sdk/preview-next/tailwind-sources.css";
1200
1200
  @import "@riverbankcms/sdk/preview-next/chrome.css";
1201
1201
 
1202
+ ` : "";
1203
+ const portalBlock = config.withPortalAuth ? `/* Customer portal pages (layout, tabs, modals, tickets) use Riverbank's
1204
+ * packaged styles. The portal route helpers supply the site theme scope.
1205
+ */
1206
+ @import "@riverbankcms/sdk/styles.css";
1207
+
1202
1208
  ` : "";
1203
1209
  return {
1204
1210
  path: "src/app/globals.css",
1205
- content: `${previewBlock}/* Add host-owned styles below. */
1211
+ content: `${previewBlock}${portalBlock}/* Add host-owned styles below. */
1206
1212
  `
1207
1213
  };
1208
1214
  }
@@ -1249,6 +1255,9 @@ function nextConfigTemplate() {
1249
1255
  content: `import type { NextConfig } from "next";
1250
1256
 
1251
1257
  const nextConfig: NextConfig = {
1258
+ async headers() {
1259
+ return [{ source: "/redeem", headers: [{ key: "Referrer-Policy", value: "no-referrer" }] }];
1260
+ },
1252
1261
  // Transpile workspace packages (needed for linked packages / local development).
1253
1262
  transpilePackages: ${JSON.stringify(["@riverbankcms/sdk"])},
1254
1263
 
@@ -1311,6 +1320,30 @@ export default function NotFound() {
1311
1320
  };
1312
1321
  }
1313
1322
 
1323
+ //#endregion
1324
+ //#region src/scaffold/templates/portal-pages.ts
1325
+ function portalPageTemplate(path$1, helper, configPath, metadata = "") {
1326
+ return {
1327
+ path: `src/app/${path$1}/page.tsx`,
1328
+ content: `import { ${helper} } from '@riverbankcms/sdk/next';
1329
+ import { getRiverbankClient } from '@/lib/riverbank-client';
1330
+ import config from '${configPath}';
1331
+
1332
+ ${metadata}export const dynamic = 'force-dynamic';
1333
+ export default ${helper}({ getClient: getRiverbankClient, config });
1334
+ `
1335
+ };
1336
+ }
1337
+ function portalPageTemplates() {
1338
+ return [
1339
+ portalPageTemplate("redeem", "createRedeemPage", "../../../riverbank.config", "export const metadata = { title: 'Redeem a gift card', referrer: 'no-referrer', robots: { index: false, follow: false } };\n"),
1340
+ portalPageTemplate("portal", "createPortalPage", "../../../riverbank.config"),
1341
+ portalPageTemplate("portal/tickets/[registrationId]", "createPortalTicketPage", "../../../../../riverbank.config"),
1342
+ portalPageTemplate("portal/digital-products/[entitlementId]", "createPortalDigitalProductPage", "../../../../../riverbank.config"),
1343
+ portalPageTemplate("portal/digital-products/[entitlementId]/files/[assetId]", "createPortalDigitalProductFilePage", "../../../../../../../riverbank.config")
1344
+ ];
1345
+ }
1346
+
1314
1347
  //#endregion
1315
1348
  //#region src/scaffold/templates/portal-callback-route.ts
1316
1349
  function portalCallbackRouteTemplate() {
@@ -1326,6 +1359,7 @@ function portalCallbackRouteTemplate() {
1326
1359
  import { handlePortalCallback } from '@riverbankcms/sdk/portal';
1327
1360
 
1328
1361
  export const GET = handlePortalCallback();
1362
+ export const POST = handlePortalCallback();
1329
1363
  `
1330
1364
  };
1331
1365
  }
@@ -2050,7 +2084,11 @@ function starterContentTemplates(config) {
2050
2084
  //#region src/scaffold/templates/index.ts
2051
2085
  function generateAllTemplates(config) {
2052
2086
  const preview = config.withPreviewMode ? [previewKeepAliveRouteTemplate(), previewModeClientTemplate()] : [];
2053
- const portal = config.withPortalAuth ? [portalCallbackRouteTemplate(), portalLogoutRouteTemplate()] : [];
2087
+ const portal = config.withPortalAuth ? [
2088
+ portalCallbackRouteTemplate(),
2089
+ portalLogoutRouteTemplate(),
2090
+ ...portalPageTemplates()
2091
+ ] : [];
2054
2092
  return [
2055
2093
  gitignoreTemplate(),
2056
2094
  envConfigTemplate(config),
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "../src-Dixcv4PB.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-HAEMHc5T.mjs";
2
+ import "../src-zzwJUef5.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-C4IphkPc.mjs";
4
4
 
5
5
  export { deriveGeneratedPackageRanges, deriveScaffoldLocalManifest, deriveScaffoldPlan, findPackageManifestPath, loadGeneratedPackageRanges, preflightScaffoldPlan, preflightScaffoldTargets, prepareScaffoldExecution, writeScaffoldFiles };