@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/app.js +15 -5
- package/dist/client.js +38 -43
- package/dist/core.js +40 -46
- package/dist/functions.js +40 -46
- package/dist/plugin.js +163 -85
- package/dist/richtext.js +12 -19
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +142 -95
- package/package.json +14 -3
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
|
|
18
|
-
import
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
311
|
-
import
|
|
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 =
|
|
341
|
-
this.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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
592
|
+
modifiedBy: options?.modifiedBy || "root-cms-client"
|
|
507
593
|
},
|
|
508
594
|
strings: {}
|
|
509
595
|
};
|
|
510
|
-
if (
|
|
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 =
|
|
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:
|
|
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 = !
|
|
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 =
|
|
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 (
|
|
701
|
+
if (options?.ids && options.ids.length > 0) {
|
|
616
702
|
query = query.where("id", "in", options.ids);
|
|
617
703
|
}
|
|
618
|
-
if (
|
|
704
|
+
if (options?.tags && options.tags.length > 0) {
|
|
619
705
|
query = query.where("tags", "array-contains", options.tags);
|
|
620
706
|
}
|
|
621
|
-
if (
|
|
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
|
-
...
|
|
738
|
+
...options?.fallbackLocales || []
|
|
653
739
|
]);
|
|
654
740
|
const fallbackLocales = Array.from(localeSet);
|
|
655
741
|
const multiLocaleStrings = await this.loadTranslations({
|
|
656
|
-
mode: options
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
1061
|
+
const batch = options?.batch || this.db.batch();
|
|
977
1062
|
const versionTags = ["published"];
|
|
978
|
-
if (options
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
1408
|
-
...
|
|
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 =
|
|
1423
|
-
const 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:
|
|
1447
|
-
...
|
|
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 (!
|
|
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:
|
|
1473
|
-
headers:
|
|
1474
|
-
body:
|
|
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 =
|
|
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:
|
|
1581
|
-
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 =
|
|
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
|
|
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
|
-
|
|
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:
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2679
|
+
return fs3.access(filepath).then(() => true).catch(() => false);
|
|
2602
2680
|
}
|
|
2603
2681
|
export {
|
|
2604
2682
|
cmsPlugin
|