@blinkk/root-cms 2.5.8 → 2.5.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/dist/app.js +11 -1
- package/dist/{chunk-4YTAYZTP.js → chunk-7QY6372S.js} +2 -2
- package/dist/{chunk-AFL5KJ4E.js → chunk-PKD2TDSV.js} +10 -1
- package/dist/{chunk-5N2KLMQH.js → chunk-YMUZ5H5C.js} +186 -3
- package/dist/cli.js +1 -1
- package/dist/{client-B3U47cNR.d.ts → client-CFCGhGA0.d.ts} +129 -3
- 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 -4
- package/dist/functions.js +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +100 -5
- package/dist/project.d.ts +1 -1
- package/dist/{schema-Bht24XmU.d.ts → schema-D7MOj-YC.d.ts} +8 -1
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +150 -150
- package/dist/ui/ui.js.LEGAL.txt +1 -0
- package/package.json +3 -3
package/dist/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getServerVersion
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7QY6372S.js";
|
|
4
4
|
import {
|
|
5
5
|
getCollectionSchema,
|
|
6
6
|
getProjectSchemas
|
|
@@ -128,6 +128,13 @@ async function renderApp(req, res, options) {
|
|
|
128
128
|
label: c.label,
|
|
129
129
|
description: c.description,
|
|
130
130
|
collections: c.collections
|
|
131
|
+
})),
|
|
132
|
+
translations: (cmsConfig.translations || []).map((t) => ({
|
|
133
|
+
id: t.id,
|
|
134
|
+
label: t.label,
|
|
135
|
+
icon: t.icon,
|
|
136
|
+
hasImport: isFunction(t.onImport),
|
|
137
|
+
hasExport: isFunction(t.onExport)
|
|
131
138
|
}))
|
|
132
139
|
};
|
|
133
140
|
const projectName = cmsConfig.name || cmsConfig.id || "";
|
|
@@ -158,6 +165,9 @@ function serializeCollection(collection) {
|
|
|
158
165
|
sortOptions: collection.sortOptions
|
|
159
166
|
};
|
|
160
167
|
}
|
|
168
|
+
function isFunction(value) {
|
|
169
|
+
return typeof value === "function";
|
|
170
|
+
}
|
|
161
171
|
function SignIn(props) {
|
|
162
172
|
return /* @__PURE__ */ jsxs("html", { children: [
|
|
163
173
|
/* @__PURE__ */ jsxs("head", { children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@blinkk/root-cms",
|
|
4
|
-
version: "2.5.
|
|
4
|
+
version: "2.5.10",
|
|
5
5
|
author: "s@blinkk.com",
|
|
6
6
|
license: "MIT",
|
|
7
7
|
engines: {
|
|
@@ -169,7 +169,7 @@ var package_default = {
|
|
|
169
169
|
yjs: "13.6.27"
|
|
170
170
|
},
|
|
171
171
|
peerDependencies: {
|
|
172
|
-
"@blinkk/root": "2.5.
|
|
172
|
+
"@blinkk/root": "2.5.10",
|
|
173
173
|
"firebase-admin": ">=11",
|
|
174
174
|
"firebase-functions": ">=4",
|
|
175
175
|
preact: ">=10",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RootCMSClient,
|
|
3
3
|
getCmsPlugin
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YMUZ5H5C.js";
|
|
5
5
|
|
|
6
6
|
// core/versions.ts
|
|
7
7
|
import path from "path";
|
|
@@ -109,6 +109,11 @@ var VersionsService = class {
|
|
|
109
109
|
async function runCronJobs(rootConfig) {
|
|
110
110
|
await Promise.all([
|
|
111
111
|
runCronJob("publishScheduledDocs", runPublishScheduledDocs, rootConfig),
|
|
112
|
+
runCronJob(
|
|
113
|
+
"syncScheduledDataSources",
|
|
114
|
+
runSyncScheduledDataSources,
|
|
115
|
+
rootConfig
|
|
116
|
+
),
|
|
112
117
|
runCronJob("saveVersions", runSaveVersions, rootConfig)
|
|
113
118
|
]);
|
|
114
119
|
}
|
|
@@ -126,6 +131,10 @@ async function runPublishScheduledDocs(rootConfig) {
|
|
|
126
131
|
await cmsClient.publishScheduledDocs();
|
|
127
132
|
await cmsClient.publishScheduledReleases();
|
|
128
133
|
}
|
|
134
|
+
async function runSyncScheduledDataSources(rootConfig) {
|
|
135
|
+
const cmsClient = new RootCMSClient(rootConfig);
|
|
136
|
+
await cmsClient.syncScheduledDataSources();
|
|
137
|
+
}
|
|
129
138
|
async function runSaveVersions(rootConfig) {
|
|
130
139
|
const service = new VersionsService(rootConfig);
|
|
131
140
|
await service.saveVersions();
|
|
@@ -1288,6 +1288,69 @@ ${errorMessages}`);
|
|
|
1288
1288
|
});
|
|
1289
1289
|
}
|
|
1290
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Syncs data sources that have cron scheduling enabled and are due for sync.
|
|
1293
|
+
*/
|
|
1294
|
+
async syncScheduledDataSources() {
|
|
1295
|
+
const dataSourcesPath = `Projects/${this.projectId}/DataSources`;
|
|
1296
|
+
const query = this.db.collection(dataSourcesPath).where("cron.enabled", "==", true);
|
|
1297
|
+
const querySnapshot = await query.get();
|
|
1298
|
+
const now = Date.now();
|
|
1299
|
+
for (const snapshot of querySnapshot.docs) {
|
|
1300
|
+
const dataSource = snapshot.data();
|
|
1301
|
+
const cron = dataSource.cron;
|
|
1302
|
+
if (!cron || !cron.enabled || cron.interval == null || !cron.unit) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const interval = Number(cron.interval);
|
|
1306
|
+
if (!Number.isFinite(interval) || interval <= 0 || !Number.isInteger(interval)) {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
let intervalMs = interval;
|
|
1310
|
+
switch (cron.unit) {
|
|
1311
|
+
case "minutes":
|
|
1312
|
+
intervalMs *= 60 * 1e3;
|
|
1313
|
+
break;
|
|
1314
|
+
case "hours":
|
|
1315
|
+
intervalMs *= 60 * 60 * 1e3;
|
|
1316
|
+
break;
|
|
1317
|
+
case "days":
|
|
1318
|
+
intervalMs *= 24 * 60 * 60 * 1e3;
|
|
1319
|
+
break;
|
|
1320
|
+
default:
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
let lastSyncMs = dataSource.syncedAt ? dataSource.syncedAt.toMillis() : 0;
|
|
1324
|
+
if (lastSyncMs > now) {
|
|
1325
|
+
lastSyncMs = now;
|
|
1326
|
+
}
|
|
1327
|
+
if (now - lastSyncMs < intervalMs) {
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
try {
|
|
1331
|
+
console.log(`cron: syncing data source: ${dataSource.id}`);
|
|
1332
|
+
await this.syncDataSource(dataSource.id, { syncedBy: "cron" });
|
|
1333
|
+
if (cron.autoPublish) {
|
|
1334
|
+
console.log(`cron: auto-publishing data source: ${dataSource.id}`);
|
|
1335
|
+
await this.publishDataSource(dataSource.id, {
|
|
1336
|
+
publishedBy: "cron"
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
await this.logAction("datasource.cron_sync", {
|
|
1340
|
+
by: "cron",
|
|
1341
|
+
metadata: {
|
|
1342
|
+
datasourceId: dataSource.id,
|
|
1343
|
+
autoPublish: cron.autoPublish || false
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
} catch (err) {
|
|
1347
|
+
console.error(
|
|
1348
|
+
`cron: failed to sync data source ${dataSource.id}:`,
|
|
1349
|
+
String(err)
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1291
1354
|
/**
|
|
1292
1355
|
* Checks if a doc is currently "locked" for publishing.
|
|
1293
1356
|
*/
|
|
@@ -1450,7 +1513,7 @@ ${errorMessages}`);
|
|
|
1450
1513
|
if (!dataSource) {
|
|
1451
1514
|
throw new Error(`data source not found: ${dataSourceId}`);
|
|
1452
1515
|
}
|
|
1453
|
-
const
|
|
1516
|
+
const result = await this.fetchData(dataSource);
|
|
1454
1517
|
const dataSourceDocRef = this.db.doc(
|
|
1455
1518
|
`Projects/${this.projectId}/DataSources/${dataSourceId}`
|
|
1456
1519
|
);
|
|
@@ -1466,7 +1529,8 @@ ${errorMessages}`);
|
|
|
1466
1529
|
const batch = this.db.batch();
|
|
1467
1530
|
batch.set(dataDocRef, {
|
|
1468
1531
|
dataSource: updatedDataSource,
|
|
1469
|
-
data
|
|
1532
|
+
data: result.data,
|
|
1533
|
+
...result.headers ? { headers: result.headers } : {}
|
|
1470
1534
|
});
|
|
1471
1535
|
batch.update(dataSourceDocRef, {
|
|
1472
1536
|
syncedAt: Timestamp2.now(),
|
|
@@ -1554,7 +1618,10 @@ ${errorMessages}`);
|
|
|
1554
1618
|
}
|
|
1555
1619
|
async fetchData(dataSource) {
|
|
1556
1620
|
if (dataSource.type === "http") {
|
|
1557
|
-
return await this.fetchHttpData(dataSource);
|
|
1621
|
+
return { data: await this.fetchHttpData(dataSource) };
|
|
1622
|
+
}
|
|
1623
|
+
if (dataSource.type === "gsheet") {
|
|
1624
|
+
return await this.fetchGsheetData(dataSource);
|
|
1558
1625
|
}
|
|
1559
1626
|
throw new Error(`unsupported data source: ${dataSource.type}`);
|
|
1560
1627
|
}
|
|
@@ -1578,6 +1645,98 @@ ${errorMessages}`);
|
|
|
1578
1645
|
}
|
|
1579
1646
|
return res.text();
|
|
1580
1647
|
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Fetches data from a Google Sheet using the Google Sheets API v4.
|
|
1650
|
+
*
|
|
1651
|
+
* ## Setup
|
|
1652
|
+
*
|
|
1653
|
+
* For the server-side service account to access the Google Sheet:
|
|
1654
|
+
*
|
|
1655
|
+
* 1. **Enable the Google Sheets API** in the Google Cloud Console for the
|
|
1656
|
+
* project associated with the service account.
|
|
1657
|
+
* https://console.cloud.google.com/apis/library/sheets.googleapis.com
|
|
1658
|
+
*
|
|
1659
|
+
* 2. **Share the Google Sheet** with the service account's email address
|
|
1660
|
+
* (e.g. `my-service-account@my-project.iam.gserviceaccount.com`).
|
|
1661
|
+
* Open the sheet in Google Sheets, click "Share", and add the service
|
|
1662
|
+
* account email as a Viewer.
|
|
1663
|
+
*
|
|
1664
|
+
* - For **App Engine**: the service account email is typically
|
|
1665
|
+
* `<project-id>@appspot.gserviceaccount.com`.
|
|
1666
|
+
* - For **Cloud Run**: check the service account configured for the
|
|
1667
|
+
* Cloud Run service in the Google Cloud Console.
|
|
1668
|
+
* - For **local development**: use the service account email from the
|
|
1669
|
+
* JSON key file specified in `GOOGLE_APPLICATION_CREDENTIALS`.
|
|
1670
|
+
*/
|
|
1671
|
+
async fetchGsheetData(dataSource) {
|
|
1672
|
+
const gsheetId = parseSpreadsheetUrl(dataSource.url);
|
|
1673
|
+
if (!gsheetId?.spreadsheetId) {
|
|
1674
|
+
throw new Error(`failed to parse google sheet url: ${dataSource.url}`);
|
|
1675
|
+
}
|
|
1676
|
+
const credential = this.app.options.credential;
|
|
1677
|
+
if (!credential) {
|
|
1678
|
+
throw new Error(
|
|
1679
|
+
"firebase credential not available. ensure the firebase admin app is initialized with a service account."
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
const { access_token } = await credential.getAccessToken();
|
|
1683
|
+
let sheetTitle = "Sheet1";
|
|
1684
|
+
const metaUrl = `https://sheets.googleapis.com/v4/spreadsheets/${encodeURIComponent(
|
|
1685
|
+
gsheetId.spreadsheetId
|
|
1686
|
+
)}?fields=sheets.properties`;
|
|
1687
|
+
const metaRes = await fetch(metaUrl, {
|
|
1688
|
+
headers: { Authorization: `Bearer ${access_token}` }
|
|
1689
|
+
});
|
|
1690
|
+
if (metaRes.status !== 200) {
|
|
1691
|
+
const err = await metaRes.text();
|
|
1692
|
+
throw new Error(`failed to fetch spreadsheet metadata: ${err}`);
|
|
1693
|
+
}
|
|
1694
|
+
const meta = await metaRes.json();
|
|
1695
|
+
const sheets = meta.sheets || [];
|
|
1696
|
+
if (gsheetId.gid !== void 0) {
|
|
1697
|
+
const sheet = sheets.find((s) => s.properties?.sheetId === gsheetId.gid);
|
|
1698
|
+
if (sheet?.properties?.title) {
|
|
1699
|
+
sheetTitle = sheet.properties.title;
|
|
1700
|
+
} else if (gsheetId.gid !== 0) {
|
|
1701
|
+
throw new Error(
|
|
1702
|
+
`sheet with gid ${gsheetId.gid} not found in spreadsheet`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
const range = encodeURIComponent(sheetTitle);
|
|
1707
|
+
const valuesUrl = `https://sheets.googleapis.com/v4/spreadsheets/${encodeURIComponent(
|
|
1708
|
+
gsheetId.spreadsheetId
|
|
1709
|
+
)}/values/${range}`;
|
|
1710
|
+
const valuesRes = await fetch(valuesUrl, {
|
|
1711
|
+
headers: { Authorization: `Bearer ${access_token}` }
|
|
1712
|
+
});
|
|
1713
|
+
if (valuesRes.status !== 200) {
|
|
1714
|
+
const err = await valuesRes.text();
|
|
1715
|
+
throw new Error(`failed to fetch sheet values: ${err}`);
|
|
1716
|
+
}
|
|
1717
|
+
const valuesData = await valuesRes.json();
|
|
1718
|
+
const values = valuesData.values || [];
|
|
1719
|
+
if (values.length === 0) {
|
|
1720
|
+
return { data: [], headers: [] };
|
|
1721
|
+
}
|
|
1722
|
+
const headers = values[0];
|
|
1723
|
+
const rows = values.slice(1);
|
|
1724
|
+
const dataFormat = dataSource.dataFormat || "map";
|
|
1725
|
+
if (dataFormat === "array") {
|
|
1726
|
+
return { data: [headers, ...rows], headers };
|
|
1727
|
+
}
|
|
1728
|
+
const mapData = rows.map((row) => {
|
|
1729
|
+
const item = {};
|
|
1730
|
+
row.forEach((val, i) => {
|
|
1731
|
+
const key = headers[i];
|
|
1732
|
+
if (key) {
|
|
1733
|
+
item[key] = String(val || "");
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
return item;
|
|
1737
|
+
});
|
|
1738
|
+
return { data: mapData, headers };
|
|
1739
|
+
}
|
|
1581
1740
|
/**
|
|
1582
1741
|
* Fetches data from a data source.
|
|
1583
1742
|
*/
|
|
@@ -1887,6 +2046,30 @@ function unmarshalArray(arrObject) {
|
|
|
1887
2046
|
function isObject(data) {
|
|
1888
2047
|
return typeof data === "object" && !Array.isArray(data) && data !== null;
|
|
1889
2048
|
}
|
|
2049
|
+
function parseSpreadsheetUrl(url) {
|
|
2050
|
+
if (!url.startsWith("https://docs.google.com/spreadsheets/d/")) {
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
const parts = url.split("/");
|
|
2054
|
+
const dIndex = parts.indexOf("d");
|
|
2055
|
+
if (dIndex === -1 || dIndex + 1 >= parts.length) {
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
const spreadsheetId = parts[dIndex + 1];
|
|
2059
|
+
if (!spreadsheetId) {
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
let gid = 0;
|
|
2063
|
+
const hashIndex = url.indexOf("#");
|
|
2064
|
+
if (hashIndex !== -1) {
|
|
2065
|
+
const hash = url.slice(hashIndex);
|
|
2066
|
+
const gidMatch = hash.match(/gid=(\d+)/);
|
|
2067
|
+
if (gidMatch && gidMatch[1]) {
|
|
2068
|
+
gid = parseInt(gidMatch[1]);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return { spreadsheetId, gid };
|
|
2072
|
+
}
|
|
1890
2073
|
function randString(len) {
|
|
1891
2074
|
const result = [];
|
|
1892
2075
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RootConfig, Plugin, Request } 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-D7MOj-YC.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Supported Root AI models. Defaults to 'gemini-3-flash-preview'.
|
|
@@ -54,11 +54,81 @@ interface CMSCheck {
|
|
|
54
54
|
run: (ctx: CheckContext) => Promise<CheckResult>;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** A row of translation data keyed by locale. */
|
|
58
|
+
interface TranslationRow {
|
|
59
|
+
/** The source string. */
|
|
60
|
+
source: string;
|
|
61
|
+
/** Map of locale code to translated string. */
|
|
62
|
+
translations: Record<string, string>;
|
|
63
|
+
/** Optional translator notes/context for the source string. */
|
|
64
|
+
description?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Context passed to translation service import/export functions. */
|
|
67
|
+
interface TranslationServiceContext {
|
|
68
|
+
/** The Root.js config. */
|
|
69
|
+
rootConfig: RootConfig;
|
|
70
|
+
/** The Root CMS client for accessing the database. */
|
|
71
|
+
cmsClient: RootCMSClient;
|
|
72
|
+
/** The document ID, e.g. `Pages/index`. */
|
|
73
|
+
docId: string;
|
|
74
|
+
/** The collection ID, e.g. `Pages`. */
|
|
75
|
+
collectionId: string;
|
|
76
|
+
/** The slug, e.g. `index`. */
|
|
77
|
+
slug: string;
|
|
78
|
+
/** The locales configured for the project. */
|
|
79
|
+
locales: string[];
|
|
80
|
+
}
|
|
81
|
+
/** Result returned by an onExport handler. */
|
|
82
|
+
interface TranslationExportResult {
|
|
83
|
+
/** Optional title displayed in the notification after export. */
|
|
84
|
+
title?: string;
|
|
85
|
+
/** Optional message displayed in the notification after export. */
|
|
86
|
+
message?: string;
|
|
87
|
+
/** Optional link displayed in the notification (e.g. to the translation service). */
|
|
88
|
+
link?: {
|
|
89
|
+
/** The URL to link to. */
|
|
90
|
+
url: string;
|
|
91
|
+
/** Optional label for the link. Defaults to "Open". */
|
|
92
|
+
label?: string;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Configuration for defining a CMS translation service. */
|
|
96
|
+
interface CMSTranslationService {
|
|
97
|
+
/** Unique ID for the translation service. */
|
|
98
|
+
id: string;
|
|
99
|
+
/** Human-readable label displayed in the UI (e.g. "Crowdin"). */
|
|
100
|
+
label: string;
|
|
101
|
+
/**
|
|
102
|
+
* Optional icon URL displayed in the UI next to the label. Similar to the
|
|
103
|
+
* sidebar tools `icon` option, this should be a URL to an image.
|
|
104
|
+
*/
|
|
105
|
+
icon?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Async function to import translations from the service. Should return an
|
|
108
|
+
* array of translation rows that will be merged into the CMS translations
|
|
109
|
+
* database.
|
|
110
|
+
*/
|
|
111
|
+
onImport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<TranslationRow[]>;
|
|
112
|
+
/**
|
|
113
|
+
* Async function to export translations to the service. Receives the
|
|
114
|
+
* current translation rows for the document. Can optionally return an object
|
|
115
|
+
* with a `message` to display in the success notification.
|
|
116
|
+
*/
|
|
117
|
+
onExport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<void | TranslationExportResult>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface TranslationsCheckOptions {
|
|
121
|
+
/**
|
|
122
|
+
* Optional list of collection IDs to restrict this check to. When set, the
|
|
123
|
+
* check is only shown and runnable for documents in these collections.
|
|
124
|
+
*/
|
|
125
|
+
collections?: string[];
|
|
126
|
+
}
|
|
57
127
|
/**
|
|
58
128
|
* A first-party CMS check that verifies all translatable strings in a document
|
|
59
129
|
* have translations for each of the document's enabled locales.
|
|
60
130
|
*/
|
|
61
|
-
declare function translationsCheck(): CMSCheck;
|
|
131
|
+
declare function translationsCheck(options?: TranslationsCheckOptions): CMSCheck;
|
|
62
132
|
|
|
63
133
|
/**
|
|
64
134
|
* Built-in sidebar tools that can be toggled on/off in the CMS UI.
|
|
@@ -244,6 +314,26 @@ type CMSPluginOptions = {
|
|
|
244
314
|
* may change from version to version as we add new features.
|
|
245
315
|
*/
|
|
246
316
|
checks?: CMSCheck[];
|
|
317
|
+
/**
|
|
318
|
+
* Translation services that appear in the Localization modal's Import and
|
|
319
|
+
* Export menus. Each service defines server-side import/export functions
|
|
320
|
+
* that integrate with an external translation platform (e.g. Crowdin).
|
|
321
|
+
*
|
|
322
|
+
* Example:
|
|
323
|
+
* ```ts
|
|
324
|
+
* cmsPlugin({
|
|
325
|
+
* translations: [
|
|
326
|
+
* {
|
|
327
|
+
* id: 'crowdin',
|
|
328
|
+
* label: 'Crowdin',
|
|
329
|
+
* onImport: async (ctx, data) => { ... },
|
|
330
|
+
* onExport: async (ctx, data) => { ... },
|
|
331
|
+
* },
|
|
332
|
+
* ],
|
|
333
|
+
* });
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
translations?: CMSTranslationService[];
|
|
247
337
|
};
|
|
248
338
|
type CMSPlugin = Plugin & {
|
|
249
339
|
name: 'root-cms';
|
|
@@ -469,6 +559,13 @@ interface Doc<Fields = any> {
|
|
|
469
559
|
type DocMode = 'draft' | 'published';
|
|
470
560
|
type UserRole = 'ADMIN' | 'EDITOR' | 'CONTRIBUTOR' | 'VIEWER';
|
|
471
561
|
type HttpMethod = 'GET' | 'POST';
|
|
562
|
+
type CronUnit = 'minutes' | 'hours' | 'days';
|
|
563
|
+
interface DataSourceCron {
|
|
564
|
+
enabled: boolean;
|
|
565
|
+
interval: number;
|
|
566
|
+
unit: CronUnit;
|
|
567
|
+
autoPublish?: boolean;
|
|
568
|
+
}
|
|
472
569
|
interface DataSource {
|
|
473
570
|
id: string;
|
|
474
571
|
description?: string;
|
|
@@ -487,6 +584,7 @@ interface DataSource {
|
|
|
487
584
|
headers?: Record<string, string>;
|
|
488
585
|
body?: string;
|
|
489
586
|
};
|
|
587
|
+
cron?: DataSourceCron;
|
|
490
588
|
createdAt: Timestamp;
|
|
491
589
|
createdBy: string;
|
|
492
590
|
syncedAt?: Timestamp;
|
|
@@ -788,6 +886,10 @@ declare class RootCMSClient {
|
|
|
788
886
|
* Publishes docs in scheduled releases.
|
|
789
887
|
*/
|
|
790
888
|
publishScheduledReleases(): Promise<void>;
|
|
889
|
+
/**
|
|
890
|
+
* Syncs data sources that have cron scheduling enabled and are due for sync.
|
|
891
|
+
*/
|
|
892
|
+
syncScheduledDataSources(): Promise<void>;
|
|
791
893
|
/**
|
|
792
894
|
* Checks if a doc is currently "locked" for publishing.
|
|
793
895
|
*/
|
|
@@ -886,6 +988,30 @@ declare class RootCMSClient {
|
|
|
886
988
|
}): Promise<void>;
|
|
887
989
|
private fetchData;
|
|
888
990
|
private fetchHttpData;
|
|
991
|
+
/**
|
|
992
|
+
* Fetches data from a Google Sheet using the Google Sheets API v4.
|
|
993
|
+
*
|
|
994
|
+
* ## Setup
|
|
995
|
+
*
|
|
996
|
+
* For the server-side service account to access the Google Sheet:
|
|
997
|
+
*
|
|
998
|
+
* 1. **Enable the Google Sheets API** in the Google Cloud Console for the
|
|
999
|
+
* project associated with the service account.
|
|
1000
|
+
* https://console.cloud.google.com/apis/library/sheets.googleapis.com
|
|
1001
|
+
*
|
|
1002
|
+
* 2. **Share the Google Sheet** with the service account's email address
|
|
1003
|
+
* (e.g. `my-service-account@my-project.iam.gserviceaccount.com`).
|
|
1004
|
+
* Open the sheet in Google Sheets, click "Share", and add the service
|
|
1005
|
+
* account email as a Viewer.
|
|
1006
|
+
*
|
|
1007
|
+
* - For **App Engine**: the service account email is typically
|
|
1008
|
+
* `<project-id>@appspot.gserviceaccount.com`.
|
|
1009
|
+
* - For **Cloud Run**: check the service account configured for the
|
|
1010
|
+
* Cloud Run service in the Google Cloud Console.
|
|
1011
|
+
* - For **local development**: use the service account email from the
|
|
1012
|
+
* JSON key file specified in `GOOGLE_APPLICATION_CREDENTIALS`.
|
|
1013
|
+
*/
|
|
1014
|
+
private fetchGsheetData;
|
|
889
1015
|
/**
|
|
890
1016
|
* Fetches data from a data source.
|
|
891
1017
|
*/
|
|
@@ -1097,4 +1223,4 @@ declare class BatchResponse {
|
|
|
1097
1223
|
private getTranslationsMap;
|
|
1098
1224
|
}
|
|
1099
1225
|
|
|
1100
|
-
export { type
|
|
1226
|
+
export { type CMSAIConfig as $, type Action as A, type BatchRequestOptions as B, type CronUnit as C, type DocMode as D, type TranslationsDoc as E, BatchRequest as F, type GetDocOptions as G, type HttpMethod as H, BatchResponse as I, type Locale as J, type SourceString as K, type LoadTranslationsOptions as L, type TranslatedString as M, type TranslationsDocMode as N, type TranslationsLocaleDocHashMap as O, type TranslationsLocaleDocEntry as P, type MultiLocaleTranslationsMap as Q, RootCMSClient as R, type SetDocOptions as S, type TranslationsMap as T, type UserRole as U, type SingleLocaleTranslationsMap as V, TranslationsManager as W, buildTranslationsDbPath as X, buildTranslationsLocaleDocDbPath as Y, type CMSBuiltInSidebarTool as Z, type CMSUser as _, type LocaleTranslations as a, type CMSSidebarTool as a0, type CMSPluginOptions as a1, type CMSPlugin as a2, cmsPlugin as a3, type CMSCheck as a4, type CheckResult as a5, type CheckContext as a6, type CheckStatus as a7, translationsCheck as a8, type TranslationsCheckOptions as a9, type CMSTranslationService as aa, type TranslationExportResult as ab, type TranslationRow as ac, type TranslationServiceContext as ad, type Doc as b, type DataSourceCron as c, type DataSource as d, type DataSourceData as e, type DataSourceMode as f, type SaveDraftOptions as g, type UpdateDraftOptions as h, type ListDocsOptions as i, type GetCountOptions as j, type Translation as k, type Release as l, type ListActionsOptions as m, isRichTextData as n, getCmsPlugin as o, marshalData as p, normalizeData as q, type ArrayObject as r, marshalArray as s, toArrayObject as t, unmarshalData as u, unmarshalArray as v, translationsForLocale as w, parseDocId as x, type BatchRequestQuery as y, type BatchRequestQueryOptions as z };
|
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,
|
|
5
|
-
import './schema-
|
|
4
|
+
export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, D as DocMode, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, L as LoadTranslationsOptions, a as LocaleTranslations, l as Release, R as RootCMSClient, g as SaveDraftOptions, S as SetDocOptions, k as Translation, E as TranslationsDoc, T as TranslationsMap, h as UpdateDraftOptions, U as UserRole, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-CFCGhGA0.js';
|
|
5
|
+
import './schema-D7MOj-YC.js';
|
package/dist/client.js
CHANGED
package/dist/core.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-
|
|
2
|
-
export { A as Action,
|
|
1
|
+
import { R as RootCMSClient, D as DocMode, L as LoadTranslationsOptions, T as TranslationsMap, a as LocaleTranslations } from './client-CFCGhGA0.js';
|
|
2
|
+
export { A as Action, r as ArrayObject, F as BatchRequest, B as BatchRequestOptions, y as BatchRequestQuery, z as BatchRequestQueryOptions, I as BatchResponse, C as CronUnit, d as DataSource, c as DataSourceCron, e as DataSourceData, f as DataSourceMode, b as Doc, j as GetCountOptions, G as GetDocOptions, H as HttpMethod, m as ListActionsOptions, i as ListDocsOptions, J as Locale, Q as MultiLocaleTranslationsMap, l as Release, g as SaveDraftOptions, S as SetDocOptions, V as SingleLocaleTranslationsMap, K as SourceString, M as TranslatedString, k as Translation, E as TranslationsDoc, N as TranslationsDocMode, P as TranslationsLocaleDocEntry, O as TranslationsLocaleDocHashMap, W as TranslationsManager, h as UpdateDraftOptions, U as UserRole, X as buildTranslationsDbPath, Y as buildTranslationsLocaleDocDbPath, o as getCmsPlugin, n as isRichTextData, s as marshalArray, p as marshalData, q as normalizeData, x as parseDocId, t as toArrayObject, w as translationsForLocale, v as unmarshalArray, u as unmarshalData } from './client-CFCGhGA0.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-
|
|
5
|
+
export { s as schema } from './schema-D7MOj-YC.js';
|
|
6
6
|
import 'firebase-admin/app';
|
|
7
7
|
|
|
8
8
|
/**
|
package/dist/core.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
translationsForLocale,
|
|
17
17
|
unmarshalArray,
|
|
18
18
|
unmarshalData
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YMUZ5H5C.js";
|
|
20
20
|
import {
|
|
21
21
|
__export
|
|
22
22
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -86,7 +86,6 @@ function createRoute(options) {
|
|
|
86
86
|
const ctx = req.handlerContext;
|
|
87
87
|
const slug = getSlug(ctx.params);
|
|
88
88
|
if (slug.includes(".") || slug.includes("--")) {
|
|
89
|
-
res.setHeader("cache-control", "private");
|
|
90
89
|
return ctx.render404();
|
|
91
90
|
}
|
|
92
91
|
const mode = await getMode(req);
|
|
@@ -122,7 +121,6 @@ function createRoute(options) {
|
|
|
122
121
|
await options.notFoundHook(req, res);
|
|
123
122
|
return;
|
|
124
123
|
}
|
|
125
|
-
res.setHeader("cache-control", "private");
|
|
126
124
|
return ctx.render404();
|
|
127
125
|
}
|
|
128
126
|
const hl = getFirstQueryParam(req, "hl");
|
|
@@ -184,7 +182,6 @@ function createRoute(options) {
|
|
|
184
182
|
await options.notFoundHook(req, res);
|
|
185
183
|
return;
|
|
186
184
|
}
|
|
187
|
-
res.setHeader("cache-control", "private");
|
|
188
185
|
return ctx.render404();
|
|
189
186
|
}
|
|
190
187
|
}
|
package/dist/functions.js
CHANGED
package/dist/plugin.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 {
|
|
5
|
-
import './schema-
|
|
4
|
+
export { $ as CMSAIConfig, Z as CMSBuiltInSidebarTool, a4 as CMSCheck, a2 as CMSPlugin, a1 as CMSPluginOptions, a0 as CMSSidebarTool, aa as CMSTranslationService, _ as CMSUser, a6 as CheckContext, a5 as CheckResult, a7 as CheckStatus, ab as TranslationExportResult, ac as TranslationRow, ad as TranslationServiceContext, a9 as TranslationsCheckOptions, a3 as cmsPlugin, a8 as translationsCheck } from './client-CFCGhGA0.js';
|
|
5
|
+
import './schema-D7MOj-YC.js';
|