@avocadostudio-ai/site-sdk 0.5.1 → 0.6.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.
@@ -0,0 +1,20 @@
1
+ /** Where the standalone orchestrator listens, and what the editor assumes. */
2
+ export declare const DEFAULT_ORCHESTRATOR = "http://localhost:4200";
3
+ /**
4
+ * The warning that makes "the site should appear in the dashboard" honest, or
5
+ * `null` when it already is.
6
+ *
7
+ * Registration went to whichever orchestrator `--orchestrator` named. The
8
+ * editor reads exactly one, `VITE_ORCHESTRATOR_URL`, baked in at build time,
9
+ * and it defaults to :4200. A library-mode site registers with its own handler
10
+ * at, say, `:3002/api/avocado`, whose registry holds a completely different
11
+ * set of sites — so "the site should appear in the dashboard" is followed by a
12
+ * dashboard listing somebody else's sites, with nothing reporting a problem.
13
+ *
14
+ * Nothing *is* wrong. There are two registries and this command cannot know
15
+ * which one the editor was built against. It can say which one it just wrote
16
+ * to, and it says so only when the two visibly disagree: printing an
17
+ * environment variable after every default-target registration would be noise
18
+ * on the path almost everyone is on.
19
+ */
20
+ export declare function orchestratorMismatchNotice(orchestrator: string): string | null;
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Lives apart from `register.ts` because `register.ts` calls `main()` at module
3
+ * scope — importing it to reach one helper *registers a site*, which is a poor
4
+ * thing for a test to do and a worse thing for it to do by accident.
5
+ */
6
+ /** Where the standalone orchestrator listens, and what the editor assumes. */
7
+ export const DEFAULT_ORCHESTRATOR = "http://localhost:4200";
8
+ /**
9
+ * The warning that makes "the site should appear in the dashboard" honest, or
10
+ * `null` when it already is.
11
+ *
12
+ * Registration went to whichever orchestrator `--orchestrator` named. The
13
+ * editor reads exactly one, `VITE_ORCHESTRATOR_URL`, baked in at build time,
14
+ * and it defaults to :4200. A library-mode site registers with its own handler
15
+ * at, say, `:3002/api/avocado`, whose registry holds a completely different
16
+ * set of sites — so "the site should appear in the dashboard" is followed by a
17
+ * dashboard listing somebody else's sites, with nothing reporting a problem.
18
+ *
19
+ * Nothing *is* wrong. There are two registries and this command cannot know
20
+ * which one the editor was built against. It can say which one it just wrote
21
+ * to, and it says so only when the two visibly disagree: printing an
22
+ * environment variable after every default-target registration would be noise
23
+ * on the path almost everyone is on.
24
+ */
25
+ export function orchestratorMismatchNotice(orchestrator) {
26
+ if (orchestrator.replace(/\/+$/, "") === DEFAULT_ORCHESTRATOR)
27
+ return null;
28
+ return (`\nThis is not the default orchestrator (${DEFAULT_ORCHESTRATOR}), and the editor\n` +
29
+ `reads one URL baked in at build time. For the site to show up there, the\n` +
30
+ `editor has to be built against this one:\n\n` +
31
+ ` VITE_ORCHESTRATOR_URL=${orchestrator}\n\n` +
32
+ `Set it in the editor's .env and restart it. A dashboard listing other\n` +
33
+ `sites, or none, is the symptom of it pointing somewhere else.\n`);
34
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { orchestratorMismatchNotice } from "./register-notice.js";
4
+ /*
5
+ * `avocado-register` ends with "the site should appear in the dashboard", and
6
+ * for a library-mode site that is a promise it cannot keep: the registration
7
+ * went to the site's own handler, and the editor reads one
8
+ * `VITE_ORCHESTRATOR_URL` baked in at build time. The dashboard then opens on
9
+ * a different registry's sites and reports no problem, because there is none —
10
+ * there are simply two registries.
11
+ */
12
+ test("the default target says nothing — that is the path almost everyone is on", () => {
13
+ assert.equal(orchestratorMismatchNotice("http://localhost:4200"), null);
14
+ assert.equal(orchestratorMismatchNotice("http://localhost:4200/"), null, "a trailing slash is the same URL");
15
+ });
16
+ test("a library-mode target names the variable the editor needs", () => {
17
+ const notice = orchestratorMismatchNotice("http://localhost:3002/api/avocado");
18
+ assert.ok(notice);
19
+ assert.match(notice, /VITE_ORCHESTRATOR_URL=http:\/\/localhost:3002\/api\/avocado/);
20
+ assert.match(notice, /sites, or none/, "and names the symptom, which is what the reader is looking at");
21
+ });
@@ -33,6 +33,7 @@
33
33
  import { randomBytes } from "node:crypto";
34
34
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
35
35
  import { join, resolve } from "node:path";
36
+ import { DEFAULT_ORCHESTRATOR, orchestratorMismatchNotice } from "./register-notice.js";
36
37
  function parseArgs(argv) {
37
38
  const out = {};
38
39
  for (let i = 0; i < argv.length; i++) {
@@ -224,7 +225,7 @@ async function main() {
224
225
  // Resolve port
225
226
  const port = args.port ?? detectPortFromPackageJson(pkg) ?? 3000;
226
227
  // Resolve orchestrator URL
227
- const orchestrator = (args.orchestrator ?? process.env.ORCHESTRATOR_URL ?? "http://localhost:4200").replace(/\/+$/, "");
228
+ const orchestrator = (args.orchestrator ?? process.env.ORCHESTRATOR_URL ?? DEFAULT_ORCHESTRATOR).replace(/\/+$/, "");
228
229
  /*
229
230
  * A credentialed orchestrator refuses `/sites/register` like any other route,
230
231
  * and this CLI had no way to present a token — so the documented path for
@@ -323,7 +324,11 @@ async function main() {
323
324
  process.stdout.write(`\nNext steps:\n`);
324
325
  process.stdout.write(` 1. Start your site: pnpm dev (in this directory)\n`);
325
326
  process.stdout.write(` 2. Open the editor: http://localhost:4100\n`);
326
- process.stdout.write(` 3. The site should appear in the dashboard. If not, refresh the page.\n\n`);
327
+ process.stdout.write(` 3. The site should appear in the dashboard. If not, refresh the page.\n`);
328
+ const mismatch = orchestratorMismatchNotice(orchestrator);
329
+ if (mismatch)
330
+ process.stdout.write(mismatch);
331
+ process.stdout.write(`\n`);
327
332
  }
328
333
  function humanize(s) {
329
334
  return s
package/dist/editor.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { EditorOverlay } from "./editor-overlay.tsx";
2
2
  export { buildEditorQuerySuffix } from "./editor-query.ts";
3
- export { getPreviewWrapperProps, editableProps } from "./markers.ts";
3
+ export { getPreviewWrapperProps, editableProps, editableScopeProps } from "./markers.ts";
4
4
  export { renderBlocks } from "./render-blocks.tsx";
5
5
  export { RenderedBlocks, PreviewBlock } from "./live-preview-blocks.tsx";
6
6
  export { LivePreviewProvider, useLivePreviewBlocks } from "@avocadostudio-ai/preview-adapter";
package/dist/editor.js CHANGED
@@ -11,7 +11,7 @@ export { buildEditorQuerySuffix } from "./editor-query.js";
11
11
  * for the two-line attribute helper from here drags the whole editor into the
12
12
  * public bundle: +66 kB First Load JS, for identical markup.
13
13
  */
14
- export { getPreviewWrapperProps, editableProps } from "./markers.js";
14
+ export { getPreviewWrapperProps, editableProps, editableScopeProps } from "./markers.js";
15
15
  // Block rendering helper
16
16
  export { renderBlocks } from "./render-blocks.js";
17
17
  // Live-preview store renderer (streams field drafts through React)
package/dist/markers.d.ts CHANGED
@@ -76,3 +76,35 @@ export declare function editableProps(path: string, options?: {
76
76
  readonly "data-editable-target": string;
77
77
  readonly "data-editable-target-label": string;
78
78
  };
79
+ /**
80
+ * Mark an element as the scope its marked descendants sit inside.
81
+ *
82
+ * The field path is scoped from the block down — `items[3].question` — which
83
+ * a renderer can only write if it knows where it sits. That holds while one
84
+ * component draws the whole block, and stops holding the moment a list row is
85
+ * drawn by a component of its own: the child knows it has a `question` and
86
+ * cannot know it is `items[3]`. Without this, every component that can appear
87
+ * inside a list takes a prefix prop from its parent, and every parent passes
88
+ * one.
89
+ *
90
+ * Forgetting to is silent and *wrong*, not silent and absent. The child marks
91
+ * a bare `question`, the overlay resolves it against the enclosing block, and
92
+ * an edit to a headline inside a column patches a prop the section does not
93
+ * have.
94
+ *
95
+ * ```tsx
96
+ * {props.items.map((item, i) => (
97
+ * <div key={item.id} {...editableScopeProps(`items[${i}]`)}>
98
+ * <FaqRow item={item} /> // marks a bare "question"; needs no prefix
99
+ * </div>
100
+ * ))}
101
+ * ```
102
+ *
103
+ * Scopes nest, and compose outermost first — a `left[1]` scope inside a
104
+ * `sections[0]` scope makes a child's `text` into `sections[0].left[1].text`.
105
+ * A block boundary ends the composition, so a scope outside a block never
106
+ * reaches into it.
107
+ */
108
+ export declare function editableScopeProps(scope: string): {
109
+ readonly "data-editable-scope": string;
110
+ };
package/dist/markers.js CHANGED
@@ -85,3 +85,35 @@ export function editableProps(path, options) {
85
85
  ...(options?.kind ? { "data-editable-kind": options.kind } : {})
86
86
  };
87
87
  }
88
+ /**
89
+ * Mark an element as the scope its marked descendants sit inside.
90
+ *
91
+ * The field path is scoped from the block down — `items[3].question` — which
92
+ * a renderer can only write if it knows where it sits. That holds while one
93
+ * component draws the whole block, and stops holding the moment a list row is
94
+ * drawn by a component of its own: the child knows it has a `question` and
95
+ * cannot know it is `items[3]`. Without this, every component that can appear
96
+ * inside a list takes a prefix prop from its parent, and every parent passes
97
+ * one.
98
+ *
99
+ * Forgetting to is silent and *wrong*, not silent and absent. The child marks
100
+ * a bare `question`, the overlay resolves it against the enclosing block, and
101
+ * an edit to a headline inside a column patches a prop the section does not
102
+ * have.
103
+ *
104
+ * ```tsx
105
+ * {props.items.map((item, i) => (
106
+ * <div key={item.id} {...editableScopeProps(`items[${i}]`)}>
107
+ * <FaqRow item={item} /> // marks a bare "question"; needs no prefix
108
+ * </div>
109
+ * ))}
110
+ * ```
111
+ *
112
+ * Scopes nest, and compose outermost first — a `left[1]` scope inside a
113
+ * `sections[0]` scope makes a child's `text` into `sections[0].left[1].text`.
114
+ * A block boundary ends the composition, so a scope outside a block never
115
+ * reaches into it.
116
+ */
117
+ export function editableScopeProps(scope) {
118
+ return { "data-editable-scope": scope };
119
+ }
@@ -1,4 +1,5 @@
1
1
  import { type EditorProxyOptions } from "./proxy.ts";
2
+ export { trailingSlashRedirect, editorPreviewRewrite } from "./proxy.ts";
2
3
  /**
3
4
  * Options for the editor middleware factory.
4
5
  *
@@ -1,4 +1,10 @@
1
1
  import { createEditorProxy } from "./proxy.js";
2
+ /*
3
+ * A Next 15 site reaches for this entry point, and a Next 15 site that already
4
+ * has a `middleware.ts` needs the two halves rather than the whole factory.
5
+ * Re-exported here so composing does not mean importing from two subpaths.
6
+ */
7
+ export { trailingSlashRedirect, editorPreviewRewrite } from "./proxy.js";
2
8
  /**
3
9
  * Create a Next.js middleware function that rewrites editor/draft requests
4
10
  * to a dynamic preview route, keeping the main page route fully static.
package/dist/proxy.d.ts CHANGED
@@ -93,3 +93,29 @@ export declare function createEditorProxy(options?: EditorProxyOptions): {
93
93
  matcher: string[];
94
94
  };
95
95
  };
96
+ /**
97
+ * Re-issue the trailing-slash redirect that `skipTrailingSlashRedirect` turned
98
+ * off, or `null` when the URL is already canonical.
99
+ *
100
+ * Exported because a site that already has a `middleware.ts` cannot use
101
+ * `createEditorProxy` as its whole middleware and has to compose — and the one
102
+ * line worth not writing twice is the first one below.
103
+ *
104
+ * ```ts
105
+ * export function middleware(request: NextRequest) {
106
+ * const canonical = trailingSlashRedirect(request)
107
+ * if (canonical) return canonical
108
+ * return editorPreviewRewrite(request) ?? myOwnMiddleware(request)
109
+ * }
110
+ * ```
111
+ */
112
+ export declare function trailingSlashRedirect(request: NextRequest): NextResponse | null;
113
+ /**
114
+ * Rewrite an editor request to the dynamic preview route, or `null` when the
115
+ * request is not one — which is the caller's cue to run its own middleware
116
+ * rather than a decision to pass the request through.
117
+ *
118
+ * The half of `createEditorProxy` a site with its own middleware needs; see
119
+ * {@link trailingSlashRedirect} for the composition.
120
+ */
121
+ export declare function editorPreviewRewrite(request: NextRequest, options?: Pick<EditorProxyOptions, "previewRoute" | "editorParam" | "draftCookie">): NextResponse | null;
package/dist/proxy.js CHANGED
@@ -33,8 +33,6 @@ export { DEFAULT_PREVIEW_ROUTE, buildEditorMatcher } from "./editor-matcher.js";
33
33
  */
34
34
  export function createEditorProxy(options) {
35
35
  const previewRoute = options?.previewRoute ?? DEFAULT_PREVIEW_ROUTE;
36
- const editorParam = options?.editorParam ?? "__editor";
37
- const draftCookie = options?.draftCookie === undefined ? "__prerender_bypass" : options.draftCookie;
38
36
  const trailingSlash = options?.trailingSlash ?? false;
39
37
  function proxy(request) {
40
38
  /*
@@ -45,32 +43,80 @@ export function createEditorProxy(options) {
45
43
  * publishes only at `/about/`.
46
44
  */
47
45
  if (trailingSlash) {
48
- /*
49
- * Built from `request.url`, not from `request.nextUrl.clone()`. NextURL
50
- * normalises a trailing slash back *off* when it stringifies, so a
51
- * redirect built from a clone points at the URL it is trying to leave —
52
- * a redirect loop, and one that only a browser would ever have shown us.
53
- * A plain URL does no normalising. It also keeps `basePath`, which
54
- * `nextUrl.pathname` has already stripped.
55
- */
56
- const url = new URL(request.url);
57
- if (url.pathname.length > 1 && !url.pathname.endsWith("/")) {
58
- url.pathname = `${url.pathname}/`;
59
- // 308, not 307: the method is preserved *and* the redirect is
60
- // permanent, which is what Next's own trailing-slash redirect sends
61
- // and what the site's existing search rankings were built on.
62
- return NextResponse.redirect(url, 308);
63
- }
64
- }
65
- const isEditor = request.nextUrl.searchParams.get(editorParam) === "1";
66
- const hasDraftCookie = draftCookie !== false && request.cookies.has(draftCookie);
67
- if (isEditor || hasDraftCookie) {
68
- const url = request.nextUrl.clone();
69
- url.pathname = `${previewRoute}${url.pathname}`;
70
- return NextResponse.rewrite(url);
46
+ const redirect = trailingSlashRedirect(request);
47
+ if (redirect)
48
+ return redirect;
71
49
  }
72
- return NextResponse.next();
50
+ return editorPreviewRewrite(request, options) ?? NextResponse.next();
73
51
  }
74
52
  const config = { matcher: [buildEditorMatcher(previewRoute)] };
75
53
  return { proxy, config };
76
54
  }
55
+ /**
56
+ * Re-issue the trailing-slash redirect that `skipTrailingSlashRedirect` turned
57
+ * off, or `null` when the URL is already canonical.
58
+ *
59
+ * Exported because a site that already has a `middleware.ts` cannot use
60
+ * `createEditorProxy` as its whole middleware and has to compose — and the one
61
+ * line worth not writing twice is the first one below.
62
+ *
63
+ * ```ts
64
+ * export function middleware(request: NextRequest) {
65
+ * const canonical = trailingSlashRedirect(request)
66
+ * if (canonical) return canonical
67
+ * return editorPreviewRewrite(request) ?? myOwnMiddleware(request)
68
+ * }
69
+ * ```
70
+ */
71
+ export function trailingSlashRedirect(request) {
72
+ /*
73
+ * Built from `request.url`, not from `request.nextUrl.clone()`. NextURL
74
+ * normalises a trailing slash back *off* when it stringifies, so a redirect
75
+ * built from a clone points at the URL it is trying to leave — every page on
76
+ * the site enters an infinite redirect, and only in a browser: `curl`
77
+ * without `-L` sees one perfectly ordinary 308. A plain URL does no
78
+ * normalising. It also keeps `basePath`, which `nextUrl.pathname` has
79
+ * already stripped.
80
+ */
81
+ const url = new URL(request.url);
82
+ const { pathname } = url;
83
+ if (pathname.length <= 1 || pathname.endsWith("/"))
84
+ return null;
85
+ /*
86
+ * The set that must never gain a trailing slash. `createEditorProxy`'s own
87
+ * matcher already excludes all of it, so these are no-ops there — they are
88
+ * for the hand-composed case, where the site's matcher is its own and is
89
+ * usually wider. Redirecting `/api/editor/blocks` to `/api/editor/blocks/`
90
+ * here would re-create, by hand, the exact 308 that
91
+ * `skipTrailingSlashRedirect` was turned on to stop.
92
+ */
93
+ if (pathname.startsWith("/api/") || pathname.startsWith("/_next/"))
94
+ return null;
95
+ if (pathname.slice(pathname.lastIndexOf("/") + 1).includes("."))
96
+ return null;
97
+ url.pathname = `${pathname}/`;
98
+ // 308, not 307: the method is preserved *and* the redirect is permanent,
99
+ // which is what Next's own trailing-slash redirect sends and what the site's
100
+ // existing search rankings were built on.
101
+ return NextResponse.redirect(url, 308);
102
+ }
103
+ /**
104
+ * Rewrite an editor request to the dynamic preview route, or `null` when the
105
+ * request is not one — which is the caller's cue to run its own middleware
106
+ * rather than a decision to pass the request through.
107
+ *
108
+ * The half of `createEditorProxy` a site with its own middleware needs; see
109
+ * {@link trailingSlashRedirect} for the composition.
110
+ */
111
+ export function editorPreviewRewrite(request, options) {
112
+ const previewRoute = options?.previewRoute ?? DEFAULT_PREVIEW_ROUTE;
113
+ const editorParam = options?.editorParam ?? "__editor";
114
+ const draftCookie = options?.draftCookie === undefined ? "__prerender_bypass" : options.draftCookie;
115
+ const isEditor = request.nextUrl.searchParams.get(editorParam) === "1";
116
+ const hasDraftCookie = draftCookie !== false && request.cookies.has(draftCookie);
117
+ if (!isEditor && !hasDraftCookie)
118
+ return null;
119
+ const url = request.nextUrl.clone();
120
+ url.pathname = `${previewRoute}${url.pathname}`;
121
+ return NextResponse.rewrite(url);
122
+ }
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { NextRequest } from "next/server";
4
- import { buildEditorMatcher, createEditorProxy, DEFAULT_PREVIEW_ROUTE } from "./proxy.js";
3
+ import { NextRequest, NextResponse } from "next/server";
4
+ import { buildEditorMatcher, createEditorProxy, editorPreviewRewrite, trailingSlashRedirect, DEFAULT_PREVIEW_ROUTE } from "./proxy.js";
5
5
  import { createEditorMiddleware } from "./middleware.js";
6
6
  /*
7
7
  * The rewrite that keeps the published route static and sends editor traffic to
@@ -121,3 +121,62 @@ test("draftCookie: false leaves Next's draft cookie to whoever else is using it"
121
121
  assert.equal(rewriteOf(proxy(request("https://site.test/about", "__prerender_bypass=abc"))), null, "a Sanity or Contentful preview must not be hijacked into Avocado's route");
122
122
  assert.equal(rewriteOf(proxy(request("https://site.test/about?__editor=1"))), "https://site.test/preview-draft/about?__editor=1", "the explicit editor parameter still works — that is the whole point of the opt-out");
123
123
  });
124
+ /*
125
+ * The two halves, on their own.
126
+ *
127
+ * A site that already has a `middleware.ts` cannot hand the whole file to
128
+ * `createEditorProxy` and has to compose — which until now meant re-deriving
129
+ * the trailing-slash redirect from the docs. One integration did, and wrote
130
+ * `request.nextUrl.clone()`, because that is what every Next example uses for
131
+ * a URL you intend to modify. A `NextURL` re-applies trailing-slash
132
+ * normalisation when it serialises, so the redirect stripped the slash it had
133
+ * just added and pointed at the URL it came from: every page on the site in an
134
+ * infinite redirect, reproducing only in a browser. `curl` without `-L` sees
135
+ * one perfectly ordinary 308, which is how it survived a session and a half.
136
+ */
137
+ test("the redirect is built from request.url, not from a NextURL that would undo it", () => {
138
+ const response = trailingSlashRedirect(request("https://site.test/bern"));
139
+ assert.equal(response?.status, 308);
140
+ assert.equal(response?.headers.get("location"), "https://site.test/bern/", "a location without the slash is the loop: the browser asks again and gets the same answer");
141
+ });
142
+ test("an already-canonical URL, the root, and a file are left alone", () => {
143
+ assert.equal(trailingSlashRedirect(request("https://site.test/bern/")), null);
144
+ assert.equal(trailingSlashRedirect(request("https://site.test/")), null, "redirecting / never terminates");
145
+ assert.equal(trailingSlashRedirect(request("https://site.test/logo.svg")), null);
146
+ });
147
+ test("the API routes are never redirected, whatever the caller's matcher says", () => {
148
+ /*
149
+ * The proxy's own matcher excludes these, but a hand-composed middleware
150
+ * brings its own matcher and it is usually wider. A slash added here
151
+ * re-creates by hand the exact 308 on `/api/editor/*` that
152
+ * `skipTrailingSlashRedirect` was turned on to stop — and the symptom is a
153
+ * CORS error that names nothing.
154
+ */
155
+ assert.equal(trailingSlashRedirect(request("https://site.test/api/editor/blocks")), null);
156
+ assert.equal(trailingSlashRedirect(request("https://site.test/_next/static/chunk")), null);
157
+ });
158
+ test("the redirect keeps the query string when called directly too", () => {
159
+ const response = trailingSlashRedirect(request("https://site.test/bern?__editor=1"));
160
+ assert.equal(response?.headers.get("location"), "https://site.test/bern/?__editor=1");
161
+ });
162
+ test("editorPreviewRewrite answers null for a visitor, so the caller runs its own middleware", () => {
163
+ assert.equal(editorPreviewRewrite(request("https://site.test/about")), null, "null is 'not mine', not 'pass this through' — a composing caller has its own work to do");
164
+ assert.equal(rewriteOf(editorPreviewRewrite(request("https://site.test/about?__editor=1"))), "https://site.test/preview-draft/about?__editor=1");
165
+ });
166
+ test("the composed form behaves like the factory it was extracted from", () => {
167
+ const { proxy } = createEditorProxy({ trailingSlash: true });
168
+ const compose = (r) => trailingSlashRedirect(r) ?? editorPreviewRewrite(r) ?? NextResponse.next();
169
+ for (const url of [
170
+ "https://site.test/bern",
171
+ "https://site.test/bern/",
172
+ "https://site.test/bern?__editor=1",
173
+ "https://site.test/bern/?__editor=1",
174
+ "https://site.test/"
175
+ ]) {
176
+ const fromFactory = proxy(request(url));
177
+ const fromParts = compose(request(url));
178
+ assert.equal(fromParts.status, fromFactory.status, url);
179
+ assert.equal(fromParts.headers.get("location"), fromFactory.headers.get("location"), url);
180
+ assert.equal(rewriteOf(fromParts), rewriteOf(fromFactory), url);
181
+ }
182
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/site-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -122,16 +122,16 @@
122
122
  ],
123
123
  "dependencies": {
124
124
  "zod": "^4.3.6",
125
- "@avocadostudio-ai/blocks": "^0.5.1",
126
- "@avocadostudio-ai/preview-adapter": "^0.5.1",
127
- "@avocadostudio-ai/shared": "^0.5.1"
125
+ "@avocadostudio-ai/blocks": "^0.6.0",
126
+ "@avocadostudio-ai/preview-adapter": "^0.6.0",
127
+ "@avocadostudio-ai/shared": "^0.6.0"
128
128
  },
129
129
  "peerDependencies": {
130
130
  "next": ">=15.0.0",
131
131
  "react": ">=19.0.0",
132
132
  "react-dom": ">=19.0.0",
133
133
  "better-sqlite3": ">=12.0.0",
134
- "@avocadostudio-ai/orchestrator-core": "^0.5.1"
134
+ "@avocadostudio-ai/orchestrator-core": "^0.6.0"
135
135
  },
136
136
  "peerDependenciesMeta": {
137
137
  "@avocadostudio-ai/orchestrator-core": {