@cmssy/next 0.5.3 → 0.5.5

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/index.cjs CHANGED
@@ -4,12 +4,108 @@ var headers = require('next/headers');
4
4
  var navigation = require('next/navigation');
5
5
  var react = require('@cmssy/react');
6
6
  var client = require('@cmssy/react/client');
7
+ var jose = require('jose');
7
8
  var jsxRuntime = require('react/jsx-runtime');
8
9
  var crypto$1 = require('crypto');
9
10
  var server = require('next/server');
10
- var jose = require('jose');
11
11
 
12
12
  // src/create-cmssy-page.tsx
13
+ var CMSSY_SESSION_COOKIE = "cmssy_session";
14
+ var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
15
+ var MIN_SESSION_SECRET_LENGTH = 32;
16
+ var ACCESS_EXPIRY_SKEW_MS = 3e4;
17
+ async function deriveSessionKey(secret) {
18
+ if (typeof secret !== "string" || secret.length < MIN_SESSION_SECRET_LENGTH) {
19
+ throw new Error(
20
+ `cmssy auth sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
21
+ );
22
+ }
23
+ const digest = await crypto.subtle.digest(
24
+ "SHA-256",
25
+ new TextEncoder().encode(secret)
26
+ );
27
+ return new Uint8Array(digest);
28
+ }
29
+ async function sealSession(payload, secret, audience) {
30
+ const key = await deriveSessionKey(secret);
31
+ const jwt = new jose.EncryptJWT({ ...payload }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`);
32
+ if (audience) jwt.setAudience(audience);
33
+ return jwt.encrypt(key);
34
+ }
35
+ async function openSession(token, secret, audience) {
36
+ const key = await deriveSessionKey(secret);
37
+ try {
38
+ const { payload } = await jose.jwtDecrypt(token, key, {
39
+ keyManagementAlgorithms: ["dir"],
40
+ contentEncryptionAlgorithms: ["A256GCM"],
41
+ ...audience ? { audience } : {}
42
+ });
43
+ const { accessToken, refreshToken, accessExpiresAt, user } = payload;
44
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || !Number.isFinite(accessExpiresAt) || !user || typeof user !== "object" || typeof user.recordId !== "string" || typeof user.email !== "string") {
45
+ return null;
46
+ }
47
+ return {
48
+ accessToken,
49
+ refreshToken,
50
+ accessExpiresAt,
51
+ user: {
52
+ recordId: user.recordId,
53
+ email: user.email
54
+ }
55
+ };
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ function isAccessExpired(payload, now = Date.now()) {
61
+ return payload.accessExpiresAt <= now + ACCESS_EXPIRY_SKEW_MS;
62
+ }
63
+ function sessionCookieOptions() {
64
+ return {
65
+ httpOnly: true,
66
+ secure: process.env.NODE_ENV !== "development",
67
+ sameSite: "lax",
68
+ path: "/",
69
+ maxAge: SESSION_MAX_AGE_SECONDS
70
+ };
71
+ }
72
+
73
+ // src/config.ts
74
+ function assertAuthConfig(config) {
75
+ const auth = config.auth;
76
+ if (!auth || typeof auth.modelSlug !== "string" || !auth.modelSlug) {
77
+ throw new Error("cmssy: config.auth.modelSlug is required for auth routes");
78
+ }
79
+ if (typeof auth.sessionSecret !== "string" || auth.sessionSecret.length < MIN_SESSION_SECRET_LENGTH) {
80
+ throw new Error(
81
+ `cmssy: config.auth.sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
82
+ );
83
+ }
84
+ return auth;
85
+ }
86
+
87
+ // src/auth-server.ts
88
+ async function readValidSession(config) {
89
+ const auth = assertAuthConfig(config);
90
+ const jar = await headers.cookies();
91
+ const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
92
+ if (!raw) return null;
93
+ const session = await openSession(
94
+ raw,
95
+ auth.sessionSecret,
96
+ config.workspaceSlug
97
+ );
98
+ if (!session || isAccessExpired(session)) return null;
99
+ return session;
100
+ }
101
+ async function getCmssyUser(config) {
102
+ const session = await readValidSession(config);
103
+ return session?.user ?? null;
104
+ }
105
+ async function getCmssyAccessToken(config) {
106
+ const session = await readValidSession(config);
107
+ return session?.accessToken ?? null;
108
+ }
13
109
 
14
110
  // src/csp.ts
15
111
  function toCspOrigin(origin) {
@@ -70,6 +166,7 @@ function createCmssyPage(config, blocks, options) {
70
166
  apiUrl: config.apiUrl,
71
167
  workspaceSlug: config.workspaceSlug
72
168
  };
169
+ const client$1 = react.createCmssyClient(clientConfig);
73
170
  return async function CmssyCatchAllPage({
74
171
  params,
75
172
  searchParams
@@ -131,6 +228,27 @@ function createCmssyPage(config, blocks, options) {
131
228
  }
132
229
  ) });
133
230
  }
231
+ let auth;
232
+ if (config.auth) {
233
+ try {
234
+ const user = await getCmssyUser(config);
235
+ auth = {
236
+ isAuthenticated: !!user,
237
+ member: user ? { recordId: user.recordId, email: user.email } : null
238
+ };
239
+ } catch {
240
+ auth = void 0;
241
+ }
242
+ }
243
+ let workspace;
244
+ try {
245
+ workspace = {
246
+ id: await client$1.resolveWorkspaceId(),
247
+ slug: config.workspaceSlug
248
+ };
249
+ } catch {
250
+ workspace = void 0;
251
+ }
134
252
  return /* @__PURE__ */ jsxRuntime.jsx(client.CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsxRuntime.jsx(
135
253
  react.CmssyServerPage,
136
254
  {
@@ -139,7 +257,9 @@ function createCmssyPage(config, blocks, options) {
139
257
  locale,
140
258
  defaultLocale,
141
259
  enabledLocales,
142
- forms
260
+ forms,
261
+ auth,
262
+ workspace
143
263
  }
144
264
  ) });
145
265
  };
@@ -288,14 +408,24 @@ function createCmssyRobots(config, options = {}) {
288
408
  };
289
409
  };
290
410
  }
411
+
412
+ // src/seo-paths.ts
413
+ function resolveSeoLocales(config, siteConfig) {
414
+ const defaultLocale = config.defaultLocale ?? siteConfig?.defaultLanguage ?? "en";
415
+ const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : siteConfig?.enabledLanguages && siteConfig.enabledLanguages.length > 0 ? siteConfig.enabledLanguages : [defaultLocale];
416
+ return { defaultLocale, locales };
417
+ }
291
418
  function normalizeSlug(slug) {
292
419
  if (slug === "/" || slug === "") return "/";
293
420
  return slug.startsWith("/") ? slug : `/${slug}`;
294
421
  }
295
422
  function localizedPath(slug, locale, defaultLocale) {
296
- const normalized = slug === "/" ? "" : slug;
297
- return locale === defaultLocale ? normalized || "/" : `/${locale}${normalized}`;
423
+ const normalized = normalizeSlug(slug);
424
+ const base = normalized === "/" ? "" : normalized;
425
+ return locale === defaultLocale ? base || "/" : `/${locale}${base}`;
298
426
  }
427
+
428
+ // src/create-cmssy-sitemap.ts
299
429
  function createCmssySitemap(config, options = {}) {
300
430
  const clientConfig = {
301
431
  apiUrl: config.apiUrl,
@@ -311,15 +441,15 @@ function createCmssySitemap(config, options = {}) {
311
441
  }
312
442
  pages = [];
313
443
  }
314
- let notFoundPageId = null;
444
+ let siteConfig = null;
315
445
  try {
316
- notFoundPageId = (await react.fetchSiteConfig(clientConfig))?.notFoundPageId ?? null;
446
+ siteConfig = await react.fetchSiteConfig(clientConfig);
317
447
  } catch {
318
- notFoundPageId = null;
448
+ siteConfig = null;
319
449
  }
450
+ const notFoundPageId = siteConfig?.notFoundPageId ?? null;
320
451
  const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
321
- const defaultLocale = config.defaultLocale ?? "en";
322
- const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : [defaultLocale];
452
+ const { defaultLocale, locales } = resolveSeoLocales(config, siteConfig);
323
453
  const excluded = new Set((options.excludeSlugs ?? []).map(normalizeSlug));
324
454
  const entries = pages.map((page) => ({ ...page, slug: normalizeSlug(page.slug) })).filter((page) => page.id !== notFoundPageId && !excluded.has(page.slug)).map((page) => {
325
455
  const lastModified = page.updatedAt ?? page.publishedAt ?? void 0;
@@ -342,6 +472,67 @@ function createCmssySitemap(config, options = {}) {
342
472
  return options.extra ? [...entries, ...options.extra] : entries;
343
473
  };
344
474
  }
475
+ function pick(value, locale, defaultLocale) {
476
+ if (!value) return "";
477
+ if (typeof value === "string") return value;
478
+ return value[locale] || value[defaultLocale] || Object.values(value)[0] || "";
479
+ }
480
+ async function buildCmssyMetadata(config, path, options = {}) {
481
+ const clientConfig = {
482
+ apiUrl: config.apiUrl,
483
+ workspaceSlug: config.workspaceSlug
484
+ };
485
+ const [meta, siteConfig, baseUrl] = await Promise.all([
486
+ react.fetchPageMeta(clientConfig, path).catch(() => null),
487
+ react.fetchSiteConfig(clientConfig).catch(() => null),
488
+ resolveSeoBaseUrl(config, options.baseUrl)
489
+ ]);
490
+ const { defaultLocale, locales: enabledLocales } = resolveSeoLocales(
491
+ config,
492
+ siteConfig
493
+ );
494
+ const locale = await config.resolveLocale?.() ?? defaultLocale;
495
+ const slug = react.normalizeSlug(path);
496
+ const siteName = pick(siteConfig?.siteName, locale, defaultLocale) || siteConfig?.branding?.brandName || void 0;
497
+ const title = pick(meta?.seoTitle, locale, defaultLocale) || pick(meta?.displayName, locale, defaultLocale) || siteName || "";
498
+ const description = pick(meta?.seoDescription, locale, defaultLocale);
499
+ const keywords = meta?.seoKeywords?.length ? meta.seoKeywords : void 0;
500
+ const image = options.image ?? siteConfig?.branding?.ogImageUrl ?? void 0;
501
+ const canonical = baseUrl ? `${baseUrl}${localizedPath(slug, locale, defaultLocale)}` : void 0;
502
+ const languages = baseUrl && enabledLocales.length > 1 ? Object.fromEntries(
503
+ enabledLocales.map((l) => [
504
+ l,
505
+ `${baseUrl}${localizedPath(slug, l, defaultLocale)}`
506
+ ])
507
+ ) : void 0;
508
+ return {
509
+ ...baseUrl ? { metadataBase: new URL(baseUrl) } : {},
510
+ ...title ? { title } : {},
511
+ ...description ? { description } : {},
512
+ ...keywords ? { keywords } : {},
513
+ ...canonical || languages ? {
514
+ alternates: {
515
+ ...canonical ? { canonical } : {},
516
+ ...languages ? { languages } : {}
517
+ }
518
+ } : {},
519
+ openGraph: {
520
+ ...title ? { title } : {},
521
+ ...description ? { description } : {},
522
+ ...canonical ? { url: canonical } : {},
523
+ ...siteName ? { siteName } : {},
524
+ type: options.ogType ?? "website",
525
+ locale,
526
+ ...image ? { images: [{ url: image }] } : {}
527
+ },
528
+ twitter: {
529
+ card: image ? options.twitterCard ?? "summary_large_image" : "summary",
530
+ ...title ? { title } : {},
531
+ ...description ? { description } : {},
532
+ ...image ? { images: [image] } : {}
533
+ }
534
+ };
535
+ }
345
536
  var MIN_SECRET_LENGTH = 16;
346
537
  function secretsMatch(a, b) {
347
538
  const ha = crypto$1.createHash("sha256").update(a).digest();
@@ -438,79 +629,6 @@ function createCmssyLocaleMiddleware(config) {
438
629
  return server.NextResponse.next({ request: { headers: headers3 } });
439
630
  };
440
631
  }
441
- var CMSSY_SESSION_COOKIE = "cmssy_session";
442
- var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
443
- var MIN_SESSION_SECRET_LENGTH = 32;
444
- var ACCESS_EXPIRY_SKEW_MS = 3e4;
445
- async function deriveSessionKey(secret) {
446
- if (typeof secret !== "string" || secret.length < MIN_SESSION_SECRET_LENGTH) {
447
- throw new Error(
448
- `cmssy auth sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
449
- );
450
- }
451
- const digest = await crypto.subtle.digest(
452
- "SHA-256",
453
- new TextEncoder().encode(secret)
454
- );
455
- return new Uint8Array(digest);
456
- }
457
- async function sealSession(payload, secret, audience) {
458
- const key = await deriveSessionKey(secret);
459
- const jwt = new jose.EncryptJWT({ ...payload }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`);
460
- if (audience) jwt.setAudience(audience);
461
- return jwt.encrypt(key);
462
- }
463
- async function openSession(token, secret, audience) {
464
- const key = await deriveSessionKey(secret);
465
- try {
466
- const { payload } = await jose.jwtDecrypt(token, key, {
467
- keyManagementAlgorithms: ["dir"],
468
- contentEncryptionAlgorithms: ["A256GCM"],
469
- ...audience ? { audience } : {}
470
- });
471
- const { accessToken, refreshToken, accessExpiresAt, user } = payload;
472
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || !Number.isFinite(accessExpiresAt) || !user || typeof user !== "object" || typeof user.recordId !== "string" || typeof user.email !== "string") {
473
- return null;
474
- }
475
- return {
476
- accessToken,
477
- refreshToken,
478
- accessExpiresAt,
479
- user: {
480
- recordId: user.recordId,
481
- email: user.email
482
- }
483
- };
484
- } catch {
485
- return null;
486
- }
487
- }
488
- function isAccessExpired(payload, now = Date.now()) {
489
- return payload.accessExpiresAt <= now + ACCESS_EXPIRY_SKEW_MS;
490
- }
491
- function sessionCookieOptions() {
492
- return {
493
- httpOnly: true,
494
- secure: process.env.NODE_ENV !== "development",
495
- sameSite: "lax",
496
- path: "/",
497
- maxAge: SESSION_MAX_AGE_SECONDS
498
- };
499
- }
500
-
501
- // src/config.ts
502
- function assertAuthConfig(config) {
503
- const auth = config.auth;
504
- if (!auth || typeof auth.modelSlug !== "string" || !auth.modelSlug) {
505
- throw new Error("cmssy: config.auth.modelSlug is required for auth routes");
506
- }
507
- if (typeof auth.sessionSecret !== "string" || auth.sessionSecret.length < MIN_SESSION_SECRET_LENGTH) {
508
- throw new Error(
509
- `cmssy: config.auth.sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
510
- );
511
- }
512
- return auth;
513
- }
514
632
  var LOGIN_MUTATION = `mutation SiteMemberLogin($input: SiteMemberLoginInput!) {
515
633
  siteMemberLogin(input: $input) {
516
634
  success message accessToken refreshToken accessTokenExpiresIn
@@ -1218,27 +1336,6 @@ function createCmssyCartRoute(config) {
1218
1336
  }
1219
1337
  };
1220
1338
  }
1221
- async function readValidSession(config) {
1222
- const auth = assertAuthConfig(config);
1223
- const jar = await headers.cookies();
1224
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
1225
- if (!raw) return null;
1226
- const session = await openSession(
1227
- raw,
1228
- auth.sessionSecret,
1229
- config.workspaceSlug
1230
- );
1231
- if (!session || isAccessExpired(session)) return null;
1232
- return session;
1233
- }
1234
- async function getCmssyUser(config) {
1235
- const session = await readValidSession(config);
1236
- return session?.user ?? null;
1237
- }
1238
- async function getCmssyAccessToken(config) {
1239
- const session = await readValidSession(config);
1240
- return session?.accessToken ?? null;
1241
- }
1242
1339
  function isPrefetch(request2) {
1243
1340
  return request2.headers.get("next-router-prefetch") !== null || request2.headers.get("purpose") === "prefetch" || (request2.headers.get("sec-purpose") ?? "").includes("prefetch");
1244
1341
  }
@@ -1518,6 +1615,7 @@ exports.CmssyWebhookError = CmssyWebhookError;
1518
1615
  exports.SESSION_MAX_AGE_SECONDS = SESSION_MAX_AGE_SECONDS;
1519
1616
  exports.applyCmssyCsp = applyCmssyCsp;
1520
1617
  exports.assertAuthConfig = assertAuthConfig;
1618
+ exports.buildCmssyMetadata = buildCmssyMetadata;
1521
1619
  exports.cmssyCspHeaders = cmssyCspHeaders;
1522
1620
  exports.createCmssyAuthMiddleware = createCmssyAuthMiddleware;
1523
1621
  exports.createCmssyAuthRoute = createCmssyAuthRoute;
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
  import { CmssyPageData, CmssyFormDefinition, BlockDefinition, CmssyClientConfig, CmssyProduct, CmssyOrder } from '@cmssy/react';
4
4
  import { EditBridgeConfig } from '@cmssy/react/client';
5
- import { MetadataRoute } from 'next';
5
+ import { MetadataRoute, Metadata } from 'next';
6
6
  import { NextRequest, NextResponse } from 'next/server';
7
7
 
8
8
  interface CmssyAuthConfig {
@@ -114,6 +114,27 @@ interface CreateCmssySitemapOptions extends SeoBaseUrlOption {
114
114
  */
115
115
  declare function createCmssySitemap(config: CmssyNextConfig, options?: CreateCmssySitemapOptions): () => Promise<MetadataRoute.Sitemap>;
116
116
 
117
+ interface BuildCmssyMetadataOptions extends SeoBaseUrlOption {
118
+ /** Override the Open Graph / Twitter image (defaults to workspace branding). */
119
+ image?: string;
120
+ /** Open Graph type. Defaults to "website". */
121
+ ogType?: string;
122
+ /** Twitter card. Defaults to "summary_large_image" when an image exists. */
123
+ twitterCard?: "summary" | "summary_large_image";
124
+ }
125
+ /**
126
+ * Builds complete Next.js `Metadata` for a cmssy page from its SEO fields and
127
+ * the workspace branding: title/description/keywords, canonical + per-locale
128
+ * `hreflang` alternates, and Open Graph / Twitter cards (with the branding OG
129
+ * image). Use in a route's `generateMetadata`:
130
+ *
131
+ * export const generateMetadata = ({ params }) =>
132
+ * buildCmssyMetadata(cmssy, (await params).path);
133
+ *
134
+ * `path` is the catch-all segments with the locale prefix already stripped.
135
+ */
136
+ declare function buildCmssyMetadata(config: CmssyNextConfig, path?: string | string[], options?: BuildCmssyMetadataOptions): Promise<Metadata>;
137
+
117
138
  type CmssyDraftRouteConfig = Pick<CmssyNextConfig, "draftSecret"> & {
118
139
  defaultRedirect?: string;
119
140
  };
@@ -307,4 +328,4 @@ declare class CmssyWebhookError extends Error {
307
328
  */
308
329
  declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
309
330
 
310
- export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySessionPayload, type CmssySessionUser, CmssyWebhookError, type CmssyWebhookEvent, type CmssyWebhookOrder, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, type FetchProductOptions, type FetchProductsOptions, type MyOrdersResult, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, type VerifyCmssyWebhookOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
331
+ export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySessionPayload, type CmssySessionUser, CmssyWebhookError, type CmssyWebhookEvent, type CmssyWebhookOrder, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, type FetchProductOptions, type FetchProductsOptions, type MyOrdersResult, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, type VerifyCmssyWebhookOptions, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
  import { CmssyPageData, CmssyFormDefinition, BlockDefinition, CmssyClientConfig, CmssyProduct, CmssyOrder } from '@cmssy/react';
4
4
  import { EditBridgeConfig } from '@cmssy/react/client';
5
- import { MetadataRoute } from 'next';
5
+ import { MetadataRoute, Metadata } from 'next';
6
6
  import { NextRequest, NextResponse } from 'next/server';
7
7
 
8
8
  interface CmssyAuthConfig {
@@ -114,6 +114,27 @@ interface CreateCmssySitemapOptions extends SeoBaseUrlOption {
114
114
  */
115
115
  declare function createCmssySitemap(config: CmssyNextConfig, options?: CreateCmssySitemapOptions): () => Promise<MetadataRoute.Sitemap>;
116
116
 
117
+ interface BuildCmssyMetadataOptions extends SeoBaseUrlOption {
118
+ /** Override the Open Graph / Twitter image (defaults to workspace branding). */
119
+ image?: string;
120
+ /** Open Graph type. Defaults to "website". */
121
+ ogType?: string;
122
+ /** Twitter card. Defaults to "summary_large_image" when an image exists. */
123
+ twitterCard?: "summary" | "summary_large_image";
124
+ }
125
+ /**
126
+ * Builds complete Next.js `Metadata` for a cmssy page from its SEO fields and
127
+ * the workspace branding: title/description/keywords, canonical + per-locale
128
+ * `hreflang` alternates, and Open Graph / Twitter cards (with the branding OG
129
+ * image). Use in a route's `generateMetadata`:
130
+ *
131
+ * export const generateMetadata = ({ params }) =>
132
+ * buildCmssyMetadata(cmssy, (await params).path);
133
+ *
134
+ * `path` is the catch-all segments with the locale prefix already stripped.
135
+ */
136
+ declare function buildCmssyMetadata(config: CmssyNextConfig, path?: string | string[], options?: BuildCmssyMetadataOptions): Promise<Metadata>;
137
+
117
138
  type CmssyDraftRouteConfig = Pick<CmssyNextConfig, "draftSecret"> & {
118
139
  defaultRedirect?: string;
119
140
  };
@@ -307,4 +328,4 @@ declare class CmssyWebhookError extends Error {
307
328
  */
308
329
  declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
309
330
 
310
- export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySessionPayload, type CmssySessionUser, CmssyWebhookError, type CmssyWebhookEvent, type CmssyWebhookOrder, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, type FetchProductOptions, type FetchProductsOptions, type MyOrdersResult, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, type VerifyCmssyWebhookOptions, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
331
+ export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthConfig, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyNextConfig, type CmssyOrdersRouteHandlers, type CmssySessionPayload, type CmssySessionUser, CmssyWebhookError, type CmssyWebhookEvent, type CmssyWebhookOrder, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, type FetchProductOptions, type FetchProductsOptions, type MyOrdersResult, SESSION_MAX_AGE_SECONDS, type SessionCookieOptions, type VerifyCmssyWebhookOptions, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
package/dist/index.js CHANGED
@@ -1,13 +1,109 @@
1
1
  import { draftMode, headers, cookies } from 'next/headers';
2
2
  import { notFound, redirect } from 'next/navigation';
3
- import { resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, CmssyServerPage, fetchSiteConfig, fetchPageById, fetchPages, resolveWorkspaceId, graphqlRequest } from '@cmssy/react';
3
+ import { createCmssyClient, resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, CmssyServerPage, fetchSiteConfig, fetchPageById, fetchPages, fetchPageMeta, normalizeSlug as normalizeSlug$1, resolveWorkspaceId, graphqlRequest } from '@cmssy/react';
4
4
  import { CmssyLocaleProvider } from '@cmssy/react/client';
5
+ import { EncryptJWT, jwtDecrypt } from 'jose';
5
6
  import { jsx, jsxs } from 'react/jsx-runtime';
6
7
  import { createHmac, createHash, timingSafeEqual } from 'crypto';
7
8
  import { NextResponse } from 'next/server';
8
- import { EncryptJWT, jwtDecrypt } from 'jose';
9
9
 
10
10
  // src/create-cmssy-page.tsx
11
+ var CMSSY_SESSION_COOKIE = "cmssy_session";
12
+ var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
13
+ var MIN_SESSION_SECRET_LENGTH = 32;
14
+ var ACCESS_EXPIRY_SKEW_MS = 3e4;
15
+ async function deriveSessionKey(secret) {
16
+ if (typeof secret !== "string" || secret.length < MIN_SESSION_SECRET_LENGTH) {
17
+ throw new Error(
18
+ `cmssy auth sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
19
+ );
20
+ }
21
+ const digest = await crypto.subtle.digest(
22
+ "SHA-256",
23
+ new TextEncoder().encode(secret)
24
+ );
25
+ return new Uint8Array(digest);
26
+ }
27
+ async function sealSession(payload, secret, audience) {
28
+ const key = await deriveSessionKey(secret);
29
+ const jwt = new EncryptJWT({ ...payload }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`);
30
+ if (audience) jwt.setAudience(audience);
31
+ return jwt.encrypt(key);
32
+ }
33
+ async function openSession(token, secret, audience) {
34
+ const key = await deriveSessionKey(secret);
35
+ try {
36
+ const { payload } = await jwtDecrypt(token, key, {
37
+ keyManagementAlgorithms: ["dir"],
38
+ contentEncryptionAlgorithms: ["A256GCM"],
39
+ ...audience ? { audience } : {}
40
+ });
41
+ const { accessToken, refreshToken, accessExpiresAt, user } = payload;
42
+ if (typeof accessToken !== "string" || typeof refreshToken !== "string" || !Number.isFinite(accessExpiresAt) || !user || typeof user !== "object" || typeof user.recordId !== "string" || typeof user.email !== "string") {
43
+ return null;
44
+ }
45
+ return {
46
+ accessToken,
47
+ refreshToken,
48
+ accessExpiresAt,
49
+ user: {
50
+ recordId: user.recordId,
51
+ email: user.email
52
+ }
53
+ };
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ function isAccessExpired(payload, now = Date.now()) {
59
+ return payload.accessExpiresAt <= now + ACCESS_EXPIRY_SKEW_MS;
60
+ }
61
+ function sessionCookieOptions() {
62
+ return {
63
+ httpOnly: true,
64
+ secure: process.env.NODE_ENV !== "development",
65
+ sameSite: "lax",
66
+ path: "/",
67
+ maxAge: SESSION_MAX_AGE_SECONDS
68
+ };
69
+ }
70
+
71
+ // src/config.ts
72
+ function assertAuthConfig(config) {
73
+ const auth = config.auth;
74
+ if (!auth || typeof auth.modelSlug !== "string" || !auth.modelSlug) {
75
+ throw new Error("cmssy: config.auth.modelSlug is required for auth routes");
76
+ }
77
+ if (typeof auth.sessionSecret !== "string" || auth.sessionSecret.length < MIN_SESSION_SECRET_LENGTH) {
78
+ throw new Error(
79
+ `cmssy: config.auth.sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
80
+ );
81
+ }
82
+ return auth;
83
+ }
84
+
85
+ // src/auth-server.ts
86
+ async function readValidSession(config) {
87
+ const auth = assertAuthConfig(config);
88
+ const jar = await cookies();
89
+ const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
90
+ if (!raw) return null;
91
+ const session = await openSession(
92
+ raw,
93
+ auth.sessionSecret,
94
+ config.workspaceSlug
95
+ );
96
+ if (!session || isAccessExpired(session)) return null;
97
+ return session;
98
+ }
99
+ async function getCmssyUser(config) {
100
+ const session = await readValidSession(config);
101
+ return session?.user ?? null;
102
+ }
103
+ async function getCmssyAccessToken(config) {
104
+ const session = await readValidSession(config);
105
+ return session?.accessToken ?? null;
106
+ }
11
107
 
