@cmssy/remix 6.2.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,165 @@
1
+ 'use strict';
2
+
3
+ var core = require('@cmssy/core');
4
+
5
+ // src/loader.ts
6
+ function createCmssyLoader(config) {
7
+ return async function cmssyLoader({
8
+ request
9
+ }) {
10
+ const url = new URL(request.url);
11
+ const isEdit = await core.isVerifiedEditUrl(url, config);
12
+ const siteLocales = await core.resolveSiteLocales(config);
13
+ const segments = url.pathname.split("/").filter(Boolean);
14
+ const fromPath = core.splitLocaleFromPath(segments, siteLocales);
15
+ const locale = request.headers.get(core.CMSSY_LOCALE_HEADER) ?? fromPath.locale;
16
+ const previewSecret = isEdit ? config.draftSecret : void 0;
17
+ const [page, layouts] = await Promise.all([
18
+ core.fetchPage(config, fromPath.path, { previewSecret }),
19
+ core.fetchLayouts(config, fromPath.path, { previewSecret })
20
+ ]);
21
+ return {
22
+ page,
23
+ layouts,
24
+ locale,
25
+ defaultLocale: siteLocales.defaultLocale,
26
+ enabledLocales: siteLocales.locales,
27
+ isEdit,
28
+ editorOrigin: config.editorOrigin ?? "*"
29
+ };
30
+ };
31
+ }
32
+ function createCmssyHeaders(config) {
33
+ return function cmssyHeaders() {
34
+ return core.cmssyCspHeaders({ editorOrigin: config.editorOrigin });
35
+ };
36
+ }
37
+ function xmlEscape(value) {
38
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
39
+ }
40
+ function baseUrlFor(config, url, request) {
41
+ if (config.siteUrl) return config.siteUrl.replace(/\/+$/, "");
42
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
43
+ if (!host) return url.origin.replace(/\/+$/, "");
44
+ const protocol = request.headers.get("x-forwarded-proto") ?? (host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "https");
45
+ return `${protocol}://${host}`.replace(/\/+$/, "");
46
+ }
47
+ function createCmssySitemap(config, options = {}) {
48
+ return async function loader({
49
+ request
50
+ }) {
51
+ const baseUrl = baseUrlFor(config, new URL(request.url), request);
52
+ const { defaultLocale, locales } = await core.resolveSiteLocales(config);
53
+ const pages = await core.fetchPages(config);
54
+ const entries = [];
55
+ for (const page of pages) {
56
+ const slug = core.normalizeSlug(page.slug);
57
+ for (const locale of locales) {
58
+ entries.push({
59
+ url: `${baseUrl}${core.localizedPath(slug, locale, defaultLocale)}`,
60
+ lastModified: page.updatedAt ?? void 0
61
+ });
62
+ }
63
+ }
64
+ if (options.extra) {
65
+ entries.push(
66
+ ...await options.extra({
67
+ baseUrl,
68
+ defaultLocale,
69
+ locales
70
+ })
71
+ );
72
+ }
73
+ const body = [
74
+ '<?xml version="1.0" encoding="UTF-8"?>',
75
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
76
+ ...entries.map(
77
+ (entry) => [
78
+ " <url>",
79
+ ` <loc>${xmlEscape(entry.url)}</loc>`,
80
+ entry.lastModified ? ` <lastmod>${xmlEscape(entry.lastModified)}</lastmod>` : "",
81
+ " </url>"
82
+ ].filter(Boolean).join("\n")
83
+ ),
84
+ "</urlset>"
85
+ ].join("\n");
86
+ return new Response(body, {
87
+ headers: { "content-type": "application/xml" }
88
+ });
89
+ };
90
+ }
91
+ function createCmssyRobots(config) {
92
+ return async function loader({
93
+ request
94
+ }) {
95
+ const baseUrl = baseUrlFor(config, new URL(request.url), request);
96
+ const body = [
97
+ "User-agent: *",
98
+ "Allow: /",
99
+ `Sitemap: ${baseUrl}/sitemap.xml`,
100
+ ""
101
+ ].join("\n");
102
+ return new Response(body, { headers: { "content-type": "text/plain" } });
103
+ };
104
+ }
105
+
106
+ Object.defineProperty(exports, "CMSSY_LOCALE_HEADER", {
107
+ enumerable: true,
108
+ get: function () { return core.CMSSY_LOCALE_HEADER; }
109
+ });
110
+ Object.defineProperty(exports, "buildBlockContext", {
111
+ enumerable: true,
112
+ get: function () { return core.buildBlockContext; }
113
+ });
114
+ Object.defineProperty(exports, "createCmssyClient", {
115
+ enumerable: true,
116
+ get: function () { return core.createCmssyClient; }
117
+ });
118
+ Object.defineProperty(exports, "defineCmssyConfig", {
119
+ enumerable: true,
120
+ get: function () { return core.defineCmssyConfig; }
121
+ });
122
+ Object.defineProperty(exports, "fetchLayouts", {
123
+ enumerable: true,
124
+ get: function () { return core.fetchLayouts; }
125
+ });
126
+ Object.defineProperty(exports, "fetchPage", {
127
+ enumerable: true,
128
+ get: function () { return core.fetchPage; }
129
+ });
130
+ Object.defineProperty(exports, "fetchPageMeta", {
131
+ enumerable: true,
132
+ get: function () { return core.fetchPageMeta; }
133
+ });
134
+ Object.defineProperty(exports, "fetchPages", {
135
+ enumerable: true,
136
+ get: function () { return core.fetchPages; }
137
+ });
138
+ Object.defineProperty(exports, "fetchProduct", {
139
+ enumerable: true,
140
+ get: function () { return core.fetchProduct; }
141
+ });
142
+ Object.defineProperty(exports, "fetchProducts", {
143
+ enumerable: true,
144
+ get: function () { return core.fetchProducts; }
145
+ });
146
+ Object.defineProperty(exports, "isVerifiedEditUrl", {
147
+ enumerable: true,
148
+ get: function () { return core.isVerifiedEditUrl; }
149
+ });
150
+ Object.defineProperty(exports, "localizeHref", {
151
+ enumerable: true,
152
+ get: function () { return core.localizeHref; }
153
+ });
154
+ Object.defineProperty(exports, "localizedPath", {
155
+ enumerable: true,
156
+ get: function () { return core.localizedPath; }
157
+ });
158
+ Object.defineProperty(exports, "resolveSiteLocales", {
159
+ enumerable: true,
160
+ get: function () { return core.resolveSiteLocales; }
161
+ });
162
+ exports.createCmssyHeaders = createCmssyHeaders;
163
+ exports.createCmssyLoader = createCmssyLoader;
164
+ exports.createCmssyRobots = createCmssyRobots;
165
+ exports.createCmssySitemap = createCmssySitemap;
@@ -0,0 +1,59 @@
1
+ import { CmssyPageData, CmssyLayoutGroup, CmssyConfig } from '@cmssy/core';
2
+ export { CMSSY_LOCALE_HEADER, CmssyBlockContext, CmssyConfig, CmssyEnvConfig, CmssyLayoutGroup, CmssyPageData, buildBlockContext, createCmssyClient, defineCmssyConfig, fetchLayouts, fetchPage, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, isVerifiedEditUrl, localizeHref, localizedPath, resolveSiteLocales } from '@cmssy/core';
3
+
4
+ interface CmssyRouteData {
5
+ page: CmssyPageData | null;
6
+ layouts: CmssyLayoutGroup[];
7
+ locale: string;
8
+ defaultLocale: string;
9
+ enabledLocales: string[];
10
+ /** True for a VERIFIED editor request. The route renders the edit bridge. */
11
+ isEdit: boolean;
12
+ editorOrigin: string | string[];
13
+ }
14
+ /**
15
+ * The page loader.
16
+ *
17
+ * Note what is NOT here: the `/cmssy-edit` route the Next adapter needs. That
18
+ * route exists because a Next page can be STATIC, and a static page never sees
19
+ * the query string that would put it in edit mode. React Router renders on every
20
+ * request, so the editor can be served from the page itself - verified the same
21
+ * way, on the same protocol, with less machinery.
22
+ */
23
+ declare function createCmssyLoader(config: CmssyConfig): ({ request, }: {
24
+ request: Request;
25
+ }) => Promise<CmssyRouteData>;
26
+ /**
27
+ * The response headers a cmssy page needs: without them the admin cannot frame
28
+ * the site, and the editor shows an empty box with no error anywhere.
29
+ */
30
+ declare function createCmssyHeaders(config: CmssyConfig): () => Record<string, string>;
31
+
32
+ interface CmssySitemapEntry {
33
+ url: string;
34
+ lastModified?: string;
35
+ }
36
+ interface CmssySitemapOptions {
37
+ /**
38
+ * Records are not pages, so the sitemap cannot know about your products or
39
+ * categories. Add them here; you get the same base URL and locales the page
40
+ * entries use, so the two cannot disagree.
41
+ */
42
+ extra?: (context: {
43
+ baseUrl: string;
44
+ defaultLocale: string;
45
+ locales: string[];
46
+ }) => Promise<CmssySitemapEntry[]> | CmssySitemapEntry[];
47
+ }
48
+ /**
49
+ * One `<url>` per language, plus x-default - a translated page is not a
50
+ * duplicate, and telling Google it is keeps the translation out of the index.
51
+ */
52
+ declare function createCmssySitemap(config: CmssyConfig, options?: CmssySitemapOptions): ({ request, }: {
53
+ request: Request;
54
+ }) => Promise<Response>;
55
+ declare function createCmssyRobots(config: CmssyConfig): ({ request, }: {
56
+ request: Request;
57
+ }) => Promise<Response>;
58
+
59
+ export { type CmssyRouteData, type CmssySitemapEntry, type CmssySitemapOptions, createCmssyHeaders, createCmssyLoader, createCmssyRobots, createCmssySitemap };
@@ -0,0 +1,59 @@
1
+ import { CmssyPageData, CmssyLayoutGroup, CmssyConfig } from '@cmssy/core';
2
+ export { CMSSY_LOCALE_HEADER, CmssyBlockContext, CmssyConfig, CmssyEnvConfig, CmssyLayoutGroup, CmssyPageData, buildBlockContext, createCmssyClient, defineCmssyConfig, fetchLayouts, fetchPage, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, isVerifiedEditUrl, localizeHref, localizedPath, resolveSiteLocales } from '@cmssy/core';
3
+
4
+ interface CmssyRouteData {
5
+ page: CmssyPageData | null;
6
+ layouts: CmssyLayoutGroup[];
7
+ locale: string;
8
+ defaultLocale: string;
9
+ enabledLocales: string[];
10
+ /** True for a VERIFIED editor request. The route renders the edit bridge. */
11
+ isEdit: boolean;
12
+ editorOrigin: string | string[];
13
+ }
14
+ /**
15
+ * The page loader.
16
+ *
17
+ * Note what is NOT here: the `/cmssy-edit` route the Next adapter needs. That
18
+ * route exists because a Next page can be STATIC, and a static page never sees
19
+ * the query string that would put it in edit mode. React Router renders on every
20
+ * request, so the editor can be served from the page itself - verified the same
21
+ * way, on the same protocol, with less machinery.
22
+ */
23
+ declare function createCmssyLoader(config: CmssyConfig): ({ request, }: {
24
+ request: Request;
25
+ }) => Promise<CmssyRouteData>;
26
+ /**
27
+ * The response headers a cmssy page needs: without them the admin cannot frame
28
+ * the site, and the editor shows an empty box with no error anywhere.
29
+ */
30
+ declare function createCmssyHeaders(config: CmssyConfig): () => Record<string, string>;
31
+
32
+ interface CmssySitemapEntry {
33
+ url: string;
34
+ lastModified?: string;
35
+ }
36
+ interface CmssySitemapOptions {
37
+ /**
38
+ * Records are not pages, so the sitemap cannot know about your products or
39
+ * categories. Add them here; you get the same base URL and locales the page
40
+ * entries use, so the two cannot disagree.
41
+ */
42
+ extra?: (context: {
43
+ baseUrl: string;
44
+ defaultLocale: string;
45
+ locales: string[];
46
+ }) => Promise<CmssySitemapEntry[]> | CmssySitemapEntry[];
47
+ }
48
+ /**
49
+ * One `<url>` per language, plus x-default - a translated page is not a
50
+ * duplicate, and telling Google it is keeps the translation out of the index.
51
+ */
52
+ declare function createCmssySitemap(config: CmssyConfig, options?: CmssySitemapOptions): ({ request, }: {
53
+ request: Request;
54
+ }) => Promise<Response>;
55
+ declare function createCmssyRobots(config: CmssyConfig): ({ request, }: {
56
+ request: Request;
57
+ }) => Promise<Response>;
58
+
59
+ export { type CmssyRouteData, type CmssySitemapEntry, type CmssySitemapOptions, createCmssyHeaders, createCmssyLoader, createCmssyRobots, createCmssySitemap };
package/dist/index.js ADDED
@@ -0,0 +1,105 @@
1
+ import { isVerifiedEditUrl, resolveSiteLocales, splitLocaleFromPath, CMSSY_LOCALE_HEADER, fetchPage, fetchLayouts, cmssyCspHeaders, fetchPages, normalizeSlug, localizedPath } from '@cmssy/core';
2
+ export { CMSSY_LOCALE_HEADER, buildBlockContext, createCmssyClient, defineCmssyConfig, fetchLayouts, fetchPage, fetchPageMeta, fetchPages, fetchProduct, fetchProducts, isVerifiedEditUrl, localizeHref, localizedPath, resolveSiteLocales } from '@cmssy/core';
3
+
4
+ // src/loader.ts
5
+ function createCmssyLoader(config) {
6
+ return async function cmssyLoader({
7
+ request
8
+ }) {
9
+ const url = new URL(request.url);
10
+ const isEdit = await isVerifiedEditUrl(url, config);
11
+ const siteLocales = await resolveSiteLocales(config);
12
+ const segments = url.pathname.split("/").filter(Boolean);
13
+ const fromPath = splitLocaleFromPath(segments, siteLocales);
14
+ const locale = request.headers.get(CMSSY_LOCALE_HEADER) ?? fromPath.locale;
15
+ const previewSecret = isEdit ? config.draftSecret : void 0;
16
+ const [page, layouts] = await Promise.all([
17
+ fetchPage(config, fromPath.path, { previewSecret }),
18
+ fetchLayouts(config, fromPath.path, { previewSecret })
19
+ ]);
20
+ return {
21
+ page,
22
+ layouts,
23
+ locale,
24
+ defaultLocale: siteLocales.defaultLocale,
25
+ enabledLocales: siteLocales.locales,
26
+ isEdit,
27
+ editorOrigin: config.editorOrigin ?? "*"
28
+ };
29
+ };
30
+ }
31
+ function createCmssyHeaders(config) {
32
+ return function cmssyHeaders() {
33
+ return cmssyCspHeaders({ editorOrigin: config.editorOrigin });
34
+ };
35
+ }
36
+ function xmlEscape(value) {
37
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
38
+ }
39
+ function baseUrlFor(config, url, request) {
40
+ if (config.siteUrl) return config.siteUrl.replace(/\/+$/, "");
41
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
42
+ if (!host) return url.origin.replace(/\/+$/, "");
43
+ const protocol = request.headers.get("x-forwarded-proto") ?? (host.startsWith("localhost") || host.startsWith("127.0.0.1") ? "http" : "https");
44
+ return `${protocol}://${host}`.replace(/\/+$/, "");
45
+ }
46
+ function createCmssySitemap(config, options = {}) {
47
+ return async function loader({
48
+ request
49
+ }) {
50
+ const baseUrl = baseUrlFor(config, new URL(request.url), request);
51
+ const { defaultLocale, locales } = await resolveSiteLocales(config);
52
+ const pages = await fetchPages(config);
53
+ const entries = [];
54
+ for (const page of pages) {
55
+ const slug = normalizeSlug(page.slug);
56
+ for (const locale of locales) {
57
+ entries.push({
58
+ url: `${baseUrl}${localizedPath(slug, locale, defaultLocale)}`,
59
+ lastModified: page.updatedAt ?? void 0
60
+ });
61
+ }
62
+ }
63
+ if (options.extra) {
64
+ entries.push(
65
+ ...await options.extra({
66
+ baseUrl,
67
+ defaultLocale,
68
+ locales
69
+ })
70
+ );
71
+ }
72
+ const body = [
73
+ '<?xml version="1.0" encoding="UTF-8"?>',
74
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
75
+ ...entries.map(
76
+ (entry) => [
77
+ " <url>",
78
+ ` <loc>${xmlEscape(entry.url)}</loc>`,
79
+ entry.lastModified ? ` <lastmod>${xmlEscape(entry.lastModified)}</lastmod>` : "",
80
+ " </url>"
81
+ ].filter(Boolean).join("\n")
82
+ ),
83
+ "</urlset>"
84
+ ].join("\n");
85
+ return new Response(body, {
86
+ headers: { "content-type": "application/xml" }
87
+ });
88
+ };
89
+ }
90
+ function createCmssyRobots(config) {
91
+ return async function loader({
92
+ request
93
+ }) {
94
+ const baseUrl = baseUrlFor(config, new URL(request.url), request);
95
+ const body = [
96
+ "User-agent: *",
97
+ "Allow: /",
98
+ `Sitemap: ${baseUrl}/sitemap.xml`,
99
+ ""
100
+ ].join("\n");
101
+ return new Response(body, { headers: { "content-type": "text/plain" } });
102
+ };
103
+ }
104
+
105
+ export { createCmssyHeaders, createCmssyLoader, createCmssyRobots, createCmssySitemap };
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ var testing = require('@cmssy/core/testing');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "checkCmssyEditMode", {
8
+ enumerable: true,
9
+ get: function () { return testing.checkCmssyEditMode; }
10
+ });
@@ -0,0 +1 @@
1
+ export { EditSmokeOptions, EditSmokeResult, checkCmssyEditMode } from '@cmssy/core/testing';
@@ -0,0 +1 @@
1
+ export { EditSmokeOptions, EditSmokeResult, checkCmssyEditMode } from '@cmssy/core/testing';
@@ -0,0 +1 @@
1
+ export { checkCmssyEditMode } from '@cmssy/core/testing';
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@cmssy/remix",
3
+ "version": "6.2.0",
4
+ "description": "React Router 7 bindings for cmssy headless sites: page loader, framing CSP, sitemap and robots.",
5
+ "keywords": [
6
+ "cmssy",
7
+ "cms",
8
+ "headless",
9
+ "astro"
10
+ ],
11
+ "homepage": "https://cmssy.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/cmssy-io/cmssy-sdk.git",
15
+ "directory": "packages/astro"
16
+ },
17
+ "type": "module",
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ },
29
+ "./testing": {
30
+ "types": "./dist/testing.d.ts",
31
+ "import": "./dist/testing.js",
32
+ "require": "./dist/testing.cjs"
33
+ }
34
+ },
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "files": [
39
+ "dist"
40
+ ],
41
+ "peerDependencies": {
42
+ "react-router": ">=7"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.0.0",
46
+ "tsup": "^8.3.0",
47
+ "typescript": "^5.6.0",
48
+ "vitest": "^2.1.0"
49
+ },
50
+ "dependencies": {
51
+ "@cmssy/core": "6.2.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "test": "vitest run",
56
+ "typecheck": "tsc --noEmit"
57
+ }
58
+ }