@avocadostudio-ai/site-sdk 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor-routes.d.ts
CHANGED
|
@@ -27,6 +27,24 @@ 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
|
+
export declare const OPEN_PUBLISH_HINT: string;
|
|
30
48
|
export declare function createPublishHandler(onPublish: OnPublishFn, options?: {
|
|
31
49
|
publishSecret?: string;
|
|
32
50
|
}): {
|
package/dist/editor-routes.js
CHANGED
|
@@ -29,12 +29,45 @@ 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
|
+
export const OPEN_PUBLISH_HINT = "This publish endpoint overwrites the site's content and has no publishSecret configured, " +
|
|
50
|
+
"so it refuses every request under NODE_ENV=production. Set PUBLISH_TOKEN (the same value the " +
|
|
51
|
+
"orchestrator sends as x-publish-token), or pass publishSecret to createEditorApiHandler().";
|
|
52
|
+
let warnedOpenPublish = false;
|
|
32
53
|
export function createPublishHandler(onPublish, options) {
|
|
33
54
|
return {
|
|
34
55
|
OPTIONS: createEditorCorsOptionsHandler(),
|
|
35
56
|
async POST(request) {
|
|
36
57
|
// Verify publish token if configured
|
|
37
58
|
const secret = options?.publishSecret;
|
|
59
|
+
if (!secret) {
|
|
60
|
+
if (process.env.NODE_ENV === "production") {
|
|
61
|
+
const res = new Response(JSON.stringify({ ok: false, error: "unauthorized", reason: OPEN_PUBLISH_HINT }), { status: 401, headers: { "Content-Type": "application/json" } });
|
|
62
|
+
return applyEditorCors(res, request.headers.get("origin"));
|
|
63
|
+
}
|
|
64
|
+
// Development: publishing to your own machine is the point, so this
|
|
65
|
+
// stays open — but it is the same code path that ships, so say so once.
|
|
66
|
+
if (!warnedOpenPublish) {
|
|
67
|
+
warnedOpenPublish = true;
|
|
68
|
+
console.warn(`[avocado] ${OPEN_PUBLISH_HINT}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
38
71
|
if (secret) {
|
|
39
72
|
const provided = request.headers.get("x-publish-token")?.trim();
|
|
40
73
|
if (!provided || provided !== secret) {
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/site-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
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/preview-adapter": "^0.
|
|
151
|
-
"@avocadostudio-ai/
|
|
152
|
-
"@avocadostudio-ai/richtext": "^0.
|
|
153
|
-
"@avocadostudio-ai/
|
|
150
|
+
"@avocadostudio-ai/preview-adapter": "^0.11.0",
|
|
151
|
+
"@avocadostudio-ai/shared": "^0.11.0",
|
|
152
|
+
"@avocadostudio-ai/richtext": "^0.11.0",
|
|
153
|
+
"@avocadostudio-ai/blocks": "^0.11.0"
|
|
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.0"
|
|
161
161
|
},
|
|
162
162
|
"peerDependenciesMeta": {
|
|
163
163
|
"@avocadostudio-ai/orchestrator-core": {
|