@avocadostudio-ai/site-sdk 0.1.0 → 0.2.1
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/README.md +212 -2
- package/dist/cli/register.js +23 -1
- package/dist/create-site-page.d.ts +38 -8
- package/dist/create-site-page.js +59 -8
- package/dist/draft-common.d.ts +32 -0
- package/dist/draft-common.js +58 -0
- package/dist/draft-context-core.js +39 -6
- package/dist/draft-context-core.test.d.ts +10 -0
- package/dist/draft-context-core.test.js +146 -0
- package/dist/draft-fetch.d.ts +9 -10
- package/dist/draft-fetch.js +71 -5
- package/dist/draft-fetch.test.d.ts +1 -0
- package/dist/draft-fetch.test.js +87 -0
- package/dist/editor-cors.d.ts +12 -0
- package/dist/editor-cors.js +31 -6
- package/dist/editor-cors.test.d.ts +1 -0
- package/dist/editor-cors.test.js +66 -0
- package/dist/editor-manifest.d.ts +2 -3
- package/dist/editor-manifest.js +12 -64
- package/dist/editor-matcher.d.ts +27 -0
- package/dist/editor-matcher.js +34 -0
- package/dist/editor-query.js +7 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/integration-check.js +11 -1
- package/dist/manifest-utils.d.ts +13 -0
- package/dist/manifest-utils.js +30 -3
- package/dist/manifest-utils.test.d.ts +1 -0
- package/dist/manifest-utils.test.js +72 -0
- package/dist/middleware.d.ts +21 -19
- package/dist/middleware.js +19 -22
- package/dist/next-config.test.d.ts +1 -0
- package/dist/next-config.test.js +355 -0
- package/dist/page-metadata.d.ts +66 -0
- package/dist/page-metadata.js +110 -0
- package/dist/page-metadata.test.d.ts +1 -0
- package/dist/page-metadata.test.js +105 -0
- package/dist/proxy.d.ts +95 -0
- package/dist/proxy.js +76 -0
- package/dist/proxy.test.d.ts +1 -0
- package/dist/proxy.test.js +123 -0
- package/dist/publish/field-diff.d.ts +191 -0
- package/dist/publish/field-diff.js +252 -0
- package/dist/publish/field-diff.test.d.ts +1 -0
- package/dist/publish/field-diff.test.js +286 -0
- package/dist/server/orchestrator.d.ts +1 -117
- package/dist/server/orchestrator.js +14 -733
- package/next-config.d.ts +78 -0
- package/next-config.mjs +468 -0
- package/package.json +63 -19
package/dist/editor-manifest.js
CHANGED
|
@@ -1,65 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.map((type) => {
|
|
12
|
-
const meta = getBlockMeta(type);
|
|
13
|
-
const propsSchema = getBlockJsonSchema(type);
|
|
14
|
-
if (!propsSchema)
|
|
15
|
-
return null; // skip blocks without schema (shouldn't happen but be safe)
|
|
16
|
-
const finalSchema = applyMetaEnumsToSchema(propsSchema, meta);
|
|
17
|
-
// Only attach defaultProps that actually satisfy the block's schema.
|
|
18
|
-
// defaultPropsForType() hands custom (non-core) blocks a generic CTA-shaped
|
|
19
|
-
// fallback; if that violates a custom schema (e.g. a richtext field expecting
|
|
20
|
-
// a doc object, not a string) it would fail validateManifestDefaultProps and
|
|
21
|
-
// poison the WHOLE manifest, making every block read as "unknown" in the editor.
|
|
22
|
-
const candidateDefaults = defaultPropsForType(type);
|
|
23
|
-
const defaultProps = validateByJsonSchemaLike(finalSchema, candidateDefaults)
|
|
24
|
-
? candidateDefaults
|
|
25
|
-
: undefined;
|
|
26
|
-
return {
|
|
27
|
-
type,
|
|
28
|
-
displayName: meta?.displayName ?? type,
|
|
29
|
-
propsSchema: finalSchema,
|
|
30
|
-
defaultProps
|
|
31
|
-
};
|
|
32
|
-
})
|
|
33
|
-
.filter((b) => b !== null);
|
|
34
|
-
return { version: 1, blocks };
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Fold enum options declared in the block's registry `meta.fields` into the
|
|
38
|
-
* derived JSON schema. A site can declare `f.enum("Gap", ["sm","md","lg"])` in
|
|
39
|
-
* its block meta while the Zod prop stays a plain `z.string()` (so any value
|
|
40
|
-
* still validates). Without this, manifest-driven custom blocks lose the enum
|
|
41
|
-
* and the editor renders a free-text input instead of a dropdown. We only add
|
|
42
|
-
* `enum` (the editor reads it to pick a select control); validation is type-only
|
|
43
|
-
* (see validateByJsonSchemaLike), so widening here never rejects stored content.
|
|
1
|
+
/*
|
|
2
|
+
* The manifest builder itself now lives in `@avocadostudio-ai/shared`.
|
|
3
|
+
*
|
|
4
|
+
* It moved because the SDK was no longer its only caller: the orchestrator
|
|
5
|
+
* serves the same manifest at `GET /blocks/manifest` so an MCP agent can
|
|
6
|
+
* discover the *target site's* block vocabulary instead of whatever happens to
|
|
7
|
+
* be registered in its own process. `shared` is the one package both the SDK
|
|
8
|
+
* and orchestrator-core already depend on, so it is where a single answer can
|
|
9
|
+
* live. This module stays as the SDK's public entry point — every site
|
|
10
|
+
* template, example and doc imports `buildBlockManifest` from here.
|
|
44
11
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return schema;
|
|
48
|
-
const properties = schema.properties;
|
|
49
|
-
if (!properties || typeof properties !== "object")
|
|
50
|
-
return schema;
|
|
51
|
-
let next;
|
|
52
|
-
for (const [key, fieldMeta] of Object.entries(meta.fields)) {
|
|
53
|
-
if (fieldMeta.kind !== "enum" || !fieldMeta.options?.length)
|
|
54
|
-
continue;
|
|
55
|
-
const prop = properties[key];
|
|
56
|
-
if (!prop || typeof prop !== "object" || Array.isArray(prop))
|
|
57
|
-
continue;
|
|
58
|
-
const propObj = prop;
|
|
59
|
-
if (Array.isArray(propObj.enum))
|
|
60
|
-
continue; // schema already carries an enum
|
|
61
|
-
next ??= { ...schema, properties: { ...properties } };
|
|
62
|
-
next.properties[key] = { ...propObj, type: "string", enum: [...fieldMeta.options] };
|
|
63
|
-
}
|
|
64
|
-
return next ?? schema;
|
|
65
|
-
}
|
|
12
|
+
import { buildBlockManifest, blockDefinitionSchema, blockManifestSchema, validateByJsonSchemaLike, validateManifestDefaultProps } from "@avocadostudio-ai/shared";
|
|
13
|
+
export { buildBlockManifest, blockDefinitionSchema, blockManifestSchema, validateByJsonSchemaLike, validateManifestDefaultProps };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route matcher that decides which requests the editor rewrite even sees.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `proxy.ts` because it is pure string logic with no dependency
|
|
5
|
+
* on Next: the rewrite itself needs `NextResponse`, but the matcher is just a
|
|
6
|
+
* pattern, and Next 16 requires it to be inlined at the call site anyway. Its
|
|
7
|
+
* own module means a caller that only wants the pattern — the scaffolder
|
|
8
|
+
* templates, a test, a non-Next host — does not have to pull in `next/server`.
|
|
9
|
+
*/
|
|
10
|
+
/** Default route prefix that the dynamic editor/draft page lives under. */
|
|
11
|
+
export declare const DEFAULT_PREVIEW_ROUTE = "/preview-draft";
|
|
12
|
+
/**
|
|
13
|
+
* Build the `config.matcher` pattern that skips Next internals, the API routes,
|
|
14
|
+
* static assets, and the preview route itself.
|
|
15
|
+
*
|
|
16
|
+
* Next.js 16 requires the `config` export in a `proxy.ts` to be a **static
|
|
17
|
+
* object literal** — it is read by static analysis, so it cannot be a factory
|
|
18
|
+
* return value, a spread, or an imported constant. Use this helper at authoring
|
|
19
|
+
* time (or copy the string it produces) to write that literal by hand:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* export const config = {
|
|
23
|
+
* matcher: ["/((?!_next|preview-draft|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)"],
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildEditorMatcher(previewRoute?: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route matcher that decides which requests the editor rewrite even sees.
|
|
3
|
+
*
|
|
4
|
+
* Separate from `proxy.ts` because it is pure string logic with no dependency
|
|
5
|
+
* on Next: the rewrite itself needs `NextResponse`, but the matcher is just a
|
|
6
|
+
* pattern, and Next 16 requires it to be inlined at the call site anyway. Its
|
|
7
|
+
* own module means a caller that only wants the pattern — the scaffolder
|
|
8
|
+
* templates, a test, a non-Next host — does not have to pull in `next/server`.
|
|
9
|
+
*/
|
|
10
|
+
/** Default route prefix that the dynamic editor/draft page lives under. */
|
|
11
|
+
export const DEFAULT_PREVIEW_ROUTE = "/preview-draft";
|
|
12
|
+
/**
|
|
13
|
+
* Build the `config.matcher` pattern that skips Next internals, the API routes,
|
|
14
|
+
* static assets, and the preview route itself.
|
|
15
|
+
*
|
|
16
|
+
* Next.js 16 requires the `config` export in a `proxy.ts` to be a **static
|
|
17
|
+
* object literal** — it is read by static analysis, so it cannot be a factory
|
|
18
|
+
* return value, a spread, or an imported constant. Use this helper at authoring
|
|
19
|
+
* time (or copy the string it produces) to write that literal by hand:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* export const config = {
|
|
23
|
+
* matcher: ["/((?!_next|preview-draft|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)"],
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function buildEditorMatcher(previewRoute = DEFAULT_PREVIEW_ROUTE) {
|
|
28
|
+
// Escape special regex chars in the route prefix. The leading slash is
|
|
29
|
+
// dropped rather than sliced off blindly: `slice(1)` on a route written
|
|
30
|
+
// without one turned "preview" into "review" and matched nothing anybody
|
|
31
|
+
// could see.
|
|
32
|
+
const escapedRoute = previewRoute.replace(/^\/+/, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
return `/((?!_next|${escapedRoute}|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)`;
|
|
34
|
+
}
|
package/dist/editor-query.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/*
|
|
2
|
+
* `secret` is carried alongside these so an in-site navigation stays in editor
|
|
3
|
+
* mode when the draft cookie is blocked. It is deliberately last: everything
|
|
4
|
+
* here ends up in hrefs the preview renders, so keep the list to what editor
|
|
5
|
+
* mode actually needs to survive a click.
|
|
6
|
+
*/
|
|
7
|
+
const EDITOR_KEYS = ["session", "siteId", "editorOrigin", "__editor", "secret"];
|
|
2
8
|
export function buildEditorQuerySuffix(searchParams) {
|
|
3
9
|
const params = new URLSearchParams();
|
|
4
10
|
for (const key of EDITOR_KEYS) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export type { SiteConfig } from "@avocadostudio-ai/shared";
|
|
|
3
3
|
export { pageDocSchema } from "./types.ts";
|
|
4
4
|
export { buildSlug } from "./editor-query.ts";
|
|
5
5
|
export { renderBlocks } from "./render-blocks.tsx";
|
|
6
|
+
export { buildPageMetadata, derivePageDescription, derivePageTitle, stripMarkdown, truncateForMeta, DEFAULT_SITE_DESCRIPTION, } from "./page-metadata.ts";
|
|
7
|
+
export type { PageMetadata, BuildPageMetadataOptions } from "./page-metadata.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,5 @@ export { pageDocSchema } from "./types.js";
|
|
|
3
3
|
export { buildSlug } from "./editor-query.js";
|
|
4
4
|
// Block rendering
|
|
5
5
|
export { renderBlocks } from "./render-blocks.js";
|
|
6
|
+
// SEO / metadata
|
|
7
|
+
export { buildPageMetadata, derivePageDescription, derivePageTitle, stripMarkdown, truncateForMeta, DEFAULT_SITE_DESCRIPTION, } from "./page-metadata.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getConfiguredDraftSecret } from "@avocadostudio-ai/shared";
|
|
2
|
+
import { editorOriginAllowlist } from "./draft-common.js";
|
|
2
3
|
let checked = false;
|
|
3
4
|
/**
|
|
4
5
|
* Run once on first editor API request to validate essential integration config.
|
|
@@ -13,7 +14,16 @@ export function checkIntegrationOnce() {
|
|
|
13
14
|
const draftSecret = getConfiguredDraftSecret(process.env);
|
|
14
15
|
if (!draftSecret) {
|
|
15
16
|
warnings.push("DRAFT_MODE_SECRET is not set — editor draft mode will not work. " +
|
|
16
|
-
"Set DRAFT_MODE_SECRET (or VITE_SITE_DRAFT_SECRET) in your .env file."
|
|
17
|
+
"Set DRAFT_MODE_SECRET (or VITE_SITE_DRAFT_SECRET) in your .env file. " +
|
|
18
|
+
"In production this is what authorizes a request to see unpublished content.");
|
|
19
|
+
}
|
|
20
|
+
// 1b. Editor origin allowlist — the postMessage target and the frame allowed
|
|
21
|
+
// to drive inline edits. Without one, a caller-supplied origin has nothing to
|
|
22
|
+
// be checked against and editor mode silently has no bridge.
|
|
23
|
+
if (process.env.NODE_ENV === "production" && editorOriginAllowlist().length === 0) {
|
|
24
|
+
warnings.push("No editor origin is configured — set NEXT_PUBLIC_EDITOR_ORIGIN (and " +
|
|
25
|
+
"AVOCADO_EDITOR_ORIGINS for any additional editor deployments). Origins " +
|
|
26
|
+
"arriving in the URL are not trusted unless they are listed.");
|
|
17
27
|
}
|
|
18
28
|
// 2. Orchestrator URL
|
|
19
29
|
const orchestratorUrl = process.env.ORCHESTRATOR_URL?.trim();
|
package/dist/manifest-utils.d.ts
CHANGED
|
@@ -6,6 +6,19 @@ export type ManifestFieldInfo = {
|
|
|
6
6
|
listImageFields: Map<string, Map<string, Set<string>>>;
|
|
7
7
|
/** All list field names per block type (e.g. CardGrid → {"cards"}, FeatureGrid → {"features"}) */
|
|
8
8
|
listFieldNames: Map<string, Set<string>>;
|
|
9
|
+
/**
|
|
10
|
+
* Rich-text fields stored as a ProseMirror *document* rather than a markdown
|
|
11
|
+
* string, per block type.
|
|
12
|
+
*
|
|
13
|
+
* An integration whose CMS has real rich text (Sanity Portable Text,
|
|
14
|
+
* Contentful Rich Text) maps those fields to a document so nothing is
|
|
15
|
+
* flattened. It needs to know which props those are at both boundaries — to
|
|
16
|
+
* convert on read and to convert back on publish — and hand-maintaining the
|
|
17
|
+
* list is how the two ends drift apart.
|
|
18
|
+
*/
|
|
19
|
+
documentFields: Map<string, Set<string>>;
|
|
20
|
+
/** The same, for fields inside list items (e.g. FAQ → items → {"answer"}). */
|
|
21
|
+
listDocumentFields: Map<string, Map<string, Set<string>>>;
|
|
9
22
|
};
|
|
10
23
|
/**
|
|
11
24
|
* Derive field metadata per block type from the manifest's propsSchema.
|
package/dist/manifest-utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveManifestFieldMeta, isProseMirrorDocSchema } from "@avocadostudio-ai/shared";
|
|
2
2
|
const _cache = new WeakMap();
|
|
3
3
|
/**
|
|
4
4
|
* Derive field metadata per block type from the manifest's propsSchema.
|
|
@@ -14,14 +14,27 @@ export function getManifestImageFields(manifest) {
|
|
|
14
14
|
const imageFields = new Map();
|
|
15
15
|
const listImageFields = new Map();
|
|
16
16
|
const listFieldNames = new Map();
|
|
17
|
+
const documentFields = new Map();
|
|
18
|
+
const listDocumentFields = new Map();
|
|
17
19
|
for (const block of manifest.blocks) {
|
|
18
|
-
const { fields, listFields } =
|
|
20
|
+
const { fields, listFields } = resolveManifestFieldMeta(block);
|
|
21
|
+
const props = (block.propsSchema.properties ?? {});
|
|
19
22
|
const imgs = new Set();
|
|
20
23
|
for (const [key, meta] of Object.entries(fields)) {
|
|
21
24
|
if (meta.kind === "image")
|
|
22
25
|
imgs.add(key);
|
|
23
26
|
}
|
|
24
27
|
imageFields.set(block.type, imgs);
|
|
28
|
+
// `kind` alone cannot tell the two apart — a markdown string and a document
|
|
29
|
+
// are both "richtext" to the editor. The schema can: a document self-
|
|
30
|
+
// identifies by pinning its `type` to the literal "doc".
|
|
31
|
+
const docs = new Set();
|
|
32
|
+
for (const key of Object.keys(fields)) {
|
|
33
|
+
if (isProseMirrorDocSchema(props[key]))
|
|
34
|
+
docs.add(key);
|
|
35
|
+
}
|
|
36
|
+
if (docs.size > 0)
|
|
37
|
+
documentFields.set(block.type, docs);
|
|
25
38
|
const listNames = new Set(Object.keys(listFields));
|
|
26
39
|
if (listNames.size > 0)
|
|
27
40
|
listFieldNames.set(block.type, listNames);
|
|
@@ -37,8 +50,22 @@ export function getManifestImageFields(manifest) {
|
|
|
37
50
|
}
|
|
38
51
|
if (listImgs.size > 0)
|
|
39
52
|
listImageFields.set(block.type, listImgs);
|
|
53
|
+
const listDocs = new Map();
|
|
54
|
+
for (const [listKey, listMeta] of Object.entries(listFields)) {
|
|
55
|
+
const listSchema = props[listKey];
|
|
56
|
+
const itemProps = (listSchema?.items?.properties ?? {});
|
|
57
|
+
const itemDocs = new Set();
|
|
58
|
+
for (const itemKey of Object.keys(listMeta.itemFields)) {
|
|
59
|
+
if (isProseMirrorDocSchema(itemProps[itemKey]))
|
|
60
|
+
itemDocs.add(itemKey);
|
|
61
|
+
}
|
|
62
|
+
if (itemDocs.size > 0)
|
|
63
|
+
listDocs.set(listKey, itemDocs);
|
|
64
|
+
}
|
|
65
|
+
if (listDocs.size > 0)
|
|
66
|
+
listDocumentFields.set(block.type, listDocs);
|
|
40
67
|
}
|
|
41
|
-
const result = { imageFields, listImageFields, listFieldNames };
|
|
68
|
+
const result = { imageFields, listImageFields, listFieldNames, documentFields, listDocumentFields };
|
|
42
69
|
_cache.set(manifest, result);
|
|
43
70
|
return result;
|
|
44
71
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { getManifestImageFields } from "./manifest-utils.js";
|
|
4
|
+
/**
|
|
5
|
+
* A rich-text prop can be stored two ways, and an integration has to tell them
|
|
6
|
+
* apart at both boundaries — convert on read, convert back on publish. The
|
|
7
|
+
* editor's `FieldKind` cannot help: a markdown string and a ProseMirror
|
|
8
|
+
* document are both `richtext` to it. The schema can, because a document pins
|
|
9
|
+
* its `type` to the literal "doc".
|
|
10
|
+
*/
|
|
11
|
+
const MANIFEST = {
|
|
12
|
+
version: 1,
|
|
13
|
+
blocks: [
|
|
14
|
+
{
|
|
15
|
+
type: "RichText",
|
|
16
|
+
propsSchema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
title: { type: "string" },
|
|
20
|
+
body: { type: "string", contentMediaType: "text/markdown" }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "ArticleBody",
|
|
26
|
+
propsSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
title: { type: "string" },
|
|
30
|
+
body: { type: "object", properties: { type: { const: "doc" }, content: { type: "array" } } }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
type: "FAQ",
|
|
36
|
+
propsSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
items: {
|
|
40
|
+
type: "array",
|
|
41
|
+
items: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
q: { type: "string" },
|
|
45
|
+
answer: { type: "object", properties: { type: { const: "doc" }, content: { type: "array" } } }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
const info = getManifestImageFields(MANIFEST);
|
|
55
|
+
test("a document-valued rich-text prop is reported under documentFields", () => {
|
|
56
|
+
assert.deepEqual([...(info.documentFields.get("ArticleBody") ?? [])], ["body"]);
|
|
57
|
+
});
|
|
58
|
+
test("a markdown-string rich-text prop is not", () => {
|
|
59
|
+
assert.equal(info.documentFields.has("RichText"), false);
|
|
60
|
+
});
|
|
61
|
+
test("a document-valued field inside a list item is reported too", () => {
|
|
62
|
+
assert.deepEqual([...(info.listDocumentFields.get("FAQ")?.get("items") ?? [])], ["answer"]);
|
|
63
|
+
});
|
|
64
|
+
test("a block with no document fields is absent rather than present-and-empty", () => {
|
|
65
|
+
// Callers do `documentFields.get(type) ?? new Set()`, so an empty entry would
|
|
66
|
+
// work — but keeping the map to blocks that actually have one makes a
|
|
67
|
+
// debugging dump of it readable.
|
|
68
|
+
assert.equal(info.documentFields.has("FAQ"), false);
|
|
69
|
+
});
|
|
70
|
+
test("the derivation is cached by manifest reference", () => {
|
|
71
|
+
assert.equal(getManifestImageFields(MANIFEST), info);
|
|
72
|
+
});
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type EditorProxyOptions } from "./proxy.ts";
|
|
2
2
|
/**
|
|
3
3
|
* Options for the editor middleware factory.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated Use {@link EditorProxyOptions} from `@avocadostudio-ai/site-sdk/proxy`.
|
|
4
6
|
*/
|
|
5
|
-
export type EditorMiddlewareOptions =
|
|
6
|
-
/**
|
|
7
|
-
* The internal route prefix that the dynamic editor/draft page lives under.
|
|
8
|
-
* @default "/preview-draft"
|
|
9
|
-
*/
|
|
10
|
-
previewRoute?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Query parameter that signals an editor iframe request.
|
|
13
|
-
* @default "__editor"
|
|
14
|
-
*/
|
|
15
|
-
editorParam?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Name of the Next.js draft-mode bypass cookie.
|
|
18
|
-
* @default "__prerender_bypass"
|
|
19
|
-
*/
|
|
20
|
-
draftCookie?: string;
|
|
21
|
-
};
|
|
7
|
+
export type EditorMiddlewareOptions = EditorProxyOptions;
|
|
22
8
|
/**
|
|
23
9
|
* Create a Next.js middleware function that rewrites editor/draft requests
|
|
24
10
|
* to a dynamic preview route, keeping the main page route fully static.
|
|
@@ -28,9 +14,25 @@ export type EditorMiddlewareOptions = {
|
|
|
28
14
|
* import { createEditorMiddleware } from "@avocadostudio-ai/site-sdk/middleware"
|
|
29
15
|
* export const { middleware, config } = createEditorMiddleware()
|
|
30
16
|
* ```
|
|
17
|
+
*
|
|
18
|
+
* @deprecated Next.js 16 renamed the `middleware` file convention to `proxy`.
|
|
19
|
+
* Rename `middleware.ts` to `proxy.ts` and switch to `createEditorProxy` from
|
|
20
|
+
* `@avocadostudio-ai/site-sdk/proxy`. Note that Next 16 rejects this destructured
|
|
21
|
+
* form — `config` must be a static object literal in the file:
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { createEditorProxy } from "@avocadostudio-ai/site-sdk/proxy"
|
|
24
|
+
*
|
|
25
|
+
* export const proxy = createEditorProxy().proxy
|
|
26
|
+
*
|
|
27
|
+
* export const config = {
|
|
28
|
+
* matcher: ["/((?!_next|preview-draft|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)"],
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
* This entry point stays for Next.js 15 sites, where the destructured form
|
|
32
|
+
* works, and returns the identical rewrite under the older export name.
|
|
31
33
|
*/
|
|
32
34
|
export declare function createEditorMiddleware(options?: EditorMiddlewareOptions): {
|
|
33
|
-
middleware: (request: NextRequest) => NextResponse<unknown>;
|
|
35
|
+
middleware: (request: import("next/server").NextRequest) => import("next/server").NextResponse<unknown>;
|
|
34
36
|
config: {
|
|
35
37
|
matcher: string[];
|
|
36
38
|
};
|
package/dist/middleware.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createEditorProxy } from "./proxy.js";
|
|
2
2
|
/**
|
|
3
3
|
* Create a Next.js middleware function that rewrites editor/draft requests
|
|
4
4
|
* to a dynamic preview route, keeping the main page route fully static.
|
|
@@ -8,27 +8,24 @@ import { NextResponse } from "next/server";
|
|
|
8
8
|
* import { createEditorMiddleware } from "@avocadostudio-ai/site-sdk/middleware"
|
|
9
9
|
* export const { middleware, config } = createEditorMiddleware()
|
|
10
10
|
* ```
|
|
11
|
+
*
|
|
12
|
+
* @deprecated Next.js 16 renamed the `middleware` file convention to `proxy`.
|
|
13
|
+
* Rename `middleware.ts` to `proxy.ts` and switch to `createEditorProxy` from
|
|
14
|
+
* `@avocadostudio-ai/site-sdk/proxy`. Note that Next 16 rejects this destructured
|
|
15
|
+
* form — `config` must be a static object literal in the file:
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createEditorProxy } from "@avocadostudio-ai/site-sdk/proxy"
|
|
18
|
+
*
|
|
19
|
+
* export const proxy = createEditorProxy().proxy
|
|
20
|
+
*
|
|
21
|
+
* export const config = {
|
|
22
|
+
* matcher: ["/((?!_next|preview-draft|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)"],
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
* This entry point stays for Next.js 15 sites, where the destructured form
|
|
26
|
+
* works, and returns the identical rewrite under the older export name.
|
|
11
27
|
*/
|
|
12
28
|
export function createEditorMiddleware(options) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
const draftCookie = options?.draftCookie ?? "__prerender_bypass";
|
|
16
|
-
function middleware(request) {
|
|
17
|
-
const isEditor = request.nextUrl.searchParams.get(editorParam) === "1";
|
|
18
|
-
const hasDraftCookie = request.cookies.has(draftCookie);
|
|
19
|
-
if (isEditor || hasDraftCookie) {
|
|
20
|
-
const url = request.nextUrl.clone();
|
|
21
|
-
url.pathname = `${previewRoute}${url.pathname}`;
|
|
22
|
-
return NextResponse.rewrite(url);
|
|
23
|
-
}
|
|
24
|
-
return NextResponse.next();
|
|
25
|
-
}
|
|
26
|
-
// Escape special regex chars in the route prefix (strip leading slash for the pattern)
|
|
27
|
-
const escapedRoute = previewRoute.slice(1).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
|
-
const config = {
|
|
29
|
-
matcher: [
|
|
30
|
-
`/((?!_next|${escapedRoute}|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)`
|
|
31
|
-
],
|
|
32
|
-
};
|
|
33
|
-
return { middleware, config };
|
|
29
|
+
const { proxy, config } = createEditorProxy(options);
|
|
30
|
+
return { middleware: proxy, config };
|
|
34
31
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|