@blinkk/root-cms 2.0.1 → 2.0.3

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/plugin.js CHANGED
@@ -1,12 +1,42 @@
1
+ var __create = Object.create;
1
2
  var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
7
  var __esm = (fn, res) => function __init() {
4
8
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
9
  };
10
+ var __commonJS = (cb, mod) => function __require() {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ };
6
13
  var __export = (target, all) => {
7
14
  for (var name in all)
8
15
  __defProp(target, name, { get: all[name], enumerable: true });
9
16
  };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // shared/ai/prompts/edit.txt
35
+ var require_edit = __commonJS({
36
+ "shared/ai/prompts/edit.txt"(exports, module) {
37
+ module.exports = 'Role: You are a highly accurate AI assistant specializing in converting unstructured documents into JSON files compatible with Root CMS. Root CMS is a web-based CMS for building websites and landing pages, storing its content as JSON files in a Firebase Firestore database.\n\nObjective: To edit and/or generate highly accurate JSON files compatible with Root CMS, based on user input (screenshots, chat messages, etc.) and, when editing JSON files, the original JSON file you are editing. The resulting content should contain all of the marketing content provided by the user input (and none of the annotations or superfluous content). The JSON files should be perfectly compatible with Root CMS.\n\nInput Requirements & Handling:\n\n- You will receive unstructured documents and you must convert them into JSON files compatible with Root CMS by either editing the existing JSON files or generating new ones from scratch. The JSON files you produce will typically represent a single section of a page of the website, such as a hero section, a feature section, or a footer.\n\n- When editing an existing JSON file, it will be annotated with a key `_type` that indicates the type of section it is, such as `Hero`, `Feature`, or `Footer`. You must ensure that the resulting JSON file is compatible with Root CMS and contains all of the marketing content provided by the user input. You will also be given a TypeScript interface in `root-cms.d.ts` that describes the structure of the JSON file you are editing. You must ensure that the resulting JSON file is compatible with this interface.\n\n- The input you receive will be broken into three parts. (1) `root-cms-d.ts` (the TypeScript interface), (2) the existing JSON file you are editing (or an empty JSON file when generating new content), and (3) the user input (screenshots, chat messages, instructions, etc.). You must ensure that the resulting JSON file is compatible with the structure defined by the TypeScript interface and contains all of the marketing content provided by the user input.\n\n- When you provide your response, if the user instructs you to only edit one part of the original JSON, make sure to leave the rest of the JSON file untouched. Your response, however, should always contain the _entire_ JSON file combining the original contents with your new edits (think of it like you\'re applying a PATCH operation and the response is the entire file).\n\nRoot CMS JSON File Structure:\n\n- Root CMS JSON files are standard JSON files described by the TypeScript interface in `root-cms.d.ts`. However, there is one critical syntax rule that you must understand and always follow regarding array values. Array values are expressed as hash maps. The order of the resulting data when Root CMS unmarshals the data is provided by a special key called `_array`. For example, to represent an array [1, 2, 3] in the CMS, the JSON data may look like this. You may generate your own UIDs for the keys and the items within `_array`.\n\n```json\n{\n "_array": ["2HGwoV", "LjFHWk", "OLakGA", "UaGHJh"],\n "2HGwoV": 1,\n "LjFHWk": 2,\n "OLakGA": 3,\n "UaGHJh": 4\n}\n```\n\nWhen creating arrays in your JSON output, you must _never_ forget to add an `_array` key. If you forget to add an `_array` key, the output will NOT work with Root CMS. When adding new array items, you can create your own array keys.\n\n2. Images and file fields have system-provided metadata, that looks like this example:\n\n```\n{\n "src": "https://storage.googleapis.com/rootjs-dev.appspot.com/www/uploads/2cad5d65bb7690bad5dcd4a041cf2fef46361678.svg",\n "filename": "600x400.svg",\n "uploadedAt": "1754023634564",\n "alt": "",\n "width": "600",\n "uploadedBy": "user@example.com",\n "height": "400"\n}\n```\n\n3. Rich Text fields are stored as Editor.js documents, which look like this. You may generate your own UIDs for the `id` fields.\n\n```\n{\n "version": "2.28.2",\n "time": 1754024041637,\n "blocks": [\n {\n "type": "paragraph",\n "data": {\n "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <a href=\\"https://example.com\\">This accepts HTML</a>."\n },\n "id": "_8PEf3kBx2"\n },\n {\n "data": {\n "style": "unordered",\n "items": [\n {\n "content": "This is list item 1.",\n "items": []\n },\n {\n "items": [],\n "content": "This is list item 2."\n }\n ]\n },\n "id": "G5zMDP1ytM",\n "type": "unorderedList"\n }\n ]\n}\n```\n\n4. Root CMS supports "oneOf" fields, where values can be "one of" another schema. These are typically used as array values or object values, where the user can from one of other schema types to structure the data value. For example, each module in a page\'s module list is "one of" the modules available in the library. One of values have a system-provided `_type` key that describes the type of the value. For example, this is a hero module, whose type is "TemplateHeadline", one of the available templates from the module library:\n\n```\n{\n "eyebrow": "Lorem ipsum",\n "_type": "TemplateHeadline",\n "body": {\n "time": 1754023554170,\n "version": "2.28.2",\n "blocks": [\n {\n "type": "paragraph",\n "data": {\n "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."\n },\n "id": "IVmxEVsxmt"\n },\n {\n "id": "aMiHiywkQD",\n "type": "paragraph",\n "data": {\n "text": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."\n }\n }\n ]\n },\n "title": "Welcome to our website"\n}\n```\n\nWhen adding new items to arrays that use `oneOf` fields, try your best to figure out which content type would best satisfy the user\'s request. You must never _invent_ new content types, for example, if "TemplateFoo" doesn\'t exist in `root-cms.d.ts`, you must never add a value with `{"_type": "TemplateFoo"}`. If none of the available content types within `root-cms.d.ts` satisfy the user\'s request, avoid creating the new array item and inform the user that there is no existing template compatible.\n\nAdditionally, when adding new items to existing JSON data, you must carefully check the structure of those items as defined by `root-cms.d.ts`. Avoid inventing new fields for content types, use only those defined by the `root-cms.d.ts`. For example, if you are adding a button to a module, carefully check the button structure defined in `root-cms.d.ts` and only populate fields that are defined there.\n\n5. Here is the `root-cms.d.ts` file for this project:\n\n```\n{{ROOT_CMS_DEFS}}\n```\n\nFinally, when you provide your response, it MUST be structured in the following way for handling by the chat client.\n\n{\n "data": <The JSON generated by you via the instructions above>,\n "message": <A brief message that describes what you did.>\n}\n\nYou must always ONLY reply in this exact format. The brief message that describes what you did should be written succinctly, tersely, in a friendly but not cute tone, and be no more than a sentence or two.\n';
38
+ }
39
+ });
10
40
 
