@blinkk/root-cms 2.4.3 → 2.4.4

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 CHANGED
@@ -83,7 +83,7 @@ function convertOneOfTypes(collection) {
83
83
  // package.json
84
84
  var package_default = {
85
85
  name: "@blinkk/root-cms",
86
- version: "2.4.3",
86
+ version: "2.4.4",
87
87
  author: "s@blinkk.com",
88
88
  license: "MIT",
89
89
  engines: {
@@ -148,6 +148,10 @@ var package_default = {
148
148
  "test:watch": "pnpm build && firebase emulators:exec 'vitest'"
149
149
  },
150
150
  dependencies: {
151
+ "@ag-grid-community/client-side-row-model": "32.3.9",
152
+ "@ag-grid-community/core": "32.3.9",
153
+ "@ag-grid-community/react": "32.3.9",
154
+ "@ag-grid-community/styles": "32.3.9",
151
155
  "@genkit-ai/ai": "1.18.0",
152
156
  "@genkit-ai/core": "1.18.0",
153
157
  "@genkit-ai/vertexai": "1.18.0",
@@ -238,7 +242,7 @@ var package_default = {
238
242
  yjs: "13.6.27"
239
243
  },
240
244
  peerDependencies: {
241
- "@blinkk/root": "2.4.3",
245
+ "@blinkk/root": "2.4.4",
242
246
  "firebase-admin": ">=11",
243
247
  "firebase-functions": ">=4",
244
248
  preact: ">=10",
@@ -381,6 +385,7 @@ function serializeCollection(collection) {
381
385
  id: collection.id,
382
386
  name: collection.name ?? collection.id,
383
387
  description: collection.description,
388
+ group: collection.group,
384
389
  domain: collection.domain,
385
390
  url: collection.url,
386
391
  previewUrl: collection.previewUrl,
package/dist/core.d.ts CHANGED
@@ -2,7 +2,7 @@ import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as Tr
2
2
  export { A as Action, p as ArrayObject, z as BatchRequest, B as BatchRequestOptions, w as BatchRequestQuery, x as BatchRequestQueryOptions, C as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, h as GetCountOptions, G as GetDocOptions, H as HttpMethod, k as ListActionsOptions, g as ListDocsOptions, E as Locale, N as MultiLocaleTranslationsMap, j as Release, f as SaveDraftOptions, S as SetDocOptions, O as SingleLocaleTranslationsMap, F as SourceString, I as TranslatedString, i as Translation, y as TranslationsDoc, J as TranslationsDocMode, M as TranslationsLocaleDocEntry, K as TranslationsLocaleDocHashMap, P as TranslationsManager, U as UserRole, Q as buildTranslationsDbPath, V as buildTranslationsLocaleDocDbPath, m as getCmsPlugin, l as isRichTextData, q as marshalArray, n as marshalData, o as normalizeData, v as parseDocId, t as toArrayObject, s as translationsForLocale, r as unmarshalArray, u as unmarshalData } from './client-DYO2WWlf.js';
3
3
  import { Request, RootConfig, Response, RouteParams, GetStaticProps, GetStaticPaths } from '@blinkk/root';
4
4
  import { Query } from 'firebase-admin/firestore';
5
- export { s as schema } from './schema-sKTRRRSq.js';
5
+ export { s as schema } from './schema-BxPmMeRc.js';
6
6
  import 'firebase-admin/app';
7
7
 
8
8
  /**
package/dist/plugin.js CHANGED
@@ -45,6 +45,287 @@ var require_altText = __commonJS({
45
45
  }
46
46
  });
47
47
 
48
+ // core/ai.ts
49
+ var ai_exports = {};
50
+ __export(ai_exports, {
51
+ Chat: () => Chat,
52
+ ChatClient: () => ChatClient,
53
+ generateImage: () => generateImage,
54
+ summarizeDiff: () => summarizeDiff
55
+ });
56
+ import crypto from "crypto";
57
+ import fs from "fs";
58
+ import path from "path";
59
+ import { vertexAI } from "@genkit-ai/vertexai";
60
+ import { Timestamp } from "firebase-admin/firestore";
61
+ import { genkit } from "genkit";
62
+ import { logger } from "genkit/logging";
63
+ async function summarizeDiff(cmsClient, options) {
64
+ const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
65
+ const firebaseConfig = cmsPluginOptions.firebaseConfig;
66
+ const model = (typeof cmsPluginOptions.experiments?.ai === "object" ? cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
67
+ const ai = genkit({
68
+ plugins: [
69
+ vertexAI({
70
+ projectId: firebaseConfig.projectId,
71
+ location: firebaseConfig.location || "us-central1"
72
+ })
73
+ ]
74
+ });
75
+ const beforeJson = JSON.stringify(options.before ?? null, null, 2);
76
+ const afterJson = JSON.stringify(options.after ?? null, null, 2);
77
+ const systemPrompt = [
78
+ "You are an assistant that summarizes changes made to CMS documents stored as JSON.",
79
+ "Provide a concise description of the most important updates using short bullet points.",
80
+ 'If there are no meaningful differences, respond with "No significant changes."',
81
+ `Focus on just the content changes, ignore insignificant changes to richtext blocks and structure, such as updates to the richtext block's "timestamp" and "version" fields.`
82
+ ].join("\n");
83
+ const diffPrompt = [
84
+ "Previous version JSON:",
85
+ "```json",
86
+ beforeJson,
87
+ "```",
88
+ "",
89
+ "Updated version JSON:",
90
+ "```json",
91
+ afterJson,
92
+ "```",
93
+ "",
94
+ "Summarize the differences between the two payloads."
95
+ ].join("\n");
96
+ const res = await ai.generate({
97
+ model,
98
+ messages: [
99
+ {
100
+ role: "system",
101
+ content: [{ text: systemPrompt }]
102
+ }
103
+ ],
104
+ prompt: [{ text: diffPrompt }]
105
+ });
106
+ return res.text?.trim() || "";
107
+ }
108
+ async function generateImage(cmsClient, options) {
109
+ const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
110
+ const firebaseConfig = cmsPluginOptions.firebaseConfig;
111
+ const model = "vertexai/gemini-2.5-flash-image";
112
+ const ai = genkit({
113
+ plugins: [
114
+ vertexAI({
115
+ projectId: firebaseConfig.projectId,
116
+ location: firebaseConfig.location || "us-central1"
117
+ })
118
+ ]
119
+ });
120
+ const res = await ai.generate({
121
+ model,
122
+ prompt: [
123
+ {
124
+ text: options.prompt
125
+ }
126
+ ],
127
+ config: {
128
+ imageConfig: {
129
+ aspectRatio: options.aspectRatio
130
+ }
131
+ },
132
+ output: {
133
+ format: "media"
134
+ }
135
+ });
136
+ if (!res.media) {
137
+ throw new Error("No image generated");
138
+ }
139
+ return res.media.url;
140
+ }
141
+ var DEFAULT_MODEL, Chat, ChatClient;
142
+ var init_ai = __esm({
143
+ "core/ai.ts"() {
144
+ "use strict";
145
+ logger.setLogLevel("warn");
146
+ DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
147
+ Chat = class {
148
+ constructor(chatClient, id, options) {
149
+ this.chatClient = chatClient;
150
+ this.cmsClient = chatClient.cmsClient;
151
+ this.cmsPluginOptions = this.cmsClient.cmsPlugin.getConfig();
152
+ this.id = id;
153
+ this.history = options?.history ?? [];
154
+ this.model = options?.model || (typeof this.cmsPluginOptions.experiments?.ai === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
155
+ const firebaseConfig = this.cmsPluginOptions.firebaseConfig;
156
+ this.ai = genkit({
157
+ plugins: [
158
+ vertexAI({
159
+ projectId: firebaseConfig.projectId,
160
+ location: firebaseConfig.location || "us-central1"
161
+ })
162
+ ]
163
+ });
164
+ }
165
+ /** Builds the messages for the AI request. */
166
+ async buildMessages(options) {
167
+ const messages = this.history;
168
+ const hasSystemPrompt = messages.some(
169
+ (msg) => msg.role === "system" && msg.content.length > 0
170
+ );
171
+ if (!hasSystemPrompt) {
172
+ messages.push({
173
+ role: "system",
174
+ content: [{ text: await this.buildSystemPrompt(options) }]
175
+ });
176
+ }
177
+ if (options.mode === "edit") {
178
+ messages.push({
179
+ role: "user",
180
+ content: [
181
+ {
182
+ text: [
183
+ "The JSON you must edit is:",
184
+ "",
185
+ JSON.stringify(options.editData || {}, null, 2)
186
+ ].join("")
187
+ }
188
+ ]
189
+ });
190
+ }
191
+ return messages;
192
+ }
193
+ /** Builds the request sent to the AI based on the `ChatMode`. */
194
+ async buildGenerateRequest(prompt, options) {
195
+ if (options.mode === "edit") {
196
+ return {
197
+ messages: await this.buildMessages(options),
198
+ model: this.model,
199
+ prompt
200
+ };
201
+ }
202
+ return {
203
+ messages: await this.buildMessages(options),
204
+ model: this.model,
205
+ prompt
206
+ };
207
+ }
208
+ /** Sends the request to the AI and stores the history in the session and the database. */
209
+ async sendPrompt(prompt, options = {}) {
210
+ const chatRequest = await this.buildGenerateRequest(prompt, options);
211
+ const res = await this.ai.generate({
212
+ model: chatRequest.model,
213
+ messages: chatRequest.messages,
214
+ prompt: Array.isArray(prompt) ? prompt.flat() : prompt
215
+ });
216
+ this.history = res.messages;
217
+ await this.dbDoc().update({
218
+ history: this.history,
219
+ modifiedAt: Timestamp.now()
220
+ });
221
+ if (options.mode === "edit") {
222
+ const aiResponse = res.output;
223
+ if (aiResponse?.data && !aiResponse.message) {
224
+ aiResponse.message = "";
225
+ }
226
+ return aiResponse;
227
+ }
228
+ return { message: res.text, data: null };
229
+ }
230
+ dbDoc() {
231
+ return this.chatClient.dbCollection().doc(this.id);
232
+ }
233
+ /**
234
+ * Builds the system prompt sent to the AI, based on the `ChatMode` and
235
+ * supplied `SendPromptOptions`. `SendPromptOptions` may contain data or
236
+ * references to information needed to construct the prompt.
237
+ */
238
+ async buildSystemPrompt(options) {
239
+ const serializedRootConfig = JSON.stringify(
240
+ this.cmsClient.rootConfig,
241
+ null,
242
+ 2
243
+ );
244
+ if (options.mode === "edit") {
245
+ const rootDir = process.cwd();
246
+ const rootCmsDefsPath = path.resolve(rootDir, "root-cms.d.ts");
247
+ const rootCmsDefs = fs.existsSync(rootCmsDefsPath) ? fs.readFileSync(rootCmsDefsPath, {
248
+ encoding: "utf8"
249
+ }) : null;
250
+ const text = [(await Promise.resolve().then(() => __toESM(require_edit(), 1))).default];
251
+ if (rootCmsDefs) {
252
+ text.push(
253
+ "Here is the `root-cms.d.ts` file for this project:",
254
+ "```",
255
+ rootCmsDefs,
256
+ "```"
257
+ );
258
+ }
259
+ return text.join("\n");
260
+ }
261
+ if (options.mode === "altText") {
262
+ return (await Promise.resolve().then(() => __toESM(require_altText(), 1))).default;
263
+ }
264
+ const systemText = [
265
+ `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.`,
266
+ "",
267
+ "Here is the root.config.ts file for the site:",
268
+ "```",
269
+ serializedRootConfig,
270
+ "```",
271
+ "",
272
+ "Here are the docs that exist in the system:"
273
+ ];
274
+ const pages = await this.cmsClient.listDocs("Pages", { mode: "draft" });
275
+ pages.docs.forEach((doc) => {
276
+ systemText.push(JSON.stringify(doc));
277
+ });
278
+ return systemText.join("\n");
279
+ }
280
+ };
281
+ ChatClient = class {
282
+ constructor(cmsClient, user) {
283
+ this.cmsClient = cmsClient;
284
+ this.user = user;
285
+ }
286
+ async createChat() {
287
+ const chatId = crypto.randomUUID();
288
+ const docRef = this.dbCollection().doc(chatId);
289
+ const chat = new Chat(this, chatId);
290
+ await docRef.set({
291
+ id: chatId,
292
+ createdBy: this.user,
293
+ createdAt: Timestamp.now(),
294
+ modifiedAt: Timestamp.now(),
295
+ model: chat.model
296
+ });
297
+ return chat;
298
+ }
299
+ async getOrCreateChat(chatId) {
300
+ return chatId ? this.getChat(chatId) : this.createChat();
301
+ }
302
+ async getChat(chatId) {
303
+ const docRef = this.dbCollection().doc(chatId);
304
+ const chatDoc = await docRef.get();
305
+ if (!chatDoc.exists) {
306
+ throw new Error(`${chatId} does not exist`);
307
+ }
308
+ const chatData = chatDoc.data() || {};
309
+ return new Chat(this, chatId, {
310
+ history: chatData.history,
311
+ model: chatData.model
312
+ });
313
+ }
314
+ async listChats(options) {
315
+ const limit = options?.limit || 20;
316
+ const query = this.dbCollection().where("createdBy", "==", this.user).limit(limit).orderBy("createdAt", "desc");
317
+ const res = await query.get();
318
+ return res.docs.map((doc) => doc.data());
319
+ }
320
+ dbCollection() {
321
+ return this.cmsClient.db.collection(
322
+ `Projects/${this.cmsClient.projectId}/Experiments/ai/Chat`
323
+ );
324
+ }
325
+ };
326
+ }
327
+ });
328
+
48
329
  // cli/generate-types.ts
49
330
  var generate_types_exports = {};
50
331
  __export(generate_types_exports, {
@@ -383,245 +664,11 @@ var SSEEvent = {
383
664
  };
384
665
 
385
666
  // core/api.ts
667
+ init_ai();
386
668
  import { promises as fs2 } from "fs";
387
669
  import path3 from "path";
388
670
  import { multipartMiddleware } from "@blinkk/root/middleware";
389
671
 
390
- // core/ai.ts
391
- import crypto from "crypto";
392
- import fs from "fs";
393
- import path from "path";
394
- import { vertexAI } from "@genkit-ai/vertexai";
395
- import { Timestamp } from "firebase-admin/firestore";
396
- import { genkit } from "genkit";
397
- import { logger } from "genkit/logging";
398
- logger.setLogLevel("warn");
399
- var DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
400
- async function summarizeDiff(cmsClient, options) {
401
- const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
402
- const firebaseConfig = cmsPluginOptions.firebaseConfig;
403
- const model = (typeof cmsPluginOptions.experiments?.ai === "object" ? cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
404
- const ai = genkit({
405
- plugins: [
406
- vertexAI({
407
- projectId: firebaseConfig.projectId,
408
- location: firebaseConfig.location || "us-central1"
409
- })
410
- ]
411
- });
412
- const beforeJson = JSON.stringify(options.before ?? null, null, 2);
413
- const afterJson = JSON.stringify(options.after ?? null, null, 2);
414
- const systemPrompt = [
415
- "You are an assistant that summarizes changes made to CMS documents stored as JSON.",
416
- "Provide a concise description of the most important updates using short bullet points.",
417
- 'If there are no meaningful differences, respond with "No significant changes."',
418
- `Focus on just the content changes, ignore insignificant changes to richtext blocks and structure, such as updates to the richtext block's "timestamp" and "version" fields.`
419
- ].join("\n");
420
- const diffPrompt = [
421
- "Previous version JSON:",
422
- "```json",
423
- beforeJson,
424
- "```",
425
- "",
426
- "Updated version JSON:",
427
- "```json",
428
- afterJson,
429
- "```",
430
- "",
431
- "Summarize the differences between the two payloads."
432
- ].join("\n");
433
- const res = await ai.generate({
434
- model,
435
- messages: [
436
- {
437
- role: "system",
438
- content: [{ text: systemPrompt }]
439
- }
440
- ],
441
- prompt: [{ text: diffPrompt }]
442
- });
443
- return res.text?.trim() || "";
444
- }
445
- var Chat = class {
446
- constructor(chatClient, id, options) {
447
- this.chatClient = chatClient;
448
- this.cmsClient = chatClient.cmsClient;
449
- this.cmsPluginOptions = this.cmsClient.cmsPlugin.getConfig();
450
- this.id = id;
451
- this.history = options?.history ?? [];
452
- this.model = options?.model || (typeof this.cmsPluginOptions.experiments?.ai === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
453
- const firebaseConfig = this.cmsPluginOptions.firebaseConfig;
454
- this.ai = genkit({
455
- plugins: [
456
- vertexAI({
457
- projectId: firebaseConfig.projectId,
458
- location: firebaseConfig.location || "us-central1"
459
- })
460
- ]
461
- });
462
- }
463
- /** Builds the messages for the AI request. */
464
- async buildMessages(options) {
465
- const messages = this.history;
466
- const hasSystemPrompt = messages.some(
467
- (msg) => msg.role === "system" && msg.content.length > 0
468
- );
469
- if (!hasSystemPrompt) {
470
- messages.push({
471
- role: "system",
472
- content: [{ text: await this.buildSystemPrompt(options) }]
473
- });
474
- }
475
- if (options.mode === "edit") {
476
- messages.push({
477
- role: "user",
478
- content: [
479
- {
480
- text: [
481
- "The JSON you must edit is:",
482
- "",
483
- JSON.stringify(options.editData || {}, null, 2)
484
- ].join("")
485
- }
486
- ]
487
- });
488
- }
489
- return messages;
490
- }
491
- /** Builds the request sent to the AI based on the `ChatMode`. */
492
- async buildGenerateRequest(prompt, options) {
493
- if (options.mode === "edit") {
494
- return {
495
- messages: await this.buildMessages(options),
496
- model: this.model,
497
- prompt
498
- };
499
- }
500
- return {
501
- messages: await this.buildMessages(options),
502
- model: this.model,
503
- prompt
504
- };
505
- }
506
- /** Sends the request to the AI and stores the history in the session and the database. */
507
- async sendPrompt(prompt, options = {}) {
508
- const chatRequest = await this.buildGenerateRequest(prompt, options);
509
- const res = await this.ai.generate({
510
- model: chatRequest.model,
511
- messages: chatRequest.messages,
512
- prompt: Array.isArray(prompt) ? prompt.flat() : prompt
513
- });
514
- this.history = res.messages;
515
- await this.dbDoc().update({
516
- history: this.history,
517
- modifiedAt: Timestamp.now()
518
- });
519
- if (options.mode === "edit") {
520
- const aiResponse = res.output;
521
- if (aiResponse?.data && !aiResponse.message) {
522
- aiResponse.message = "";
523
- }
524
- return aiResponse;
525
- }
526
- return { message: res.text, data: null };
527
- }
528
- dbDoc() {
529
- return this.chatClient.dbCollection().doc(this.id);
530
- }
531
- /**
532
- * Builds the system prompt sent to the AI, based on the `ChatMode` and
533
- * supplied `SendPromptOptions`. `SendPromptOptions` may contain data or
534
- * references to information needed to construct the prompt.
535
- */
536
- async buildSystemPrompt(options) {
537
- const serializedRootConfig = JSON.stringify(
538
- this.cmsClient.rootConfig,
539
- null,
540
- 2
541
- );
542
- if (options.mode === "edit") {
543
- const rootDir = process.cwd();
544
- const rootCmsDefsPath = path.resolve(rootDir, "root-cms.d.ts");
545
- const rootCmsDefs = fs.existsSync(rootCmsDefsPath) ? fs.readFileSync(rootCmsDefsPath, {
546
- encoding: "utf8"
547
- }) : null;
548
- const text = [(await Promise.resolve().then(() => __toESM(require_edit(), 1))).default];
549
- if (rootCmsDefs) {
550
- text.push(
551
- "Here is the `root-cms.d.ts` file for this project:",
552
- "```",
553
- rootCmsDefs,
554
- "```"
555
- );
556
- }
557
- return text.join("\n");
558
- }
559
- if (options.mode === "altText") {
560
- return (await Promise.resolve().then(() => __toESM(require_altText(), 1))).default;
561
- }
562
- const systemText = [
563
- `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.`,
564
- "",
565
- "Here is the root.config.ts file for the site:",
566
- "```",
567
- serializedRootConfig,
568
- "```",
569
- "",
570
- "Here are the docs that exist in the system:"
571
- ];
572
- const pages = await this.cmsClient.listDocs("Pages", { mode: "draft" });
573
- pages.docs.forEach((doc) => {
574
- systemText.push(JSON.stringify(doc));
575
- });
576
- return systemText.join("\n");
577
- }
578
- };
579
- var ChatClient = class {
580
- constructor(cmsClient, user) {
581
- this.cmsClient = cmsClient;
582
- this.user = user;
583
- }
584
- async createChat() {
585
- const chatId = crypto.randomUUID();
586
- const docRef = this.dbCollection().doc(chatId);
587
- const chat = new Chat(this, chatId);
588
- await docRef.set({
589
- id: chatId,
590
- createdBy: this.user,
591
- createdAt: Timestamp.now(),
592
- modifiedAt: Timestamp.now(),
593
- model: chat.model
594
- });
595
- return chat;
596
- }
597
- async getOrCreateChat(chatId) {
598
- return chatId ? this.getChat(chatId) : this.createChat();
599
- }
600
- async getChat(chatId) {
601
- const docRef = this.dbCollection().doc(chatId);
602
- const chatDoc = await docRef.get();
603
- if (!chatDoc.exists) {
604
- throw new Error(`${chatId} does not exist`);
605
- }
606
- const chatData = chatDoc.data() || {};
607
- return new Chat(this, chatId, {
608
- history: chatData.history,
609
- model: chatData.model
610
- });
611
- }
612
- async listChats(options) {
613
- const limit = options?.limit || 20;
614
- const query = this.dbCollection().where("createdBy", "==", this.user).limit(limit).orderBy("createdAt", "desc");
615
- const res = await query.get();
616
- return res.docs.map((doc) => doc.data());
617
- }
618
- dbCollection() {
619
- return this.cmsClient.db.collection(
620
- `Projects/${this.cmsClient.projectId}/Experiments/ai/Chat`
621
- );
622
- }
623
- };
624
-
625
672
  // core/client.ts
626
673
  import crypto2 from "crypto";
627
674
  import {
@@ -2555,12 +2602,44 @@ function api(server, options) {
2555
2602
  res.status(500).json({ success: false, error: "UNKNOWN" });
2556
2603
  }
2557
2604
  });
2605
+ server.use(
2606
+ "/cms/api/ai.generate_image",
2607
+ async (req, res) => {
2608
+ if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
2609
+ res.status(400).json({ success: false, error: "BAD_REQUEST" });
2610
+ return;
2611
+ }
2612
+ if (!req.user?.email) {
2613
+ res.status(401).json({ success: false, error: "UNAUTHORIZED" });
2614
+ return;
2615
+ }
2616
+ const reqBody = req.body || {};
2617
+ const prompt = reqBody.prompt;
2618
+ const aspectRatio = reqBody.aspectRatio;
2619
+ if (!prompt || !aspectRatio) {
2620
+ res.status(400).json({ success: false, error: "MISSING_REQUIRED_FIELD" });
2621
+ return;
2622
+ }
2623
+ try {
2624
+ const cmsClient = new RootCMSClient(req.rootConfig);
2625
+ const { generateImage: generateImage2 } = await Promise.resolve().then(() => (init_ai(), ai_exports));
2626
+ const imageUrl = await generateImage2(cmsClient, {
2627
+ prompt,
2628
+ aspectRatio
2629
+ });
2630
+ res.status(200).json({ success: true, image: imageUrl });
2631
+ } catch (err) {
2632
+ console.error(err.stack || err);
2633
+ res.status(500).json({ success: false, error: err.message || "UNKNOWN" });
2634
+ }
2635
+ }
2636
+ );
2558
2637
  }
2559
2638
 
2560
2639
  // package.json
2561
2640
  var package_default = {
2562
2641
  name: "@blinkk/root-cms",
2563
- version: "2.4.3",
2642
+ version: "2.4.4",
2564
2643
  author: "s@blinkk.com",
2565
2644
  license: "MIT",
2566
2645
  engines: {
@@ -2625,6 +2704,10 @@ var package_default = {
2625
2704
  "test:watch": "pnpm build && firebase emulators:exec 'vitest'"
2626
2705
  },
2627
2706
  dependencies: {
2707
+ "@ag-grid-community/client-side-row-model": "32.3.9",
2708
+ "@ag-grid-community/core": "32.3.9",
2709
+ "@ag-grid-community/react": "32.3.9",
2710
+ "@ag-grid-community/styles": "32.3.9",
2628
2711
  "@genkit-ai/ai": "1.18.0",
2629
2712
  "@genkit-ai/core": "1.18.0",
2630
2713
  "@genkit-ai/vertexai": "1.18.0",
@@ -2715,7 +2798,7 @@ var package_default = {
2715
2798
  yjs: "13.6.27"
2716
2799
  },
2717
2800
  peerDependencies: {
2718
- "@blinkk/root": "2.4.3",
2801
+ "@blinkk/root": "2.4.4",
2719
2802
  "firebase-admin": ">=11",
2720
2803
  "firebase-functions": ">=4",
2721
2804
  preact: ">=10",
@@ -3139,6 +3222,22 @@ function cmsPlugin(options) {
3139
3222
  res.status(500).send("UNKNOWN SERVER ERROR");
3140
3223
  }
3141
3224
  });
3225
+ process.on("unhandledRejection", (reason) => {
3226
+ const err = reason;
3227
+ if (err?.message?.includes("reauth related error") || err?.message?.includes("invalid_grant")) {
3228
+ console.log("\n");
3229
+ console.log("==================================================");
3230
+ console.log("Google Cloud Reauthentication Required");
3231
+ console.log("==================================================");
3232
+ console.log(
3233
+ "Your Google Cloud credentials have expired or require reauthentication."
3234
+ );
3235
+ console.log("Please run the following command to reauthenticate:");
3236
+ console.log("\n gcloud auth application-default login\n");
3237
+ console.log("==================================================");
3238
+ console.log("\n");
3239
+ }
3240
+ });
3142
3241
  }
3143
3242
  };
3144
3243
  if (process.env.NODE_ENV === "development" && options.watch !== false) {
package/dist/project.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { S as Schema, C as Collection } from './schema-sKTRRRSq.js';
1
+ import { S as Schema, C as Collection } from './schema-BxPmMeRc.js';
2
2
 
3
3
  /**
4
4
  * Loads various files or configurations from the project.
@@ -24,6 +24,11 @@ type StringField = CommonFieldProps & {
24
24
  variant?: 'input' | 'textarea';
25
25
  /** For textarea variant, the maximum number of rows of text to show. */
26
26
  maxRows?: number;
27
+ /**
28
+ * For textarea variant, set to `true` to allow the textarea to automatically
29
+ * resize its height based on its content.
30
+ */
31
+ autosize?: boolean;
27
32
  };
28
33
  declare function string(field: Omit<StringField, 'type'>): StringField;
29
34
  type NumberField = CommonFieldProps & {
@@ -230,6 +235,10 @@ type Collection = SchemaWithTypes & {
230
235
  * `<id>.schema.ts`.
231
236
  */
232
237
  id: string;
238
+ /**
239
+ * Group name for organizing collections together into a hierarchy.
240
+ */
241
+ group?: string;
233
242
  /**
234
243
  * Domain where the collection serves from. Used for multi-domain sites,
235
244
  * defaults to the "domain" value `from root.config.ts`.