@avocadostudio-ai/site-sdk 0.11.8 → 0.12.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/cli/register-notice.d.ts +14 -0
- package/dist/cli/register-notice.js +28 -0
- package/dist/cli/register.d.ts +5 -2
- package/dist/cli/register.js +85 -27
- package/package.json +6 -6
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/** Where the standalone orchestrator listens, and what the editor assumes. */
|
|
2
2
|
export declare const DEFAULT_ORCHESTRATOR = "http://localhost:4200";
|
|
3
|
+
/**
|
|
4
|
+
* What to print when the POST could not connect at all.
|
|
5
|
+
*
|
|
6
|
+
* This used to say `pnpm dev:orchestrator`, which is a script in Avocado's own
|
|
7
|
+
* monorepo and does not exist in the project the reader is standing in. Most
|
|
8
|
+
* readers reaching this message are in library mode, where there is no
|
|
9
|
+
* orchestrator to start: it is mounted inside their own Next app, so the only
|
|
10
|
+
* thing that can be down is `next dev`, and the only thing that can be wrong is
|
|
11
|
+
* this command's target — which defaults to the standalone address.
|
|
12
|
+
*
|
|
13
|
+
* `port` is the site's dev port, so the suggestion is the URL they would
|
|
14
|
+
* actually pass rather than a shape to fill in.
|
|
15
|
+
*/
|
|
16
|
+
export declare function unreachableOrchestratorNotice(orchestrator: string, port: number): string;
|
|
3
17
|
/**
|
|
4
18
|
* The warning that makes "the site should appear in the dashboard" honest, or
|
|
5
19
|
* `null` when it already is.
|
|
@@ -5,6 +5,34 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Where the standalone orchestrator listens, and what the editor assumes. */
|
|
7
7
|
export const DEFAULT_ORCHESTRATOR = "http://localhost:4200";
|
|
8
|
+
/**
|
|
9
|
+
* What to print when the POST could not connect at all.
|
|
10
|
+
*
|
|
11
|
+
* This used to say `pnpm dev:orchestrator`, which is a script in Avocado's own
|
|
12
|
+
* monorepo and does not exist in the project the reader is standing in. Most
|
|
13
|
+
* readers reaching this message are in library mode, where there is no
|
|
14
|
+
* orchestrator to start: it is mounted inside their own Next app, so the only
|
|
15
|
+
* thing that can be down is `next dev`, and the only thing that can be wrong is
|
|
16
|
+
* this command's target — which defaults to the standalone address.
|
|
17
|
+
*
|
|
18
|
+
* `port` is the site's dev port, so the suggestion is the URL they would
|
|
19
|
+
* actually pass rather than a shape to fill in.
|
|
20
|
+
*/
|
|
21
|
+
export function unreachableOrchestratorNotice(orchestrator, port) {
|
|
22
|
+
const libraryUrl = `http://localhost:${port}/api/avocado`;
|
|
23
|
+
const targetedDefault = orchestrator.replace(/\/+$/, "") === DEFAULT_ORCHESTRATOR;
|
|
24
|
+
return (`\nCould not reach an orchestrator at ${orchestrator}.\n` +
|
|
25
|
+
(targetedDefault
|
|
26
|
+
? `\nThat is the STANDALONE server's address, which is this command's default\n` +
|
|
27
|
+
`and is wrong for most integrations. If you mounted createOrchestrator\n` +
|
|
28
|
+
`inside your own app (library mode), the orchestrator is part of your dev\n` +
|
|
29
|
+
`server and the target is:\n\n` +
|
|
30
|
+
` npx avocado-register --name "…" --orchestrator ${libraryUrl}\n\n` +
|
|
31
|
+
`If you are running the standalone server, start it and re-run this.\n`
|
|
32
|
+
: `\nCheck that the URL is right and that whatever serves it is up. In library\n` +
|
|
33
|
+
`mode that is your own dev server — the orchestrator is mounted inside it,\n` +
|
|
34
|
+
`so there is no separate process to start.\n`));
|
|
35
|
+
}
|
|
8
36
|
/**
|
|
9
37
|
* The warning that makes "the site should appear in the dashboard" honest, or
|
|
10
38
|
* `null` when it already is.
|
package/dist/cli/register.d.ts
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* DRAFT_MODE_SECRET. If missing, generates a cryptographically random
|
|
15
15
|
* secret (32 random bytes, hex-encoded) and writes it to `.env.local`.
|
|
16
16
|
* Also fills in ORCHESTRATOR_URL and the NEXT_PUBLIC_* vars if absent.
|
|
17
|
-
* 3. POSTs the site config to `<ORCHESTRATOR_URL>/sites/register`.
|
|
17
|
+
* 3. POSTs the site config to `<ORCHESTRATOR_URL>/sites/register`. Optional
|
|
18
|
+
* in library mode, where the mount already knows its one site — so an
|
|
19
|
+
* orchestrator it cannot reach is reported, not treated as failure.
|
|
18
20
|
* 4. Prints next steps (and any warnings the orchestrator returned about
|
|
19
21
|
* secret mismatches with the editor's build-time config).
|
|
20
22
|
*
|
|
@@ -26,7 +28,8 @@
|
|
|
26
28
|
* Defaults:
|
|
27
29
|
* --id kebab-case of --name (or the project's package.json `name`)
|
|
28
30
|
* --port parsed from `scripts.dev` in package.json, falls back to 3000
|
|
29
|
-
* --orchestrator $ORCHESTRATOR_URL or
|
|
31
|
+
* --orchestrator $ORCHESTRATOR_URL, .env.local's ORCHESTRATOR_URL, or
|
|
32
|
+
* http://localhost:4200 (the standalone server)
|
|
30
33
|
* --session dev
|
|
31
34
|
* --cwd process.cwd()
|
|
32
35
|
*/
|
package/dist/cli/register.js
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* DRAFT_MODE_SECRET. If missing, generates a cryptographically random
|
|
15
15
|
* secret (32 random bytes, hex-encoded) and writes it to `.env.local`.
|
|
16
16
|
* Also fills in ORCHESTRATOR_URL and the NEXT_PUBLIC_* vars if absent.
|
|
17
|
-
* 3. POSTs the site config to `<ORCHESTRATOR_URL>/sites/register`.
|
|
17
|
+
* 3. POSTs the site config to `<ORCHESTRATOR_URL>/sites/register`. Optional
|
|
18
|
+
* in library mode, where the mount already knows its one site — so an
|
|
19
|
+
* orchestrator it cannot reach is reported, not treated as failure.
|
|
18
20
|
* 4. Prints next steps (and any warnings the orchestrator returned about
|
|
19
21
|
* secret mismatches with the editor's build-time config).
|
|
20
22
|
*
|
|
@@ -26,14 +28,15 @@
|
|
|
26
28
|
* Defaults:
|
|
27
29
|
* --id kebab-case of --name (or the project's package.json `name`)
|
|
28
30
|
* --port parsed from `scripts.dev` in package.json, falls back to 3000
|
|
29
|
-
* --orchestrator $ORCHESTRATOR_URL or
|
|
31
|
+
* --orchestrator $ORCHESTRATOR_URL, .env.local's ORCHESTRATOR_URL, or
|
|
32
|
+
* http://localhost:4200 (the standalone server)
|
|
30
33
|
* --session dev
|
|
31
34
|
* --cwd process.cwd()
|
|
32
35
|
*/
|
|
33
36
|
import { randomBytes } from "node:crypto";
|
|
34
37
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
35
38
|
import { join, resolve } from "node:path";
|
|
36
|
-
import { DEFAULT_ORCHESTRATOR, orchestratorMismatchNotice } from "./register-notice.js";
|
|
39
|
+
import { DEFAULT_ORCHESTRATOR, orchestratorMismatchNotice, unreachableOrchestratorNotice, } from "./register-notice.js";
|
|
37
40
|
function parseArgs(argv) {
|
|
38
41
|
const out = {};
|
|
39
42
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -98,7 +101,12 @@ REQUIRED
|
|
|
98
101
|
OPTIONAL
|
|
99
102
|
--id <kebab-case> Site ID (default: kebab-case of name or package.json name)
|
|
100
103
|
--port <number> Dev server port (default: parsed from package.json scripts.dev, or 3000)
|
|
101
|
-
--orchestrator <url> Orchestrator URL
|
|
104
|
+
--orchestrator <url> Orchestrator URL. Default: $ORCHESTRATOR_URL, else
|
|
105
|
+
ORCHESTRATOR_URL from .env.local, else
|
|
106
|
+
http://localhost:4200 — which is the STANDALONE
|
|
107
|
+
server. In library mode the orchestrator is mounted
|
|
108
|
+
inside your own app, so the target is your dev
|
|
109
|
+
server: http://localhost:<port>/api/avocado
|
|
102
110
|
--secret <string> DRAFT_MODE_SECRET (default: read from .env.local, or generate random)
|
|
103
111
|
--session <string> Orchestrator session (default: dev)
|
|
104
112
|
--purpose <string> One-line site description for AI context
|
|
@@ -202,6 +210,20 @@ function mergeEnvFile(envPath, existing, additions) {
|
|
|
202
210
|
writeFileSync(envPath, original + newLines.join("\n") + "\n", "utf-8");
|
|
203
211
|
return added;
|
|
204
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* What was written to `.env.local`, in a form that reads the same whether or
|
|
215
|
+
* not the registration that follows it succeeded — because this half runs
|
|
216
|
+
* first and is the half the site cannot work without.
|
|
217
|
+
*/
|
|
218
|
+
function envReport(envPath, added, secretGenerated, secret) {
|
|
219
|
+
const secretLine = secretGenerated
|
|
220
|
+
? ` Secret: generated (${secret.slice(0, 8)}…)\n`
|
|
221
|
+
: ` Secret: reused from .env.local\n`;
|
|
222
|
+
const fileLine = added.length > 0
|
|
223
|
+
? `\n${envPath} updated. Added: ${added.join(", ")}\n`
|
|
224
|
+
: `\n${envPath} unchanged (all keys already present).\n`;
|
|
225
|
+
return secretLine + fileLine;
|
|
226
|
+
}
|
|
205
227
|
async function main() {
|
|
206
228
|
const args = parseArgs(process.argv.slice(2));
|
|
207
229
|
if (args.help) {
|
|
@@ -224,8 +246,23 @@ async function main() {
|
|
|
224
246
|
}
|
|
225
247
|
// Resolve port
|
|
226
248
|
const port = args.port ?? detectPortFromPackageJson(pkg) ?? 3000;
|
|
227
|
-
|
|
228
|
-
const
|
|
249
|
+
const envPath = join(cwd, ".env.local");
|
|
250
|
+
const existingEnv = existsSync(envPath) ? parseEnvFile(readFileSync(envPath, "utf-8")) : {};
|
|
251
|
+
/*
|
|
252
|
+
* `.env.local` outranks the built-in default.
|
|
253
|
+
*
|
|
254
|
+
* A library-mode project has `ORCHESTRATOR_URL=http://localhost:3000/api/avocado`
|
|
255
|
+
* sitting in that file — the quickstart writes it, and so does this command.
|
|
256
|
+
* Reading the file only *after* resolving the target meant a second run
|
|
257
|
+
* ignored what the first one wrote, defaulted to the standalone address,
|
|
258
|
+
* registered with whatever happened to be listening on :4200, and reported
|
|
259
|
+
* success. The file is the project saying where its orchestrator is; only an
|
|
260
|
+
* explicit flag or the caller's own environment should outrank it.
|
|
261
|
+
*/
|
|
262
|
+
const orchestratorAsserted = args.orchestrator ?? process.env.ORCHESTRATOR_URL;
|
|
263
|
+
const orchestrator = (orchestratorAsserted ??
|
|
264
|
+
existingEnv.ORCHESTRATOR_URL ??
|
|
265
|
+
DEFAULT_ORCHESTRATOR).replace(/\/+$/, "");
|
|
229
266
|
/*
|
|
230
267
|
* A credentialed orchestrator refuses `/sites/register` like any other route,
|
|
231
268
|
* and this CLI had no way to present a token — so the documented path for
|
|
@@ -234,18 +271,25 @@ async function main() {
|
|
|
234
271
|
*/
|
|
235
272
|
const accessToken = (args.token ?? process.env.ORCHESTRATOR_ACCESS_TOKEN ?? "").trim();
|
|
236
273
|
// Resolve / generate the draft secret
|
|
237
|
-
const envPath = join(cwd, ".env.local");
|
|
238
|
-
const existingEnv = existsSync(envPath) ? parseEnvFile(readFileSync(envPath, "utf-8")) : {};
|
|
239
274
|
let secret = args.secret ?? existingEnv.DRAFT_MODE_SECRET;
|
|
240
275
|
let secretGenerated = false;
|
|
241
276
|
if (!secret) {
|
|
242
277
|
secret = randomBytes(32).toString("hex");
|
|
243
278
|
secretGenerated = true;
|
|
244
279
|
}
|
|
245
|
-
|
|
246
|
-
|
|
280
|
+
/*
|
|
281
|
+
* Append-only merge — only add keys that aren't already present, preserving
|
|
282
|
+
* the user's existing file formatting and comments.
|
|
283
|
+
*
|
|
284
|
+
* `ORCHESTRATOR_URL` is held back unless the caller named it, and written
|
|
285
|
+
* after the registration answers. A default nobody chose, recorded in the
|
|
286
|
+
* project's env file, is a guess promoted to configuration: the run that
|
|
287
|
+
* could not reach :4200 would write :4200 anyway, and every later run would
|
|
288
|
+
* then read it back as the project's own answer. Everything else here is
|
|
289
|
+
* independent of whether any orchestrator exists.
|
|
290
|
+
*/
|
|
247
291
|
const added = mergeEnvFile(envPath, existingEnv, {
|
|
248
|
-
ORCHESTRATOR_URL: orchestrator,
|
|
292
|
+
...(orchestratorAsserted ? { ORCHESTRATOR_URL: orchestrator } : {}),
|
|
249
293
|
DRAFT_MODE_SECRET: secret,
|
|
250
294
|
NEXT_PUBLIC_DEFAULT_SITE_ID: siteId,
|
|
251
295
|
NEXT_PUBLIC_SITE_NAME: name,
|
|
@@ -278,11 +322,28 @@ async function main() {
|
|
|
278
322
|
});
|
|
279
323
|
}
|
|
280
324
|
catch (err) {
|
|
325
|
+
/*
|
|
326
|
+
* Unreachable is not failure. The env half of this command already ran —
|
|
327
|
+
* the secret is generated and the four variables are in `.env.local` — and
|
|
328
|
+
* that is the half a site cannot work without. Registration is the optional
|
|
329
|
+
* half: a library-mode mount already knows the one site it serves and
|
|
330
|
+
* reports it from `GET /sites` whether or not anyone registered it.
|
|
331
|
+
*
|
|
332
|
+
* Exiting 1 here described the run as a total failure, so integrators
|
|
333
|
+
* re-ran it, hand-edited `.env.local` against the advice not to, or went
|
|
334
|
+
* looking for a service they never needed. Say precisely which half
|
|
335
|
+
* happened and leave the exit code clean. An orchestrator that ANSWERS and
|
|
336
|
+
* refuses is a different matter and still exits 1 below.
|
|
337
|
+
*/
|
|
281
338
|
const aborted = err.name === "AbortError";
|
|
282
|
-
process.
|
|
283
|
-
process.
|
|
284
|
-
process.
|
|
285
|
-
process.
|
|
339
|
+
process.stdout.write(envReport(envPath, added, secretGenerated, secret));
|
|
340
|
+
process.stdout.write(unreachableOrchestratorNotice(orchestrator, port));
|
|
341
|
+
process.stdout.write(` (${aborted ? "timed out after 10 seconds" : err.message})\n`);
|
|
342
|
+
process.stdout.write(`\nThe site is NOT registered. Nothing else is missing: in library mode the\n` +
|
|
343
|
+
`orchestrator already serves the one site it is mounted in, so open the\n` +
|
|
344
|
+
`editor and it should be there. Re-run this command to add the name,\n` +
|
|
345
|
+
`preview URL and purpose to its registry.\n\n`);
|
|
346
|
+
process.exit(0);
|
|
286
347
|
}
|
|
287
348
|
finally {
|
|
288
349
|
clearTimeout(timeoutId);
|
|
@@ -298,23 +359,20 @@ async function main() {
|
|
|
298
359
|
process.exit(1);
|
|
299
360
|
}
|
|
300
361
|
const result = (await response.json());
|
|
362
|
+
/*
|
|
363
|
+
* Now it is not a guess: something answered `/sites/register` there, so the
|
|
364
|
+
* URL is worth recording for the next run and for the SDK's server-side draft
|
|
365
|
+
* fetch, which falls back to :4200 when nothing is set.
|
|
366
|
+
*/
|
|
367
|
+
if (!orchestratorAsserted) {
|
|
368
|
+
added.push(...mergeEnvFile(envPath, existingEnv, { ORCHESTRATOR_URL: orchestrator }));
|
|
369
|
+
}
|
|
301
370
|
// Friendly output
|
|
302
371
|
process.stdout.write(`\nRegistered "${name}" with the orchestrator.\n`);
|
|
303
372
|
process.stdout.write(` Site ID: ${siteId}\n`);
|
|
304
373
|
process.stdout.write(` Preview URL: ${previewUrl}\n`);
|
|
305
374
|
process.stdout.write(` Orchestrator: ${orchestrator}\n`);
|
|
306
|
-
|
|
307
|
-
process.stdout.write(` Secret: generated (${secret.slice(0, 8)}…)\n`);
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
process.stdout.write(` Secret: reused from .env.local\n`);
|
|
311
|
-
}
|
|
312
|
-
if (added.length > 0) {
|
|
313
|
-
process.stdout.write(`\n.env.local updated. Added: ${added.join(", ")}\n`);
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
process.stdout.write(`\n.env.local unchanged (all keys already present).\n`);
|
|
317
|
-
}
|
|
375
|
+
process.stdout.write(envReport(envPath, added, secretGenerated, secret));
|
|
318
376
|
if (Array.isArray(result.warnings) && result.warnings.length > 0) {
|
|
319
377
|
process.stdout.write(`\nWarnings:\n`);
|
|
320
378
|
for (const w of result.warnings) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/site-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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/blocks": "^0.
|
|
151
|
-
"@avocadostudio-ai/preview-adapter": "^0.
|
|
152
|
-
"@avocadostudio-ai/
|
|
153
|
-
"@avocadostudio-ai/
|
|
150
|
+
"@avocadostudio-ai/blocks": "^0.12.0",
|
|
151
|
+
"@avocadostudio-ai/preview-adapter": "^0.12.0",
|
|
152
|
+
"@avocadostudio-ai/shared": "^0.12.0",
|
|
153
|
+
"@avocadostudio-ai/richtext": "^0.12.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.12.0"
|
|
161
161
|
},
|
|
162
162
|
"peerDependenciesMeta": {
|
|
163
163
|
"@avocadostudio-ai/orchestrator-core": {
|