@blinkk/root-cms 2.5.3 → 2.5.5
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 +20 -2
- package/dist/{chunk-YAQCD6SS.js → chunk-4XSWQVCI.js} +1 -1
- package/dist/{chunk-XMR3KFP5.js → chunk-UEJ2UTTJ.js} +20 -0
- package/dist/{chunk-WVOTD7EW.js → chunk-WNWP376I.js} +4 -4
- package/dist/cli.js +304 -78
- package/dist/{client-C1pZQL7M.d.ts → client-TgoRyFWJ.d.ts} +5 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/core.d.ts +3 -3
- package/dist/core.js +1 -1
- package/dist/functions.js +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +3 -3
- package/dist/project.d.ts +1 -1
- package/dist/{schema-BKfPP_s9.d.ts → schema-Bht24XmU.d.ts} +4 -0
- package/dist/ui/signin.css +1 -1
- package/dist/ui/signin.js +1 -1
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +241 -241
- package/dist/ui/ui.js.LEGAL.txt +341 -1155
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -2,18 +2,22 @@ import {
|
|
|
2
2
|
generateTypes
|
|
3
3
|
} from "./chunk-KDXHFMIH.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
RootCMSClient,
|
|
6
|
+
getCmsPlugin,
|
|
7
|
+
unmarshalData
|
|
8
|
+
} from "./chunk-UEJ2UTTJ.js";
|
|
7
9
|
import "./chunk-MLKGABMK.js";
|
|
8
10
|
|
|
9
11
|
// cli/cli.ts
|
|
10
12
|
import { Command } from "commander";
|
|
11
13
|
import { bgGreen, black } from "kleur/colors";
|
|
12
14
|
|
|
13
|
-
// cli/
|
|
15
|
+
// cli/docs.ts
|
|
14
16
|
import fs from "fs";
|
|
15
17
|
import path from "path";
|
|
18
|
+
import * as readline from "readline";
|
|
16
19
|
import { loadRootConfig } from "@blinkk/root/node";
|
|
20
|
+
import { Timestamp, GeoPoint } from "firebase-admin/firestore";
|
|
17
21
|
|
|
18
22
|
// cli/utils.ts
|
|
19
23
|
function parseFilters(filterStr) {
|
|
@@ -25,9 +29,9 @@ function parseFilters(filterStr) {
|
|
|
25
29
|
const excludes = parts.filter((p) => p.startsWith("!")).map((p) => p.slice(1));
|
|
26
30
|
return { includes, excludes };
|
|
27
31
|
}
|
|
28
|
-
function getPathStatus(
|
|
32
|
+
function getPathStatus(path4, includes, excludes) {
|
|
29
33
|
for (const pattern of excludes) {
|
|
30
|
-
if (globMatch(
|
|
34
|
+
if (globMatch(path4, pattern)) {
|
|
31
35
|
return "EXCLUDE";
|
|
32
36
|
}
|
|
33
37
|
}
|
|
@@ -37,7 +41,7 @@ function getPathStatus(path3, includes, excludes) {
|
|
|
37
41
|
let isIncluded = false;
|
|
38
42
|
let shouldTraverse = false;
|
|
39
43
|
for (const pattern of includes) {
|
|
40
|
-
const match = checkMatch(
|
|
44
|
+
const match = checkMatch(path4, pattern);
|
|
41
45
|
if (match === "FULL") {
|
|
42
46
|
isIncluded = true;
|
|
43
47
|
} else if (match === "PARTIAL") {
|
|
@@ -52,11 +56,11 @@ function getPathStatus(path3, includes, excludes) {
|
|
|
52
56
|
}
|
|
53
57
|
return "SKIP";
|
|
54
58
|
}
|
|
55
|
-
function globMatch(
|
|
56
|
-
return checkMatch(
|
|
59
|
+
function globMatch(path4, pattern) {
|
|
60
|
+
return checkMatch(path4, pattern) === "FULL";
|
|
57
61
|
}
|
|
58
|
-
function checkMatch(
|
|
59
|
-
const pathParts =
|
|
62
|
+
function checkMatch(path4, pattern) {
|
|
63
|
+
const pathParts = path4.split("/");
|
|
60
64
|
const patternParts = pattern.split("/");
|
|
61
65
|
if (matchSegments(pathParts, patternParts)) {
|
|
62
66
|
return "FULL";
|
|
@@ -92,6 +96,25 @@ function matchSegments(pathParts, patternParts) {
|
|
|
92
96
|
}
|
|
93
97
|
return true;
|
|
94
98
|
}
|
|
99
|
+
function convertForExport(obj) {
|
|
100
|
+
if (obj === null || obj === void 0) {
|
|
101
|
+
return obj;
|
|
102
|
+
}
|
|
103
|
+
if (typeof obj === "object" && typeof obj.path === "string" && obj.constructor?.name === "DocumentReference") {
|
|
104
|
+
return { _referencePath: obj.path };
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(obj)) {
|
|
107
|
+
return obj.map((item) => convertForExport(item));
|
|
108
|
+
}
|
|
109
|
+
if (typeof obj === "object") {
|
|
110
|
+
const converted = {};
|
|
111
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
112
|
+
converted[key] = convertForExport(value);
|
|
113
|
+
}
|
|
114
|
+
return converted;
|
|
115
|
+
}
|
|
116
|
+
return obj;
|
|
117
|
+
}
|
|
95
118
|
function pLimit(concurrency) {
|
|
96
119
|
const queue = [];
|
|
97
120
|
let activeCount = 0;
|
|
@@ -117,10 +140,204 @@ function pLimit(concurrency) {
|
|
|
117
140
|
};
|
|
118
141
|
}
|
|
119
142
|
|
|
143
|
+
// cli/docs.ts
|
|
144
|
+
async function docsGet(docId, outputPath, options) {
|
|
145
|
+
const mode = validateMode(options.mode);
|
|
146
|
+
const rootDir = process.cwd();
|
|
147
|
+
const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
|
|
148
|
+
const client = new RootCMSClient(rootConfig);
|
|
149
|
+
const { collection, slug } = parseDocId(docId);
|
|
150
|
+
const rawData = await client.getRawDoc(collection, slug, { mode });
|
|
151
|
+
if (!rawData) {
|
|
152
|
+
throw new Error(`doc not found: ${docId}`);
|
|
153
|
+
}
|
|
154
|
+
const data = options.raw ? rawData : unmarshalData(rawData);
|
|
155
|
+
const json = JSON.stringify(convertForExport(data), null, 2);
|
|
156
|
+
if (outputPath) {
|
|
157
|
+
const resolvedPath = path.resolve(outputPath);
|
|
158
|
+
const dir = path.dirname(resolvedPath);
|
|
159
|
+
if (!fs.existsSync(dir)) {
|
|
160
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
fs.writeFileSync(resolvedPath, json);
|
|
163
|
+
console.error(`Wrote ${docId} to ${resolvedPath}`);
|
|
164
|
+
} else {
|
|
165
|
+
process.stdout.write(json + "\n");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function docsSet(docId, filepath, options) {
|
|
169
|
+
const mode = validateMode(options.mode);
|
|
170
|
+
const rootDir = process.cwd();
|
|
171
|
+
const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
|
|
172
|
+
const client = new RootCMSClient(rootConfig);
|
|
173
|
+
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
174
|
+
const db = cmsPlugin.getFirestore();
|
|
175
|
+
let jsonStr;
|
|
176
|
+
if (filepath) {
|
|
177
|
+
const filePath = path.resolve(filepath);
|
|
178
|
+
if (!fs.existsSync(filePath)) {
|
|
179
|
+
throw new Error(`file not found: ${filePath}`);
|
|
180
|
+
}
|
|
181
|
+
jsonStr = fs.readFileSync(filePath, "utf-8");
|
|
182
|
+
} else {
|
|
183
|
+
jsonStr = await readStdin();
|
|
184
|
+
}
|
|
185
|
+
const rawJson = JSON.parse(jsonStr);
|
|
186
|
+
const data = convertFirestoreTypes(rawJson, db);
|
|
187
|
+
const { collection, slug } = parseDocId(docId);
|
|
188
|
+
await client.setRawDoc(collection, slug, data, { mode });
|
|
189
|
+
console.log(`Updated ${docId} (${mode})`);
|
|
190
|
+
}
|
|
191
|
+
async function docsDownload(collectionId, outputDirArg, options) {
|
|
192
|
+
const mode = validateMode(options.mode);
|
|
193
|
+
const rootDir = process.cwd();
|
|
194
|
+
const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
|
|
195
|
+
const client = new RootCMSClient(rootConfig);
|
|
196
|
+
const outputDir = path.resolve(
|
|
197
|
+
outputDirArg || path.join("dist", collectionId)
|
|
198
|
+
);
|
|
199
|
+
if (fs.existsSync(outputDir)) {
|
|
200
|
+
const entries = fs.readdirSync(outputDir);
|
|
201
|
+
if (entries.length > 0) {
|
|
202
|
+
const proceed = await promptYesNo(
|
|
203
|
+
`Output directory "${outputDir}" is not empty. Delete contents before downloading? (yes/no): `
|
|
204
|
+
);
|
|
205
|
+
if (!proceed) {
|
|
206
|
+
console.log("Download cancelled.");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const entry of entries) {
|
|
210
|
+
fs.rmSync(path.join(outputDir, entry), { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
const { docs } = await client.listDocs(collectionId, {
|
|
217
|
+
mode,
|
|
218
|
+
raw: true
|
|
219
|
+
});
|
|
220
|
+
if (docs.length === 0) {
|
|
221
|
+
console.log(`No docs found in collection "${collectionId}" (${mode}).`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const doc of docs) {
|
|
225
|
+
const slug = doc.slug || doc.id?.split("/")[1] || "unknown";
|
|
226
|
+
const data = convertForExport(doc);
|
|
227
|
+
const filePath = path.join(outputDir, `${slug}.json`);
|
|
228
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
229
|
+
}
|
|
230
|
+
console.log(
|
|
231
|
+
`Downloaded ${docs.length} doc(s) from "${collectionId}" (${mode}) to ${outputDir}`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
async function docsUpload(collectionId, dir, options) {
|
|
235
|
+
const mode = validateMode(options.mode);
|
|
236
|
+
const rootDir = process.cwd();
|
|
237
|
+
const rootConfig = await loadRootConfig(rootDir, { command: "root-cms" });
|
|
238
|
+
const client = new RootCMSClient(rootConfig);
|
|
239
|
+
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
240
|
+
const db = cmsPlugin.getFirestore();
|
|
241
|
+
const inputDir = path.resolve(dir);
|
|
242
|
+
if (!fs.existsSync(inputDir)) {
|
|
243
|
+
throw new Error(`directory not found: ${inputDir}`);
|
|
244
|
+
}
|
|
245
|
+
const files = fs.readdirSync(inputDir).filter((f) => f.endsWith(".json"));
|
|
246
|
+
if (files.length === 0) {
|
|
247
|
+
console.log(`No JSON files found in ${inputDir}.`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
let count = 0;
|
|
251
|
+
for (const file of files) {
|
|
252
|
+
const slug = path.basename(file, ".json");
|
|
253
|
+
const filePath = path.join(inputDir, file);
|
|
254
|
+
const rawJson = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
255
|
+
const data = convertFirestoreTypes(rawJson, db);
|
|
256
|
+
await client.setRawDoc(collectionId, slug, data, { mode });
|
|
257
|
+
count++;
|
|
258
|
+
}
|
|
259
|
+
console.log(
|
|
260
|
+
`Uploaded ${count} doc(s) to "${collectionId}" (${mode}) from ${inputDir}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
async function readStdin() {
|
|
264
|
+
const chunks = [];
|
|
265
|
+
for await (const chunk of process.stdin) {
|
|
266
|
+
chunks.push(chunk);
|
|
267
|
+
}
|
|
268
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
269
|
+
}
|
|
270
|
+
async function promptYesNo(question) {
|
|
271
|
+
const rl = readline.createInterface({
|
|
272
|
+
input: process.stdin,
|
|
273
|
+
output: process.stdout
|
|
274
|
+
});
|
|
275
|
+
return new Promise((resolve) => {
|
|
276
|
+
rl.question(question, (answer) => {
|
|
277
|
+
rl.close();
|
|
278
|
+
const normalized = answer.trim().toLowerCase();
|
|
279
|
+
resolve(normalized === "yes" || normalized === "y");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function validateMode(mode) {
|
|
284
|
+
if (!mode || mode === "draft") {
|
|
285
|
+
return "draft";
|
|
286
|
+
}
|
|
287
|
+
if (mode === "published") {
|
|
288
|
+
return "published";
|
|
289
|
+
}
|
|
290
|
+
throw new Error(`invalid mode: "${mode}". Must be "draft" or "published".`);
|
|
291
|
+
}
|
|
292
|
+
function parseDocId(docId) {
|
|
293
|
+
const sepIndex = docId.indexOf("/");
|
|
294
|
+
if (sepIndex <= 0) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`invalid doc id: "${docId}". Expected format: <collection>/<slug>`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const collection = docId.slice(0, sepIndex);
|
|
300
|
+
const slug = docId.slice(sepIndex + 1);
|
|
301
|
+
if (!collection || !slug) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`invalid doc id: "${docId}". Expected format: <collection>/<slug>`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
return { collection, slug };
|
|
307
|
+
}
|
|
308
|
+
function convertFirestoreTypes(obj, db) {
|
|
309
|
+
if (obj === null || obj === void 0) {
|
|
310
|
+
return obj;
|
|
311
|
+
}
|
|
312
|
+
if (typeof obj === "object" && "_seconds" in obj && "_nanoseconds" in obj && Object.keys(obj).length === 2) {
|
|
313
|
+
return new Timestamp(obj._seconds, obj._nanoseconds);
|
|
314
|
+
}
|
|
315
|
+
if (typeof obj === "object" && "_latitude" in obj && "_longitude" in obj && Object.keys(obj).length === 2) {
|
|
316
|
+
return new GeoPoint(obj._latitude, obj._longitude);
|
|
317
|
+
}
|
|
318
|
+
if (typeof obj === "object" && "_referencePath" in obj && Object.keys(obj).length === 1) {
|
|
319
|
+
return db.doc(obj._referencePath);
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(obj)) {
|
|
322
|
+
return obj.map((item) => convertFirestoreTypes(item, db));
|
|
323
|
+
}
|
|
324
|
+
if (typeof obj === "object") {
|
|
325
|
+
const converted = {};
|
|
326
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
327
|
+
converted[key] = convertFirestoreTypes(value, db);
|
|
328
|
+
}
|
|
329
|
+
return converted;
|
|
330
|
+
}
|
|
331
|
+
return obj;
|
|
332
|
+
}
|
|
333
|
+
|
|
120
334
|
// cli/export.ts
|
|
335
|
+
import fs2 from "fs";
|
|
336
|
+
import path2 from "path";
|
|
337
|
+
import { loadRootConfig as loadRootConfig2 } from "@blinkk/root/node";
|
|
121
338
|
async function exportData(options) {
|
|
122
339
|
const rootDir = process.cwd();
|
|
123
|
-
const rootConfig = await
|
|
340
|
+
const rootConfig = await loadRootConfig2(rootDir, { command: "root-cms" });
|
|
124
341
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
125
342
|
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
126
343
|
const siteId = options.site || cmsPluginOptions.id || "default";
|
|
@@ -149,15 +366,15 @@ async function exportData(options) {
|
|
|
149
366
|
Filter: options.filter || "(none)"
|
|
150
367
|
});
|
|
151
368
|
console.log("");
|
|
152
|
-
if (!
|
|
153
|
-
|
|
369
|
+
if (!fs2.existsSync(exportDir)) {
|
|
370
|
+
fs2.mkdirSync(exportDir, { recursive: true });
|
|
154
371
|
}
|
|
155
372
|
if (!options.filter) {
|
|
156
373
|
const projectDoc = await projectRef.get();
|
|
157
374
|
if (projectDoc.exists) {
|
|
158
375
|
const projectData = projectDoc.data();
|
|
159
|
-
const projectFilePath =
|
|
160
|
-
|
|
376
|
+
const projectFilePath = path2.join(exportDir, "__data.json");
|
|
377
|
+
fs2.writeFileSync(
|
|
161
378
|
projectFilePath,
|
|
162
379
|
JSON.stringify(convertForExport(projectData), null, 2)
|
|
163
380
|
);
|
|
@@ -168,7 +385,7 @@ async function exportData(options) {
|
|
|
168
385
|
for (const collectionType of collectionsToExport) {
|
|
169
386
|
console.log(`Exporting ${collectionType}...`);
|
|
170
387
|
const collectionPath = `Projects/${siteId}/${collectionType}`;
|
|
171
|
-
const collectionDir =
|
|
388
|
+
const collectionDir = path2.join(exportDir, collectionType);
|
|
172
389
|
await exportCollection(
|
|
173
390
|
db,
|
|
174
391
|
collectionPath,
|
|
@@ -231,8 +448,8 @@ async function exportCollection(db, collectionPath, outputDir, displayName, rela
|
|
|
231
448
|
console.log(` - ${displayName}: ${stats.count} documents`);
|
|
232
449
|
}
|
|
233
450
|
async function exportCollectionRecursive(db, collectionPath, outputDir, stats, spinner, relativePath, includes, excludes, limit) {
|
|
234
|
-
if (!
|
|
235
|
-
|
|
451
|
+
if (!fs2.existsSync(outputDir)) {
|
|
452
|
+
fs2.mkdirSync(outputDir, { recursive: true });
|
|
236
453
|
}
|
|
237
454
|
const collection = db.collection(collectionPath);
|
|
238
455
|
const refs = await limit(() => collection.listDocuments());
|
|
@@ -255,15 +472,15 @@ async function exportCollectionRecursive(db, collectionPath, outputDir, stats, s
|
|
|
255
472
|
const docData = doc.data();
|
|
256
473
|
let filePath;
|
|
257
474
|
if (hasSubcollections) {
|
|
258
|
-
const docDir =
|
|
259
|
-
if (!
|
|
260
|
-
|
|
475
|
+
const docDir = path2.join(outputDir, doc.id);
|
|
476
|
+
if (!fs2.existsSync(docDir)) {
|
|
477
|
+
fs2.mkdirSync(docDir, { recursive: true });
|
|
261
478
|
}
|
|
262
|
-
filePath =
|
|
479
|
+
filePath = path2.join(docDir, "__data.json");
|
|
263
480
|
} else {
|
|
264
|
-
filePath =
|
|
481
|
+
filePath = path2.join(outputDir, `${doc.id}.json`);
|
|
265
482
|
}
|
|
266
|
-
|
|
483
|
+
fs2.writeFileSync(
|
|
267
484
|
filePath,
|
|
268
485
|
JSON.stringify(convertForExport(docData), null, 2)
|
|
269
486
|
);
|
|
@@ -276,7 +493,7 @@ async function exportCollectionRecursive(db, collectionPath, outputDir, stats, s
|
|
|
276
493
|
if (status === "SKIP" || status === "EXCLUDE") {
|
|
277
494
|
continue;
|
|
278
495
|
}
|
|
279
|
-
const subcollectionDir =
|
|
496
|
+
const subcollectionDir = path2.join(outputDir, ref.id, subcollection.id);
|
|
280
497
|
await exportCollectionRecursive(
|
|
281
498
|
db,
|
|
282
499
|
subcollection.path,
|
|
@@ -300,33 +517,14 @@ function formatTimestamp(date) {
|
|
|
300
517
|
const minute = String(date.getMinutes()).padStart(2, "0");
|
|
301
518
|
return `${year}${month}${day}t${hour}${minute}`;
|
|
302
519
|
}
|
|
303
|
-
function convertForExport(obj) {
|
|
304
|
-
if (obj === null || obj === void 0) {
|
|
305
|
-
return obj;
|
|
306
|
-
}
|
|
307
|
-
if (typeof obj === "object" && typeof obj.path === "string" && obj.constructor.name === "DocumentReference") {
|
|
308
|
-
return { _referencePath: obj.path };
|
|
309
|
-
}
|
|
310
|
-
if (Array.isArray(obj)) {
|
|
311
|
-
return obj.map((item) => convertForExport(item));
|
|
312
|
-
}
|
|
313
|
-
if (typeof obj === "object") {
|
|
314
|
-
const converted = {};
|
|
315
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
316
|
-
converted[key] = convertForExport(value);
|
|
317
|
-
}
|
|
318
|
-
return converted;
|
|
319
|
-
}
|
|
320
|
-
return obj;
|
|
321
|
-
}
|
|
322
520
|
|
|
323
521
|
// cli/import.ts
|
|
324
|
-
import
|
|
325
|
-
import
|
|
326
|
-
import * as
|
|
327
|
-
import { loadRootConfig as
|
|
522
|
+
import fs3 from "fs";
|
|
523
|
+
import path3 from "path";
|
|
524
|
+
import * as readline2 from "readline";
|
|
525
|
+
import { loadRootConfig as loadRootConfig3 } from "@blinkk/root/node";
|
|
328
526
|
import cliProgress from "cli-progress";
|
|
329
|
-
import { Timestamp, GeoPoint } from "firebase-admin/firestore";
|
|
527
|
+
import { Timestamp as Timestamp2, GeoPoint as GeoPoint2 } from "firebase-admin/firestore";
|
|
330
528
|
async function importData(options) {
|
|
331
529
|
if (!options.dir) {
|
|
332
530
|
throw new Error(
|
|
@@ -334,18 +532,18 @@ async function importData(options) {
|
|
|
334
532
|
);
|
|
335
533
|
}
|
|
336
534
|
const importDir = options.dir;
|
|
337
|
-
if (!
|
|
535
|
+
if (!fs3.existsSync(importDir)) {
|
|
338
536
|
throw new Error(`Error: Directory not found: ${importDir}`);
|
|
339
537
|
}
|
|
340
538
|
const rootDir = process.cwd();
|
|
341
|
-
const rootConfig = await
|
|
539
|
+
const rootConfig = await loadRootConfig3(rootDir, { command: "root-cms" });
|
|
342
540
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
343
541
|
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
344
542
|
const siteId = options.site || cmsPluginOptions.id || "default";
|
|
345
543
|
const databaseId = options.database || "(default)";
|
|
346
544
|
const gcpProjectId = options.project || cmsPluginOptions.firebaseConfig?.projectId || "unknown";
|
|
347
545
|
const db = cmsPlugin.getFirestore({ databaseId });
|
|
348
|
-
const entries =
|
|
546
|
+
const entries = fs3.readdirSync(importDir, { withFileTypes: true });
|
|
349
547
|
const availableCollections = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
350
548
|
const { includes, excludes } = parseFilters(options.filter);
|
|
351
549
|
const collectionsToImport = availableCollections.filter((id) => {
|
|
@@ -361,8 +559,8 @@ async function importData(options) {
|
|
|
361
559
|
}
|
|
362
560
|
const summary = [];
|
|
363
561
|
for (const collectionType of collectionsToImport) {
|
|
364
|
-
const collectionDir =
|
|
365
|
-
if (
|
|
562
|
+
const collectionDir = path3.join(importDir, collectionType);
|
|
563
|
+
if (fs3.existsSync(collectionDir)) {
|
|
366
564
|
const count = countJsonFilesRecursive(
|
|
367
565
|
collectionDir,
|
|
368
566
|
collectionType,
|
|
@@ -392,7 +590,7 @@ async function importData(options) {
|
|
|
392
590
|
console.log("");
|
|
393
591
|
console.table(tableData);
|
|
394
592
|
console.log("");
|
|
395
|
-
const proceed = await
|
|
593
|
+
const proceed = await promptYesNo2("Proceed with import? (yes/no): ");
|
|
396
594
|
if (!proceed) {
|
|
397
595
|
console.log("Import cancelled.");
|
|
398
596
|
return;
|
|
@@ -401,7 +599,7 @@ async function importData(options) {
|
|
|
401
599
|
const limit = pLimit(10);
|
|
402
600
|
for (const item of summary) {
|
|
403
601
|
const collectionPath = `Projects/${siteId}/${item.collectionType}`;
|
|
404
|
-
const collectionDir =
|
|
602
|
+
const collectionDir = path3.join(importDir, item.collectionType);
|
|
405
603
|
await importCollection(
|
|
406
604
|
db,
|
|
407
605
|
collectionPath,
|
|
@@ -414,11 +612,11 @@ async function importData(options) {
|
|
|
414
612
|
limit
|
|
415
613
|
);
|
|
416
614
|
}
|
|
417
|
-
const projectFilePath =
|
|
418
|
-
if (
|
|
615
|
+
const projectFilePath = path3.join(importDir, "__data.json");
|
|
616
|
+
if (fs3.existsSync(projectFilePath)) {
|
|
419
617
|
console.log("Importing project document...");
|
|
420
|
-
const rawData = JSON.parse(
|
|
421
|
-
const projectData =
|
|
618
|
+
const rawData = JSON.parse(fs3.readFileSync(projectFilePath, "utf-8"));
|
|
619
|
+
const projectData = convertFirestoreTypes2(rawData, db);
|
|
422
620
|
const projectPath = `Projects/${siteId}`;
|
|
423
621
|
await db.doc(projectPath).set(projectData, { merge: true });
|
|
424
622
|
console.log(" - Project document updated");
|
|
@@ -427,14 +625,14 @@ async function importData(options) {
|
|
|
427
625
|
}
|
|
428
626
|
function countJsonFilesRecursive(dir, relativePath, includes, excludes) {
|
|
429
627
|
let count = 0;
|
|
430
|
-
const entries =
|
|
628
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
431
629
|
for (const entry of entries) {
|
|
432
630
|
if (entry.isDirectory()) {
|
|
433
631
|
const subRelativePath = `${relativePath}/${entry.name}`;
|
|
434
632
|
const status = getPathStatus(subRelativePath, includes, excludes);
|
|
435
633
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
436
634
|
count += countJsonFilesRecursive(
|
|
437
|
-
|
|
635
|
+
path3.join(dir, entry.name),
|
|
438
636
|
subRelativePath,
|
|
439
637
|
includes,
|
|
440
638
|
excludes
|
|
@@ -448,7 +646,7 @@ function countJsonFilesRecursive(dir, relativePath, includes, excludes) {
|
|
|
448
646
|
}
|
|
449
647
|
continue;
|
|
450
648
|
}
|
|
451
|
-
const docId =
|
|
649
|
+
const docId = path3.basename(entry.name, ".json");
|
|
452
650
|
const docRelativePath = `${relativePath}/${docId}`;
|
|
453
651
|
const status = getPathStatus(docRelativePath, includes, excludes);
|
|
454
652
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -480,43 +678,43 @@ async function importCollection(db, collectionPath, inputDir, displayName, total
|
|
|
480
678
|
progressBar.stop();
|
|
481
679
|
console.log(` - ${displayName}: ${totalDocs} documents`);
|
|
482
680
|
}
|
|
483
|
-
function
|
|
681
|
+
function convertFirestoreTypes2(obj, db) {
|
|
484
682
|
if (obj === null || obj === void 0) {
|
|
485
683
|
return obj;
|
|
486
684
|
}
|
|
487
685
|
if (typeof obj === "object" && "_seconds" in obj && "_nanoseconds" in obj && Object.keys(obj).length === 2) {
|
|
488
|
-
return new
|
|
686
|
+
return new Timestamp2(obj._seconds, obj._nanoseconds);
|
|
489
687
|
}
|
|
490
688
|
if (typeof obj === "object" && "_latitude" in obj && "_longitude" in obj && Object.keys(obj).length === 2) {
|
|
491
|
-
return new
|
|
689
|
+
return new GeoPoint2(obj._latitude, obj._longitude);
|
|
492
690
|
}
|
|
493
691
|
if (typeof obj === "object" && "_referencePath" in obj && Object.keys(obj).length === 1) {
|
|
494
692
|
return db.doc(obj._referencePath);
|
|
495
693
|
}
|
|
496
694
|
if (Array.isArray(obj)) {
|
|
497
|
-
return obj.map((item) =>
|
|
695
|
+
return obj.map((item) => convertFirestoreTypes2(item, db));
|
|
498
696
|
}
|
|
499
697
|
if (typeof obj === "object") {
|
|
500
698
|
const converted = {};
|
|
501
699
|
for (const [key, value] of Object.entries(obj)) {
|
|
502
|
-
converted[key] =
|
|
700
|
+
converted[key] = convertFirestoreTypes2(value, db);
|
|
503
701
|
}
|
|
504
702
|
return converted;
|
|
505
703
|
}
|
|
506
704
|
return obj;
|
|
507
705
|
}
|
|
508
706
|
async function importCollectionRecursive(db, collectionPath, inputDir, progressBar, relativePath, includes, excludes, limit) {
|
|
509
|
-
if (!
|
|
707
|
+
if (!fs3.existsSync(inputDir)) {
|
|
510
708
|
return;
|
|
511
709
|
}
|
|
512
|
-
const entries =
|
|
710
|
+
const entries = fs3.readdirSync(inputDir, { withFileTypes: true });
|
|
513
711
|
await Promise.all(
|
|
514
712
|
entries.map(async (entry) => {
|
|
515
713
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
516
714
|
const rawData = JSON.parse(
|
|
517
|
-
|
|
715
|
+
fs3.readFileSync(path3.join(inputDir, entry.name), "utf-8")
|
|
518
716
|
);
|
|
519
|
-
const docData =
|
|
717
|
+
const docData = convertFirestoreTypes2(rawData, db);
|
|
520
718
|
if (entry.name === "__data.json") {
|
|
521
719
|
const status = includes && excludes ? getPathStatus(relativePath || "", includes, excludes) : "INCLUDE";
|
|
522
720
|
if (status === "INCLUDE") {
|
|
@@ -529,7 +727,7 @@ async function importCollectionRecursive(db, collectionPath, inputDir, progressB
|
|
|
529
727
|
}
|
|
530
728
|
}
|
|
531
729
|
} else {
|
|
532
|
-
const docId =
|
|
730
|
+
const docId = path3.basename(entry.name, ".json");
|
|
533
731
|
const docRelativePath = relativePath ? `${relativePath}/${docId}` : docId;
|
|
534
732
|
const status = includes && excludes ? getPathStatus(docRelativePath, includes, excludes) : "INCLUDE";
|
|
535
733
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -547,7 +745,7 @@ async function importCollectionRecursive(db, collectionPath, inputDir, progressB
|
|
|
547
745
|
}
|
|
548
746
|
} else if (entry.isDirectory()) {
|
|
549
747
|
const subCollectionPath = `${collectionPath}/${entry.name}`;
|
|
550
|
-
const subCollectionDir =
|
|
748
|
+
const subCollectionDir = path3.join(inputDir, entry.name);
|
|
551
749
|
const subRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
552
750
|
const status = includes && excludes ? getPathStatus(subRelativePath, includes, excludes) : "INCLUDE";
|
|
553
751
|
if (status !== "SKIP" && status !== "EXCLUDE") {
|
|
@@ -566,8 +764,8 @@ async function importCollectionRecursive(db, collectionPath, inputDir, progressB
|
|
|
566
764
|
})
|
|
567
765
|
);
|
|
568
766
|
}
|
|
569
|
-
async function
|
|
570
|
-
const rl =
|
|
767
|
+
async function promptYesNo2(question) {
|
|
768
|
+
const rl = readline2.createInterface({
|
|
571
769
|
input: process.stdin,
|
|
572
770
|
output: process.stdout
|
|
573
771
|
});
|
|
@@ -581,7 +779,7 @@ async function promptYesNo(question) {
|
|
|
581
779
|
}
|
|
582
780
|
|
|
583
781
|
// cli/init-firebase.ts
|
|
584
|
-
import { loadRootConfig as
|
|
782
|
+
import { loadRootConfig as loadRootConfig4 } from "@blinkk/root/node";
|
|
585
783
|
|
|
586
784
|
// core/security.ts
|
|
587
785
|
import { initializeApp } from "firebase-admin/app";
|
|
@@ -659,7 +857,7 @@ async function applySecurityRules(projectId) {
|
|
|
659
857
|
// cli/init-firebase.ts
|
|
660
858
|
async function initFirebase(options) {
|
|
661
859
|
const rootDir = process.cwd();
|
|
662
|
-
const rootConfig = await
|
|
860
|
+
const rootConfig = await loadRootConfig4(rootDir, { command: "root-cms" });
|
|
663
861
|
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
664
862
|
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
665
863
|
const gcpProjectId = options.project || cmsPluginOptions.firebaseConfig.projectId;
|
|
@@ -733,11 +931,39 @@ var CliRunner = class {
|
|
|
733
931
|
"--database <databaseId>",
|
|
734
932
|
'firestore database id (overrides root config, default: "(default)")'
|
|
735
933
|
).option("--project <projectId>", "gcp project id (overrides root config)").action(importData);
|
|
934
|
+
program.command("docs.get <docId> [outputPath]").description(
|
|
935
|
+
"fetches a single doc and outputs it as JSON\n\nIf an output path is provided, writes to a file. Otherwise, writes to stdout.\n\nUsage examples:\n $ root-cms docs.get Pages/home\n $ root-cms docs.get Pages/home ./out/home.json\n $ root-cms docs.get Pages/home --mode published\n $ root-cms docs.get Pages/home | jq .fields"
|
|
936
|
+
).option(
|
|
937
|
+
"--mode <mode>",
|
|
938
|
+
'doc mode: "draft" or "published" (default: "draft")'
|
|
939
|
+
).option("--raw", "output raw firestore data without unmarshaling").action(docsGet);
|
|
940
|
+
program.command("docs.set <docId> [filepath]").description(
|
|
941
|
+
"updates a single doc from a JSON file or stdin\n\nIf a filepath is provided, reads from the file. Otherwise, reads from stdin.\n\nUsage examples:\n $ root-cms docs.set Pages/home home.json\n $ root-cms docs.set Pages/home home.json --mode published\n $ cat data.json | root-cms docs.set Pages/home"
|
|
942
|
+
).option(
|
|
943
|
+
"--mode <mode>",
|
|
944
|
+
'doc mode: "draft" or "published" (default: "draft")'
|
|
945
|
+
).action(docsSet);
|
|
946
|
+
program.command("docs.download <collection> [outputDir]").description(
|
|
947
|
+
"downloads all docs in a collection to a local directory\n\nUsage examples:\n $ root-cms docs.download Pages\n $ root-cms docs.download Pages ./my-pages\n $ root-cms docs.download Pages --mode published"
|
|
948
|
+
).option(
|
|
949
|
+
"--mode <mode>",
|
|
950
|
+
'doc mode: "draft" or "published" (default: "draft")'
|
|
951
|
+
).action(docsDownload);
|
|
952
|
+
program.command("docs.upload <collection> <dir>").description(
|
|
953
|
+
"uploads docs from a local directory to a collection\n\nUsage examples:\n $ root-cms docs.upload Pages ./Pages\n $ root-cms docs.upload Pages ./my-pages --mode published"
|
|
954
|
+
).option(
|
|
955
|
+
"--mode <mode>",
|
|
956
|
+
'doc mode: "draft" or "published" (default: "draft")'
|
|
957
|
+
).action(docsUpload);
|
|
736
958
|
await program.parseAsync(argv);
|
|
737
959
|
}
|
|
738
960
|
};
|
|
739
961
|
export {
|
|
740
962
|
CliRunner,
|
|
963
|
+
docsDownload,
|
|
964
|
+
docsGet,
|
|
965
|
+
docsSet,
|
|
966
|
+
docsUpload,
|
|
741
967
|
exportData,
|
|
742
968
|
generateTypes,
|
|
743
969
|
importData,
|
|
@@ -1,7 +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-
|
|
4
|
+
import { C as Collection } from './schema-Bht24XmU.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Supported Root AI models. Defaults to 'gemini-3-flash-preview'.
|
|
@@ -836,6 +836,10 @@ declare class RootCMSClient {
|
|
|
836
836
|
* Verifies user exists in the ACL list.
|
|
837
837
|
*/
|
|
838
838
|
userExistsInAcl(email: string): Promise<boolean>;
|
|
839
|
+
/**
|
|
840
|
+
* Lists action logs from the database.
|
|
841
|
+
*/
|
|
842
|
+
listActions(options?: ListActionsOptions): Promise<Action[]>;
|
|
839
843
|
logAction(action: string, options?: {
|
|
840
844
|
by?: string;
|
|
841
845
|
metadata?: any;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@blinkk/root';
|
|
2
2
|
import 'firebase-admin/app';
|
|
3
3
|
import 'firebase-admin/firestore';
|
|
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-
|
|
5
|
-
import './schema-
|
|
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-TgoRyFWJ.js';
|
|
5
|
+
import './schema-Bht24XmU.js';
|