11
41
  // cli/generate-types.ts
12
42
  var generate_types_exports = {};
@@ -14,21 +44,21 @@ __export(generate_types_exports, {
14
44
  generateSchemaDts: () => generateSchemaDts,
15
45
  generateTypes: () => generateTypes
16
46
  });
17
- import { promises as fs } from "node:fs";
18
- import path2 from "node:path";
47
+ import { promises as fs2 } from "node:fs";
48
+ import path3 from "node:path";
19
49
  import { fileURLToPath } from "node:url";
20
50
  import { loadRootConfig, viteSsrLoadModule } from "@blinkk/root/node";
21
51
  import * as dom from "dts-dom";
22
52
  async function generateTypes() {
23
53
  const rootDir = process.cwd();
24
54
  const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
25
- const modulePath = path2.resolve(__dirname, "./project.js");
55
+ const modulePath = path3.resolve(__dirname, "./project.js");
26
56
  const project = await viteSsrLoadModule(
27
57
  rootConfig,
28
58
  modulePath
29
59
  );
30
60
  const schemas = project.getProjectSchemas();
31
- const outputPath = path2.resolve(rootDir, "root-cms.d.ts");
61
+ const outputPath = path3.resolve(rootDir, "root-cms.d.ts");
32
62
  await generateSchemaDts(outputPath, schemas);
33
63
  console.log("saved root-cms.d.ts!");
34
64
  }
@@ -39,7 +69,7 @@ async function generateSchemaDts(outputPath, schemas) {
39
69
  dtsFormatter.addSchemaFile(fileId, schema);
40
70
  }
41
71
  const output = dtsFormatter.toString();
42
- await fs.writeFile(outputPath, output, "utf-8");
72
+ await fs2.writeFile(outputPath, output, "utf-8");
43
73
  }
