@blinkk/root-cms 2.4.8 → 2.4.10
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/README.md +17 -3
- package/dist/ai-DRQJXU4N.js +13 -0
- package/dist/altText-RDKJNVGH.js +7 -0
- package/dist/app.js +9 -269
- package/dist/chunk-377TGNX2.js +82 -0
- package/dist/chunk-62EVNFXB.js +1883 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-RYF3UTHQ.js +302 -0
- package/dist/chunk-SI44FG3H.js +136 -0
- package/dist/chunk-Y65VGJLE.js +193 -0
- package/dist/chunk-ZOEPOKGE.js +294 -0
- package/dist/cli.js +54 -362
- package/dist/{client-pSzji9ZN.d.ts → client-PhodvL2Q.d.ts} +32 -2
- package/dist/client.d.ts +2 -1
- package/dist/client.js +15 -1509
- package/dist/core.d.ts +2 -2
- package/dist/core.js +24 -1519
- package/dist/edit-XX3LAGK6.js +7 -0
- package/dist/functions.js +8 -1632
- package/dist/generate-types-TQBCE2SG.js +9 -0
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.js +66 -2515
- package/dist/project.js +6 -76
- package/dist/richtext.js +2 -0
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +237 -157
- package/dist/ui/ui.js.LEGAL.txt +124 -101
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateTypes
|
|
3
|
+
} from "./chunk-RYF3UTHQ.js";
|
|
4
|
+
import {
|
|
5
|
+
getCmsPlugin
|
|
6
|
+
} from "./chunk-62EVNFXB.js";
|
|
7
|
+
import "./chunk-MLKGABMK.js";
|
|
8
|
+
|
|
1
9
|
// cli/cli.ts
|
|
2
10
|
import { Command } from "commander";
|
|
3
11
|
import { bgGreen, black } from "kleur/colors";
|
|
@@ -7,36 +15,6 @@ import fs from "fs";
|
|
|
7
15
|
import path from "path";
|
|
8
16
|
import { loadRootConfig } from "@blinkk/root/node";
|
|
9
17
|
|
|
10
|
-
// core/client.ts
|
|
11
|
-
import crypto from "crypto";
|
|
12
|
-
import {
|
|
13
|
-
FieldValue as FieldValue2,
|
|
14
|
-
Timestamp as Timestamp2
|
|
15
|
-
} from "firebase-admin/firestore";
|
|
16
|
-
|
|
17
|
-
// core/translations-manager.ts
|
|
18
|
-
import {
|
|
19
|
-
FieldValue,
|
|
20
|
-
Timestamp
|
|
21
|
-
} from "firebase-admin/firestore";
|
|
22
|
-
|
|
23
|
-
// shared/strings.ts
|
|
24
|
-
import fnv from "fnv-plus";
|
|
25
|
-
|
|
26
|
-
// core/translations-manager.ts
|
|
27
|
-
var TRANSLATIONS_DB_PATH_FORMAT = "/Projects/{project}/TranslationsManager/{mode}/Translations";
|
|
28
|
-
var TRANSLATIONS_LOCALE_DOC_DB_PATH_FORMAT = `${TRANSLATIONS_DB_PATH_FORMAT}/{id}:{locale}`;
|
|
29
|
-
|
|
30
|
-
// core/client.ts
|
|
31
|
-
function getCmsPlugin(rootConfig) {
|
|
32
|
-
const plugins = rootConfig.plugins || [];
|
|
33
|
-
const plugin = plugins.find((plugin2) => plugin2.name === "root-cms");
|
|
34
|
-
if (!plugin) {
|
|
35
|
-
throw new Error("could not find root-cms plugin config in root.config.ts");
|
|
36
|
-
}
|
|
37
|
-
return plugin;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
18
|
// cli/utils.ts
|
|
41
19
|
function parseFilters(filterStr) {
|
|
42
20
|
if (!filterStr) {
|
|
@@ -47,9 +25,9 @@ function parseFilters(filterStr) {
|
|
|
47
25
|
const excludes = parts.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
|
|
48
26
|
return { includes, excludes };
|
|
49
27
|
}
|
|
50
|
-
function getPathStatus(
|
|
28
|
+
function getPathStatus(path3, includes, excludes) {
|
|
51
29
|
for (const pattern of excludes) {
|
|
52
|
-
if (globMatch(
|
|
30
|
+
if (globMatch(path3, pattern)) {
|
|
53
31
|
return "EXCLUDE";
|
|
54
32
|
}
|
|
55
33
|
}
|
|
@@ -59,7 +37,7 @@ function getPathStatus(path4, includes, excludes) {
|
|
|
59
37
|
let isIncluded = false;
|
|
60
38
|
let shouldTraverse = false;
|
|
61
39
|
for (const pattern of includes) {
|
|
62
|
-
const match = checkMatch(
|
|
40
|
+
const match = checkMatch(path3, pattern);
|
|
63
41
|
if (match === "FULL") {
|
|
64
42
|
isIncluded = true;
|
|
65
43
|
} else if (match === "PARTIAL") {
|
|
@@ -74,11 +52,11 @@ function getPathStatus(path4, includes, excludes) {
|
|
|
74
52
|
}
|
|
75
53
|
return "SKIP";
|
|
76
54
|
}
|
|
77
|
-
function globMatch(
|
|
78
|
-
return checkMatch(
|
|
55
|
+
function globMatch(path3, pattern) {
|
|
56
|
+
return checkMatch(path3, pattern) === "FULL";
|
|
79
57
|
}
|
|
80
|
-
function checkMatch(
|
|
81
|
-
const pathParts =
|
|
58
|
+
function checkMatch(path3, pattern) {
|
|
59
|
+
const pathParts = path3.split("/");
|
|
82
60
|
const patternParts = pattern.split("/");
|
|
83
61
|
if (matchSegments(pathParts, patternParts)) {
|
|
84
62
|
return "FULL";
|
|
@@ -342,311 +320,13 @@ function convertForExport(obj) {
|
|
|
342
320
|
return obj;
|
|
343
321
|
}
|
|
344
322
|
|
|
345
|
-
// cli/generate-types.ts
|
|
346
|
-
import { promises as fs2 } from "fs";
|
|
347
|
-
import path2 from "path";
|
|
348
|
-
import { fileURLToPath } from "url";
|
|
349
|
-
import { loadRootConfig as loadRootConfig2, viteSsrLoadModule } from "@blinkk/root/node";
|
|
350
|
-
import * as dom from "dts-dom";
|
|
351
|
-
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
352
|
-
async function generateTypes() {
|
|
353
|
-
const rootDir = process.cwd();
|
|
354
|
-
const rootConfig = await loadRootConfig2(rootDir, { command: "root-cms" });
|
|
355
|
-
const modulePath = path2.resolve(__dirname, "./project.js");
|
|
356
|
-
const project = await viteSsrLoadModule(
|
|
357
|
-
rootConfig,
|
|
358
|
-
modulePath
|
|
359
|
-
);
|
|
360
|
-
const schemas = await project.getProjectSchemas();
|
|
361
|
-
const outputPath = path2.resolve(rootDir, "root-cms.d.ts");
|
|
362
|
-
await generateSchemaDts(outputPath, schemas);
|
|
363
|
-
console.log("saved root-cms.d.ts!");
|
|
364
|
-
}
|
|
365
|
-
var TEMPLATE = `/* eslint-disable */
|
|
366
|
-
/** Root.js CMS types. This file is autogenerated. */
|
|
367
|
-
|
|
368
|
-
export interface RootCMSFile {
|
|
369
|
-
src: string;
|
|
370
|
-
width?: number;
|
|
371
|
-
height?: number;
|
|
372
|
-
alt?: string;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export type RootCMSImage = RootCMSFile;
|
|
376
|
-
|
|
377
|
-
export type RootCMSOneOf<T = any> = T;
|
|
378
|
-
|
|
379
|
-
export type RootCMSOneOfOption<T, Base> = Base & {_type: T};
|
|
380
|
-
|
|
381
|
-
export interface RootCMSRichTextBlock {
|
|
382
|
-
type: string;
|
|
383
|
-
data: any;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export interface RootCMSRichText {
|
|
387
|
-
blocks: RootCMSRichTextBlock[];
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export interface RootCMSReference {
|
|
391
|
-
/** The id of the doc, e.g. "Pages/foo-bar". */
|
|
392
|
-
id: string;
|
|
393
|
-
/** The collection id of the doc, e.g. "Pages". */
|
|
394
|
-
collection: string;
|
|
395
|
-
/** The slug of the doc, e.g. "foo-bar". */
|
|
396
|
-
slug: string;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
export interface RootCMSDoc<Fields extends {}> {
|
|
400
|
-
/** The id of the doc, e.g. "Pages/foo-bar". */
|
|
401
|
-
id: string;
|
|
402
|
-
/** The collection id of the doc, e.g. "Pages". */
|
|
403
|
-
collection: string;
|
|
404
|
-
/** The slug of the doc, e.g. "foo-bar". */
|
|
405
|
-
slug: string;
|
|
406
|
-
/** System-level metadata. */
|
|
407
|
-
sys: {
|
|
408
|
-
createdAt: number;
|
|
409
|
-
createdBy: string;
|
|
410
|
-
modifiedAt: number;
|
|
411
|
-
modifiedBy: string;
|
|
412
|
-
firstPublishedAt?: number;
|
|
413
|
-
firstPublishedBy?: string;
|
|
414
|
-
publishedAt?: number;
|
|
415
|
-
publishedBy?: string;
|
|
416
|
-
locales?: string[];
|
|
417
|
-
};
|
|
418
|
-
/** User-entered field values from the CMS. */
|
|
419
|
-
fields?: Fields;
|
|
420
|
-
}`;
|
|
421
|
-
var DtsFormatter = class {
|
|
422
|
-
constructor() {
|
|
423
|
-
/**
|
|
424
|
-
* Field types defined in either `.schema.ts` files or used by `oneOf()`
|
|
425
|
-
* fields.
|
|
426
|
-
*/
|
|
427
|
-
this.fieldsTypes = {};
|
|
428
|
-
/**
|
|
429
|
-
* For `collections/*.schema.ts` files, output a corresponding "RootCMSDoc"
|
|
430
|
-
* type. For example, `collections/BlogPosts.schema.ts` would output a type
|
|
431
|
-
* called `BlogPostsDoc`.
|
|
432
|
-
*/
|
|
433
|
-
this.docTypes = {};
|
|
434
|
-
}
|
|
435
|
-
/** Adds the types for a `.schema.ts` file to the `.d.ts` file. */
|
|
436
|
-
addSchemaFile(fileId, schema) {
|
|
437
|
-
const jsdoc = `Generated from \`${fileId}\`.`;
|
|
438
|
-
const typeId = alphanumeric(path2.parse(fileId).name.split(".")[0]);
|
|
439
|
-
const oneOfTypes = {};
|
|
440
|
-
const fieldsTypeId = `${typeId}Fields`;
|
|
441
|
-
const fieldsType = dom.create.interface(
|
|
442
|
-
fieldsTypeId,
|
|
443
|
-
dom.DeclarationFlags.Export
|
|
444
|
-
);
|
|
445
|
-
fieldsType.jsDocComment = jsdoc;
|
|
446
|
-
for (const field of schema.fields) {
|
|
447
|
-
if (!field.id) {
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
fieldsType.members.push(fieldProperty(field, { typeId, oneOfTypes }));
|
|
451
|
-
}
|
|
452
|
-
for (const oneOfTypeId in oneOfTypes) {
|
|
453
|
-
if (!this.fieldsTypes[fieldsTypeId]) {
|
|
454
|
-
this.fieldsTypes[oneOfTypeId] = oneOfTypes[oneOfTypeId];
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
this.fieldsTypes[fieldsTypeId] = fieldsType;
|
|
458
|
-
if (fileId.startsWith("/collections/")) {
|
|
459
|
-
const docTypeId = `${typeId}Doc`;
|
|
460
|
-
const baseType = dom.create.namedTypeReference("RootCMSDoc");
|
|
461
|
-
baseType.typeArguments.push(dom.create.namedTypeReference(fieldsTypeId));
|
|
462
|
-
const docType = dom.create.alias(
|
|
463
|
-
docTypeId,
|
|
464
|
-
baseType,
|
|
465
|
-
dom.DeclarationFlags.Export
|
|
466
|
-
);
|
|
467
|
-
docType.jsDocComment = jsdoc;
|
|
468
|
-
this.docTypes[docTypeId] = docType;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/** Generates the `.d.ts` output as a string. */
|
|
472
|
-
toString() {
|
|
473
|
-
const results = [TEMPLATE];
|
|
474
|
-
const sortedFieldsTypes = Object.keys(this.fieldsTypes).sort();
|
|
475
|
-
for (const fieldsTypeId of sortedFieldsTypes) {
|
|
476
|
-
const fieldsType = this.fieldsTypes[fieldsTypeId];
|
|
477
|
-
results.push(this.typeToString(fieldsType));
|
|
478
|
-
const docTypeId = this.fieldsTypeToDocType(fieldsTypeId);
|
|
479
|
-
const docType = this.docTypes[docTypeId];
|
|
480
|
-
if (docType) {
|
|
481
|
-
results.push(this.typeToString(docType));
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
const output = results.join("\n\n").replaceAll("/** ", "/** ").replaceAll(" */", " */").replace(/\r\n|\r|\n/g, "\n") + "\n";
|
|
485
|
-
return output;
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Converts "<Name>Fields" to "<Name>Doc".
|
|
489
|
-
*/
|
|
490
|
-
fieldsTypeToDocType(fieldsTypeId) {
|
|
491
|
-
if (!fieldsTypeId.endsWith("Fields")) {
|
|
492
|
-
throw new Error(`"${fieldsTypeId}" should be suffixed with "Fields"`);
|
|
493
|
-
}
|
|
494
|
-
const name = fieldsTypeId.slice(0, -6);
|
|
495
|
-
return `${name}Doc`;
|
|
496
|
-
}
|
|
497
|
-
typeToString(type2) {
|
|
498
|
-
return this.reformatOutput(dom.emit(type2, { singleLineJsDocComments: true }));
|
|
499
|
-
}
|
|
500
|
-
/**
|
|
501
|
-
* Formats the output to Google style conventions, e.g. 2-space indents and
|
|
502
|
-
* single quote strings.
|
|
503
|
-
*/
|
|
504
|
-
reformatOutput(input) {
|
|
505
|
-
const lines = input.trim().split("\n");
|
|
506
|
-
const results = [];
|
|
507
|
-
for (const line of lines) {
|
|
508
|
-
const convertedLine = line.replace(/ {4}/g, " ").replaceAll('"', "'");
|
|
509
|
-
results.push(convertedLine);
|
|
510
|
-
}
|
|
511
|
-
return results.join("\n");
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
async function generateSchemaDts(outputPath, schemas) {
|
|
515
|
-
const dtsFormatter = new DtsFormatter();
|
|
516
|
-
for (const fileId in schemas) {
|
|
517
|
-
const schema = schemas[fileId];
|
|
518
|
-
dtsFormatter.addSchemaFile(fileId, schema);
|
|
519
|
-
}
|
|
520
|
-
const output = dtsFormatter.toString();
|
|
521
|
-
await fs2.writeFile(outputPath, output, "utf-8");
|
|
522
|
-
}
|
|
523
|
-
function fieldProperty(field, options) {
|
|
524
|
-
const prop = dom.create.property(
|
|
525
|
-
field.id,
|
|
526
|
-
fieldType(field, options),
|
|
527
|
-
dom.DeclarationFlags.Optional
|
|
528
|
-
);
|
|
529
|
-
const jsdoc = [];
|
|
530
|
-
if (field.label) {
|
|
531
|
-
if (field.help) {
|
|
532
|
-
jsdoc.push(`${field.label}. ${field.help}`);
|
|
533
|
-
} else {
|
|
534
|
-
jsdoc.push(field.label);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (field.deprecated) {
|
|
538
|
-
jsdoc.push("@deprecated");
|
|
539
|
-
}
|
|
540
|
-
if (jsdoc.length > 0) {
|
|
541
|
-
prop.jsDocComment = jsdoc.join("\n");
|
|
542
|
-
}
|
|
543
|
-
return prop;
|
|
544
|
-
}
|
|
545
|
-
function fieldType(field, options) {
|
|
546
|
-
if (field.type === "array") {
|
|
547
|
-
return dom.type.array(fieldType(field.of, options));
|
|
548
|
-
}
|
|
549
|
-
if (field.type === "boolean") {
|
|
550
|
-
return dom.type.boolean;
|
|
551
|
-
}
|
|
552
|
-
if (field.type === "date") {
|
|
553
|
-
return dom.type.string;
|
|
554
|
-
}
|
|
555
|
-
if (field.type === "datetime") {
|
|
556
|
-
return dom.type.number;
|
|
557
|
-
}
|
|
558
|
-
if (field.type === "file") {
|
|
559
|
-
const fileType = dom.create.namedTypeReference("RootCMSFile");
|
|
560
|
-
return fileType;
|
|
561
|
-
}
|
|
562
|
-
if (field.type === "image") {
|
|
563
|
-
const imageType = dom.create.namedTypeReference("RootCMSImage");
|
|
564
|
-
return imageType;
|
|
565
|
-
}
|
|
566
|
-
if (field.type === "multiselect") {
|
|
567
|
-
return dom.type.array(dom.type.string);
|
|
568
|
-
}
|
|
569
|
-
if (field.type === "oneof") {
|
|
570
|
-
const oneOf = dom.create.namedTypeReference("RootCMSOneOf");
|
|
571
|
-
if (field.types && Array.isArray(field.types)) {
|
|
572
|
-
const unionTypes = [];
|
|
573
|
-
field.types.forEach((schema) => {
|
|
574
|
-
let typeName;
|
|
575
|
-
if (typeof schema === "string") {
|
|
576
|
-
typeName = schema;
|
|
577
|
-
return;
|
|
578
|
-
} else {
|
|
579
|
-
typeName = schema.name;
|
|
580
|
-
}
|
|
581
|
-
if (!typeName) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
const cleanName = alphanumeric(typeName);
|
|
585
|
-
const oneOfTypeId = `${cleanName}Fields`;
|
|
586
|
-
if (typeof schema === "object" && !options.oneOfTypes[oneOfTypeId]) {
|
|
587
|
-
const oneOfTypeInterface = dom.create.interface(
|
|
588
|
-
oneOfTypeId,
|
|
589
|
-
dom.DeclarationFlags.Export
|
|
590
|
-
);
|
|
591
|
-
if (schema.description) {
|
|
592
|
-
oneOfTypeInterface.jsDocComment = schema.description;
|
|
593
|
-
}
|
|
594
|
-
const oneOfTypeFields = schema.fields || [];
|
|
595
|
-
oneOfTypeFields.forEach((f) => {
|
|
596
|
-
oneOfTypeInterface.members.push(fieldProperty(f, options));
|
|
597
|
-
});
|
|
598
|
-
options.oneOfTypes[oneOfTypeId] = oneOfTypeInterface;
|
|
599
|
-
}
|
|
600
|
-
const oneOfOption = dom.create.namedTypeReference("RootCMSOneOfOption");
|
|
601
|
-
oneOfOption.typeArguments = [
|
|
602
|
-
dom.type.stringLiteral(typeName),
|
|
603
|
-
dom.create.namedTypeReference(oneOfTypeId)
|
|
604
|
-
];
|
|
605
|
-
unionTypes.push(oneOfOption);
|
|
606
|
-
});
|
|
607
|
-
if (unionTypes.length > 0) {
|
|
608
|
-
oneOf.typeArguments = [dom.create.union(unionTypes)];
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return oneOf;
|
|
612
|
-
}
|
|
613
|
-
if (field.type === "reference") {
|
|
614
|
-
const referenceType = dom.create.namedTypeReference("RootCMSReference");
|
|
615
|
-
return referenceType;
|
|
616
|
-
}
|
|
617
|
-
if (field.type === "references") {
|
|
618
|
-
const referenceType = dom.create.namedTypeReference("RootCMSReference");
|
|
619
|
-
return dom.type.array(referenceType);
|
|
620
|
-
}
|
|
621
|
-
if (field.type === "richtext") {
|
|
622
|
-
const richtextType = dom.create.namedTypeReference("RootCMSRichText");
|
|
623
|
-
return richtextType;
|
|
624
|
-
}
|
|
625
|
-
if (field.type === "select") {
|
|
626
|
-
return dom.type.string;
|
|
627
|
-
}
|
|
628
|
-
if (field.type === "string") {
|
|
629
|
-
return dom.type.string;
|
|
630
|
-
}
|
|
631
|
-
if (field.type === "object") {
|
|
632
|
-
const subproperties = (field.fields || []).map(
|
|
633
|
-
(f) => fieldProperty(f, options)
|
|
634
|
-
);
|
|
635
|
-
return dom.create.objectType(subproperties);
|
|
636
|
-
}
|
|
637
|
-
return dom.type.unknown;
|
|
638
|
-
}
|
|
639
|
-
function alphanumeric(input) {
|
|
640
|
-
return input.replace(/[^a-zA-Z0-9]/g, "");
|
|
641
|
-
}
|
|
642
|
-
|
|
643
323
|
// cli/import.ts
|
|
644
|
-
import
|
|
645
|
-
import
|
|
324
|
+
import fs2 from "fs";
|
|
325
|
+
import path2 from "path";
|
|
646
326
|
import * as readline from "readline";
|
|
647
|
-
import { loadRootConfig as
|
|
327
|
+
import { loadRootConfig as loadRootConfig2 } from "@blinkk/root/node";
|
|
648
328
|
import cliProgress from "cli-progress";
|
|
649
|
-
import { Timestamp
|
|
329
|
+
import { Timestamp, GeoPoint } from "firebase-admin/firestore";
|
|
650
330
|
async function importData(options) {
|
|
651
331
|
if (!options.dir) {
|
|
652
332
|
throw new Error(
|
|
@@ -654,18 +334,18 @@ async function importData(options) {
|
|
|
654
334
|
);
|
|
655
335
|
}
|
|
656
336
|
const importDir = options.dir;
|
|
657
|
-
if (!
|
|
337
|
+
if (!fs2.existsSync(importDir)) {
|
|
658
338
|
throw new Error(`Error: Directory not found: ${importDir}`);
|
|
659
339
|
}
|
|
660
340
|
const rootDir = process.cwd();
|
|
661
|
-
const rootConfig = await
|
|
341
|
+
const rootConfig = await loadRootConfig2(rootDir, { command: "root-cms" });
|
|
662
342
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
663
343
|
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
664
344
|
const siteId = options.site || cmsPluginOptions.id || "default";
|
|
665
345
|
const databaseId = options.database || "(default)";
|
|
666
346
|
const gcpProjectId = options.project || cmsPluginOptions.firebaseConfig?.projectId || "unknown";
|
|
667
347
|
const db = cmsPlugin.getFirestore({ databaseId });
|
|
668
|
-
const entries =
|
|
348
|
+
const entries = fs2.readdirSync(importDir, { withFileTypes: true });
|
|
669
349
|
const availableCollections = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
670
350
|
const { includes, excludes } = parseFilters(options.filter);
|
|
671
351
|
const collectionsToImport = availableCollections.filter((id) => {
|
|
@@ -681,8 +361,8 @@ async function importData(options) {
|
|
|
681
361
|
}
|
|
682
362
|
const summary = [];
|
|
683
363
|
for (const collectionType of collectionsToImport) {
|
|
684
|
-
const collectionDir =
|
|
685
|
-
if (
|
|
364
|
+
const collectionDir = path2.join(importDir, collectionType);
|
|
365
|
+
if (fs2.existsSync(collectionDir)) {
|
|
686
366
|
const count = countJsonFilesRecursive(
|
|
687
367
|
collectionDir,
|
|
688
368
|
collectionType,
|
|
@@ -721,7 +401,7 @@ async function importData(options) {
|
|
|
721
401
|
const limit = pLimit(10);
|
|
722
402
|
for (const item of summary) {
|
|
723
403
|
const collectionPath = `Projects/${siteId}/${item.collectionType}`;
|
|
724
|
-
const collectionDir =
|
|
404
|
+
const collectionDir = path2.join(importDir, item.collectionType);
|
|
725
405
|
await importCollection(
|
|
726
406
|
db,
|
|
727
407
|
collectionPath,
|
|
@@ -734,10 +414,10 @@ async function importData(options) {
|
|
|
734
414
|
limit
|
|
735
415
|
);
|
|
736
416
|
}
|
|
737
|
-
const projectFilePath =
|
|
738
|
-
if (
|
|
417
|
+
const projectFilePath = path2.join(importDir, "__data.json");
|
|
418
|
+
if (fs2.existsSync(projectFilePath)) {
|
|
739
419
|
console.log("Importing project document...");
|
|
740
|
-
const rawData = JSON.parse(
|
|
420
|
+
const rawData = JSON.parse(fs2.readFileSync(projectFilePath, "utf-8"));
|
|
741
421
|
const projectData = convertFirestoreTypes(rawData, db);
|
|
742
422
|
const projectPath = `Projects/${siteId}`;
|
|
743
423
|
await db.doc(projectPath).set(projectData, { merge: true });
|
|
@@ -747,14 +427,14 @@ async function importData(options) {
|
|
|
747
427
|
}
|
|
748
428
|
function countJsonFilesRecursive(dir, relativePath, includes, excludes) {
|
|
749
429
|
let count = 0;
|
|
750
|
-
const entries =
|
|
430
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
751
431
|
for (const entry of entries) {
|
|
752
432
|
if (entry.isDirectory()) {
|
|
753
433
|
const subRelativePath = `${relativePath}/${entry.name}`;
|
|
754
434
|
const status = getPathStatus(subRelativePath, includes, excludes);
|
|
755
435
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
756
436
|
count += countJsonFilesRecursive(
|
|
757
|
-
|
|
437
|
+
path2.join(dir, entry.name),
|
|
758
438
|
subRelativePath,
|
|
759
439
|
includes,
|
|
760
440
|
excludes
|
|
@@ -768,7 +448,7 @@ function countJsonFilesRecursive(dir, relativePath, includes, excludes) {
|
|
|
768
448
|
}
|
|
769
449
|
continue;
|
|
770
450
|
}
|
|
771
|
-
const docId =
|
|
451
|
+
const docId = path2.basename(entry.name, ".json");
|
|
772
452
|
const docRelativePath = `${relativePath}/${docId}`;
|
|
773
453
|
const status = getPathStatus(docRelativePath, includes, excludes);
|
|
774
454
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -805,7 +485,7 @@ function convertFirestoreTypes(obj, db) {
|
|
|
805
485
|
return obj;
|
|
806
486
|
}
|
|
807
487
|
if (typeof obj === "object" && "_seconds" in obj && "_nanoseconds" in obj && Object.keys(obj).length === 2) {
|
|
808
|
-
return new
|
|
488
|
+
return new Timestamp(obj._seconds, obj._nanoseconds);
|
|
809
489
|
}
|
|
810
490
|
if (typeof obj === "object" && "_latitude" in obj && "_longitude" in obj && Object.keys(obj).length === 2) {
|
|
811
491
|
return new GeoPoint(obj._latitude, obj._longitude);
|
|
@@ -826,15 +506,15 @@ function convertFirestoreTypes(obj, db) {
|
|
|
826
506
|
return obj;
|
|
827
507
|
}
|
|
828
508
|
async function importCollectionRecursive(db, collectionPath, inputDir, progressBar, relativePath, includes, excludes, limit) {
|
|
829
|
-
if (!
|
|
509
|
+
if (!fs2.existsSync(inputDir)) {
|
|
830
510
|
return;
|
|
831
511
|
}
|
|
832
|
-
const entries =
|
|
512
|
+
const entries = fs2.readdirSync(inputDir, { withFileTypes: true });
|
|
833
513
|
await Promise.all(
|
|
834
514
|
entries.map(async (entry) => {
|
|
835
515
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
836
516
|
const rawData = JSON.parse(
|
|
837
|
-
|
|
517
|
+
fs2.readFileSync(path2.join(inputDir, entry.name), "utf-8")
|
|
838
518
|
);
|
|
839
519
|
const docData = convertFirestoreTypes(rawData, db);
|
|
840
520
|
if (entry.name === "__data.json") {
|
|
@@ -849,7 +529,7 @@ async function importCollectionRecursive(db, collectionPath, inputDir, progressB
|
|
|
849
529
|
}
|
|
850
530
|
}
|
|
851
531
|
} else {
|
|
852
|
-
const docId =
|
|
532
|
+
const docId = path2.basename(entry.name, ".json");
|
|
853
533
|
const docRelativePath = relativePath ? `${relativePath}/${docId}` : docId;
|
|
854
534
|
const status = includes && excludes ? getPathStatus(docRelativePath, includes, excludes) : "INCLUDE";
|
|
855
535
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -867,7 +547,7 @@ async function importCollectionRecursive(db, collectionPath, inputDir, progressB
|
|
|
867
547
|
}
|
|
868
548
|
} else if (entry.isDirectory()) {
|
|
869
549
|
const subCollectionPath = `${collectionPath}/${entry.name}`;
|
|
870
|
-
const subCollectionDir =
|
|
550
|
+
const subCollectionDir = path2.join(inputDir, entry.name);
|
|
871
551
|
const subRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
872
552
|
const status = includes && excludes ? getPathStatus(subRelativePath, includes, excludes) : "INCLUDE";
|
|
873
553
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -901,7 +581,7 @@ async function promptYesNo(question) {
|
|
|
901
581
|
}
|
|
902
582
|
|
|
903
583
|
// cli/init-firebase.ts
|
|
904
|
-
import { loadRootConfig as
|
|
584
|
+
import { loadRootConfig as loadRootConfig3 } from "@blinkk/root/node";
|
|
905
585
|
|
|
906
586
|
// core/security.ts
|
|
907
587
|
import { initializeApp } from "firebase-admin/app";
|
|
@@ -921,11 +601,16 @@ service cloud.firestore {
|
|
|
921
601
|
|
|
922
602
|
match /{collection}/{document=**} {
|
|
923
603
|
allow write:
|
|
924
|
-
if isSignedIn() &&
|
|
604
|
+
if isSignedIn() && userCanPublish();
|
|
925
605
|
allow read:
|
|
926
606
|
if isSignedIn() && userCanRead();
|
|
927
607
|
}
|
|
928
608
|
|
|
609
|
+
match /Collections/{collectionId}/Drafts/{document=**} {
|
|
610
|
+
allow write:
|
|
611
|
+
if isSignedIn() && userCanEdit();
|
|
612
|
+
}
|
|
613
|
+
|
|
929
614
|
function isSignedIn() {
|
|
930
615
|
return request.auth != null;
|
|
931
616
|
}
|
|
@@ -938,16 +623,23 @@ service cloud.firestore {
|
|
|
938
623
|
let roles = getRoles();
|
|
939
624
|
let email = request.auth.token.email;
|
|
940
625
|
let domain = '*@' + email.split('@')[1];
|
|
941
|
-
return (roles[email] in ['ADMIN', 'EDITOR', 'VIEWER']) || (roles[domain] in ['ADMIN', 'EDITOR', 'VIEWER']);
|
|
626
|
+
return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']);
|
|
942
627
|
}
|
|
943
628
|
|
|
944
|
-
function
|
|
629
|
+
function userCanPublish() {
|
|
945
630
|
let roles = getRoles();
|
|
946
631
|
let email = request.auth.token.email;
|
|
947
632
|
let domain = '*@' + email.split('@')[1];
|
|
948
633
|
return (roles[email] in ['ADMIN', 'EDITOR']) || (roles[domain] in ['ADMIN', 'EDITOR']);
|
|
949
634
|
}
|
|
950
635
|
|
|
636
|
+
function userCanEdit() {
|
|
637
|
+
let roles = getRoles();
|
|
638
|
+
let email = request.auth.token.email;
|
|
639
|
+
let domain = '*@' + email.split('@')[1];
|
|
640
|
+
return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']);
|
|
641
|
+
}
|
|
642
|
+
|
|
951
643
|
function userIsAdmin() {
|
|
952
644
|
let roles = getRoles();
|
|
953
645
|
let email = request.auth.token.email;
|
|
@@ -967,7 +659,7 @@ async function applySecurityRules(projectId) {
|
|
|
967
659
|
// cli/init-firebase.ts
|
|
968
660
|
async function initFirebase(options) {
|
|
969
661
|
const rootDir = process.cwd();
|
|
970
|
-
const rootConfig = await
|
|
662
|
+
const rootConfig = await loadRootConfig3(rootDir, { command: "root-cms" });
|
|
971
663
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
972
664
|
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
973
665
|
const gcpProjectId = options.project || cmsPluginOptions.firebaseConfig.projectId;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Plugin, Request, RootConfig } from '@blinkk/root';
|
|
2
2
|
import { App } from 'firebase-admin/app';
|
|
3
3
|
import { Firestore, WriteBatch, Timestamp, Query } from 'firebase-admin/firestore';
|
|
4
|
+
import { C as Collection } from './schema-Bux4PrV2.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Supported Root AI models. Defaults to 'gemini-3-flash-preview'.
|
|
@@ -385,7 +386,7 @@ interface Doc<Fields = any> {
|
|
|
385
386
|
fields: Fields;
|
|
386
387
|
}
|
|
387
388
|
type DocMode = 'draft' | 'published';
|
|
388
|
-
type UserRole = 'ADMIN' | 'EDITOR' | 'VIEWER';
|
|
389
|
+
type UserRole = 'ADMIN' | 'EDITOR' | 'CONTRIBUTOR' | 'VIEWER';
|
|
389
390
|
type HttpMethod = 'GET' | 'POST';
|
|
390
391
|
interface DataSource {
|
|
391
392
|
id: string;
|
|
@@ -436,6 +437,18 @@ interface SaveDraftOptions {
|
|
|
436
437
|
* Email of user modifying the doc. If blank, defaults to `root-cms-client`.
|
|
437
438
|
*/
|
|
438
439
|
modifiedBy?: string;
|
|
440
|
+
/**
|
|
441
|
+
* Whether to validate fieldsData against the collection schema before saving.
|
|
442
|
+
* If validation fails, an error will be thrown with details about the validation errors.
|
|
443
|
+
*/
|
|
444
|
+
validate?: boolean;
|
|
445
|
+
}
|
|
446
|
+
interface UpdateDraftOptions {
|
|
447
|
+
/**
|
|
448
|
+
* Whether to validate the updated field against the collection schema.
|
|
449
|
+
* If validation fails, an error will be thrown with details about the validation errors.
|
|
450
|
+
*/
|
|
451
|
+
validate?: boolean;
|
|
439
452
|
}
|
|
440
453
|
interface ListDocsOptions {
|
|
441
454
|
mode: DocMode;
|
|
@@ -553,6 +566,11 @@ declare class RootCMSClient {
|
|
|
553
566
|
dbDocRef(collectionId: string, slug: string, options: {
|
|
554
567
|
mode: 'draft' | 'published';
|
|
555
568
|
}): FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
|
|
569
|
+
/**
|
|
570
|
+
* Returns a collection's schema definition as defined in
|
|
571
|
+
* `/collections/<id>.schema.ts`.
|
|
572
|
+
*/
|
|
573
|
+
getCollection(collectionId: string): Promise<Collection | null>;
|
|
556
574
|
/**
|
|
557
575
|
* Saves draft data to a doc.
|
|
558
576
|
*
|
|
@@ -560,6 +578,18 @@ declare class RootCMSClient {
|
|
|
560
578
|
* modify the sys-level attributes of the doc, use `setRawDoc()`.
|
|
561
579
|
*/
|
|
562
580
|
saveDraftData(docId: string, fieldsData: any, options?: SaveDraftOptions): Promise<void>;
|
|
581
|
+
/**
|
|
582
|
+
* Updates a specific field path in a draft doc.
|
|
583
|
+
*
|
|
584
|
+
* This allows partial updates to nested fields without replacing the entire document.
|
|
585
|
+
* For example: `updateDraftData('Pages/home', 'hero.title', 'New Title')`
|
|
586
|
+
*
|
|
587
|
+
* @param docId - The document ID (e.g., 'Pages/home')
|
|
588
|
+
* @param path - JSON path to the field (e.g., 'hero.title' or 'content.0.text')
|
|
589
|
+
* @param fieldValue - The value to set at the specified path
|
|
590
|
+
* @param options - Update options including validation
|
|
591
|
+
*/
|
|
592
|
+
updateDraftData(docId: string, path: string, fieldValue: any, options?: UpdateDraftOptions): Promise<void>;
|
|
563
593
|
/**
|
|
564
594
|
* Prefer `saveDraftData('Pages/foo', data)`. Only use this if you know what
|
|
565
595
|
* you're doing.
|
|
@@ -896,4 +926,4 @@ declare class BatchResponse {
|
|
|
896
926
|
private getTranslationsMap;
|
|
897
927
|
}
|
|
898
928
|
|
|
899
|
-
export { type
|
|
929
|
+
export { type CMSPluginOptions as $, type Action as A, type BatchRequestOptions as B, BatchRequest as C, type DocMode as D, BatchResponse as E, type Locale as F, type GetDocOptions as G, type HttpMethod as H, type SourceString as I, type TranslatedString as J, type TranslationsDocMode as K, type LoadTranslationsOptions as L, type TranslationsLocaleDocHashMap as M, type TranslationsLocaleDocEntry as N, type MultiLocaleTranslationsMap as O, type SingleLocaleTranslationsMap as P, TranslationsManager as Q, RootCMSClient as R, type SetDocOptions as S, type TranslationsMap as T, type UserRole as U, buildTranslationsDbPath as V, buildTranslationsLocaleDocDbPath as W, type CMSBuiltInSidebarTool as X, type CMSUser as Y, type CMSAIConfig as Z, type CMSSidebarTool as _, type LocaleTranslations as a, type CMSPlugin as a0, cmsPlugin as a1, type Doc as b, type DataSource as c, type DataSourceData as d, type DataSourceMode as e, type SaveDraftOptions as f, type UpdateDraftOptions as g, type ListDocsOptions as h, type GetCountOptions as i, type Translation as j, type Release as k, type ListActionsOptions as l, isRichTextData as m, getCmsPlugin as n, marshalData as o, normalizeData as p, type ArrayObject as q, marshalArray as r, unmarshalArray as s, toArrayObject as t, unmarshalData as u, translationsForLocale as v, parseDocId as w, type BatchRequestQuery as x, type BatchRequestQueryOptions as y, type TranslationsDoc as z };
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@blinkk/root';
|
|
2
2
|
import 'firebase-admin/app';
|
|
3
3
|
import 'firebase-admin/firestore';
|
|
4
|
-
export { A as Action,
|
|
4
|
+
export { A as Action, q as ArrayObject, C as BatchRequest, B as BatchRequestOptions, x as BatchRequestQuery, y as BatchRequestQueryOptions, E as BatchResponse, c as DataSource, d as DataSourceData, e as DataSourceMode, b as Doc, D as DocMode, i as GetCountOptions, G as GetDocOptions, H as HttpMethod, l as ListActionsOptions, h as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, k as Release, R as RootCMSClient, f as SaveDraftOptions, S as SetDocOptions, j as Translation, z as TranslationsDoc, T as TranslationsMap, g as UpdateDraftOptions, U as UserRole, n as getCmsPlugin, m as isRichTextData, r as marshalArray, o as marshalData, p as normalizeData, w as parseDocId, t as toArrayObject, v as translationsForLocale, s as unmarshalArray, u as unmarshalData } from './client-PhodvL2Q.js';
|
|
5
|
+
import './schema-Bux4PrV2.js';
|