12
108
  // src/csp.ts
13
109
  function toCspOrigin(origin) {
@@ -68,6 +164,7 @@ function createCmssyPage(config, blocks, options) {
68
164
  apiUrl: config.apiUrl,
69
165
  workspaceSlug: config.workspaceSlug
70
166
  };
167
+ const client = createCmssyClient(clientConfig);
71
168
  return async function CmssyCatchAllPage({
72
169
  params,
73
170
  searchParams
@@ -129,6 +226,27 @@ function createCmssyPage(config, blocks, options) {
129
226
  }
130
227
  ) });
131
228
  }
229
+ let auth;
230
+ if (config.auth) {
231
+ try {
232
+ const user = await getCmssyUser(config);
233
+ auth = {
234
+ isAuthenticated: !!user,
235
+ member: user ? { recordId: user.recordId, email: user.email } : null
236
+ };
237
+ } catch {
238
+ auth = void 0;
239
+ }
240
+ }
241
+ let workspace;
242
+ try {
243
+ workspace = {
244
+ id: await client.resolveWorkspaceId(),
245
+ slug: config.workspaceSlug
246
+ };
247
+ } catch {
248
+ workspace = void 0;
249
+ }
132
250
  return /* @__PURE__ */ jsx(CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsx(
133
251
  CmssyServerPage,
134
252
  {
@@ -137,7 +255,9 @@ function createCmssyPage(config, blocks, options) {
137
255
  locale,
138
256
  defaultLocale,
139
257
  enabledLocales,
140
- forms
258
+ forms,
259
+ auth,
260
+ workspace
141
261
  }
142
262
  ) });
