create-avocado-site 0.9.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 +65 -0
- package/assets/generated-images/gdrive_1ql_bdk2KM_27u01rkMd8KoiCRJ_R7UOy.webp +0 -0
- package/assets/generated-images/gen_1774991902955_0p1i2o.webp +0 -0
- package/assets/generated-images/gen_1776458907692_zlrbkg.webp +0 -0
- package/assets/generated-images/gen_1788955681679_454dc316.webp +0 -0
- package/assets/generated-images/var_1773434968055_8g99nk.webp +0 -0
- package/assets/generated-images/var_1773435042084_wvet78.webp +0 -0
- package/assets/generated-images/var_1773435061562_6hf8s6.webp +0 -0
- package/assets/generated-images/var_1773435080764_6ocahu.webp +0 -0
- package/assets/generated-images/var_1773435101045_1y544z.webp +0 -0
- package/assets/generated-images/var_1773872370443_poycxy.webp +0 -0
- package/assets/generated-images/var_1776858715876_iuu72w.webp +0 -0
- package/assets/logos/avocado-hub.svg +7 -0
- package/dist/cms-config.d.ts +34 -0
- package/dist/cms-config.js +103 -0
- package/dist/generator.d.ts +22 -0
- package/dist/generator.js +92 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +109 -0
- package/dist/instructions.d.ts +19 -0
- package/dist/instructions.js +82 -0
- package/dist/next-version.d.ts +15 -0
- package/dist/next-version.js +43 -0
- package/dist/ports.d.ts +11 -0
- package/dist/ports.js +61 -0
- package/dist/prompts.d.ts +13 -0
- package/dist/prompts.js +119 -0
- package/dist/templates/assets.d.ts +2 -0
- package/dist/templates/assets.js +41 -0
- package/dist/templates/common.d.ts +45 -0
- package/dist/templates/common.js +174 -0
- package/dist/templates/contentful.d.ts +2 -0
- package/dist/templates/contentful.js +379 -0
- package/dist/templates/demo-app.d.ts +3 -0
- package/dist/templates/demo-app.js +465 -0
- package/dist/templates/demo-content.d.ts +2 -0
- package/dist/templates/demo-content.js +7 -0
- package/dist/templates/sanity.d.ts +2 -0
- package/dist/templates/sanity.js +289 -0
- package/dist/templates/static.d.ts +2 -0
- package/dist/templates/static.js +81 -0
- package/dist/templates/strapi.d.ts +2 -0
- package/dist/templates/strapi.js +235 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.js +1 -0
- package/dist/versions.d.ts +32 -0
- package/dist/versions.js +32 -0
- package/package.json +51 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { AVOCADO, NEXT, REACT, TYPESCRIPT, TYPES_NODE, TYPES_REACT, TYPES_REACT_DOM } from "../versions.js";
|
|
2
|
+
import { DEMO_SITE_CONFIG_JSON } from "./demo-content.js";
|
|
3
|
+
/**
|
|
4
|
+
* The name the scaffolded site renders in its own header. Read from the seed
|
|
5
|
+
* rather than restated, so it cannot drift from what the preview shows — the
|
|
6
|
+
* editor used to title-case the directory name instead, which put two
|
|
7
|
+
* different names for one site on the same screen.
|
|
8
|
+
*/
|
|
9
|
+
const DEMO_SITE_NAME = JSON.parse(DEMO_SITE_CONFIG_JSON).name ?? "Demo site";
|
|
10
|
+
/*
|
|
11
|
+
* The files that turn a scaffold into something you can run.
|
|
12
|
+
*
|
|
13
|
+
* Until now the scaffolder wrote eight files into a Next.js app you were
|
|
14
|
+
* assumed to already have, then printed five manual steps — edit next.config,
|
|
15
|
+
* edit globals.css, npm install this list, copy an env file, "start the
|
|
16
|
+
* orchestrator and editor (see docs/integration/README.md)", a path that does
|
|
17
|
+
* not exist. Every one of those steps is a place to stop, and the last one is
|
|
18
|
+
* the whole product.
|
|
19
|
+
*
|
|
20
|
+
* So this module supplies the missing half: a package.json, a Next config, a
|
|
21
|
+
* layout, an orchestrator mounted in-process, and one `npm run dev` that
|
|
22
|
+
* brings up both halves. Nothing here is a step for a person to perform.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* `withAvocado` is the entire Next config.
|
|
26
|
+
*
|
|
27
|
+
* It fills in `transpilePackages` from what is actually linked, merges
|
|
28
|
+
* Avocado's image hosts into `images.remotePatterns` (the demo's photography
|
|
29
|
+
* is on `images.unsplash.com`, and an undeclared host is a hard render error,
|
|
30
|
+
* not a broken image), sets `serverExternalPackages` together with the
|
|
31
|
+
* matching server externals so the native dependencies resolve, and sets
|
|
32
|
+
* `skipTrailingSlashRedirect` so a CORS preflight on `/api/*` is not answered
|
|
33
|
+
* with a 308 the browser will not follow.
|
|
34
|
+
*
|
|
35
|
+
* Those four used to be copied by hand into every integration, which is four
|
|
36
|
+
* chances to get it wrong and no signal when you did.
|
|
37
|
+
*/
|
|
38
|
+
function nextConfig() {
|
|
39
|
+
return `import type { NextConfig } from "next"
|
|
40
|
+
import { withAvocado } from "@avocadostudio-ai/site-sdk/next-config"
|
|
41
|
+
|
|
42
|
+
const nextConfig: NextConfig = {}
|
|
43
|
+
|
|
44
|
+
export default withAvocado(nextConfig)
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function packageJson(config) {
|
|
48
|
+
return `${JSON.stringify({
|
|
49
|
+
name: config.siteId,
|
|
50
|
+
version: "0.1.0",
|
|
51
|
+
private: true,
|
|
52
|
+
type: "module",
|
|
53
|
+
// better-sqlite3 ships prebuilt binaries for Node 22; on an older Node
|
|
54
|
+
// npm falls through to compiling it from source, which needs a toolchain
|
|
55
|
+
// the reader did not sign up for.
|
|
56
|
+
engines: { node: ">=22" },
|
|
57
|
+
scripts: {
|
|
58
|
+
dev: "node scripts/dev.mjs",
|
|
59
|
+
"dev:site": `next dev -p ${config.sitePort}`,
|
|
60
|
+
"dev:editor": `avocadostudio start --orchestrator http://localhost:${config.sitePort}/api/avocado --preview http://localhost:${config.sitePort} --port ${config.editorPort}`,
|
|
61
|
+
build: "next build",
|
|
62
|
+
start: `next start -p ${config.sitePort}`,
|
|
63
|
+
},
|
|
64
|
+
dependencies: {
|
|
65
|
+
"@avocadostudio-ai/blocks": `^${AVOCADO}`,
|
|
66
|
+
"@avocadostudio-ai/orchestrator-core": `^${AVOCADO}`,
|
|
67
|
+
"@avocadostudio-ai/preview-adapter": `^${AVOCADO}`,
|
|
68
|
+
"@avocadostudio-ai/shared": `^${AVOCADO}`,
|
|
69
|
+
"@avocadostudio-ai/site-sdk": `^${AVOCADO}`,
|
|
70
|
+
next: NEXT,
|
|
71
|
+
react: REACT,
|
|
72
|
+
"react-dom": REACT,
|
|
73
|
+
},
|
|
74
|
+
devDependencies: {
|
|
75
|
+
"@avocadostudio-ai/cli": `^${AVOCADO}`,
|
|
76
|
+
"@types/node": TYPES_NODE,
|
|
77
|
+
"@types/react": TYPES_REACT,
|
|
78
|
+
"@types/react-dom": TYPES_REACT_DOM,
|
|
79
|
+
typescript: TYPESCRIPT,
|
|
80
|
+
},
|
|
81
|
+
}, null, 2)}\n`;
|
|
82
|
+
}
|
|
83
|
+
function tsconfig() {
|
|
84
|
+
return `${JSON.stringify({
|
|
85
|
+
compilerOptions: {
|
|
86
|
+
target: "ES2022",
|
|
87
|
+
lib: ["dom", "dom.iterable", "ES2022"],
|
|
88
|
+
allowJs: true,
|
|
89
|
+
skipLibCheck: true,
|
|
90
|
+
strict: true,
|
|
91
|
+
noEmit: true,
|
|
92
|
+
esModuleInterop: true,
|
|
93
|
+
module: "esnext",
|
|
94
|
+
moduleResolution: "bundler",
|
|
95
|
+
resolveJsonModule: true,
|
|
96
|
+
isolatedModules: true,
|
|
97
|
+
jsx: "preserve",
|
|
98
|
+
incremental: true,
|
|
99
|
+
plugins: [{ name: "next" }],
|
|
100
|
+
paths: { "@/*": ["./*"] },
|
|
101
|
+
},
|
|
102
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
103
|
+
exclude: ["node_modules"],
|
|
104
|
+
}, null, 2)}\n`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* `.data/` is the line that matters.
|
|
108
|
+
*
|
|
109
|
+
* The orchestrator writes its SQLite state there, in the project root, on the
|
|
110
|
+
* first request — so it does not exist when the scaffold runs and does not
|
|
111
|
+
* exist when you look at the directory. `create-next-app`'s .gitignore does
|
|
112
|
+
* not cover it, and the first `git add .` after the first `npm run dev`
|
|
113
|
+
* commits a database plus its write-ahead log.
|
|
114
|
+
*/
|
|
115
|
+
function gitignore() {
|
|
116
|
+
return `node_modules/
|
|
117
|
+
.next/
|
|
118
|
+
out/
|
|
119
|
+
build/
|
|
120
|
+
|
|
121
|
+
# Orchestrator state — SQLite, written on the first request.
|
|
122
|
+
.data/
|
|
123
|
+
|
|
124
|
+
# public/generated-images/ is deliberately NOT ignored. The editor writes
|
|
125
|
+
# generated and uploaded images there, and they are content: the pages that
|
|
126
|
+
# reference them are committed, so a deploy built from git without them renders
|
|
127
|
+
# broken images. Eleven of this demo's own images live in that directory.
|
|
128
|
+
|
|
129
|
+
.env.local
|
|
130
|
+
.env*.local
|
|
131
|
+
next-env.d.ts
|
|
132
|
+
*.tsbuildinfo
|
|
133
|
+
.DS_Store
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Written with no key in it, on purpose.
|
|
138
|
+
*
|
|
139
|
+
* The demo renders, selects, and shows its property panel with no API key at
|
|
140
|
+
* all — so the first run costs nothing to look at and nothing to sign up for.
|
|
141
|
+
* Chat is the one thing that needs a key, and it says so itself rather than
|
|
142
|
+
* failing with a stack trace.
|
|
143
|
+
*
|
|
144
|
+
* `DRAFT_MODE_SECRET` is not optional even though nothing warns loudly: draft
|
|
145
|
+
* mode silently does not work without it, which presents as an editor that
|
|
146
|
+
* loads the published page and ignores every edit.
|
|
147
|
+
*/
|
|
148
|
+
function envLocal(config) {
|
|
149
|
+
return `# One of these switches chat on. Everything else already works without them.
|
|
150
|
+
#
|
|
151
|
+
# ANTHROPIC_API_KEY the best-tested planner
|
|
152
|
+
# OPENAI_API_KEY also powers gpt-image image generation
|
|
153
|
+
# GOOGLE_GENAI_API_KEY also needs \`npm install @google/genai\` — it is an
|
|
154
|
+
# optional peer dependency, so no package manager
|
|
155
|
+
# installs it for you and a Gemini plan fails on the
|
|
156
|
+
# first call without it.
|
|
157
|
+
#
|
|
158
|
+
# ANTHROPIC_API_KEY=
|
|
159
|
+
|
|
160
|
+
# The orchestrator runs inside this app, at /api/avocado. There is no third
|
|
161
|
+
# service to start and nothing to deploy separately.
|
|
162
|
+
ORCHESTRATOR_URL=http://localhost:${config.sitePort}/api/avocado
|
|
163
|
+
|
|
164
|
+
# Where the editor is served from. This site names that origin in two places
|
|
165
|
+
# that must agree — CORS on the editor API, and \`frame-ancestors\` on the
|
|
166
|
+
# preview — and \`withAvocado\` derives both from this one variable.
|
|
167
|
+
#
|
|
168
|
+
# It is not optional here. The default is port 4100, and these ports are chosen
|
|
169
|
+
# at scaffold time from what was actually free, so on a machine already running
|
|
170
|
+
# an editor this is some other port. When the two disagree the halves fail
|
|
171
|
+
# separately and silently: the editor API answers 200 with no
|
|
172
|
+
# access-control-allow-origin, and the browser refuses the frame — so the site
|
|
173
|
+
# reads as offline and the preview is a blank rectangle. Neither failure
|
|
174
|
+
# mentions a port, CORS, or CSP.
|
|
175
|
+
EDITOR_CORS_ORIGINS=http://localhost:${config.editorPort}
|
|
176
|
+
|
|
177
|
+
# Any long random string. Without it draft mode does not work, and the editor
|
|
178
|
+
# quietly renders the published page instead of your edits.
|
|
179
|
+
DRAFT_MODE_SECRET=${config.draftSecret}
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* One command, two processes.
|
|
184
|
+
*
|
|
185
|
+
* Written as a script rather than a `concurrently` dependency because a
|
|
186
|
+
* scaffold that installs a process runner to run two processes is a scaffold
|
|
187
|
+
* explaining itself badly — and because the interesting part is not running
|
|
188
|
+
* them, it is the banner and the shutdown. A person running this for the
|
|
189
|
+
* first time needs to be told which of the two URLs is the one to open; the
|
|
190
|
+
* editor is at :4100 and the site at :3000, and opening the site directly
|
|
191
|
+
* shows the finished page with no editor around it, which reads as "the
|
|
192
|
+
* product did not start".
|
|
193
|
+
*/
|
|
194
|
+
function devScript(config) {
|
|
195
|
+
return `#!/usr/bin/env node
|
|
196
|
+
/*
|
|
197
|
+
* Starts the site and the editor together, then opens the editor.
|
|
198
|
+
*
|
|
199
|
+
* The site is a normal \`next dev\`. The editor is a static bundle served by
|
|
200
|
+
* @avocadostudio-ai/cli, pointed at the orchestrator running inside the site
|
|
201
|
+
* at /api/avocado. Two processes, one terminal, no third service.
|
|
202
|
+
*/
|
|
203
|
+
import { spawn } from "node:child_process"
|
|
204
|
+
import { createConnection } from "node:net"
|
|
205
|
+
|
|
206
|
+
const SITE_PORT = process.env.PORT ?? "${config.sitePort}"
|
|
207
|
+
const EDITOR_PORT = process.env.EDITOR_PORT ?? "${config.editorPort}"
|
|
208
|
+
const SITE_URL = \`http://localhost:\${SITE_PORT}\`
|
|
209
|
+
const EDITOR_URL = \`http://localhost:\${EDITOR_PORT}\`
|
|
210
|
+
|
|
211
|
+
/*
|
|
212
|
+
* The editor has to be told which site it is editing, and the only channel is
|
|
213
|
+
* the URL.
|
|
214
|
+
*
|
|
215
|
+
* The CLI serves a prebuilt editor bundle and injects the orchestrator and
|
|
216
|
+
* preview origins into it, but not a site id — so the bundle falls back to its
|
|
217
|
+
* own built-in default, "avocado-hub". Every draft request then names a
|
|
218
|
+
* session this project never configured. It happens to work, because the
|
|
219
|
+
* orchestrator bootstraps an unknown session from the adapter and gets the
|
|
220
|
+
* same nine pages, and that is exactly what makes it worth pinning: the demo
|
|
221
|
+
* looks correct while the id in the URL and the id in the route file are
|
|
222
|
+
* different strings.
|
|
223
|
+
*/
|
|
224
|
+
const SITE_ID = "${config.siteId}"
|
|
225
|
+
const OPEN_URL = \`\${EDITOR_URL}/?siteId=\${SITE_ID}&session=dev\`
|
|
226
|
+
|
|
227
|
+
const children = []
|
|
228
|
+
|
|
229
|
+
function run(command, args) {
|
|
230
|
+
const child = spawn(command, args, { stdio: "inherit", shell: process.platform === "win32" })
|
|
231
|
+
children.push(child)
|
|
232
|
+
child.on("exit", (code) => {
|
|
233
|
+
// If either half dies the other is useless, so take both down rather than
|
|
234
|
+
// leaving a half-running stack that looks alive.
|
|
235
|
+
if (!shuttingDown) shutdown(code ?? 0)
|
|
236
|
+
})
|
|
237
|
+
return child
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let shuttingDown = false
|
|
241
|
+
function shutdown(code) {
|
|
242
|
+
if (shuttingDown) return
|
|
243
|
+
shuttingDown = true
|
|
244
|
+
for (const child of children) child.kill("SIGTERM")
|
|
245
|
+
process.exit(code)
|
|
246
|
+
}
|
|
247
|
+
process.on("SIGINT", () => shutdown(0))
|
|
248
|
+
process.on("SIGTERM", () => shutdown(0))
|
|
249
|
+
|
|
250
|
+
/** Resolves once something is listening on \`port\`, or after \`timeoutMs\`. */
|
|
251
|
+
function waitForPort(port, timeoutMs = 60_000) {
|
|
252
|
+
const deadline = Date.now() + timeoutMs
|
|
253
|
+
return new Promise((resolve) => {
|
|
254
|
+
const attempt = () => {
|
|
255
|
+
const socket = createConnection({ port: Number(port), host: "127.0.0.1" })
|
|
256
|
+
socket.on("connect", () => { socket.destroy(); resolve(true) })
|
|
257
|
+
socket.on("error", () => {
|
|
258
|
+
socket.destroy()
|
|
259
|
+
if (Date.now() > deadline) resolve(false)
|
|
260
|
+
else setTimeout(attempt, 250)
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
attempt()
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function open(url) {
|
|
268
|
+
const command =
|
|
269
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"
|
|
270
|
+
try {
|
|
271
|
+
spawn(command, [url], { stdio: "ignore", detached: true, shell: process.platform === "win32" }).unref()
|
|
272
|
+
} catch {
|
|
273
|
+
// Headless, or no handler registered. The banner already printed the URL.
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
run("npx", ["next", "dev", "-p", SITE_PORT])
|
|
278
|
+
|
|
279
|
+
// The editor probes the orchestrator's /health on startup, and the
|
|
280
|
+
// orchestrator is inside the site — so starting it first means it reports the
|
|
281
|
+
// site as unreachable and the first thing a new user sees is an error screen.
|
|
282
|
+
const siteUp = await waitForPort(SITE_PORT)
|
|
283
|
+
|
|
284
|
+
run("npx", [
|
|
285
|
+
"avocadostudio",
|
|
286
|
+
"start",
|
|
287
|
+
"--orchestrator", \`\${SITE_URL}/api/avocado\`,
|
|
288
|
+
"--preview", SITE_URL,
|
|
289
|
+
"--port", EDITOR_PORT,
|
|
290
|
+
])
|
|
291
|
+
|
|
292
|
+
await waitForPort(EDITOR_PORT)
|
|
293
|
+
|
|
294
|
+
process.stdout.write(
|
|
295
|
+
\`\\n \\u25b2 Site \${SITE_URL}\\n\` +
|
|
296
|
+
\` \\u{1F951} Editor \${OPEN_URL}\\n\` +
|
|
297
|
+
\` \\u2191 open this one\\n\\n\`
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
if (siteUp) open(OPEN_URL)
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* The stylesheet import is the whole reason this file is scaffolded.
|
|
305
|
+
*
|
|
306
|
+
* Without \`@avocadostudio-ai/blocks/styles.css\` every block renders correct,
|
|
307
|
+
* complete, unstyled HTML — which does not read as "a stylesheet is missing",
|
|
308
|
+
* it reads as "this product produces ugly pages".
|
|
309
|
+
*/
|
|
310
|
+
function globalsCss() {
|
|
311
|
+
return `@import "@avocadostudio-ai/blocks/styles.css";
|
|
312
|
+
|
|
313
|
+
:root {
|
|
314
|
+
color-scheme: light dark;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
* {
|
|
318
|
+
box-sizing: border-box;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
html,
|
|
322
|
+
body {
|
|
323
|
+
margin: 0;
|
|
324
|
+
padding: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
body {
|
|
328
|
+
font-family:
|
|
329
|
+
ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
330
|
+
-webkit-font-smoothing: antialiased;
|
|
331
|
+
}
|
|
332
|
+
`;
|
|
333
|
+
}
|
|
334
|
+
function layout(config) {
|
|
335
|
+
return `import type { ReactNode } from "react"
|
|
336
|
+
import "./globals.css"
|
|
337
|
+
|
|
338
|
+
/*
|
|
339
|
+
* Deliberately minimal. Every page's own <title> and description come from
|
|
340
|
+
* \`generateMetadata\` in app/[[...slug]]/page.tsx, which derives them from the
|
|
341
|
+
* page content — a title set here would be inherited by all nine pages and
|
|
342
|
+
* override none of them.
|
|
343
|
+
*/
|
|
344
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
345
|
+
return (
|
|
346
|
+
<html lang="en">
|
|
347
|
+
<body>{children}</body>
|
|
348
|
+
</html>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Library mode: the orchestrator runs inside the site.
|
|
355
|
+
*
|
|
356
|
+
* This is the shape most integrations use and the one that makes the demo a
|
|
357
|
+
* two-process story instead of three. `jsonFileAdapter` points it at the same
|
|
358
|
+
* `content/pages.json` the pages render from, so a publish from the editor
|
|
359
|
+
* lands in the file on disk and the next request serves it.
|
|
360
|
+
*
|
|
361
|
+
* `createOrchestrator` returns one callable. Assign it to GET, POST and
|
|
362
|
+
* OPTIONS rather than destructuring — there is nothing to destructure, and
|
|
363
|
+
* `const { GET } = createOrchestrator()` yields undefined with no type error.
|
|
364
|
+
*/
|
|
365
|
+
function orchestratorRoute(config) {
|
|
366
|
+
return `import { resolve } from "node:path"
|
|
367
|
+
import { jsonFileAdapter } from "@avocadostudio-ai/orchestrator-core/cms"
|
|
368
|
+
import { createOrchestrator } from "@avocadostudio-ai/site-sdk/server"
|
|
369
|
+
|
|
370
|
+
export const runtime = "nodejs"
|
|
371
|
+
export const dynamic = "force-dynamic"
|
|
372
|
+
|
|
373
|
+
const handler = createOrchestrator({
|
|
374
|
+
// Must match the siteId in app/[[...slug]]/page.tsx. If the two drift, the
|
|
375
|
+
// page asks for a draft session the orchestrator never seeded and the editor
|
|
376
|
+
// shows the published content with every edit silently going nowhere.
|
|
377
|
+
siteId: "${config.siteId}",
|
|
378
|
+
// The editor title-cases the id when nobody tells it otherwise, so a project
|
|
379
|
+
// in my-shop/ would be greeted as "My Shop".
|
|
380
|
+
siteName: "${DEMO_SITE_NAME}",
|
|
381
|
+
// This project starts on the shipped Avocado Hub content. The editor keys its
|
|
382
|
+
// first-run suggestions on the answer; without it they fall back to the
|
|
383
|
+
// generic list written for a site nobody has seen.
|
|
384
|
+
demoContent: true,
|
|
385
|
+
adapter: jsonFileAdapter({ path: resolve(process.cwd(), "content/pages.json") }),
|
|
386
|
+
previewUrl: "http://localhost:${config.sitePort}",
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
export const GET = handler
|
|
390
|
+
export const POST = handler
|
|
391
|
+
export const OPTIONS = handler
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
function readme(config) {
|
|
395
|
+
return `# ${config.siteId}
|
|
396
|
+
|
|
397
|
+
The Avocado Hub demo — nine pages, sixty blocks, every built-in block type —
|
|
398
|
+
running on your machine, editable by chat.
|
|
399
|
+
|
|
400
|
+
\`\`\`bash
|
|
401
|
+
npm run dev
|
|
402
|
+
\`\`\`
|
|
403
|
+
|
|
404
|
+
| | |
|
|
405
|
+
|---|---|
|
|
406
|
+
| Editor | http://localhost:${config.editorPort}/?siteId=${config.siteId}&session=dev — **open this one** |
|
|
407
|
+
| Site | http://localhost:${config.sitePort} |
|
|
408
|
+
|
|
409
|
+
Opening the site directly shows the finished pages with no editor around them.
|
|
410
|
+
The editor is the product; the site is what it edits.
|
|
411
|
+
|
|
412
|
+
The \`siteId\` on the editor URL is not decoration. The editor is a prebuilt
|
|
413
|
+
bundle that is told the orchestrator's address but not which site to open, so
|
|
414
|
+
without it it falls back to its own default name and asks the orchestrator for
|
|
415
|
+
a session this project never configured.
|
|
416
|
+
|
|
417
|
+
## Turning chat on
|
|
418
|
+
|
|
419
|
+
Everything except chat works with no API key: the pages render, click-to-select
|
|
420
|
+
works, and the property panel edits real fields. Chat needs a model. Put one
|
|
421
|
+
key in \`.env.local\` and restart:
|
|
422
|
+
|
|
423
|
+
\`\`\`
|
|
424
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
425
|
+
\`\`\`
|
|
426
|
+
|
|
427
|
+
\`OPENAI_API_KEY\` and \`GOOGLE_GENAI_API_KEY\` work too. Avocado runs on your key
|
|
428
|
+
and never resells tokens. For Gemini also run \`npm install @google/genai\` — it
|
|
429
|
+
is an optional peer dependency, so nothing installs it for you.
|
|
430
|
+
|
|
431
|
+
## What is where
|
|
432
|
+
|
|
433
|
+
| Path | What it is |
|
|
434
|
+
|---|---|
|
|
435
|
+
| \`content/pages.json\` | The nine pages. Publishing from the editor rewrites this file. |
|
|
436
|
+
| \`content/site.json\` | Site name and navigation, including the dropdown groups. |
|
|
437
|
+
| \`lib/content.ts\` | The one module that reads both. |
|
|
438
|
+
| \`app/[[...slug]]/page.tsx\` | Renders every page. |
|
|
439
|
+
| \`app/api/avocado/[[...path]]/route.ts\` | The orchestrator, running inside this app. |
|
|
440
|
+
| \`app/api/editor/[...path]/route.ts\` | What the editor reads and publishes through. |
|
|
441
|
+
| \`.data/\` | The orchestrator's SQLite state. Git-ignored; delete it to start over. |
|
|
442
|
+
|
|
443
|
+
## Next: your own site
|
|
444
|
+
|
|
445
|
+
This demo keeps its content in a JSON file so the loop is visible end to end in
|
|
446
|
+
one sitting. Bringing in a real site — your components, your CMS, your content
|
|
447
|
+
— is a different and larger job. The paths for it are at
|
|
448
|
+
https://docs.avocadostudio.dev/sites.
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
/** The files that exist only in a bootstrapped demo, on top of the shared wiring. */
|
|
452
|
+
export function demoAppTemplates(config) {
|
|
453
|
+
return [
|
|
454
|
+
{ path: "package.json", content: packageJson(config) },
|
|
455
|
+
{ path: "next.config.ts", content: nextConfig() },
|
|
456
|
+
{ path: "tsconfig.json", content: tsconfig() },
|
|
457
|
+
{ path: ".gitignore", content: gitignore() },
|
|
458
|
+
{ path: ".env.local", content: envLocal(config) },
|
|
459
|
+
{ path: "README.md", content: readme(config) },
|
|
460
|
+
{ path: "scripts/dev.mjs", content: devScript(config) },
|
|
461
|
+
{ path: "app/layout.tsx", content: layout(config) },
|
|
462
|
+
{ path: "app/globals.css", content: globalsCss() },
|
|
463
|
+
{ path: "app/api/avocado/[[...path]]/route.ts", content: orchestratorRoute(config) },
|
|
464
|
+
];
|
|
465
|
+
}
|