@cmssy/next 4.2.0 → 4.4.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 +5 -2
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -2
- package/dist/testing.cjs +67 -0
- package/dist/testing.d.cts +39 -0
- package/dist/testing.d.ts +39 -0
- package/dist/testing.js +65 -0
- package/package.json +8 -3
package/dist/index.cjs
CHANGED
|
@@ -399,7 +399,7 @@ function resolveBridgeOrigin(editorOrigin) {
|
|
|
399
399
|
return origins.length === 1 ? origins[0] : origins;
|
|
400
400
|
}
|
|
401
401
|
var CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
402
|
-
async function cmssyEditRewrite(request2, config) {
|
|
402
|
+
async function cmssyEditRewrite(request2, config, options = {}) {
|
|
403
403
|
const { pathname, searchParams } = request2.nextUrl;
|
|
404
404
|
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
405
405
|
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
@@ -408,7 +408,10 @@ async function cmssyEditRewrite(request2, config) {
|
|
|
408
408
|
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
409
409
|
const url = request2.nextUrl.clone();
|
|
410
410
|
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
411
|
-
return server.NextResponse.rewrite(
|
|
411
|
+
return server.NextResponse.rewrite(
|
|
412
|
+
url,
|
|
413
|
+
options.requestHeaders ? { request: { headers: options.requestHeaders } } : void 0
|
|
414
|
+
);
|
|
412
415
|
}
|
|
413
416
|
function createCmssyEditMiddleware(config) {
|
|
414
417
|
return async function cmssyEditMiddleware(request2) {
|
package/dist/index.d.cts
CHANGED
|
@@ -128,6 +128,14 @@ declare const CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
|
128
128
|
*/
|
|
129
129
|
declare function cmssyEditRewrite(request: NextRequest, config: {
|
|
130
130
|
draftSecret: string;
|
|
131
|
+
}, options?: {
|
|
132
|
+
/**
|
|
133
|
+
* Headers to forward to the edit route, for a site whose middleware tells
|
|
134
|
+
* the app something the path alone does not - a resolved locale, say.
|
|
135
|
+
* Without them the editor preview renders in the default language while the
|
|
136
|
+
* public page renders in the visitor's.
|
|
137
|
+
*/
|
|
138
|
+
requestHeaders?: Headers;
|
|
131
139
|
}): Promise<NextResponse | null>;
|
|
132
140
|
/** Standalone middleware when the consumer has no other middleware to compose. */
|
|
133
141
|
declare function createCmssyEditMiddleware(config: {
|
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,14 @@ declare const CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
|
128
128
|
*/
|
|
129
129
|
declare function cmssyEditRewrite(request: NextRequest, config: {
|
|
130
130
|
draftSecret: string;
|
|
131
|
+
}, options?: {
|
|
132
|
+
/**
|
|
133
|
+
* Headers to forward to the edit route, for a site whose middleware tells
|
|
134
|
+
* the app something the path alone does not - a resolved locale, say.
|
|
135
|
+
* Without them the editor preview renders in the default language while the
|
|
136
|
+
* public page renders in the visitor's.
|
|
137
|
+
*/
|
|
138
|
+
requestHeaders?: Headers;
|
|
131
139
|
}): Promise<NextResponse | null>;
|
|
132
140
|
/** Standalone middleware when the consumer has no other middleware to compose. */
|
|
133
141
|
declare function createCmssyEditMiddleware(config: {
|
package/dist/index.js
CHANGED
|
@@ -398,7 +398,7 @@ function resolveBridgeOrigin(editorOrigin) {
|
|
|
398
398
|
return origins.length === 1 ? origins[0] : origins;
|
|
399
399
|
}
|
|
400
400
|
var CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
|
|
401
|
-
async function cmssyEditRewrite(request2, config) {
|
|
401
|
+
async function cmssyEditRewrite(request2, config, options = {}) {
|
|
402
402
|
const { pathname, searchParams } = request2.nextUrl;
|
|
403
403
|
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
404
404
|
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
@@ -407,7 +407,10 @@ async function cmssyEditRewrite(request2, config) {
|
|
|
407
407
|
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
408
408
|
const url = request2.nextUrl.clone();
|
|
409
409
|
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
410
|
-
return NextResponse.rewrite(
|
|
410
|
+
return NextResponse.rewrite(
|
|
411
|
+
url,
|
|
412
|
+
options.requestHeaders ? { request: { headers: options.requestHeaders } } : void 0
|
|
413
|
+
);
|
|
411
414
|
}
|
|
412
415
|
function createCmssyEditMiddleware(config) {
|
|
413
416
|
return async function cmssyEditMiddleware(request2) {
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/testing/edit-smoke.ts
|
|
4
|
+
var EDITOR_MARKER = /CmssyEditor|cmssy-edit/;
|
|
5
|
+
var SERVER_CHROME = /<header|<footer/;
|
|
6
|
+
async function html(url) {
|
|
7
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
8
|
+
return { status: response.status, body: await response.text() };
|
|
9
|
+
}
|
|
10
|
+
async function checkCmssyEditMode(options) {
|
|
11
|
+
const { baseUrl, secret, path = "/" } = options;
|
|
12
|
+
const failures = [];
|
|
13
|
+
const url = (suffix) => `${baseUrl.replace(/\/+$/, "")}${suffix}`;
|
|
14
|
+
const publicPage = await html(url(path));
|
|
15
|
+
if (publicPage.status !== 200) {
|
|
16
|
+
failures.push(`public ${path}: expected 200, got ${publicPage.status}`);
|
|
17
|
+
}
|
|
18
|
+
if (EDITOR_MARKER.test(publicPage.body)) {
|
|
19
|
+
failures.push(`public ${path}: the editor is mounted on a public page`);
|
|
20
|
+
}
|
|
21
|
+
if (!SERVER_CHROME.test(publicPage.body)) {
|
|
22
|
+
failures.push(
|
|
23
|
+
`public ${path}: no server-rendered chrome - the layout blocks are missing`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const unverified = await html(url(`${path}?cmssyEdit=1`));
|
|
27
|
+
if (EDITOR_MARKER.test(unverified.body)) {
|
|
28
|
+
failures.push(
|
|
29
|
+
`${path}?cmssyEdit=1: edit mode without a secret - an unverified request must not open the editor (CMS-948)`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const verified = await html(
|
|
33
|
+
url(`${path}?cmssyEdit=1&cmssySecret=${encodeURIComponent(secret)}`)
|
|
34
|
+
);
|
|
35
|
+
if (verified.status !== 200) {
|
|
36
|
+
failures.push(`edit ${path}: expected 200, got ${verified.status}`);
|
|
37
|
+
}
|
|
38
|
+
if (!EDITOR_MARKER.test(verified.body)) {
|
|
39
|
+
failures.push(
|
|
40
|
+
`edit ${path}: no editor in the response - is the /cmssy-edit route mounted?`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (SERVER_CHROME.test(verified.body)) {
|
|
44
|
+
failures.push(
|
|
45
|
+
`edit ${path}: the chrome is still server-rendered - the header and footer will be selectable but have no fields (is CMSSY_EDIT_HEADER set on the rewrite?)`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const { localizedPath, localizedMarker } = options;
|
|
49
|
+
if (localizedPath && localizedMarker) {
|
|
50
|
+
const localized = await html(
|
|
51
|
+
url(
|
|
52
|
+
`${localizedPath}?cmssyEdit=1&cmssySecret=${encodeURIComponent(secret)}`
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
if (!EDITOR_MARKER.test(localized.body)) {
|
|
56
|
+
failures.push(`edit ${localizedPath}: no editor in the response`);
|
|
57
|
+
}
|
|
58
|
+
if (!localized.body.includes(localizedMarker)) {
|
|
59
|
+
failures.push(
|
|
60
|
+
`edit ${localizedPath}: "${localizedMarker}" is missing - the preview renders in the default language, not the one the URL asks for`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { ok: failures.length === 0, failures };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
exports.checkCmssyEditMode = checkCmssyEditMode;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface EditSmokeOptions {
|
|
2
|
+
/** A running build of the consumer app, e.g. http://localhost:3000. */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** The site's CMSSY_DRAFT_SECRET. Without it nothing can be verified. */
|
|
5
|
+
secret: string;
|
|
6
|
+
/** A published page to exercise. Defaults to "/". */
|
|
7
|
+
path?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The same page under a language prefix, e.g. "/no". Pass it on a site whose
|
|
10
|
+
* URLs carry the language, and the check also proves the preview renders in
|
|
11
|
+
* THAT language rather than the default one.
|
|
12
|
+
*/
|
|
13
|
+
localizedPath?: string;
|
|
14
|
+
/** A word only the localized page says - "Handlekurv", say. */
|
|
15
|
+
localizedMarker?: string;
|
|
16
|
+
}
|
|
17
|
+
interface EditSmokeResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
failures: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Proves a consumer app's EDIT path still works - the path a build cannot check,
|
|
23
|
+
* because the site compiles and serves fine while being uneditable.
|
|
24
|
+
*
|
|
25
|
+
* It asserts three independent things:
|
|
26
|
+
* 1. the public page renders WITHOUT the editor, chrome server-rendered;
|
|
27
|
+
* 2. a bare `?cmssyEdit=1` does NOT enter edit mode (an unverified pair must
|
|
28
|
+
* not open the door - CMS-948);
|
|
29
|
+
* 3. a verified `cmssyEdit=1` + `cmssySecret` renders the editor AND moves the
|
|
30
|
+
* chrome onto the edit bridge.
|
|
31
|
+
*
|
|
32
|
+
* Run it against a started production build:
|
|
33
|
+
*
|
|
34
|
+
* const result = await checkCmssyEditMode({ baseUrl, secret });
|
|
35
|
+
* expect(result.failures).toEqual([]);
|
|
36
|
+
*/
|
|
37
|
+
declare function checkCmssyEditMode(options: EditSmokeOptions): Promise<EditSmokeResult>;
|
|
38
|
+
|
|
39
|
+
export { type EditSmokeOptions, type EditSmokeResult, checkCmssyEditMode };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface EditSmokeOptions {
|
|
2
|
+
/** A running build of the consumer app, e.g. http://localhost:3000. */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/** The site's CMSSY_DRAFT_SECRET. Without it nothing can be verified. */
|
|
5
|
+
secret: string;
|
|
6
|
+
/** A published page to exercise. Defaults to "/". */
|
|
7
|
+
path?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The same page under a language prefix, e.g. "/no". Pass it on a site whose
|
|
10
|
+
* URLs carry the language, and the check also proves the preview renders in
|
|
11
|
+
* THAT language rather than the default one.
|
|
12
|
+
*/
|
|
13
|
+
localizedPath?: string;
|
|
14
|
+
/** A word only the localized page says - "Handlekurv", say. */
|
|
15
|
+
localizedMarker?: string;
|
|
16
|
+
}
|
|
17
|
+
interface EditSmokeResult {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
failures: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Proves a consumer app's EDIT path still works - the path a build cannot check,
|
|
23
|
+
* because the site compiles and serves fine while being uneditable.
|
|
24
|
+
*
|
|
25
|
+
* It asserts three independent things:
|
|
26
|
+
* 1. the public page renders WITHOUT the editor, chrome server-rendered;
|
|
27
|
+
* 2. a bare `?cmssyEdit=1` does NOT enter edit mode (an unverified pair must
|
|
28
|
+
* not open the door - CMS-948);
|
|
29
|
+
* 3. a verified `cmssyEdit=1` + `cmssySecret` renders the editor AND moves the
|
|
30
|
+
* chrome onto the edit bridge.
|
|
31
|
+
*
|
|
32
|
+
* Run it against a started production build:
|
|
33
|
+
*
|
|
34
|
+
* const result = await checkCmssyEditMode({ baseUrl, secret });
|
|
35
|
+
* expect(result.failures).toEqual([]);
|
|
36
|
+
*/
|
|
37
|
+
declare function checkCmssyEditMode(options: EditSmokeOptions): Promise<EditSmokeResult>;
|
|
38
|
+
|
|
39
|
+
export { type EditSmokeOptions, type EditSmokeResult, checkCmssyEditMode };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// src/testing/edit-smoke.ts
|
|
2
|
+
var EDITOR_MARKER = /CmssyEditor|cmssy-edit/;
|
|
3
|
+
var SERVER_CHROME = /<header|<footer/;
|
|
4
|
+
async function html(url) {
|
|
5
|
+
const response = await fetch(url, { redirect: "manual" });
|
|
6
|
+
return { status: response.status, body: await response.text() };
|
|
7
|
+
}
|
|
8
|
+
async function checkCmssyEditMode(options) {
|
|
9
|
+
const { baseUrl, secret, path = "/" } = options;
|
|
10
|
+
const failures = [];
|
|
11
|
+
const url = (suffix) => `${baseUrl.replace(/\/+$/, "")}${suffix}`;
|
|
12
|
+
const publicPage = await html(url(path));
|
|
13
|
+
if (publicPage.status !== 200) {
|
|
14
|
+
failures.push(`public ${path}: expected 200, got ${publicPage.status}`);
|
|
15
|
+
}
|
|
16
|
+
if (EDITOR_MARKER.test(publicPage.body)) {
|
|
17
|
+
failures.push(`public ${path}: the editor is mounted on a public page`);
|
|
18
|
+
}
|
|
19
|
+
if (!SERVER_CHROME.test(publicPage.body)) {
|
|
20
|
+
failures.push(
|
|
21
|
+
`public ${path}: no server-rendered chrome - the layout blocks are missing`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
const unverified = await html(url(`${path}?cmssyEdit=1`));
|
|
25
|
+
if (EDITOR_MARKER.test(unverified.body)) {
|
|
26
|
+
failures.push(
|
|
27
|
+
`${path}?cmssyEdit=1: edit mode without a secret - an unverified request must not open the editor (CMS-948)`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const verified = await html(
|
|
31
|
+
url(`${path}?cmssyEdit=1&cmssySecret=${encodeURIComponent(secret)}`)
|
|
32
|
+
);
|
|
33
|
+
if (verified.status !== 200) {
|
|
34
|
+
failures.push(`edit ${path}: expected 200, got ${verified.status}`);
|
|
35
|
+
}
|
|
36
|
+
if (!EDITOR_MARKER.test(verified.body)) {
|
|
37
|
+
failures.push(
|
|
38
|
+
`edit ${path}: no editor in the response - is the /cmssy-edit route mounted?`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (SERVER_CHROME.test(verified.body)) {
|
|
42
|
+
failures.push(
|
|
43
|
+
`edit ${path}: the chrome is still server-rendered - the header and footer will be selectable but have no fields (is CMSSY_EDIT_HEADER set on the rewrite?)`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const { localizedPath, localizedMarker } = options;
|
|
47
|
+
if (localizedPath && localizedMarker) {
|
|
48
|
+
const localized = await html(
|
|
49
|
+
url(
|
|
50
|
+
`${localizedPath}?cmssyEdit=1&cmssySecret=${encodeURIComponent(secret)}`
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
if (!EDITOR_MARKER.test(localized.body)) {
|
|
54
|
+
failures.push(`edit ${localizedPath}: no editor in the response`);
|
|
55
|
+
}
|
|
56
|
+
if (!localized.body.includes(localizedMarker)) {
|
|
57
|
+
failures.push(
|
|
58
|
+
`edit ${localizedPath}: "${localizedMarker}" is missing - the preview renders in the default language, not the one the URL asks for`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { ok: failures.length === 0, failures };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { checkCmssyEditMode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/next",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Next.js App Router bindings for cmssy headless sites (createCmssyPage + draft preview)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
"types": "./dist/client.d.ts",
|
|
33
33
|
"import": "./dist/client.js",
|
|
34
34
|
"require": "./dist/client.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./testing": {
|
|
37
|
+
"types": "./dist/testing.d.ts",
|
|
38
|
+
"import": "./dist/testing.js",
|
|
39
|
+
"require": "./dist/testing.cjs"
|
|
35
40
|
}
|
|
36
41
|
},
|
|
37
42
|
"main": "./dist/index.cjs",
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
"dist"
|
|
42
47
|
],
|
|
43
48
|
"peerDependencies": {
|
|
44
|
-
"@cmssy/react": "^4.
|
|
49
|
+
"@cmssy/react": "^4.4.0",
|
|
45
50
|
"next": ">=15",
|
|
46
51
|
"react": "^18.2.0 || ^19.0.0",
|
|
47
52
|
"react-dom": "^18.2.0 || ^19.0.0"
|
|
@@ -55,7 +60,7 @@
|
|
|
55
60
|
"tsup": "^8.3.0",
|
|
56
61
|
"typescript": "^5.6.0",
|
|
57
62
|
"vitest": "^2.1.0",
|
|
58
|
-
"@cmssy/react": "4.
|
|
63
|
+
"@cmssy/react": "4.4.0"
|
|
59
64
|
},
|
|
60
65
|
"dependencies": {
|
|
61
66
|
"jose": "^6.2.3",
|