@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.
Files changed (50) hide show
  1. package/README.md +212 -2
  2. package/dist/cli/register.js +23 -1
  3. package/dist/create-site-page.d.ts +38 -8
  4. package/dist/create-site-page.js +59 -8
  5. package/dist/draft-common.d.ts +32 -0
  6. package/dist/draft-common.js +58 -0
  7. package/dist/draft-context-core.js +39 -6
  8. package/dist/draft-context-core.test.d.ts +10 -0
  9. package/dist/draft-context-core.test.js +146 -0
  10. package/dist/draft-fetch.d.ts +9 -10
  11. package/dist/draft-fetch.js +71 -5
  12. package/dist/draft-fetch.test.d.ts +1 -0
  13. package/dist/draft-fetch.test.js +87 -0
  14. package/dist/editor-cors.d.ts +12 -0
  15. package/dist/editor-cors.js +31 -6
  16. package/dist/editor-cors.test.d.ts +1 -0
  17. package/dist/editor-cors.test.js +66 -0
  18. package/dist/editor-manifest.d.ts +2 -3
  19. package/dist/editor-manifest.js +12 -64
  20. package/dist/editor-matcher.d.ts +27 -0
  21. package/dist/editor-matcher.js +34 -0
  22. package/dist/editor-query.js +7 -1
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +2 -0
  25. package/dist/integration-check.js +11 -1
  26. package/dist/manifest-utils.d.ts +13 -0
  27. package/dist/manifest-utils.js +30 -3
  28. package/dist/manifest-utils.test.d.ts +1 -0
  29. package/dist/manifest-utils.test.js +72 -0
  30. package/dist/middleware.d.ts +21 -19
  31. package/dist/middleware.js +19 -22
  32. package/dist/next-config.test.d.ts +1 -0
  33. package/dist/next-config.test.js +355 -0
  34. package/dist/page-metadata.d.ts +66 -0
  35. package/dist/page-metadata.js +110 -0
  36. package/dist/page-metadata.test.d.ts +1 -0
  37. package/dist/page-metadata.test.js +105 -0
  38. package/dist/proxy.d.ts +95 -0
  39. package/dist/proxy.js +76 -0
  40. package/dist/proxy.test.d.ts +1 -0
  41. package/dist/proxy.test.js +123 -0
  42. package/dist/publish/field-diff.d.ts +191 -0
  43. package/dist/publish/field-diff.js +252 -0
  44. package/dist/publish/field-diff.test.d.ts +1 -0
  45. package/dist/publish/field-diff.test.js +286 -0
  46. package/dist/server/orchestrator.d.ts +1 -117
  47. package/dist/server/orchestrator.js +14 -733
  48. package/next-config.d.ts +78 -0
  49. package/next-config.mjs +468 -0
  50. package/package.json +63 -19
@@ -1,65 +1,13 @@
1
- import { getBlockJsonSchema, getBlockMeta, allowedBlockTypes, defaultPropsForType } from "@avocadostudio-ai/shared";
2
- import { blockDefinitionSchema, blockManifestSchema, validateByJsonSchemaLike, validateManifestDefaultProps } from "@avocadostudio-ai/shared";
3
- export { blockDefinitionSchema, blockManifestSchema, validateByJsonSchemaLike, validateManifestDefaultProps };
4
- // --- Manifest builder ---
5
- // Built fresh each time to include custom blocks registered after initial load.
6
- // Custom site blocks call registerBlock() in their schema.ts (imported via blocks/register.ts),
7
- // which adds them to allowedBlockTypes. The manifest must reflect ALL registered blocks,
8
- // not just the standard library ones.
9
- export function buildBlockManifest() {
10
- const blocks = allowedBlockTypes
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
- function applyMetaEnumsToSchema(schema, meta) {
46
- if (!meta?.fields)
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
+ }
@@ -1,4 +1,10 @@
1
- const EDITOR_KEYS = ["session", "siteId", "editorOrigin", "__editor"];
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();
@@ -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.
@@ -1,4 +1,4 @@
1
- import { deriveFieldMetaFromSchema } from "@avocadostudio-ai/shared";
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 } = deriveFieldMetaFromSchema(block.propsSchema);
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
+ });
@@ -1,24 +1,10 @@
1
- import { NextResponse, type NextRequest } from "next/server";
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
  };
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
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 previewRoute = options?.previewRoute ?? "/preview-draft";
14
- const editorParam = options?.editorParam ?? "__editor";
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 {};