143
263
  };
@@ -286,14 +406,24 @@ function createCmssyRobots(config, options = {}) {
286
406
  };
287
407
  };
288
408
  }
409
+
410
+ // src/seo-paths.ts
411
+ function resolveSeoLocales(config, siteConfig) {
412
+ const defaultLocale = config.defaultLocale ?? siteConfig?.defaultLanguage ?? "en";
413
+ const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : siteConfig?.enabledLanguages && siteConfig.enabledLanguages.length > 0 ? siteConfig.enabledLanguages : [defaultLocale];
414
+ return { defaultLocale, locales };
415
+ }
289
416
  function normalizeSlug(slug) {
290
417
  if (slug === "/" || slug === "") return "/";
291
418
  return slug.startsWith("/") ? slug : `/${slug}`;
292
419
  }
293
420
  function localizedPath(slug, locale, defaultLocale) {
294
- const normalized = slug === "/" ? "" : slug;
295
- return locale === defaultLocale ? normalized || "/" : `/${locale}${normalized}`;
421
+ const normalized = normalizeSlug(slug);
422
+ const base = normalized === "/" ? "" : normalized;
423
+ return locale === defaultLocale ? base || "/" : `/${locale}${base}`;
296
424
  }
425
+
426
+ // src/create-cmssy-sitemap.ts
297
427
  function createCmssySitemap(config, options = {}) {
298
428
  const clientConfig = {
299
429
  apiUrl: config.apiUrl,
@@ -309,15 +439,15 @@ function createCmssySitemap(config, options = {}) {
309
439
  }
310
440
  pages = [];
311
441
  }
312
- let notFoundPageId = null;
442
+ let siteConfig = null;
313
443
  try {
314
- notFoundPageId = (await fetchSiteConfig(clientConfig))?.notFoundPageId ?? null;
444
+ siteConfig = await fetchSiteConfig(clientConfig);
315
445
  } catch {
316
- notFoundPageId = null;
446
+ siteConfig = null;
317
447
  }
448
+ const notFoundPageId = siteConfig?.notFoundPageId ?? null;
318
449
  const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
319
- const defaultLocale = config.defaultLocale ?? "en";
320
- const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : [defaultLocale];
450
+ const { defaultLocale, locales } = resolveSeoLocales(config, siteConfig);
321
451
  const excluded = new Set((options.excludeSlugs ?? []).map(normalizeSlug));
322
452
  const entries = pages.map((page) => ({ ...page, slug: normalizeSlug(page.slug) })).filter((page) => page.id !== notFoundPageId && !excluded.has(page.slug)).map((page) => {
323
453
  const lastModified = page.updatedAt ?? page.publishedAt ?? void 0;
@@ -340,6 +470,67 @@ function createCmssySitemap(config, options = {}) {
340
470
  return options.extra ? [...entries, ...options.extra] : entries;
341
471
  };
342
472
  }
473
+ function pick(value, locale, defaultLocale) {
474
+ if (!value) return "";
475
+ if (typeof value === "string") return value;
476
+ return value[locale] || value[defaultLocale] || Object.values(value)[0] || "";
477
+ }
478
+ async function buildCmssyMetadata(config, path, options = {}) {
479
+ const clientConfig = {
480
+ apiUrl: config.apiUrl,
481
+ workspaceSlug: config.workspaceSlug
482
+ };
483
+ const [meta, siteConfig, baseUrl] = await Promise.all([
484
+ fetchPageMeta(clientConfig, path).catch(() => null),
485
+ fetchSiteConfig(clientConfig).catch(() => null),
486
+ resolveSeoBaseUrl(config, options.baseUrl)
487
+ ]);
488
+ const { defaultLocale, locales: enabledLocales } = resolveSeoLocales(
489
+ config,
490
+ siteConfig
491
+ );
492
+ const locale = await config.resolveLocale?.() ?? defaultLocale;
493
+ const slug = normalizeSlug$1(path);
494
+ const siteName = pick(siteConfig?.siteName, locale, defaultLocale) || siteConfig?.branding?.brandName || void 0;
495
+ const title = pick(meta?.seoTitle, locale, defaultLocale) || pick(meta?.displayName, locale, defaultLocale) || siteName || "";
496
+ const description = pick(meta?.seoDescription, locale, defaultLocale);
497
+ const keywords = meta?.seoKeywords?.length ? meta.seoKeywords : void 0;
498
+ const image = options.image ?? siteConfig?.branding?.ogImageUrl ?? void 0;
499
+ const canonical = baseUrl ? `${baseUrl}${localizedPath(slug, locale, defaultLocale)}` : void 0;
500
+ const languages = baseUrl && enabledLocales.length > 1 ? Object.fromEntries(
501
+ enabledLocales.map((l) => [
502
+ l,
503
+ `${baseUrl}${localizedPath(slug, l, defaultLocale)}`
504
+ ])
505
+ ) : void 0;
506
+ return {
507
+ ...baseUrl ? { metadataBase: new URL(baseUrl) } : {},
508
+ ...title ? { title } : {},
509
+ ...description ? { description } : {},
510
+ ...keywords ? { keywords } : {},
511
+ ...canonical || languages ? {
512
+ alternates: {
513
+ ...canonical ? { canonical } : {},
514
+ ...languages ? { languages } : {}
515
+ }
516
+ } : {},
517
+ openGraph: {
518
+ ...title ? { title } : {},
519
+ ...description ? { description } : {},
520
+ ...canonical ? { url: canonical } : {},
521
+ ...siteName ? { siteName } : {},
522
+ type: options.ogType ?? "website",
523
+ locale,
524
+ ...image ? { images: [{ url: image }] } : {}
525
+ },
526
+ twitter: {
527
+ card: image ? options.twitterCard ?? "summary_large_image" : "summary",
528
+ ...title ? { title } : {},
529
+ ...description ? { description } : {},
530
+ ...image ? { images: [image] } : {}
531
+ }
532
+ };
533
+ }
343
534
  var MIN_SECRET_LENGTH = 16;
344
535
  function secretsMatch(a, b) {
345
536
  const ha = createHash("sha256").update(a).digest();
@@ -436,79 +627,6 @@ function createCmssyLocaleMiddleware(config) {
436
627
  return NextResponse.next({ request: { headers: headers3 } });
437
628
  };
438
629
  }
439
- var CMSSY_SESSION_COOKIE = "cmssy_session";
440
- var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
441
- var MIN_SESSION_SECRET_LENGTH = 32;
442
- var ACCESS_EXPIRY_SKEW_MS = 3e4;
443
- async function deriveSessionKey(secret) {
444
- if (typeof secret !== "string" || secret.length < MIN_SESSION_SECRET_LENGTH) {
445
- throw new Error(
446
- `cmssy auth sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
447
- );
448
- }
449
- const digest = await crypto.subtle.digest(
450
- "SHA-256",
451
- new TextEncoder().encode(secret)
452
- );
453
- return new Uint8Array(digest);
454
- }
455
- async function sealSession(payload, secret, audience) {
456
- const key = await deriveSessionKey(secret);
457
- const jwt = new EncryptJWT({ ...payload }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`);
458
- if (audience) jwt.setAudience(audience);
459
- return jwt.encrypt(key);
460
- }
461
- async function openSession(token, secret, audience) {
462
- const key = await deriveSessionKey(secret);
463
- try {
464
- const { payload } = await jwtDecrypt(token, key, {
465
- keyManagementAlgorithms: ["dir"],
466
- contentEncryptionAlgorithms: ["A256GCM"],
467
- ...audience ? { audience } : {}
468
- });
469
- const { accessToken, refreshToken, accessExpiresAt, user } = payload;
470
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || !Number.isFinite(accessExpiresAt) || !user || typeof user !== "object" || typeof user.recordId !== "string" || typeof user.email !== "string") {
471
- return null;
472
- }
473
- return {
474
- accessToken,
475
- refreshToken,
476
- accessExpiresAt,
477
- user: {
478
- recordId: user.recordId,
479
- email: user.email
480
- }
481
- };
482
- } catch {
483
- return null;
484
- }
485
- }
486
- function isAccessExpired(payload, now = Date.now()) {
487
- return payload.accessExpiresAt <= now + ACCESS_EXPIRY_SKEW_MS;
488
- }
489
- function sessionCookieOptions() {
490
- return {
491
- httpOnly: true,
492
- secure: process.env.NODE_ENV !== "development",
493
- sameSite: "lax",
494
- path: "/",
495
- maxAge: SESSION_MAX_AGE_SECONDS
496
- };
497
- }
498
-
499
- // src/config.ts
500
- function assertAuthConfig(config) {
501
- const auth = config.auth;
502
- if (!auth || typeof auth.modelSlug !== "string" || !auth.modelSlug) {
503
- throw new Error("cmssy: config.auth.modelSlug is required for auth routes");
504
- }
505
- if (typeof auth.sessionSecret !== "string" || auth.sessionSecret.length < MIN_SESSION_SECRET_LENGTH) {
506
- throw new Error(
507
- `cmssy: config.auth.sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
508
- );
509
- }
510
- return auth;
511
- }
512
630
  var LOGIN_MUTATION = `mutation SiteMemberLogin($input: SiteMemberLoginInput!) {
513
631
  siteMemberLogin(input: $input) {
514
632
  success message accessToken refreshToken accessTokenExpiresIn
@@ -1216,27 +1334,6 @@ function createCmssyCartRoute(config) {
1216
1334
  }
1217
1335
  };
1218
1336
  }
1219
- async function readValidSession(config) {
1220
- const auth = assertAuthConfig(config);
1221
- const jar = await cookies();
1222
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
1223
- if (!raw) return null;
1224
- const session = await openSession(
1225
- raw,
1226
- auth.sessionSecret,
1227
- config.workspaceSlug
1228
- );
1229
- if (!session || isAccessExpired(session)) return null;
1230
- return session;
1231
- }
1232
- async function getCmssyUser(config) {
1233
- const session = await readValidSession(config);
1234
- return session?.user ?? null;
1235
- }
1236
- async function getCmssyAccessToken(config) {
1237
- const session = await readValidSession(config);
1238
- return session?.accessToken ?? null;
1239
- }
1240
1337
  function isPrefetch(request2) {
1241
1338
  return request2.headers.get("next-router-prefetch") !== null || request2.headers.get("purpose") === "prefetch" || (request2.headers.get("sec-purpose") ?? "").includes("prefetch");
1242
1339
  }
@@ -1508,4 +1605,4 @@ function verifyCmssyWebhook(options) {
1508
1605
  return parsed;
1509
1606
  }
1510
1607
 
1511
- export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, CmssyWebhookError, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
1608
+ export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, CmssyWebhookError, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/next",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "Next.js App Router bindings for cmssy headless sites (createCmssyPage + draft preview)",
5
5
  "keywords": [
6
6
  "cmssy",
@@ -41,7 +41,7 @@
41
41
  "dist"
42
42
  ],
43
43
  "peerDependencies": {
44
- "@cmssy/react": "^0.5.3",
44
+ "@cmssy/react": "^0.5.5",
45
45
  "next": ">=15",
46
46
  "react": "^18.2.0 || ^19.0.0",
47
47
  "react-dom": "^18.2.0 || ^19.0.0"
@@ -54,7 +54,7 @@
54
54
  "tsup": "^8.3.0",
55
55
  "typescript": "^5.6.0",
56
56
  "vitest": "^2.1.0",
57
- "@cmssy/react": "0.5.3"
57
+ "@cmssy/react": "0.5.5"
58
58
  },
59
59
  "dependencies": {
60
60
  "jose": "^6.2.3"