@avocadostudio-ai/site-sdk 0.1.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/LICENSE +201 -0
- package/README.md +145 -0
- package/dist/cli/register.d.ts +33 -0
- package/dist/cli/register.js +315 -0
- package/dist/create-site-page.d.ts +95 -0
- package/dist/create-site-page.js +127 -0
- package/dist/draft-common.d.ts +4 -0
- package/dist/draft-common.js +17 -0
- package/dist/draft-context-core.d.ts +11 -0
- package/dist/draft-context-core.js +26 -0
- package/dist/draft-context.d.ts +8 -0
- package/dist/draft-context.js +14 -0
- package/dist/draft-fetch.d.ts +14 -0
- package/dist/draft-fetch.js +115 -0
- package/dist/draft-routes-core.d.ts +11 -0
- package/dist/draft-routes-core.js +41 -0
- package/dist/draft-routes.d.ts +2 -0
- package/dist/draft-routes.js +27 -0
- package/dist/draft.d.ts +3 -0
- package/dist/draft.js +6 -0
- package/dist/editor-api-handler.d.ts +59 -0
- package/dist/editor-api-handler.js +89 -0
- package/dist/editor-cors.d.ts +3 -0
- package/dist/editor-cors.js +31 -0
- package/dist/editor-manifest.d.ts +3 -0
- package/dist/editor-manifest.js +65 -0
- package/dist/editor-overlay-inner.d.ts +5 -0
- package/dist/editor-overlay-inner.js +7 -0
- package/dist/editor-overlay.d.ts +4 -0
- package/dist/editor-overlay.js +14 -0
- package/dist/editor-query.d.ts +2 -0
- package/dist/editor-query.js +16 -0
- package/dist/editor-routes.d.ts +35 -0
- package/dist/editor-routes.js +66 -0
- package/dist/editor.d.ts +20 -0
- package/dist/editor.js +22 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/integration-check.d.ts +5 -0
- package/dist/integration-check.js +29 -0
- package/dist/live-preview-blocks.d.ts +5 -0
- package/dist/live-preview-blocks.js +30 -0
- package/dist/manifest-utils.d.ts +17 -0
- package/dist/manifest-utils.js +44 -0
- package/dist/middleware.d.ts +37 -0
- package/dist/middleware.js +34 -0
- package/dist/navigation.d.ts +48 -0
- package/dist/navigation.js +95 -0
- package/dist/publish-handlers/json-file.d.ts +24 -0
- package/dist/publish-handlers/json-file.js +40 -0
- package/dist/publish-utils.d.ts +28 -0
- package/dist/publish-utils.js +92 -0
- package/dist/render-blocks.d.ts +4 -0
- package/dist/render-blocks.js +26 -0
- package/dist/revalidate-handler.d.ts +42 -0
- package/dist/revalidate-handler.js +77 -0
- package/dist/routes.d.ts +10 -0
- package/dist/routes.js +12 -0
- package/dist/server/orchestrator.d.ts +117 -0
- package/dist/server/orchestrator.js +733 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for CMS webhook → ISR revalidation + orchestrator bootstrap routes.
|
|
3
|
+
*
|
|
4
|
+
* All three CMS integrations follow the same pattern:
|
|
5
|
+
* 1. Validate webhook secret
|
|
6
|
+
* 2. Extract slug from CMS-specific body shape
|
|
7
|
+
* 3. Call Next.js revalidatePath()
|
|
8
|
+
* 4. Re-bootstrap orchestrator with fresh content
|
|
9
|
+
*
|
|
10
|
+
* This factory extracts that into a single configurable handler.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* export const POST = createRevalidateHandler({
|
|
15
|
+
* secretEnvVar: "SANITY_WEBHOOK_SECRET",
|
|
16
|
+
* extractSlug: (body) => (body as any)?.slug?.current ?? null,
|
|
17
|
+
* getPages: getSanityPages,
|
|
18
|
+
* siteId: "sanity-site",
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function createRevalidateHandler(config) {
|
|
23
|
+
const { secretEnvVar, extractSlug, getPages, siteId, session = "dev" } = config;
|
|
24
|
+
const secretHeader = config.secretHeader === undefined ? "x-revalidate-secret" : config.secretHeader;
|
|
25
|
+
return async function POST(request) {
|
|
26
|
+
const configuredSecret = process.env[secretEnvVar]?.trim();
|
|
27
|
+
if (!configuredSecret) {
|
|
28
|
+
return Response.json({ error: `${secretEnvVar} not configured` }, { status: 500 });
|
|
29
|
+
}
|
|
30
|
+
// Validate secret: check configured sources in order
|
|
31
|
+
let provided;
|
|
32
|
+
const sources = secretHeader === null ? ["query:secret"]
|
|
33
|
+
: Array.isArray(secretHeader) ? secretHeader
|
|
34
|
+
: [secretHeader];
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
for (const src of sources) {
|
|
37
|
+
if (src.startsWith("query:")) {
|
|
38
|
+
provided = url.searchParams.get(src.slice(6))?.trim() ?? undefined;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
provided = request.headers.get(src)?.trim() ?? undefined;
|
|
42
|
+
}
|
|
43
|
+
if (provided)
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
if (!provided || provided !== configuredSecret) {
|
|
47
|
+
return Response.json({ error: "Invalid secret" }, { status: 401 });
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const body = await request.json();
|
|
51
|
+
const rawSlug = extractSlug(body) ?? "/";
|
|
52
|
+
const path = rawSlug === "/" ? "/" : `/${rawSlug.replace(/^\//, "")}`;
|
|
53
|
+
// ISR revalidation — dynamic import to avoid hard dep on next
|
|
54
|
+
try {
|
|
55
|
+
const { revalidatePath } = await import(/* webpackIgnore: true */ "next/cache");
|
|
56
|
+
revalidatePath(path);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Not running in Next.js or revalidatePath unavailable
|
|
60
|
+
}
|
|
61
|
+
// Re-bootstrap orchestrator with fresh CMS content
|
|
62
|
+
const orchestratorUrl = process.env.ORCHESTRATOR_URL;
|
|
63
|
+
if (orchestratorUrl) {
|
|
64
|
+
const pages = await getPages();
|
|
65
|
+
await fetch(`${orchestratorUrl}/draft/bootstrap`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "content-type": "application/json" },
|
|
68
|
+
body: JSON.stringify({ session, siteId, pages, overwrite: true }),
|
|
69
|
+
}).catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
return Response.json({ revalidated: true, path });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return Response.json({ error: "Invalid request body" }, { status: 400 });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.ts";
|
|
2
|
+
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.ts";
|
|
3
|
+
export type { OnPublishFn, InlineAsset, PublishContext } from "./editor-routes.ts";
|
|
4
|
+
export { createEditorApiHandler } from "./editor-api-handler.ts";
|
|
5
|
+
export type { EditorApiHandlerConfig } from "./editor-api-handler.ts";
|
|
6
|
+
export { isSafeImageUrl, createImageResolver } from "./publish-utils.ts";
|
|
7
|
+
export type { ImageUploader } from "./publish-utils.ts";
|
|
8
|
+
export { createRevalidateHandler } from "./revalidate-handler.ts";
|
|
9
|
+
export type { RevalidateHandlerConfig } from "./revalidate-handler.ts";
|
|
10
|
+
export { getManifestImageFields } from "./manifest-utils.ts";
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Draft route handler factories
|
|
2
|
+
export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.js";
|
|
3
|
+
// Editor route handler factories
|
|
4
|
+
export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.js";
|
|
5
|
+
// Catch-all editor API handler
|
|
6
|
+
export { createEditorApiHandler } from "./editor-api-handler.js";
|
|
7
|
+
// Publish utilities (SSRF check, image resolution)
|
|
8
|
+
export { isSafeImageUrl, createImageResolver } from "./publish-utils.js";
|
|
9
|
+
// Revalidation handler factory
|
|
10
|
+
export { createRevalidateHandler } from "./revalidate-handler.js";
|
|
11
|
+
// Manifest utilities (derive image fields from block manifest)
|
|
12
|
+
export { getManifestImageFields } from "./manifest-utils.js";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type AIProvider, type ModelKey } from "@avocadostudio-ai/orchestrator-core/state/session-state.js";
|
|
2
|
+
import { type Logger } from "@avocadostudio-ai/orchestrator-core/logger.js";
|
|
3
|
+
import type { CmsAdapter } from "@avocadostudio-ai/orchestrator-core/cms/adapter.js";
|
|
4
|
+
export interface CreateOrchestratorConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Provider model overrides. Defaults to env vars (OPENAI_MODEL_*, ANTHROPIC_MODEL_*,
|
|
7
|
+
* GOOGLE_GENAI_MODEL_*). Pass an explicit object to override per-tier model names.
|
|
8
|
+
*/
|
|
9
|
+
modelLookup?: Record<AIProvider, Record<ModelKey, string>>;
|
|
10
|
+
/**
|
|
11
|
+
* Override available providers. Defaults to whichever API keys are present in
|
|
12
|
+
* process.env (OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_GENAI_API_KEY).
|
|
13
|
+
*/
|
|
14
|
+
availableProviders?: AIProvider[];
|
|
15
|
+
/** Pino-shaped logger. Defaults to a console-backed implementation. */
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
/**
|
|
18
|
+
* Allowed CORS origins. Defaults to "*". For production, pass an explicit
|
|
19
|
+
* allow-list (Next.js can also handle CORS via middleware — pass null here
|
|
20
|
+
* to disable the SDK's CORS handling entirely).
|
|
21
|
+
*/
|
|
22
|
+
corsOrigins?: string[] | "*" | null;
|
|
23
|
+
/**
|
|
24
|
+
* Register the default builtin tools (unsplash-search, image-generate,
|
|
25
|
+
* gdrive-browse). Defaults to `false` — opt in only if you want those
|
|
26
|
+
* features, since registering them pulls in sharp + googleapis + image-gen
|
|
27
|
+
* SDKs at module load. Pass `true` for env-gated defaults, or an explicit
|
|
28
|
+
* subset like `["unsplash-search"]`.
|
|
29
|
+
*/
|
|
30
|
+
builtinTools?: boolean | Array<"unsplash-search" | "image-generate" | "gdrive-browse">;
|
|
31
|
+
/**
|
|
32
|
+
* URL path prefix the handler is mounted under. Everything matching this
|
|
33
|
+
* prefix is stripped off `url.pathname` before route matching, so the rest
|
|
34
|
+
* of the handler can reason in terms of `/chat`, `/chat/stream`, etc.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to `/api/avocado` (the catch-all path used in the Next.js
|
|
37
|
+
* example app). Set to `""` if you want to match against the full pathname
|
|
38
|
+
* yourself, or override to e.g. `/api/orchestrator` if you mount the
|
|
39
|
+
* catch-all there.
|
|
40
|
+
*/
|
|
41
|
+
basePath?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Source-of-truth adapter for the site's existing content. Called once per
|
|
44
|
+
* session on the first chat request to seed SQLite with the site's pages.
|
|
45
|
+
* Without an adapter the orchestrator starts with an empty draft, which
|
|
46
|
+
* means the planner has no pages to edit — every "edit the homepage" turn
|
|
47
|
+
* returns `page not found`.
|
|
48
|
+
*
|
|
49
|
+
* Provided implementations:
|
|
50
|
+
* - `jsonFileAdapter({ path })` — read PageDoc[] from a JSON file
|
|
51
|
+
* - `editorApiAdapter({ origin })` — fetch from `${origin}/api/editor/pages`
|
|
52
|
+
*
|
|
53
|
+
* Import from `@avocadostudio-ai/orchestrator-core/cms`.
|
|
54
|
+
*/
|
|
55
|
+
adapter?: CmsAdapter;
|
|
56
|
+
/**
|
|
57
|
+
* Site identifier used to scope session state in SQLite. When `adapter` is
|
|
58
|
+
* set, defaults to `"library"` so the demo-content seed path is bypassed
|
|
59
|
+
* (an unscoped session falls back to the orchestrator's bundled demo pages).
|
|
60
|
+
* Override if you want to run multiple distinct sites against one process.
|
|
61
|
+
*
|
|
62
|
+
* Precedence on incoming requests:
|
|
63
|
+
* - adapter configured: `siteId` (or its `"library"` default) ALWAYS wins
|
|
64
|
+
* over `body.siteId`. The library-mode orchestrator owns its identity.
|
|
65
|
+
* - no adapter: explicit `siteId` wins; otherwise `body.siteId` is used.
|
|
66
|
+
*/
|
|
67
|
+
siteId?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Register the site's block schemas before the orchestrator's first chat
|
|
70
|
+
* request. Use this instead of relying on side-effect import order: in
|
|
71
|
+
* library mode, the orchestrator's transitive imports of
|
|
72
|
+
* `@avocadostudio-ai/shared` re-register canonical Hero/CTA/etc. on the
|
|
73
|
+
* shared globalThis registry, often AFTER the host app's overrides.
|
|
74
|
+
*
|
|
75
|
+
* **Fires once per orchestrator runtime** — at `buildRuntime` time, not
|
|
76
|
+
* per-request. For long-lived production processes this is fine, but if
|
|
77
|
+
* the host re-builds the runtime on hot-reload, ensure the schemas survive
|
|
78
|
+
* (registerBlock is idempotent — calling again is safe). Pair this with
|
|
79
|
+
* the same `registerBlocks` passed to `createEditorApiHandler`, which
|
|
80
|
+
* re-runs on every `/blocks` request, to cover request-time too.
|
|
81
|
+
*/
|
|
82
|
+
registerBlocks?: () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Local directory where `POST /image/upload` writes uploaded files and
|
|
85
|
+
* `GET /generated-images/:fileName` reads them back. Defaults to
|
|
86
|
+
* `.data/generated-images` under the host process CWD.
|
|
87
|
+
*
|
|
88
|
+
* **POC-grade storage.** Files live on the orchestrator host's local disk
|
|
89
|
+
* with no CDN, no image transforms, and no durability guarantee — on an
|
|
90
|
+
* ephemeral filesystem (e.g. a container without a mounted volume) uploads
|
|
91
|
+
* vanish on redeploy. See `docs/image-storage-options.md` for the path to a
|
|
92
|
+
* blob/CDN backend; that swap is contained to these two routes.
|
|
93
|
+
*/
|
|
94
|
+
imageDir?: string;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* A handler returned by {@link createOrchestrator}. Callable like the bare
|
|
98
|
+
* Web `(Request) => Promise<Response>` it always was, with an extra
|
|
99
|
+
* `dispose()` method to release the resumable-stream sweep timer (call this
|
|
100
|
+
* during dev hot-reload or test teardown).
|
|
101
|
+
*/
|
|
102
|
+
export type OrchestratorHandler = ((request: Request) => Promise<Response>) & {
|
|
103
|
+
dispose(): Promise<void>;
|
|
104
|
+
};
|
|
105
|
+
export type { CmsAdapter, CmsInlineAsset, CmsPublishContext, CmsPublishResult } from "@avocadostudio-ai/orchestrator-core/cms/adapter.js";
|
|
106
|
+
export { jsonFileAdapter, editorApiAdapter } from "@avocadostudio-ai/orchestrator-core/cms/index.js";
|
|
107
|
+
/**
|
|
108
|
+
* Build a Web-standard request handler that wraps the orchestrator brain.
|
|
109
|
+
*
|
|
110
|
+
* Usage in Next.js App Router (`app/api/avocado/[[...path]]/route.ts`):
|
|
111
|
+
*
|
|
112
|
+
* export const runtime = "nodejs"
|
|
113
|
+
* const handler = createOrchestrator()
|
|
114
|
+
* export const POST = handler
|
|
115
|
+
* export const OPTIONS = handler
|
|
116
|
+
*/
|
|
117
|
+
export declare function createOrchestrator(config?: CreateOrchestratorConfig): OrchestratorHandler;
|