44
74
  function fieldProperty(field, options) {
45
75
  const prop = dom.create.property(
@@ -153,7 +183,7 @@ var __dirname, TEMPLATE, DtsFormatter;
153
183
  var init_generate_types = __esm({
154
184
  "cli/generate-types.ts"() {
155
185
  "use strict";
156
- __dirname = path2.dirname(fileURLToPath(import.meta.url));
186
+ __dirname = path3.dirname(fileURLToPath(import.meta.url));
157
187
  TEMPLATE = `/* eslint-disable */
158
188
  /** Root.js CMS types. This file is autogenerated. */
159
189
 
@@ -227,7 +257,7 @@ export interface RootCMSDoc<Fields extends {}> {
227
257
  /** Adds the types for a `.schema.ts` file to the `.d.ts` file. */
228
258
  addSchemaFile(fileId, schema) {
229
259
  const jsdoc = `Generated from \`${fileId}\`.`;
230
- const typeId = alphanumeric(path2.parse(fileId).name.split(".")[0]);
260
+ const typeId = alphanumeric(path3.parse(fileId).name.split(".")[0]);
231
261
  const oneOfTypes = {};
232
262
  const fieldsTypeId = `${typeId}Fields`;
233
263
  const fieldsType = dom.create.interface(
@@ -307,8 +337,8 @@ export interface RootCMSDoc<Fields extends {}> {
307
337
  });
308
338
 
309
339
  // core/plugin.ts
310
- import { promises as fs2 } from "node:fs";
311
- import path3 from "node:path";
340
+ import { promises as fs3 } from "node:fs";
341
+ import path4 from "node:path";
312
342
  import { fileURLToPath as fileURLToPath2 } from "node:url";
313
343
  import bodyParser from "body-parser";
314
344
  import {
@@ -326,19 +356,22 @@ import { multipartMiddleware } from "@blinkk/root/middleware";
326
356
 
327
357
  // core/ai.ts
328
358
  import crypto from "node:crypto";
359
+ import fs from "node:fs";
360
+ import path from "node:path";
329
361
  import { vertexAI } from "@genkit-ai/vertexai";
330
362
  import { Timestamp } from "firebase-admin/firestore";
331
363
  import { genkit } from "genkit";
364
+ import { logger } from "genkit/logging";
365
+ logger.setLogLevel("warn");
332
366
  var DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
333
367
  var Chat = class {
334
368
  constructor(chatClient, id, options) {
335
- var _a;
336
369
  this.chatClient = chatClient;
337
370
  this.cmsClient = chatClient.cmsClient;
338
371
  this.cmsPluginOptions = this.cmsClient.cmsPlugin.getConfig();
339
372
  this.id = id;
340
- this.history = (options == null ? void 0 : options.history) ?? [];
341
- this.model = (options == null ? void 0 : options.model) || (typeof ((_a = this.cmsPluginOptions.experiments) == null ? void 0 : _a.ai) === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
373
+ this.history = options?.history ?? [];
374
+ this.model = options?.model || (typeof this.cmsPluginOptions.experiments?.ai === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
342
375
  const firebaseConfig = this.cmsPluginOptions.firebaseConfig;
343
376
  this.ai = genkit({
344
377
  plugins: [
@@ -350,21 +383,52 @@ var Chat = class {
350
383
  });
351
384
  }
352
385
  /** Builds the messages for the AI request. */
353
- async buildMessages() {
386
+ async buildMessages(options) {
354
387
  const messages = this.history;
355
388
  if (messages.length === 0) {
356
389
  messages.push({
357
390
  role: "system",
358
- content: [{ text: await this.buildSystemPrompt() }]
391
+ content: [{ text: await this.buildSystemPrompt(options) }]
392
+ });
393
+ }
394
+ if (options.mode === "edit") {
395
+ messages.push({
396
+ role: "user",
397
+ content: [
398
+ {
399
+ text: [
400
+ "The JSON you must edit is:",
401
+ "",
402
+ JSON.stringify(options.editData || {}, null, 2)
403
+ ].join("")
404
+ }
405
+ ]
359
406
  });
360
407
  }
361
408
  return messages;
362
409
  }
363
- async sendPrompt(prompt) {
364
- const res = await this.ai.generate({
365
- messages: await this.buildMessages(),
410
+ /** Builds the request sent to the AI based on the `ChatMode`. */
411
+ async buildChatRequest(prompt, options) {
412
+ if (options.mode === "edit") {
413
+ return {
414
+ messages: await this.buildMessages(options),
415
+ model: this.model,
416
+ prompt
417
+ };
418
+ }
419
+ return {
420
+ messages: await this.buildMessages(options),
366
421
  model: this.model,
367
422
  prompt
423
+ };
424
+ }
425
+ /** Sends the request to the AI and stores the history in the session and the database. */
426
+ async sendPrompt(prompt, options = {}) {
427
+ const chatRequest = await this.buildChatRequest(prompt, options);
428
+ const res = await this.ai.generate({
429
+ model: chatRequest.model,
430
+ messages: chatRequest.messages,
431
+ prompt: Array.isArray(prompt) ? prompt.flat() : prompt
368
432
  });
369
433
  this.history = res.messages;
370
434
  await this.dbDoc().update({
@@ -376,13 +440,35 @@ var Chat = class {
376
440
  dbDoc() {
377
441
  return this.chatClient.dbCollection().doc(this.id);
378
442
  }
379
- async buildSystemPrompt() {
443
+ /**
444
+ * Builds the system prompt sent to the AI, based on the `ChatMode` and
445
+ * supplied `SendPromptOptions`. `SendPromptOptions` may contain data or
446
+ * references to information needed to construct the prompt.
447
+ */
448
+ async buildSystemPrompt(options) {
449
+ const serializedRootConfig = JSON.stringify(
450
+ this.cmsClient.rootConfig,
451
+ null,
452
+ 2
453
+ );
454
+ if (options.mode === "edit") {
455
+ const rootDir = process.cwd();
456
+ const rootCmsDefs = fs.readFileSync(
457
+ path.resolve(rootDir, "root-cms.d.ts"),
458
+ {
459
+ encoding: "utf8"
460
+ }
461
+ );
462
+ const text = (await Promise.resolve().then(() => __toESM(require_edit(), 1))).default;
463
+ text.replace("{{ROOT_CMS_DEFS}}", rootCmsDefs);
464
+ return text;
465
+ }
380
466
  const systemText = [
381
467
  `You are an assistant for a headless CMS called Root CMS which is used on a website called ${this.cmsPluginOptions.name || this.cmsPluginOptions.id}. Your job is to answer questions about the docs in the system, and if requested, help suggest changes to the JSON data in the docs. If you don't know the answer, just say that you don't know, don't try to make up an answer. Be friendly and playful with your messaging.`,
382
468
  "",
383
469
  "Here is the root.config.ts file for the site:",
384
470
  "```",
385
- JSON.stringify(this.cmsClient.rootConfig, null, 2),
471
+ serializedRootConfig,
386
472
  "```",
387
473
  "",
388
474
  "Here are the docs that exist in the system:"
@@ -425,7 +511,7 @@ var ChatClient = class {
425
511
  });
426
512
  }
427
513
  async listChats(options) {
428
- const limit = (options == null ? void 0 : options.limit) || 20;
514
+ const limit = options?.limit || 20;
429
515
  const query = this.dbCollection().where("createdBy", "==", this.user).limit(limit).orderBy("createdAt", "desc");
430
516
  const res = await query.get();
431
517
  return res.docs.map((doc) => doc.data());
@@ -503,11 +589,11 @@ var TranslationsManager = class {
503
589
  locale,
504
590
  sys: {
505
591
  modifiedAt: Timestamp2.now(),
506
- modifiedBy: (options == null ? void 0 : options.modifiedBy) || "root-cms-client"
592
+ modifiedBy: options?.modifiedBy || "root-cms-client"
507
593
  },
508
594
  strings: {}
509
595
  };
510
- if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
596
+ if (options?.tags && options.tags.length > 0) {
511
597
  updates.tags = FieldValue.arrayUnion(...options.tags);
512
598
  }
513
599
  let numUpdates = 0;
@@ -547,13 +633,13 @@ var TranslationsManager = class {
547
633
  console.warn(`no translations to publish for ${id}`);
548
634
  return;
549
635
  }
550
- const batch = (options == null ? void 0 : options.batch) || db.batch();
636
+ const batch = options?.batch || db.batch();
551
637
  res.docs.forEach((doc) => {
552
638
  const translationsLocaleDoc = doc.data();
553
639
  const sys = {
554
640
  ...translationsLocaleDoc.sys,
555
641
  publishedAt: Timestamp2.now(),
556
- publishedBy: (options == null ? void 0 : options.publishedBy) || "root-cms-client"
642
+ publishedBy: options?.publishedBy || "root-cms-client"
557
643
  };
558
644
  batch.update(doc.ref, { sys });
559
645
  const publishedDocPath = buildTranslationsLocaleDocDbPath({
@@ -565,7 +651,7 @@ var TranslationsManager = class {
565
651
  const publishedDocRef = db.doc(publishedDocPath);
566
652
  batch.set(publishedDocRef, { ...translationsLocaleDoc, sys });
567
653
  });
568
- const shouldCommitBatch = !(options == null ? void 0 : options.batch);
654
+ const shouldCommitBatch = !options?.batch;
569
655
  if (shouldCommitBatch) {
570
656
  await batch.commit();
571
657
  }
@@ -606,19 +692,19 @@ var TranslationsManager = class {
606
692
  * ```
607
693
  */
608
694
  async loadTranslations(options) {
609
- const mode = (options == null ? void 0 : options.mode) || "published";
695
+ const mode = options?.mode || "published";
610
696
  const dbPath = buildTranslationsDbPath({
611
697
  project: this.cmsClient.projectId,
612
698
  mode
613
699
  });
614
700
  let query = this.cmsClient.db.collection(dbPath);
615
- if ((options == null ? void 0 : options.ids) && options.ids.length > 0) {
701
+ if (options?.ids && options.ids.length > 0) {
616
702
  query = query.where("id", "in", options.ids);
617
703
  }
618
- if ((options == null ? void 0 : options.tags) && options.tags.length > 0) {
704
+ if (options?.tags && options.tags.length > 0) {
619
705
  query = query.where("tags", "array-contains", options.tags);
620
706
  }
621
- if ((options == null ? void 0 : options.locales) && options.locales.length > 0) {
707
+ if (options?.locales && options.locales.length > 0) {
622
708
  query = query.where("locale", "in", options.locales);
623
709
  }
624
710
  const results = await query.get();
@@ -649,11 +735,11 @@ var TranslationsManager = class {
649
735
  async loadTranslationsForLocale(locale, options) {
650
736
  const localeSet = /* @__PURE__ */ new Set([
651
737
  locale,
652
- ...(options == null ? void 0 : options.fallbackLocales) || []
738
+ ...options?.fallbackLocales || []
653
739
  ]);
654
740
  const fallbackLocales = Array.from(localeSet);
655
741
  const multiLocaleStrings = await this.loadTranslations({
656
- mode: options == null ? void 0 : options.mode,
742
+ mode: options?.mode,
657
743
  locales: fallbackLocales
658
744
  });
659
745
  return this.toSingleLocaleMap(multiLocaleStrings, fallbackLocales);
@@ -724,7 +810,6 @@ var TranslationsManager = class {
724
810
  * Import translations from the v1 system to the TranslationsManager.
725
811
  */
726
812
  async importTranslationsFromV1() {
727
- var _a;
728
813
  const projectId = this.cmsClient.projectId;
729
814
  const db = this.cmsClient.db;
730
815
  const dbPath = `Projects/${projectId}/Translations`;
@@ -765,7 +850,7 @@ var TranslationsManager = class {
765
850
  const doc = await this.cmsClient.getDoc(collection, slug, {
766
851
  mode: "draft"
767
852
  });
768
- const linkedSheet = (_a = doc == null ? void 0 : doc.sys) == null ? void 0 : _a.l10nSheet;
853
+ const linkedSheet = doc?.sys?.l10nSheet;
769
854
  if (linkedSheet) {
770
855
  translationsDocs[docId].sys.linkedSheet = linkedSheet;
771
856
  }
@@ -868,7 +953,7 @@ var RootCMSClient = class {
868
953
  const { collection, slug } = parseDocId(docId);
869
954
  const draftDoc = await this.getRawDoc(collection, slug, { mode: "draft" }) || {};
870
955
  const draftSys = draftDoc.sys || {};
871
- const modifiedBy = (options == null ? void 0 : options.modifiedBy) || "root-cms-client";
956
+ const modifiedBy = options?.modifiedBy || "root-cms-client";
872
957
  const fields = marshalData(fieldsData || {});
873
958
  const data = {
874
959
  id: docId,
@@ -880,7 +965,7 @@ var RootCMSClient = class {
880
965
  createdBy: draftSys.createdBy ?? modifiedBy,
881
966
  modifiedAt: Timestamp3.now(),
882
967
  modifiedBy,
883
- locales: (options == null ? void 0 : options.locales) ?? draftSys.locales ?? ["en"]
968
+ locales: options?.locales ?? draftSys.locales ?? ["en"]
884
969
  },
885
970
  fields
886
971
  };
@@ -950,7 +1035,7 @@ var RootCMSClient = class {
950
1035
  */
951
1036
  async publishDocs(docIds, options) {
952
1037
  const projectCollectionsPath = `Projects/${this.projectId}/Collections`;
953
- const publishedBy = (options == null ? void 0 : options.publishedBy) || "root-cms-client";
1038
+ const publishedBy = options?.publishedBy || "root-cms-client";
954
1039
  const docRefs = docIds.map((docId) => {
955
1040
  const [collection, slug] = docId.split("/");
956
1041
  if (!collection || !slug) {
@@ -973,9 +1058,9 @@ var RootCMSClient = class {
973
1058
  }
974
1059
  }
975
1060
  let batchCount = 0;
976
- const batch = (options == null ? void 0 : options.batch) || this.db.batch();
1061
+ const batch = options?.batch || this.db.batch();
977
1062
  const versionTags = ["published"];
978
- if (options == null ? void 0 : options.releaseId) {
1063
+ if (options?.releaseId) {
979
1064
  versionTags.push(`release:${options.releaseId}`);
980
1065
  }
981
1066
  const publishedDocs = [];
@@ -1058,12 +1143,11 @@ var RootCMSClient = class {
1058
1143
  const now = Math.ceil((/* @__PURE__ */ new Date()).getTime());
1059
1144
  const snapshot = await this.db.collectionGroup("Scheduled").get();
1060
1145
  const docs = snapshot.docs.filter((d) => {
1061
- var _a;
1062
1146
  if (!d.ref.path.startsWith(projectCollectionsPath)) {
1063
1147
  return false;
1064
1148
  }
1065
1149
  const data = d.data() || {};
1066
- const scheduledAt = (_a = data.sys) == null ? void 0 : _a.scheduledAt;
1150
+ const scheduledAt = data.sys?.scheduledAt;
1067
1151
  return scheduledAt && scheduledAt.toMillis && scheduledAt.toMillis() <= now;
1068
1152
  }).map((d) => {
1069
1153
  const dbPath = d.ref.path;
@@ -1194,8 +1278,7 @@ var RootCMSClient = class {
1194
1278
  * Checks if a doc is currently "locked" for publishing.
1195
1279
  */
1196
1280
  testPublishingLocked(doc) {
1197
- var _a;
1198
- if ((_a = doc.sys) == null ? void 0 : _a.publishingLocked) {
1281
+ if (doc.sys?.publishingLocked) {
1199
1282
  if (doc.sys.publishingLocked.until) {
1200
1283
  const now = Timestamp3.now().toMillis();
1201
1284
  const until = doc.sys.publishingLocked.until.toMillis();
@@ -1220,9 +1303,8 @@ var RootCMSClient = class {
1220
1303
  * replace the v1 translations system.
1221
1304
  */
1222
1305
  getTranslationsManager() {
1223
- var _a;
1224
1306
  const cmsPluginOptions = this.cmsPlugin.getConfig();
1225
- if ((_a = cmsPluginOptions.experiments) == null ? void 0 : _a.v2TranslationsManager) {
1307
+ if (cmsPluginOptions.experiments?.v2TranslationsManager) {
1226
1308
  throw new Error(
1227
1309
  "`v2TranslationsManager` is not enabled. update root.config.ts and add: `{experiments: {v2TranslationsManager: true}}`"
1228
1310
  );
@@ -1243,7 +1325,7 @@ var RootCMSClient = class {
1243
1325
  async loadTranslations(options) {
1244
1326
  const dbPath = `Projects/${this.projectId}/Translations`;
1245
1327
  let query = this.db.collection(dbPath);
1246
- if (options == null ? void 0 : options.tags) {
1328
+ if (options?.tags) {
1247
1329
  query = query.where("tags", "array-contains-any", options.tags);
1248
1330
  }
1249
1331
  const querySnapshot = await query.get();
@@ -1361,7 +1443,7 @@ var RootCMSClient = class {
1361
1443
  const dataDocRef = this.db.doc(
1362
1444
  `Projects/${this.projectId}/DataSources/${dataSourceId}/Data/draft`
1363
1445
  );
1364
- const syncedBy = (options == null ? void 0 : options.syncedBy) || "root-cms-client";
1446
+ const syncedBy = options?.syncedBy || "root-cms-client";
1365
1447
  const updatedDataSource = {
1366
1448
  ...dataSource,
1367
1449
  syncedAt: Timestamp3.now(),
@@ -1395,7 +1477,7 @@ var RootCMSClient = class {
1395
1477
  `Projects/${this.projectId}/DataSources/${dataSourceId}/published`
1396
1478
  );
1397
1479
  const dataRes = await this.getFromDataSource(dataSourceId, { mode: "draft" });
1398
- const publishedBy = (options == null ? void 0 : options.publishedBy) || "root-cms-client";
1480
+ const publishedBy = options?.publishedBy || "root-cms-client";
1399
1481
  const updatedDataSource = {
1400
1482
  ...dataSource,
1401
1483
  publishedAt: Timestamp3.now(),
@@ -1404,8 +1486,8 @@ var RootCMSClient = class {
1404
1486
  const batch = this.db.batch();
1405
1487
  batch.set(dataDocRefPublished, {
1406
1488
  dataSource: updatedDataSource,
1407
- data: (dataRes == null ? void 0 : dataRes.data) || null,
1408
- ...(dataRes == null ? void 0 : dataRes.headers) ? { headers: dataRes.headers } : {}
1489
+ data: dataRes?.data || null,
1490
+ ...dataRes?.headers ? { headers: dataRes.headers } : {}
1409
1491
  });
1410
1492
  batch.update(dataDocRefDraft, {
1411
1493
  dataSource: updatedDataSource
@@ -1419,8 +1501,8 @@ var RootCMSClient = class {
1419
1501
  console.log(`published by: ${publishedBy}`);
1420
1502
  }
1421
1503
  async publishDataSources(dataSourceIds, options) {
1422
- const publishedBy = (options == null ? void 0 : options.publishedBy) || "root-cms-client";
1423
- const batch = (options == null ? void 0 : options.batch) || this.db.batch();
1504
+ const publishedBy = options?.publishedBy || "root-cms-client";
1505
+ const batch = options?.batch || this.db.batch();
1424
1506
  for (const id of dataSourceIds) {
1425
1507
  const dataSource = await this.getDataSource(id);
1426
1508
  if (!dataSource) {
@@ -1443,8 +1525,8 @@ var RootCMSClient = class {
1443
1525
  };
1444
1526
  batch.set(dataDocRefPublished, {
1445
1527
  dataSource: updatedDataSource,
1446
- data: (dataRes == null ? void 0 : dataRes.data) || null,
1447
- ...(dataRes == null ? void 0 : dataRes.headers) ? { headers: dataRes.headers } : {}
1528
+ data: dataRes?.data || null,
1529
+ ...dataRes?.headers ? { headers: dataRes.headers } : {}
1448
1530
  });
1449
1531
  batch.update(dataDocRefDraft, { dataSource: updatedDataSource });
1450
1532
  batch.update(dataSourceDocRef, {
@@ -1452,7 +1534,7 @@ var RootCMSClient = class {
1452
1534
  publishedBy
1453
1535
  });
1454
1536
  }
1455
- if (!(options == null ? void 0 : options.batch) || (options == null ? void 0 : options.commitBatch)) {
1537
+ if (!options?.batch || options?.commitBatch) {
1456
1538
  await batch.commit();
1457
1539
  }
1458
1540
  }
@@ -1463,15 +1545,14 @@ var RootCMSClient = class {
1463
1545
  throw new Error(`unsupported data source: ${dataSource.type}`);
1464
1546
  }
1465
1547
  async fetchHttpData(dataSource) {
1466
- var _a, _b, _c;
1467
1548
  const url = dataSource.url || "";
1468
1549
  if (!url.startsWith("https://")) {
1469
1550
  throw new Error(`url not supported: ${url}`);
1470
1551
  }
1471
1552
  const res = await fetch(url, {
1472
- method: ((_a = dataSource.httpOptions) == null ? void 0 : _a.method) || "GET",
1473
- headers: ((_b = dataSource.httpOptions) == null ? void 0 : _b.headers) || [],
1474
- body: ((_c = dataSource.httpOptions) == null ? void 0 : _c.body) || void 0
1553
+ method: dataSource.httpOptions?.method || "GET",
1554
+ headers: dataSource.httpOptions?.headers || [],
1555
+ body: dataSource.httpOptions?.body || void 0
1475
1556
  });
1476
1557
  if (res.status !== 200) {
1477
1558
  const err = await res.text();
@@ -1487,7 +1568,7 @@ var RootCMSClient = class {
1487
1568
  * Fetches data from a data source.
1488
1569
  */
1489
1570
  async getFromDataSource(dataSourceId, options) {
1490
- const mode = (options == null ? void 0 : options.mode) || "published";
1571
+ const mode = options?.mode || "published";
1491
1572
  if (!(mode === "draft" || mode === "published")) {
1492
1573
  throw new Error(`invalid mode: ${mode}`);
1493
1574
  }
@@ -1577,12 +1658,12 @@ var RootCMSClient = class {
1577
1658
  const data = {
1578
1659
  action,
1579
1660
  timestamp: Timestamp3.now(),
1580
- by: (options == null ? void 0 : options.by) || "system",
1581
- metadata: (options == null ? void 0 : options.metadata) || {}
1661
+ by: options?.by || "system",
1662
+ metadata: options?.metadata || {}
1582
1663
  };
1583
1664
  const colRef = this.db.collection(`Projects/${this.projectId}/ActionLogs`);
1584
1665
  await colRef.add(data);
1585
- const metaStr = (options == null ? void 0 : options.metadata) ? stringifyObj(options.metadata) : "";
1666
+ const metaStr = options?.metadata ? stringifyObj(options.metadata) : "";
1586
1667
  console.log(`[${data.timestamp.toMillis()}] action: ${action} ${metaStr}`);
1587
1668
  const cmsPluginConfig = this.cmsPlugin.getConfig();
1588
1669
  if (cmsPluginConfig.onAction) {
@@ -1940,7 +2021,7 @@ var BatchResponse = class {
1940
2021
  };
1941
2022
 
1942
2023
  // core/versions.ts
1943
- import path from "node:path";
2024
+ import path2 from "node:path";
1944
2025
  import { Timestamp as Timestamp4 } from "firebase-admin/firestore";
1945
2026
  import glob from "tiny-glob";
1946
2027
  var DOCUMENT_SAVE_OFFSET = 5 * 60 * 1e3;
@@ -1973,8 +2054,7 @@ var VersionsService = class {
1973
2054
  async saveVersionsToFirestore(versions) {
1974
2055
  const batch = this.db.batch();
1975
2056
  versions.forEach((version) => {
1976
- var _a;
1977
- if (!version.collection || !version.slug || !((_a = version.sys) == null ? void 0 : _a.modifiedAt)) {
2057
+ if (!version.collection || !version.slug || !version.sys?.modifiedAt) {
1978
2058
  return;
1979
2059
  }
1980
2060
  const modifiedAtMillis = version.sys.modifiedAt.toMillis();
@@ -2033,7 +2113,7 @@ var VersionsService = class {
2033
2113
  async listCollections() {
2034
2114
  const collectionIds = [];
2035
2115
  const collectionFileNames = await glob("*.schema.ts", {
2036
- cwd: path.join(this.rootConfig.rootDir, "collections")
2116
+ cwd: path2.join(this.rootConfig.rootDir, "collections")
2037
2117
  });
2038
2118
  collectionFileNames.forEach((filename) => {
2039
2119
  collectionIds.push(filename.slice(0, -10));
@@ -2160,12 +2240,11 @@ function api(server, options) {
2160
2240
  }
2161
2241
  );
2162
2242
  server.use("/cms/api/data.sync", async (req, res) => {
2163
- var _a;
2164
2243
  if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
2165
2244
  res.status(400).json({ success: false, error: "BAD_REQUEST" });
2166
2245
  return;
2167
2246
  }
2168
- if (!((_a = req.user) == null ? void 0 : _a.email)) {
2247
+ if (!req.user?.email) {
2169
2248
  res.status(401).json({ success: false, error: "UNAUTHORIZED" });
2170
2249
  }
2171
2250
  const reqBody = req.body || {};
@@ -2184,12 +2263,11 @@ function api(server, options) {
2184
2263
  }
2185
2264
  });
2186
2265
  server.use("/cms/api/actions.log", async (req, res) => {
2187
- var _a, _b;
2188
2266
  if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
2189
2267
  res.status(400).json({ success: false, error: "BAD_REQUEST" });
2190
2268
  return;
2191
2269
  }
2192
- if (!((_a = req.user) == null ? void 0 : _a.email)) {
2270
+ if (!req.user?.email) {
2193
2271
  res.status(401).json({ success: false, error: "UNAUTHORIZED" });
2194
2272
  }
2195
2273
  const reqBody = req.body || {};
@@ -2205,7 +2283,7 @@ function api(server, options) {
2205
2283
  const cmsClient = new RootCMSClient(req.rootConfig);
2206
2284
  try {
2207
2285
  await cmsClient.logAction(action, {
2208
- by: (_b = req.user) == null ? void 0 : _b.email,
2286
+ by: req.user?.email,
2209
2287
  metadata
2210
2288
  });
2211
2289
  res.status(200).json({ success: true });
@@ -2215,12 +2293,11 @@ function api(server, options) {
2215
2293
  }
2216
2294
  });
2217
2295
  server.use("/cms/api/ai.chat", async (req, res) => {
2218
- var _a;
2219
2296
  if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
2220
2297
  res.status(400).json({ success: false, error: "BAD_REQUEST" });
2221
2298
  return;
2222
2299
  }
2223
- if (!((_a = req.user) == null ? void 0 : _a.email)) {
2300
+ if (!req.user?.email) {
2224
2301
  res.status(401).json({ success: false, error: "UNAUTHORIZED" });
2225
2302
  return;
2226
2303
  }
@@ -2239,7 +2316,10 @@ function api(server, options) {
2239
2316
  } else {
2240
2317
  chat = await chatClient.createChat();
2241
2318
  }
2242
- const chatResponse = await chat.sendPrompt(prompt);
2319
+ const chatResponse = await chat.sendPrompt(prompt, {
2320
+ mode: reqBody.options?.mode || "chat",
2321
+ editData: reqBody.options?.editData
2322
+ });
2243
2323
  res.status(200).json({ success: true, chatId: chat.id, response: chatResponse });
2244
2324
  } catch (err) {
2245
2325
  console.error(err.stack || err);
@@ -2247,12 +2327,11 @@ function api(server, options) {
2247
2327
  }
2248
2328
  });
2249
2329
  server.use("/cms/api/ai.list_chats", async (req, res) => {
2250
- var _a;
2251
2330
  if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
2252
2331
  res.status(400).json({ success: false, error: "BAD_REQUEST" });
2253
2332
  return;
2254
2333
  }
2255
- if (!((_a = req.user) == null ? void 0 : _a.email)) {
2334
+ if (!req.user?.email) {
2256
2335
  res.status(401).json({ success: false, error: "UNAUTHORIZED" });
2257
2336
  return;
2258
2337
  }
@@ -2271,7 +2350,7 @@ function api(server, options) {
2271
2350
  }
2272
2351
 
2273
2352
  // core/plugin.ts
2274
- var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
2353
+ var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
2275
2354
  var SESSION_COOKIE_AUTH = "root-cms-auth";
2276
2355
  var NONCONF_STATIC_PATHS = [
2277
2356
  "/cms/static/signin.css",
@@ -2330,8 +2409,7 @@ function cmsPlugin(options) {
2330
2409
  return false;
2331
2410
  }
2332
2411
  async function verifyUserLogin(req) {
2333
- var _a;
2334
- const idToken = (_a = req.body) == null ? void 0 : _a.idToken;
2412
+ const idToken = req.body?.idToken;
2335
2413
  if (!idToken) {
2336
2414
  console.log("login failed: no id token");
2337
2415
  return { authorized: false, reason: "no token" };
@@ -2389,7 +2467,7 @@ function cmsPlugin(options) {
2389
2467
  }
2390
2468
  if (process.env.NODE_ENV === "development") {
2391
2469
  const jwt = jsonwebtoken.decode(sessionCookie);
2392
- if (!(jwt == null ? void 0 : jwt.email) || !(jwt == null ? void 0 : jwt.email_verified)) {
2470
+ if (!jwt?.email || !jwt?.email_verified) {
2393
2471
  return null;
2394
2472
  }
2395
2473
  if (options.isUserAuthorized) {
@@ -2477,7 +2555,7 @@ function cmsPlugin(options) {
2477
2555
  * A map of ssr files to include when running `root build`.
2478
2556
  */
2479
2557
  ssrInput: () => ({
2480
- cms: path3.resolve(__dirname2, "./app.js")
2558
+ cms: path4.resolve(__dirname2, "./app.js")
2481
2559
  }),
2482
2560
  /**
2483
2561
  * Attaches CMS-specific middleware to the Root.js server.
@@ -2486,13 +2564,13 @@ function cmsPlugin(options) {
2486
2564
  server.use(bodyParser.json());
2487
2565
  async function getRenderer(req) {
2488
2566
  if (serverOptions.type === "dev") {
2489
- const appFilePath = path3.resolve(__dirname2, "./app.js");
2567
+ const appFilePath = path4.resolve(__dirname2, "./app.js");
2490
2568
  const app3 = await req.viteServer.ssrLoadModule(
2491
2569
  appFilePath
2492
2570
  );
2493
2571
  return app3;
2494
2572
  }
2495
- const appImportPath = path3.resolve(
2573
+ const appImportPath = path4.resolve(
2496
2574
  req.rootConfig.rootDir,
2497
2575
  "dist/server/cms.js"
2498
2576
  );
@@ -2564,7 +2642,7 @@ function cmsPlugin(options) {
2564
2642
  }
2565
2643
  next();
2566
2644
  });
2567
- const staticDir = path3.resolve(__dirname2, "ui");
2645
+ const staticDir = path4.resolve(__dirname2, "ui");
2568
2646
  server.use("/cms/static", sirv(staticDir, { dev: false }));
2569
2647
  api(server, { getRenderer });
2570
2648
  server.use("/cms", async (req, res) => {
@@ -2598,7 +2676,7 @@ function cmsPlugin(options) {
2598
2676
  return plugin;
2599
2677
  }
2600
2678
  function fileExists(filepath) {
2601
- return fs2.access(filepath).then(() => true).catch(() => false);
2679
+ return fs3.access(filepath).then(() => true).catch(() => false);
2602
2680
  }
2603
2681
  export {
2604
2682
  cmsPlugin