@avocadostudio-ai/site-sdk 0.11.7 → 0.11.9

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.
@@ -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.
@@ -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 http://localhost:4200
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
  */
@@ -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 http://localhost:4200
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 (default: $ORCHESTRATOR_URL or http://localhost:4200)
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
- // Resolve orchestrator URL
228
- const orchestrator = (args.orchestrator ?? process.env.ORCHESTRATOR_URL ?? DEFAULT_ORCHESTRATOR).replace(/\/+$/, "");
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
- // Append-only merge — only add keys that aren't already present, preserving
246
- // the user's existing file formatting and comments.
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.stderr.write(`\nError: could not reach the orchestrator at ${orchestrator}\n`);
283
- process.stderr.write(` ${aborted ? "Request timed out after 10 seconds." : err.message}\n`);
284
- process.stderr.write(`\nMake sure the orchestrator is running:\n pnpm dev:orchestrator\n`);
285
- process.exit(1);
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
- if (secretGenerated) {
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.11.7",
3
+ "version": "0.11.9",
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.11.7",
151
- "@avocadostudio-ai/richtext": "^0.11.7",
152
- "@avocadostudio-ai/preview-adapter": "^0.11.7",
153
- "@avocadostudio-ai/shared": "^0.11.7"
150
+ "@avocadostudio-ai/blocks": "^0.11.9",
151
+ "@avocadostudio-ai/richtext": "^0.11.9",
152
+ "@avocadostudio-ai/shared": "^0.11.9",
153
+ "@avocadostudio-ai/preview-adapter": "^0.11.9"
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.11.7"
160
+ "@avocadostudio-ai/orchestrator-core": "^0.11.9"
161
161
  },
162
162
  "peerDependenciesMeta": {
163
163
  "@avocadostudio-ai/orchestrator-core": {