@avocadostudio-ai/site-sdk 0.10.0 → 0.11.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 +11 -1
- package/dist/editor-api-handler-core.d.ts +10 -0
- package/dist/editor-api-handler-core.js +9 -1
- package/dist/editor-routes.d.ts +74 -0
- package/dist/editor-routes.js +119 -0
- package/dist/publish-handlers/json-file.d.ts +17 -0
- package/dist/publish-handlers/json-file.js +17 -2
- package/dist/routes-core.d.ts +2 -1
- package/dist/routes-core.js +2 -0
- package/dist/routes.d.ts +2 -1
- package/dist/routes.js +2 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -133,6 +133,16 @@ export const { GET, POST, OPTIONS } = createEditorApiHandler({
|
|
|
133
133
|
})
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
**A publish may not remove every page.** The route used to validate shape and
|
|
137
|
+
nothing else — `[]` is an array, so a publish that deleted the whole site was
|
|
138
|
+
indistinguishable from one that fixed a heading, and an empty `pages` is
|
|
139
|
+
overwhelmingly a client that failed to load its own state rather than somebody
|
|
140
|
+
deleting nine pages on purpose. Such a publish is refused with `409`; removing
|
|
141
|
+
*some* pages stays an ordinary edit. To empty a site deliberately, send
|
|
142
|
+
`"allowDelete": true` in the body. Set `maxPagesRemoved` if you want a tighter
|
|
143
|
+
bound than "not all of them" — it is measured against your own `getPages`, so
|
|
144
|
+
it applies only when that read succeeds.
|
|
145
|
+
|
|
136
146
|
### 5. Add styles
|
|
137
147
|
|
|
138
148
|
```tsx
|
|
@@ -247,7 +257,7 @@ Your site exposes these endpoints via `createEditorApiHandler`:
|
|
|
247
257
|
| `/api/editor/draft/disable` | GET | Disable draft mode |
|
|
248
258
|
| `/api/editor/blocks` | GET | Return block manifest (schemas + defaults) |
|
|
249
259
|
| `/api/editor/pages` | GET | Return all published pages |
|
|
250
|
-
| `/api/editor/publish` | POST | Receive pages from editor, persist to CMS |
|
|
260
|
+
| `/api/editor/publish` | POST | Receive pages from editor, persist to CMS. Refuses a publish that removes every page unless the body sets `allowDelete: true`. |
|
|
251
261
|
|
|
252
262
|
## Library mode mounts two handlers, not one
|
|
253
263
|
|
|
@@ -46,6 +46,16 @@ export interface EditorApiHandlerConfig {
|
|
|
46
46
|
onPublish?: OnPublishFn;
|
|
47
47
|
/** Secret token required for publish requests. Checked against x-publish-token header. */
|
|
48
48
|
publishSecret?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Refuse a publish that removes more than this many pages, unless the body
|
|
51
|
+
* carries `allowDelete: true`.
|
|
52
|
+
*
|
|
53
|
+
* A publish that removes *every* page is refused whatever this is set to —
|
|
54
|
+
* see `checkDestructivePublish`. This option exists for sites that want a
|
|
55
|
+
* tighter bound than "not all of them", and it is measured against
|
|
56
|
+
* `getPages`, so it is only enforceable when that read succeeds.
|
|
57
|
+
*/
|
|
58
|
+
maxPagesRemoved?: number;
|
|
49
59
|
}
|
|
50
60
|
export interface EditorApiCoreConfig extends EditorApiHandlerConfig {
|
|
51
61
|
/**
|
|
@@ -36,7 +36,15 @@ export function createEditorApiHandlerCore(config) {
|
|
|
36
36
|
const blocksHandler = createBlocksHandler(manifestBuilder ? { getManifest: manifestBuilder } : undefined);
|
|
37
37
|
const pagesHandler = createPagesHandler(config.getPages, config.getSiteConfig);
|
|
38
38
|
const publishHandler = config.onPublish
|
|
39
|
-
? createPublishHandler(config.onPublish, {
|
|
39
|
+
? createPublishHandler(config.onPublish, {
|
|
40
|
+
publishSecret: config.publishSecret,
|
|
41
|
+
// The same getter `/api/editor/pages` serves, reused as the
|
|
42
|
+
// destructive-publish baseline. Every route built through this handler
|
|
43
|
+
// has one, so the guard can count what a publish would remove rather
|
|
44
|
+
// than only noticing that it removes everything.
|
|
45
|
+
getPages: config.getPages,
|
|
46
|
+
maxPagesRemoved: config.maxPagesRemoved
|
|
47
|
+
})
|
|
40
48
|
: null;
|
|
41
49
|
/*
|
|
42
50
|
* `/api/editor/draft/disable` is two segments, so the fallback cannot simply
|
package/dist/editor-routes.d.ts
CHANGED
|
@@ -27,8 +27,82 @@ export declare function createPagesHandler(getPages: () => PageDoc[] | Promise<P
|
|
|
27
27
|
GET: (request: Request) => Promise<Response>;
|
|
28
28
|
OPTIONS: (request: Request) => Response;
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Why an unconfigured publish endpoint is refused in production rather than
|
|
32
|
+
* left open.
|
|
33
|
+
*
|
|
34
|
+
* `publishSecret` was optional, and every scaffold and example wired it as
|
|
35
|
+
* `process.env.PUBLISH_TOKEN?.trim() || undefined` — a variable none of them
|
|
36
|
+
* generated, mentioned in their `.env.local`, or required. So the value was
|
|
37
|
+
* always `undefined`, `if (secret)` was always false, and `POST
|
|
38
|
+
* /api/editor/publish` accepted any caller's `pages` array and overwrote the
|
|
39
|
+
* site's content with it. A deployment that had followed its own README to the
|
|
40
|
+
* letter, `ACCESS_PASSWORD_HASH` and all, was gated on `/api/avocado/*` and
|
|
41
|
+
* wide open here: the two route groups are different handlers, and only one of
|
|
42
|
+
* them had ever been asked about auth.
|
|
43
|
+
*
|
|
44
|
+
* An optional guard on a write endpoint is not a guard. This one now fails
|
|
45
|
+
* closed wherever it matters and says which variable opens it.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Why a publish that empties the site is refused.
|
|
49
|
+
*
|
|
50
|
+
* The only validation this route ever had was shape: `Array.isArray(body.pages)`.
|
|
51
|
+
* `[]` is an array, so a publish that removes every page was indistinguishable
|
|
52
|
+
* from one that fixes a heading. A clean-room run at 0.11.0 sent
|
|
53
|
+
* `{"pages":[]}` with the token the scaffold itself had generated, under
|
|
54
|
+
* `NODE_ENV=production`, and got `{"ok":true,"slugs":[]}` back: nine pages
|
|
55
|
+
* gone, every URL 404, no backup and no confirmation.
|
|
56
|
+
*
|
|
57
|
+
* The reason this is a guard rather than a warning is what actually produces an
|
|
58
|
+
* empty array. It is not somebody deleting their site one page at a time — it
|
|
59
|
+
* is a client whose state failed to load publishing what it thinks it has.
|
|
60
|
+
* Losing a site to a failed fetch is not a decision anybody made.
|
|
61
|
+
*
|
|
62
|
+
* So the rule is the narrowest one that holds: **a publish may not remove every
|
|
63
|
+
* page.** Deleting one page is an ordinary edit and stays ordinary; deleting
|
|
64
|
+
* the last one needs `allowDelete: true` in the body, which is a thing somebody
|
|
65
|
+
* types on purpose and cannot arrive at by omission. Hosts that want a tighter
|
|
66
|
+
* bound than "not all of them" set `maxPagesRemoved`.
|
|
67
|
+
*/
|
|
68
|
+
export declare const DESTRUCTIVE_PUBLISH_HINT: string;
|
|
69
|
+
export type PublishGuardResult = {
|
|
70
|
+
refused: false;
|
|
71
|
+
} | {
|
|
72
|
+
refused: true;
|
|
73
|
+
reason: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Decide whether a publish is destructive enough to refuse.
|
|
77
|
+
*
|
|
78
|
+
* Exported because it is the whole of the rule, and a rule nobody can test in
|
|
79
|
+
* isolation is a rule that drifts. `before` is `undefined` when the caller has
|
|
80
|
+
* no way to read the current state — `createPublishHandler` used directly,
|
|
81
|
+
* rather than through `createEditorApiHandler`, which always has `getPages`.
|
|
82
|
+
* Without a baseline an empty publish is still refused: not knowing what is
|
|
83
|
+
* there is not a reason to overwrite it with nothing.
|
|
84
|
+
*/
|
|
85
|
+
export declare function checkDestructivePublish(args: {
|
|
86
|
+
next: readonly {
|
|
87
|
+
slug?: string;
|
|
88
|
+
}[];
|
|
89
|
+
before: readonly {
|
|
90
|
+
slug?: string;
|
|
91
|
+
}[] | undefined;
|
|
92
|
+
allowDelete: boolean;
|
|
93
|
+
maxPagesRemoved?: number;
|
|
94
|
+
}): PublishGuardResult;
|
|
95
|
+
export declare const OPEN_PUBLISH_HINT: string;
|
|
30
96
|
export declare function createPublishHandler(onPublish: OnPublishFn, options?: {
|
|
31
97
|
publishSecret?: string;
|
|
98
|
+
/**
|
|
99
|
+
* The currently published pages, used only as the destructive-publish
|
|
100
|
+
* baseline. `createEditorApiHandler` passes its own `getPages`; a handler
|
|
101
|
+
* built by hand may omit it and gets the baseline-free rule.
|
|
102
|
+
*/
|
|
103
|
+
getPages?: () => PageDoc[] | Promise<PageDoc[]>;
|
|
104
|
+
/** Refuse a publish removing more than this many pages. See `checkDestructivePublish`. */
|
|
105
|
+
maxPagesRemoved?: number;
|
|
32
106
|
}): {
|
|
33
107
|
POST: (request: Request) => Promise<Response>;
|
|
34
108
|
OPTIONS: (request: Request) => Response;
|
package/dist/editor-routes.js
CHANGED
|
@@ -29,12 +29,105 @@ export function createPagesHandler(getPages, getSiteConfig) {
|
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Why an unconfigured publish endpoint is refused in production rather than
|
|
34
|
+
* left open.
|
|
35
|
+
*
|
|
36
|
+
* `publishSecret` was optional, and every scaffold and example wired it as
|
|
37
|
+
* `process.env.PUBLISH_TOKEN?.trim() || undefined` — a variable none of them
|
|
38
|
+
* generated, mentioned in their `.env.local`, or required. So the value was
|
|
39
|
+
* always `undefined`, `if (secret)` was always false, and `POST
|
|
40
|
+
* /api/editor/publish` accepted any caller's `pages` array and overwrote the
|
|
41
|
+
* site's content with it. A deployment that had followed its own README to the
|
|
42
|
+
* letter, `ACCESS_PASSWORD_HASH` and all, was gated on `/api/avocado/*` and
|
|
43
|
+
* wide open here: the two route groups are different handlers, and only one of
|
|
44
|
+
* them had ever been asked about auth.
|
|
45
|
+
*
|
|
46
|
+
* An optional guard on a write endpoint is not a guard. This one now fails
|
|
47
|
+
* closed wherever it matters and says which variable opens it.
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* Why a publish that empties the site is refused.
|
|
51
|
+
*
|
|
52
|
+
* The only validation this route ever had was shape: `Array.isArray(body.pages)`.
|
|
53
|
+
* `[]` is an array, so a publish that removes every page was indistinguishable
|
|
54
|
+
* from one that fixes a heading. A clean-room run at 0.11.0 sent
|
|
55
|
+
* `{"pages":[]}` with the token the scaffold itself had generated, under
|
|
56
|
+
* `NODE_ENV=production`, and got `{"ok":true,"slugs":[]}` back: nine pages
|
|
57
|
+
* gone, every URL 404, no backup and no confirmation.
|
|
58
|
+
*
|
|
59
|
+
* The reason this is a guard rather than a warning is what actually produces an
|
|
60
|
+
* empty array. It is not somebody deleting their site one page at a time — it
|
|
61
|
+
* is a client whose state failed to load publishing what it thinks it has.
|
|
62
|
+
* Losing a site to a failed fetch is not a decision anybody made.
|
|
63
|
+
*
|
|
64
|
+
* So the rule is the narrowest one that holds: **a publish may not remove every
|
|
65
|
+
* page.** Deleting one page is an ordinary edit and stays ordinary; deleting
|
|
66
|
+
* the last one needs `allowDelete: true` in the body, which is a thing somebody
|
|
67
|
+
* types on purpose and cannot arrive at by omission. Hosts that want a tighter
|
|
68
|
+
* bound than "not all of them" set `maxPagesRemoved`.
|
|
69
|
+
*/
|
|
70
|
+
export const DESTRUCTIVE_PUBLISH_HINT = "This publish would remove every page from the site. If that is intended, resend it with " +
|
|
71
|
+
'`"allowDelete": true` in the body. An empty `pages` array is most often a client that failed ' +
|
|
72
|
+
"to load its own state, so it is refused by default.";
|
|
73
|
+
/**
|
|
74
|
+
* Decide whether a publish is destructive enough to refuse.
|
|
75
|
+
*
|
|
76
|
+
* Exported because it is the whole of the rule, and a rule nobody can test in
|
|
77
|
+
* isolation is a rule that drifts. `before` is `undefined` when the caller has
|
|
78
|
+
* no way to read the current state — `createPublishHandler` used directly,
|
|
79
|
+
* rather than through `createEditorApiHandler`, which always has `getPages`.
|
|
80
|
+
* Without a baseline an empty publish is still refused: not knowing what is
|
|
81
|
+
* there is not a reason to overwrite it with nothing.
|
|
82
|
+
*/
|
|
83
|
+
export function checkDestructivePublish(args) {
|
|
84
|
+
const { next, before, allowDelete, maxPagesRemoved } = args;
|
|
85
|
+
if (allowDelete)
|
|
86
|
+
return { refused: false };
|
|
87
|
+
if (next.length === 0) {
|
|
88
|
+
// A site that is already empty cannot be emptied. Publishing nothing onto
|
|
89
|
+
// nothing is a no-op, and refusing it would fail a brand-new integration on
|
|
90
|
+
// its very first publish.
|
|
91
|
+
if (before && before.length === 0)
|
|
92
|
+
return { refused: false };
|
|
93
|
+
const had = before ? `${before.length} page${before.length === 1 ? "" : "s"}` : "its pages";
|
|
94
|
+
return { refused: true, reason: `${DESTRUCTIVE_PUBLISH_HINT} The site currently has ${had}.` };
|
|
95
|
+
}
|
|
96
|
+
if (maxPagesRemoved === undefined || !before)
|
|
97
|
+
return { refused: false };
|
|
98
|
+
const keeping = new Set(next.map((p) => p.slug));
|
|
99
|
+
const removed = before.filter((p) => !keeping.has(p.slug));
|
|
100
|
+
if (removed.length <= maxPagesRemoved)
|
|
101
|
+
return { refused: false };
|
|
102
|
+
return {
|
|
103
|
+
refused: true,
|
|
104
|
+
reason: `This publish would remove ${removed.length} pages (${removed.map((p) => p.slug).join(", ")}), ` +
|
|
105
|
+
`and this site is configured to refuse a publish removing more than ${maxPagesRemoved}. ` +
|
|
106
|
+
'Resend with `"allowDelete": true` if that is intended.'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export const OPEN_PUBLISH_HINT = "This publish endpoint overwrites the site's content and has no publishSecret configured, " +
|
|
110
|
+
"so it refuses every request under NODE_ENV=production. Set PUBLISH_TOKEN (the same value the " +
|
|
111
|
+
"orchestrator sends as x-publish-token), or pass publishSecret to createEditorApiHandler().";
|
|
112
|
+
let warnedOpenPublish = false;
|
|
32
113
|
export function createPublishHandler(onPublish, options) {
|
|
33
114
|
return {
|
|
34
115
|
OPTIONS: createEditorCorsOptionsHandler(),
|
|
35
116
|
async POST(request) {
|
|
36
117
|
// Verify publish token if configured
|
|
37
118
|
const secret = options?.publishSecret;
|
|
119
|
+
if (!secret) {
|
|
120
|
+
if (process.env.NODE_ENV === "production") {
|
|
121
|
+
const res = new Response(JSON.stringify({ ok: false, error: "unauthorized", reason: OPEN_PUBLISH_HINT }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
122
|
+
return applyEditorCors(res, request.headers.get("origin"));
|
|
123
|
+
}
|
|
124
|
+
// Development: publishing to your own machine is the point, so this
|
|
125
|
+
// stays open — but it is the same code path that ships, so say so once.
|
|
126
|
+
if (!warnedOpenPublish) {
|
|
127
|
+
warnedOpenPublish = true;
|
|
128
|
+
console.warn(`[avocado] ${OPEN_PUBLISH_HINT}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
38
131
|
if (secret) {
|
|
39
132
|
const provided = request.headers.get("x-publish-token")?.trim();
|
|
40
133
|
if (!provided || provided !== secret) {
|
|
@@ -49,6 +142,32 @@ export function createPublishHandler(onPublish, options) {
|
|
|
49
142
|
return applyEditorCors(res, request.headers.get("origin"));
|
|
50
143
|
}
|
|
51
144
|
const pages = body.pages;
|
|
145
|
+
/*
|
|
146
|
+
* The baseline is best-effort on purpose. `getPages` reaches a CMS on
|
|
147
|
+
* some integrations, and a publish is not the moment to fail because a
|
|
148
|
+
* read timed out — but it is also not the moment to assume the site was
|
|
149
|
+
* empty. An unreadable baseline becomes `undefined`, which still
|
|
150
|
+
* refuses an empty publish and simply cannot enforce `maxPagesRemoved`.
|
|
151
|
+
*/
|
|
152
|
+
let before;
|
|
153
|
+
if (options?.getPages) {
|
|
154
|
+
try {
|
|
155
|
+
before = await options.getPages();
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
before = undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const guard = checkDestructivePublish({
|
|
162
|
+
next: pages,
|
|
163
|
+
before,
|
|
164
|
+
allowDelete: body.allowDelete === true,
|
|
165
|
+
maxPagesRemoved: options?.maxPagesRemoved
|
|
166
|
+
});
|
|
167
|
+
if (guard.refused) {
|
|
168
|
+
const res = new Response(JSON.stringify({ ok: false, error: "refused", reason: guard.reason }), { status: 409, headers: { "Content-Type": "application/json" } });
|
|
169
|
+
return applyEditorCors(res, request.headers.get("origin"));
|
|
170
|
+
}
|
|
52
171
|
const config = (body.siteConfig ?? {});
|
|
53
172
|
const context = { assets: body.assets };
|
|
54
173
|
const result = await onPublish(pages, config, context);
|
|
@@ -2,6 +2,21 @@ import type { OnPublishFn } from "../editor-routes.ts";
|
|
|
2
2
|
/**
|
|
3
3
|
* Publish handler that writes PageDoc[] to a local JSON file.
|
|
4
4
|
*
|
|
5
|
+
* `shape` decides what lands in the file, and it has to match whatever reads
|
|
6
|
+
* it back. It used to be neither a parameter nor a choice: the handler always
|
|
7
|
+
* wrote `{ pages, siteConfig }` while this docstring, three of the four call
|
|
8
|
+
* sites, and `jsonFileAdapter.onPublish` all said `PageDoc[]`. Publishing then
|
|
9
|
+
* broke the site that had just published — the scaffold's `lib/content.ts`
|
|
10
|
+
* does `JSON.parse(...) as PageDoc[]` and its `.find` threw on an object, so
|
|
11
|
+
* every page answered 500, and `examples/sample-site` reads a slug-keyed
|
|
12
|
+
* object and quietly lost all nine pages. Only `apps/site` survived, because
|
|
13
|
+
* its reader had already grown a branch for both shapes.
|
|
14
|
+
*
|
|
15
|
+
* So: `array` is the default, because it is what the readers and the adapter
|
|
16
|
+
* expect. Pass `wrapper` when the reader wants `siteConfig` in the same file —
|
|
17
|
+
* a plain array has nowhere to put it, so site-level settings (name, logo,
|
|
18
|
+
* nav) are dropped on publish.
|
|
19
|
+
*
|
|
5
20
|
* When `publicDir` is provided, inline assets (base64 images from the
|
|
6
21
|
* orchestrator) are written to disk and their localhost URLs are rewritten
|
|
7
22
|
* to relative paths in the JSON output.
|
|
@@ -21,4 +36,6 @@ import type { OnPublishFn } from "../editor-routes.ts";
|
|
|
21
36
|
export declare function createJsonFilePublishHandler(filePath: string, options?: {
|
|
22
37
|
publicDir?: string;
|
|
23
38
|
imagePathPrefix?: string;
|
|
39
|
+
/** `array` writes `PageDoc[]` (default). `wrapper` writes `{ pages, siteConfig }`. */
|
|
40
|
+
shape?: "array" | "wrapper";
|
|
24
41
|
}): OnPublishFn;
|
|
@@ -3,6 +3,21 @@ import { resolve } from "node:path";
|
|
|
3
3
|
/**
|
|
4
4
|
* Publish handler that writes PageDoc[] to a local JSON file.
|
|
5
5
|
*
|
|
6
|
+
* `shape` decides what lands in the file, and it has to match whatever reads
|
|
7
|
+
* it back. It used to be neither a parameter nor a choice: the handler always
|
|
8
|
+
* wrote `{ pages, siteConfig }` while this docstring, three of the four call
|
|
9
|
+
* sites, and `jsonFileAdapter.onPublish` all said `PageDoc[]`. Publishing then
|
|
10
|
+
* broke the site that had just published — the scaffold's `lib/content.ts`
|
|
11
|
+
* does `JSON.parse(...) as PageDoc[]` and its `.find` threw on an object, so
|
|
12
|
+
* every page answered 500, and `examples/sample-site` reads a slug-keyed
|
|
13
|
+
* object and quietly lost all nine pages. Only `apps/site` survived, because
|
|
14
|
+
* its reader had already grown a branch for both shapes.
|
|
15
|
+
*
|
|
16
|
+
* So: `array` is the default, because it is what the readers and the adapter
|
|
17
|
+
* expect. Pass `wrapper` when the reader wants `siteConfig` in the same file —
|
|
18
|
+
* a plain array has nowhere to put it, so site-level settings (name, logo,
|
|
19
|
+
* nav) are dropped on publish.
|
|
20
|
+
*
|
|
6
21
|
* When `publicDir` is provided, inline assets (base64 images from the
|
|
7
22
|
* orchestrator) are written to disk and their localhost URLs are rewritten
|
|
8
23
|
* to relative paths in the JSON output.
|
|
@@ -33,8 +48,8 @@ export function createJsonFilePublishHandler(filePath, options) {
|
|
|
33
48
|
}
|
|
34
49
|
output = JSON.parse(json);
|
|
35
50
|
}
|
|
36
|
-
const
|
|
37
|
-
await writeFile(filePath,
|
|
51
|
+
const body = options?.shape === "wrapper" ? { pages: output, siteConfig: config } : output;
|
|
52
|
+
await writeFile(filePath, JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
38
53
|
return { ok: true };
|
|
39
54
|
};
|
|
40
55
|
}
|
package/dist/routes-core.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ export type { DraftRouteAdapter } from "./draft-routes-core.ts";
|
|
|
3
3
|
export { createEditorApiHandlerCore } from "./editor-api-handler-core.ts";
|
|
4
4
|
export type { EditorApiHandlerConfig, EditorApiCoreConfig } from "./editor-api-handler-core.ts";
|
|
5
5
|
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.ts";
|
|
6
|
-
export type { OnPublishFn, InlineAsset, PublishContext } from "./editor-routes.ts";
|
|
6
|
+
export type { OnPublishFn, InlineAsset, PublishContext, PublishGuardResult } from "./editor-routes.ts";
|
|
7
|
+
export { checkDestructivePublish, DESTRUCTIVE_PUBLISH_HINT } from "./editor-routes.ts";
|
|
7
8
|
export { isSafeImageUrl, createImageResolver } from "./publish-utils.ts";
|
|
8
9
|
export type { ImageUploader } from "./publish-utils.ts";
|
|
9
10
|
export { createRevalidateHandler } from "./revalidate-handler.ts";
|
package/dist/routes-core.js
CHANGED
|
@@ -13,6 +13,8 @@ export { createDraftEnableHandlerCore, createDraftDisableHandlerCore } from "./d
|
|
|
13
13
|
export { createEditorApiHandlerCore } from "./editor-api-handler-core.js";
|
|
14
14
|
// Individual editor route handlers — already framework-free.
|
|
15
15
|
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.js";
|
|
16
|
+
// The destructive-publish rule, and the text of its refusal.
|
|
17
|
+
export { checkDestructivePublish, DESTRUCTIVE_PUBLISH_HINT } from "./editor-routes.js";
|
|
16
18
|
// Publish utilities (SSRF check, image resolution).
|
|
17
19
|
export { isSafeImageUrl, createImageResolver } from "./publish-utils.js";
|
|
18
20
|
// Revalidation handler factory — takes a `revalidate` callback, so the host's
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.ts";
|
|
2
2
|
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.ts";
|
|
3
|
-
export type { OnPublishFn, InlineAsset, PublishContext } from "./editor-routes.ts";
|
|
3
|
+
export type { OnPublishFn, InlineAsset, PublishContext, PublishGuardResult } from "./editor-routes.ts";
|
|
4
|
+
export { checkDestructivePublish, DESTRUCTIVE_PUBLISH_HINT } from "./editor-routes.ts";
|
|
4
5
|
export { createEditorApiHandler } from "./editor-api-handler.ts";
|
|
5
6
|
export type { EditorApiHandlerConfig } from "./editor-api-handler.ts";
|
|
6
7
|
export { isSafeImageUrl, createImageResolver } from "./publish-utils.ts";
|
package/dist/routes.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.js";
|
|
3
3
|
// Editor route handler factories
|
|
4
4
|
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.js";
|
|
5
|
+
// The destructive-publish rule, and the text of its refusal.
|
|
6
|
+
export { checkDestructivePublish, DESTRUCTIVE_PUBLISH_HINT } from "./editor-routes.js";
|
|
5
7
|
// Catch-all editor API handler
|
|
6
8
|
export { createEditorApiHandler } from "./editor-api-handler.js";
|
|
7
9
|
// Publish utilities (SSRF check, image resolution)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/site-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -147,17 +147,17 @@
|
|
|
147
147
|
],
|
|
148
148
|
"dependencies": {
|
|
149
149
|
"zod": "^4.3.6",
|
|
150
|
-
"@avocadostudio-ai/
|
|
151
|
-
"@avocadostudio-ai/
|
|
152
|
-
"@avocadostudio-ai/
|
|
153
|
-
"@avocadostudio-ai/
|
|
150
|
+
"@avocadostudio-ai/blocks": "^0.11.1",
|
|
151
|
+
"@avocadostudio-ai/richtext": "^0.11.1",
|
|
152
|
+
"@avocadostudio-ai/shared": "^0.11.1",
|
|
153
|
+
"@avocadostudio-ai/preview-adapter": "^0.11.1"
|
|
154
154
|
},
|
|
155
155
|
"peerDependencies": {
|
|
156
156
|
"next": ">=15.0.0",
|
|
157
157
|
"react": ">=19.0.0",
|
|
158
158
|
"react-dom": ">=19.0.0",
|
|
159
159
|
"better-sqlite3": ">=12.0.0",
|
|
160
|
-
"@avocadostudio-ai/orchestrator-core": "^0.
|
|
160
|
+
"@avocadostudio-ai/orchestrator-core": "^0.11.1"
|
|
161
161
|
},
|
|
162
162
|
"peerDependenciesMeta": {
|
|
163
163
|
"@avocadostudio-ai/orchestrator-core": {
|