@blinkk/root-cms 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/app.js +5 -5
  2. package/dist/{chunk-2BSW7SIH.js → chunk-N4K6ICFR.js} +4 -4
  3. package/dist/{chunk-BGTUWIV6.js → chunk-PNLLZXVY.js} +4 -4
  4. package/dist/{chunk-3HARN4U6.js → chunk-RLZFZT42.js} +176 -83
  5. package/dist/{chunk-C245C557.js → chunk-RXWDB3K4.js} +3 -3
  6. package/dist/{chunk-XLX37FRL.js → chunk-TEKMLYXS.js} +3 -4
  7. package/dist/cli/cli.d.ts +12 -0
  8. package/dist/cli/docs.d.ts +49 -0
  9. package/dist/cli/export.d.ts +11 -0
  10. package/dist/cli/generate-types.d.ts +6 -0
  11. package/dist/cli/import.d.ts +13 -0
  12. package/dist/cli/init-firebase.d.ts +7 -0
  13. package/dist/cli/utils.d.ts +27 -0
  14. package/dist/cli.js +10 -10
  15. package/dist/client.js +1 -1
  16. package/dist/core/ai-tools.d.ts +156 -0
  17. package/dist/core/ai.d.ts +330 -0
  18. package/dist/core/api.d.ts +21 -0
  19. package/dist/core/app.d.ts +16 -0
  20. package/dist/core/checks-translations.d.ts +13 -0
  21. package/dist/core/checks.d.ts +48 -0
  22. package/dist/core/client.d.ts +715 -0
  23. package/dist/core/core.d.ts +5 -0
  24. package/dist/core/cron.d.ts +19 -0
  25. package/dist/core/csv.d.ts +5 -0
  26. package/dist/core/functions.d.ts +9 -0
  27. package/dist/core/plugin.d.ts +357 -0
  28. package/dist/{project.d.ts → core/project.d.ts} +8 -12
  29. package/dist/core/richtext.d.ts +101 -0
  30. package/dist/{core.d.ts → core/route.d.ts} +13 -66
  31. package/dist/core/runtime.d.ts +46 -0
  32. package/dist/{schema-Db_xODoi.d.ts → core/schema.d.ts} +47 -100
  33. package/dist/core/search-extract.d.ts +70 -0
  34. package/dist/core/search-index.d.ts +182 -0
  35. package/dist/core/search-query.d.ts +68 -0
  36. package/dist/core/security.d.ts +6 -0
  37. package/dist/core/server-version.d.ts +5 -0
  38. package/dist/core/services-notifications.d.ts +57 -0
  39. package/dist/core/services.d.ts +44 -0
  40. package/dist/core/sse.d.ts +10 -0
  41. package/dist/core/translations-manager.d.ts +187 -0
  42. package/dist/core/translations.d.ts +83 -0
  43. package/dist/core/url-safety.d.ts +19 -0
  44. package/dist/core/validation.d.ts +30 -0
  45. package/dist/core/values.d.ts +9 -0
  46. package/dist/core/versions.d.ts +48 -0
  47. package/dist/core.js +1 -1
  48. package/dist/edit-M4PN4SGX.js +7 -0
  49. package/dist/functions.js +4 -4
  50. package/dist/{generate-types-SPV7I3A5.js → generate-types-JWIG4QVI.js} +1 -1
  51. package/dist/plugin.js +128 -133
  52. package/dist/shared/marshal.d.ts +68 -0
  53. package/dist/shared/objects.d.ts +9 -0
  54. package/dist/shared/richtext.d.ts +74 -0
  55. package/dist/shared/safe-json.d.ts +11 -0
  56. package/dist/shared/sanitize.d.ts +12 -0
  57. package/dist/shared/slug.d.ts +19 -0
  58. package/dist/shared/sse.d.ts +16 -0
  59. package/dist/shared/strings.d.ts +23 -0
  60. package/dist/ui/AIPage-YI3RFK5F.js +1 -0
  61. package/dist/ui/AssetsPage-4JNAOFYF.js +1 -0
  62. package/dist/ui/CollectionPage-JWHH5G55.js +1 -0
  63. package/dist/ui/ComparePage-YZE3EMU4.js +1 -0
  64. package/dist/ui/DataPage-VRCQSZZG.js +1 -0
  65. package/dist/ui/DataSourcePage-WMSPBZ3W.js +1 -0
  66. package/dist/ui/DocTranslationsPage-RY2LOZBX.js +1 -0
  67. package/dist/ui/DocumentPage-EEGTJMIW.js +1 -0
  68. package/dist/ui/EditDataSourcePage-LLCX6K4J.js +1 -0
  69. package/dist/ui/EditReleasePage-WQYXXMZQ.js +1 -0
  70. package/dist/ui/ImageEditorDialog-4H665KK2.js +81 -0
  71. package/dist/ui/ImageEditorDialog-4H665KK2.js.LEGAL.txt +17 -0
  72. package/dist/ui/LogsPage-IP2DKEYR.js +1 -0
  73. package/dist/ui/NewDataSourcePage-RDYJC4DH.js +1 -0
  74. package/dist/ui/NewReleasePage-DGMFU2JN.js +1 -0
  75. package/dist/ui/NotFoundPage-WDZ2K7EF.js +1 -0
  76. package/dist/ui/ProjectPage-HYJOQUWR.js +1 -0
  77. package/dist/ui/ReleasePage-2KSF23TK.js +1 -0
  78. package/dist/ui/ReleasesPage-NZIBBXU4.js +1 -0
  79. package/dist/ui/SettingsPage-36XFWV62.js +1 -0
  80. package/dist/ui/SidebarToolsPage-5C6YBWJ7.js +1 -0
  81. package/dist/ui/TaskPage-SPIFPGPP.js +1 -0
  82. package/dist/ui/TasksPage-ZOXRPBQV.js +1 -0
  83. package/dist/ui/TranslationsArbPage-UHANA7JD.js +2 -0
  84. package/dist/ui/TranslationsEditPage-PQY6W5YG.js +1 -0
  85. package/dist/ui/TranslationsPage-V2PQFCN5.js +162 -0
  86. package/dist/ui/ai-tools-5E5ELLZX.js +1 -0
  87. package/dist/ui/chunk-3IYZCUPI.js +1 -0
  88. package/dist/ui/chunk-4KE2SZKH.js +1 -0
  89. package/dist/ui/chunk-4W73RW5I.js +1 -0
  90. package/dist/ui/chunk-54DVDYU6.js +1 -0
  91. package/dist/ui/chunk-5MV6XVGA.js +1 -0
  92. package/dist/ui/chunk-62UWGWON.js +1 -0
  93. package/dist/ui/chunk-767BAQ53.js +1 -0
  94. package/dist/ui/chunk-AFWBSIPL.js +1 -0
  95. package/dist/ui/chunk-AL3TN2KI.js +1 -0
  96. package/dist/ui/chunk-BZIRGB4W.js +1 -0
  97. package/dist/ui/chunk-C5NH23G4.js +3 -0
  98. package/dist/ui/chunk-CGCKJFL4.js +1 -0
  99. package/dist/ui/chunk-DEGMKEHP.js +1 -0
  100. package/dist/ui/chunk-DTMZLUNY.js +15 -0
  101. package/dist/ui/chunk-DTMZLUNY.js.LEGAL.txt +237 -0
  102. package/dist/ui/chunk-DUJYIOLE.js +5 -0
  103. package/dist/ui/chunk-E2RCCK2T.js +1 -0
  104. package/dist/ui/chunk-EGEB6N4J.js +22 -0
  105. package/dist/ui/chunk-EGEB6N4J.js.LEGAL.txt +1059 -0
  106. package/dist/ui/chunk-FBJURT7Z.js +1 -0
  107. package/dist/ui/chunk-GXSAEPU3.js +1 -0
  108. package/dist/ui/chunk-HHMG665Z.js +1 -0
  109. package/dist/ui/chunk-HKINZLMW.js +84 -0
  110. package/dist/ui/chunk-HLMBCVRX.js +1 -0
  111. package/dist/ui/chunk-HRGYNHZ3.js +1 -0
  112. package/dist/ui/chunk-I6ICVKEC.js +1 -0
  113. package/dist/ui/chunk-JDW4UORS.js +1 -0
  114. package/dist/ui/chunk-JHBAMPGB.js +1 -0
  115. package/dist/ui/chunk-JT5L6GPZ.js +141 -0
  116. package/dist/ui/chunk-JT5L6GPZ.js.LEGAL.txt +106 -0
  117. package/dist/ui/chunk-JYFLU7DM.js +19 -0
  118. package/dist/ui/chunk-JZFTZ4X4.js +87 -0
  119. package/dist/ui/chunk-JZFTZ4X4.js.LEGAL.txt +9 -0
  120. package/dist/ui/chunk-KFAZBEPV.js +1 -0
  121. package/dist/ui/chunk-KKEWCXVV.js +1 -0
  122. package/dist/ui/chunk-KUCVLANF.js +1 -0
  123. package/dist/ui/chunk-KYDOSLTO.js +1 -0
  124. package/dist/ui/chunk-L4RHGQUG.js +1 -0
  125. package/dist/ui/chunk-MDUBCXU2.js +1 -0
  126. package/dist/ui/chunk-NXQVLOTK.js +1 -0
  127. package/dist/ui/chunk-NZHF733K.js +7 -0
  128. package/dist/ui/chunk-NZHF733K.js.LEGAL.txt +146 -0
  129. package/dist/ui/chunk-ONTGW2VA.js +1 -0
  130. package/dist/ui/chunk-ORXEMIQC.js +1 -0
  131. package/dist/ui/chunk-P3NETZZP.js +1 -0
  132. package/dist/ui/chunk-PHBMVORG.js +1 -0
  133. package/dist/ui/chunk-PJA7YP4A.js +1 -0
  134. package/dist/ui/chunk-PVAE643M.js +1 -0
  135. package/dist/ui/chunk-PWKJDYE2.js +1 -0
  136. package/dist/ui/chunk-RSVO46S3.js +1 -0
  137. package/dist/ui/chunk-SKEP5WYH.js +10 -0
  138. package/dist/ui/chunk-SKEP5WYH.js.LEGAL.txt +176 -0
  139. package/dist/ui/chunk-SQRKKWRG.js +3 -0
  140. package/dist/ui/chunk-TJBVMFNQ.js +1 -0
  141. package/dist/ui/chunk-V7S4IAGB.js +1 -0
  142. package/dist/ui/chunk-VMT7JFAM.js +1 -0
  143. package/dist/ui/chunk-W3BJ2EZX.js +1 -0
  144. package/dist/ui/chunk-W3HIZQ4Z.js +1 -0
  145. package/dist/ui/chunk-X2WHBGTP.js +1 -0
  146. package/dist/ui/chunk-XOQ3KDSR.js +3 -0
  147. package/dist/ui/chunk-YDO2EDVY.js +1 -0
  148. package/dist/ui/chunk-YFA6U2CV.js +1 -0
  149. package/dist/ui/gcs-L33FIRZU.js +1 -0
  150. package/dist/ui/prerender-Q4YSDCPB.js +1 -0
  151. package/dist/ui/signin.js +1 -1
  152. package/dist/ui/ui.css +1 -1
  153. package/dist/ui/ui.js +1 -630
  154. package/dist/ui/ui.js.LEGAL.txt +19 -732
  155. package/package.json +20 -20
  156. package/dist/client-1puwLgNx.d.ts +0 -1552
  157. package/dist/client.d.ts +0 -5
  158. package/dist/edit-MT4B37QK.js +0 -7
  159. package/dist/functions.d.ts +0 -13
  160. package/dist/plugin.d.ts +0 -5
  161. package/dist/richtext.d.ts +0 -104
@@ -0,0 +1,6 @@
1
+ export declare const FIRESTORE_RULES = "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if false;\n }\n\n match /Projects/{project} {\n allow write:\n if isSignedIn() && userIsAdmin();\n allow read:\n if isSignedIn() && userCanRead();\n\n match /{collection}/{document=**} {\n allow write:\n if isSignedIn() && userCanPublish();\n allow read:\n if isSignedIn() && userCanRead();\n }\n\n match /Collections/{collectionId}/Drafts/{document=**} {\n allow write:\n if isSignedIn() && userCanEdit();\n }\n\n function isSignedIn() {\n return request.auth != null;\n }\n\n function getRoles() {\n return get(/databases/$(database)/documents/Projects/$(project)).data.roles;\n }\n\n function userCanRead() {\n let roles = getRoles();\n let email = request.auth.token.email;\n let domain = '*@' + email.split('@')[1];\n return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR', 'VIEWER']);\n }\n\n function userCanPublish() {\n let roles = getRoles();\n let email = request.auth.token.email;\n let domain = '*@' + email.split('@')[1];\n return (roles[email] in ['ADMIN', 'EDITOR']) || (roles[domain] in ['ADMIN', 'EDITOR']);\n }\n\n function userCanEdit() {\n let roles = getRoles();\n let email = request.auth.token.email;\n let domain = '*@' + email.split('@')[1];\n return (roles[email] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']) || (roles[domain] in ['ADMIN', 'EDITOR', 'CONTRIBUTOR']);\n }\n\n function userIsAdmin() {\n let roles = getRoles();\n let email = request.auth.token.email;\n let domain = '*@' + email.split('@')[1];\n return (roles[email] == 'ADMIN') || (roles[domain] == 'ADMIN');\n }\n }\n }\n}\n";
2
+ /**
3
+ * Adds the root-cms security rules to a Firebase project.
4
+ * NOTE: This function will overwrite any existing rules.
5
+ */
6
+ export declare function applySecurityRules(projectId: string): Promise<void>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Returns a string that represents the server version. On local dev, the
3
+ * current timestamp is returned. On prod, the package.json version is returned.
4
+ */
5
+ export declare function getServerVersion(): string;
@@ -0,0 +1,57 @@
1
+ import type { Action } from './client.js';
2
+ import type { CMSService, CMSServiceContext } from './services.js';
3
+ /** Result returned by a notification service `onAction` handler. */
4
+ export interface NotificationResult {
5
+ /**
6
+ * Delivery status.
7
+ * - `'success'`: the notification was delivered.
8
+ * - `'error'`: delivery failed; `message` should describe why.
9
+ * - `'info'` (default): the service handled the action but did not
10
+ * deliver a notification (e.g. the action was filtered out).
11
+ */
12
+ status?: 'success' | 'info' | 'error';
13
+ /** Optional human-readable message describing the result. */
14
+ message?: string;
15
+ }
16
+ /** Context passed to notification service handler functions. */
17
+ export interface NotificationServiceContext extends CMSServiceContext {
18
+ }
19
+ /**
20
+ * Configuration for defining a CMS notification service.
21
+ *
22
+ * Notification services react to actions in the CMS (publishes, schema
23
+ * changes, comments, etc.) and dispatch them to an external channel.
24
+ * Initially this is intended for email, with Slack, webhooks, and other
25
+ * transports planned.
26
+ *
27
+ * Multiple notification services may be registered; each independently
28
+ * decides whether and how to handle a given action.
29
+ *
30
+ * Example:
31
+ * ```ts
32
+ * cmsPlugin({
33
+ * services: {
34
+ * notifications: [
35
+ * {
36
+ * id: 'sendgrid',
37
+ * label: 'SendGrid',
38
+ * onAction: async (ctx, action) => {
39
+ * if (action.action === 'doc.publish') {
40
+ * await sendgrid.send({ ... });
41
+ * }
42
+ * },
43
+ * },
44
+ * ],
45
+ * },
46
+ * });
47
+ * ```
48
+ */
49
+ export interface CMSNotificationService extends CMSService {
50
+ /**
51
+ * Async function called when an action occurs in the CMS. Receives the
52
+ * action and may dispatch a notification (e.g. send email, post to Slack)
53
+ * via the service's underlying transport. Can optionally return a
54
+ * `NotificationResult` describing delivery status.
55
+ */
56
+ onAction?: (ctx: NotificationServiceContext, action: Action) => Promise<void | NotificationResult>;
57
+ }
@@ -0,0 +1,44 @@
1
+ import type { RootConfig } from '@blinkk/root';
2
+ import type { RootCMSClient } from './client.js';
3
+ /**
4
+ * Base shape for a service registered with the CMS plugin.
5
+ *
6
+ * Services are extension points that let plugins provide capabilities
7
+ * (e.g. email delivery, cache, translations) that root-cms can call into.
8
+ * Every service shares the same identifying fields — `id`, `label`, optional
9
+ * `icon` — and is registered via the `services` option on `cmsPlugin()`.
10
+ *
11
+ * Concrete service interfaces (e.g. `CMSEmailService`,
12
+ * `CMSTranslationService`) extend this base and add the handler functions
13
+ * specific to their capability.
14
+ */
15
+ export interface CMSService {
16
+ /** Unique ID for the service (e.g. `'sendgrid'`, `'crowdin'`). */
17
+ id: string;
18
+ /** Human-readable label displayed in the UI (e.g. "SendGrid"). */
19
+ label: string;
20
+ /**
21
+ * Optional icon URL displayed in the UI next to the label. Similar to the
22
+ * sidebar tools `icon` option, this should be a URL to an image.
23
+ */
24
+ icon?: string;
25
+ }
26
+ /**
27
+ * Base context passed to service handler functions. Concrete services may
28
+ * extend this with additional fields specific to the call site (e.g. the
29
+ * translation context adds `docId`, `collectionId`, etc.).
30
+ */
31
+ export interface CMSServiceContext {
32
+ /** The Root.js config. */
33
+ rootConfig: RootConfig;
34
+ /** The Root CMS client for accessing the database. */
35
+ cmsClient: RootCMSClient;
36
+ /**
37
+ * Email of the user that triggered the action, if any. Some service calls
38
+ * are initiated by the system rather than a user, in which case this is
39
+ * undefined.
40
+ */
41
+ user?: {
42
+ email: string;
43
+ };
44
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Library for handling server-sent events.
3
+ * https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
4
+ */
5
+ import { Server, Response } from '@blinkk/root';
6
+ export type SSEBroadcastFn = (event: string, data?: any) => void;
7
+ export type SSEClient = Response;
8
+ export declare function sse(server: Server): {
9
+ sseBroadcast: SSEBroadcastFn;
10
+ };
@@ -0,0 +1,187 @@
1
+ import { WriteBatch } from 'firebase-admin/firestore';
2
+ import type { RootCMSClient } from './client.js';
3
+ export type Locale = string;
4
+ export type SourceString = string;
5
+ export type TranslatedString = string;
6
+ export type TranslationsDocMode = 'draft' | 'published';
7
+ export interface TranslationsLocaleDocHashMap {
8
+ /**
9
+ * A hash map of a source string's hash fingerprint to the source string and
10
+ * translated string.
11
+ */
12
+ [hash: string]: TranslationsLocaleDocEntry;
13
+ }
14
+ export interface TranslationsLocaleDocEntry {
15
+ source: SourceString;
16
+ translation: TranslatedString;
17
+ }
18
+ interface TranslationsDbPathOptions {
19
+ project: string;
20
+ mode: TranslationsDocMode;
21
+ }
22
+ type TranslationsLocaleDocDbPathOptions = TranslationsDbPathOptions & {
23
+ id: string;
24
+ locale: string;
25
+ };
26
+ /**
27
+ * A translations map containing translations for multiple locales.
28
+ *
29
+ * Example:
30
+ * ```
31
+ * {
32
+ * "one": {"es": "uno", "fr": "un"},
33
+ * "two": {"es": "dos", "fr": "deux"}
34
+ * }
35
+ * ```
36
+ */
37
+ export interface MultiLocaleTranslationsMap {
38
+ [source: SourceString]: {
39
+ [locale: Locale]: TranslatedString;
40
+ };
41
+ }
42
+ /**
43
+ * A translations map containing translations for a single locale.
44
+ *
45
+ * Example:
46
+ * ```
47
+ * {
48
+ * "one": "uno",
49
+ * "two": "dos"
50
+ * }
51
+ * ```
52
+ */
53
+ export interface SingleLocaleTranslationsMap {
54
+ [source: SourceString]: TranslatedString;
55
+ }
56
+ export declare class TranslationsManager {
57
+ cmsClient: RootCMSClient;
58
+ constructor(cmsClient: RootCMSClient);
59
+ /**
60
+ * Saves draft translations for a translations doc id.
61
+ *
62
+ * Example:
63
+ * ```
64
+ * const strings = {
65
+ * 'one': {es: 'uno', fr: 'un'},
66
+ * 'two': {es: 'dos', fr: 'deux'},
67
+ * };
68
+ * await tm.saveTranslations('Pages/index', strings);
69
+ * ```
70
+ */
71
+ saveTranslations(id: string, strings: MultiLocaleTranslationsMap, options?: {
72
+ tags?: string[];
73
+ modifiedBy?: string;
74
+ }): Promise<void>;
75
+ /**
76
+ * Publishes a translations doc.
77
+ */
78
+ publishTranslations(id: string, options?: {
79
+ batch?: WriteBatch;
80
+ publishedBy?: string;
81
+ }): Promise<void>;
82
+ /**
83
+ * Fetches translations from one or more translations docs in the translations
84
+ * manager.
85
+ *
86
+ * Example:
87
+ * ```
88
+ * await tm.loadTranslations();
89
+ * // =>
90
+ * // {
91
+ * // "one": {"es": "uno", "fr": "un"},
92
+ * // "two": {"es": "dos", "fr": "deux"}
93
+ * // }
94
+ * ```
95
+ *
96
+ * To load a specific set of translations docs by id:
97
+ * ```
98
+ * const translationsToLoad = ['Global/strings', 'Global/header', 'Global/footer', 'Pages/index'];
99
+ * await tm.loadTranslations({ids: translationsToLoad});
100
+ * // =>
101
+ * // {
102
+ * // "one": {"es": "uno", "fr": "un"},
103
+ * // "two": {"es": "dos", "fr": "deux"}
104
+ * // }
105
+ * ```
106
+ *
107
+ * To load a subset of locales (more performant):
108
+ * ```
109
+ * await tm.loadTranslations({locales: ['es']});
110
+ * // =>
111
+ * // {
112
+ * // "one": {"es": "uno"},
113
+ * // "two": {"es": "dos"}
114
+ * // }
115
+ * ```
116
+ */
117
+ loadTranslations(options?: {
118
+ ids?: string[];
119
+ tags?: string[];
120
+ locales?: Locale[];
121
+ mode?: TranslationsDocMode;
122
+ }): Promise<MultiLocaleTranslationsMap>;
123
+ /**
124
+ * Fetches translations for a given locale, with optional fallbacks.
125
+ * The return value is a map of source string to translated string.
126
+ *
127
+ * Example:
128
+ * ```
129
+ * await translationsDoc.loadTranslationsForLocale('es');
130
+ * // =>
131
+ * // {
132
+ * // "one": "uno",
133
+ * // "two": "dos",
134
+ * // }
135
+ * ```
136
+ */
137
+ loadTranslationsForLocale(locale: Locale, options?: {
138
+ mode?: TranslationsDocMode;
139
+ fallbackLocales?: Locale[];
140
+ }): Promise<SingleLocaleTranslationsMap>;
141
+ /**
142
+ * Converts a multi-locale translations map to a flat single-locale map,
143
+ * with optional support for fallback locales.
144
+ *
145
+ * ```
146
+ * const multiLocaleStrings = {
147
+ * 'one': {es: 'uno', fr: 'un'},
148
+ * 'two': {es: 'dos', fr: 'deux'}
149
+ * };
150
+ * translationsDoc.toSingleLocaleMap(multiLocaleStrings, ['es']);
151
+ * // =>
152
+ * // {
153
+ * // "one": "uno",
154
+ * // "two": "dos",
155
+ * // }
156
+ * ```
157
+ */
158
+ private toSingleLocaleMap;
159
+ /**
160
+ * Converts a multi-locale translations map to a single-locale hashed version,
161
+ * used for storage in in the DB.
162
+ *
163
+ * ```
164
+ * const multiLocaleStrings = {
165
+ * 'one': {es: 'uno', fr: 'un'},
166
+ * 'two': {es: 'dos', fr: 'deux'}
167
+ * };
168
+ * translationsDoc.toLocaleDocHashMap(multiLocaleStrings, 'es');
169
+ * // =>
170
+ * // {
171
+ * // "<hash1>": {"source": "one", "translation": "uno"},
172
+ * // "<hash2>": {"source": "two", "translation": "dos"},
173
+ * // }
174
+ * ```
175
+ *
176
+ * One reason for using hashes is because the DB has limits on the number of
177
+ * chars that can be used as the "key" in a object map.
178
+ */
179
+ private toLocaleDocHashMap;
180
+ /**
181
+ * Import translations from the v1 system to the TranslationsManager.
182
+ */
183
+ importTranslationsFromV1(): Promise<void>;
184
+ }
185
+ export declare function buildTranslationsDbPath(options: TranslationsDbPathOptions): string;
186
+ export declare function buildTranslationsLocaleDocDbPath(options: TranslationsLocaleDocDbPathOptions): string;
187
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { CMSService, CMSServiceContext } from './services.js';
2
+ /** A row of translation data keyed by locale. */
3
+ export interface TranslationRow {
4
+ /** The source string. */
5
+ source: string;
6
+ /** Map of locale code to translated string. */
7
+ translations: Record<string, string>;
8
+ /** Optional translator notes/context for the source string. */
9
+ description?: string;
10
+ }
11
+ /** Context passed to translation service import/export functions. */
12
+ export interface TranslationServiceContext extends CMSServiceContext {
13
+ /** The document ID, e.g. `Pages/index`. */
14
+ docId: string;
15
+ /** The collection ID, e.g. `Pages`. */
16
+ collectionId: string;
17
+ /** The slug, e.g. `index`. */
18
+ slug: string;
19
+ /** The locales configured for the project. */
20
+ locales: string[];
21
+ /** The email of the user performing the action. */
22
+ user: {
23
+ email: string;
24
+ };
25
+ }
26
+ /** Result returned by an onExport handler. */
27
+ export interface TranslationExportResult {
28
+ /** Optional title displayed in the notification after export. */
29
+ title?: string;
30
+ /** Optional message displayed in the notification after export. */
31
+ message?: string;
32
+ /** Optional link displayed in the notification (e.g. to the translation service). */
33
+ link?: {
34
+ /** The URL to link to. */
35
+ url: string;
36
+ /** Optional label for the link. Defaults to "Open". */
37
+ label?: string;
38
+ };
39
+ /**
40
+ * Notification status controlling the color of the notification.
41
+ * - `'success'`: green
42
+ * - `'error'`: red
43
+ * - `'info'` (default): neutral
44
+ */
45
+ status?: 'success' | 'info' | 'error';
46
+ }
47
+ /** Result returned by an onImport handler when no rows are imported. */
48
+ export interface TranslationImportResult {
49
+ /** Optional title displayed in the notification after import. */
50
+ title?: string;
51
+ /** Optional message displayed in the notification after import. */
52
+ message?: string;
53
+ /** Optional link displayed in the notification (e.g. to the translation service). */
54
+ link?: {
55
+ /** The URL to link to. */
56
+ url: string;
57
+ /** Optional label for the link. Defaults to "Open". */
58
+ label?: string;
59
+ };
60
+ /**
61
+ * Notification status controlling the color of the notification.
62
+ * - `'success'`: green
63
+ * - `'error'`: red
64
+ * - `'info'` (default): neutral
65
+ */
66
+ status?: 'success' | 'info' | 'error';
67
+ }
68
+ /** Configuration for defining a CMS translation service. */
69
+ export interface CMSTranslationService extends CMSService {
70
+ /**
71
+ * Async function to import translations from the service. Should return an
72
+ * array of translation rows that will be merged into the CMS translations
73
+ * database, or a `TranslationImportResult` object to display a notification
74
+ * without importing any rows.
75
+ */
76
+ onImport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<TranslationRow[] | TranslationImportResult>;
77
+ /**
78
+ * Async function to export translations to the service. Receives the
79
+ * current translation rows for the document. Can optionally return an object
80
+ * with a `message` to display in the success notification.
81
+ */
82
+ onExport?: (ctx: TranslationServiceContext, data: TranslationRow[]) => Promise<void | TranslationExportResult>;
83
+ }
@@ -0,0 +1,19 @@
1
+ export interface AssertUrlOptions {
2
+ /** Allowed URL schemes. Defaults to `['https:']`. */
3
+ allowedSchemes?: string[];
4
+ }
5
+ export declare class UnsafeUrlError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ /**
9
+ * Throws `UnsafeUrlError` if `input` is a URL that could be used to reach
10
+ * an internal/cloud-metadata endpoint via SSRF. Validation covers the
11
+ * scheme, the literal hostname, and the resolved DNS addresses.
12
+ *
13
+ * Caveats: this does NOT defend against time-of-check / time-of-use races
14
+ * (a hostname that resolves to a public IP at check time and a private IP
15
+ * milliseconds later), nor against redirects followed by the downstream
16
+ * HTTP client. Callers that follow redirects must re-validate each hop, or
17
+ * use an HTTP client configured to refuse private IPs at the socket layer.
18
+ */
19
+ export declare function assertPublicHttpUrl(input: string, options?: AssertUrlOptions): Promise<URL>;
@@ -0,0 +1,30 @@
1
+ import type { FieldWithId, Schema } from './schema.js';
2
+ /**
3
+ * Represents a validation error for a field.
4
+ */
5
+ export interface ValidationError {
6
+ /** The path to the field, e.g., "meta.title" or "content.blocks[0].text". */
7
+ path: string;
8
+ /** Human-readable error message. */
9
+ message: string;
10
+ /** Expected type or value. */
11
+ expected?: string;
12
+ /** Actual value received. */
13
+ received?: any;
14
+ }
15
+ /**
16
+ * Validates field data against a schema.
17
+ *
18
+ * This function validates `fieldsData` against the provided `schema`. It
19
+ * supports both partial validation (validates only fields present in the data)
20
+ * and full document validation.
21
+ *
22
+ * @param fieldsData - The data to validate.
23
+ * @param schema - The schema defining the expected structure.
24
+ * @returns An array of validation errors. Empty array if validation passes.
25
+ */
26
+ export declare function validateFields(fieldsData: any, schema: Schema): ValidationError[];
27
+ /**
28
+ * Validates a single value against a field definition.
29
+ */
30
+ export declare function validateValue(value: any, field: FieldWithId, path: string): ValidationError[];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Sets a value at a JSON path in an object.
3
+ * Supports dot notation for nested objects and array indices.
4
+ * Examples:
5
+ * setValueAtPath(obj, 'title', 'New Title')
6
+ * setValueAtPath(obj, 'hero.title', 'New Title')
7
+ * setValueAtPath(obj, 'content.0.text', 'First item')
8
+ */
9
+ export declare function setValueAtPath(obj: any, path: string, value: any): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Document verion history.
3
+ *
4
+ * Verions save the state of a document at different moments in time, allowing
5
+ * users to revert back to a version if/when needed.
6
+ *
7
+ * The saveVersions() function is meant to be run within a cron job (e.g using
8
+ * firebase functions). Every minute, the runner checks for all documents that
9
+ * have been edited since the last run. To avoid saving "partial" versions
10
+ * (when the user might be in the middle of editing a document), versions only
11
+ * run on documents that haven't been edited within the last 5 mins to ensure
12
+ * that version is "stable".
13
+ *
14
+ * In the database, versions are stored as:
15
+ * /Projects/<projectId>/Collections/<collectionId>/Drafts/<docId>/Versions/<timestamp>
16
+ *
17
+ * Where <timestamp> is the unix timestamp (millis) that the document was last
18
+ * edited.
19
+ */
20
+ import { RootConfig } from '@blinkk/root';
21
+ export declare class VersionsService {
22
+ private readonly rootConfig;
23
+ private readonly projectId;
24
+ private readonly db;
25
+ constructor(rootConfig: RootConfig);
26
+ /**
27
+ * Saves a version of all documents that have been edited since the last run.
28
+ */
29
+ saveVersions(): Promise<void>;
30
+ private saveVersionsToFirestore;
31
+ /**
32
+ * Returns the last time (in millis) saveVersions() was run, or 0 if has
33
+ * never been run.
34
+ */
35
+ private getLastRun;
36
+ /**
37
+ * Saves {versionLastRun: <timestamp>} to the Projects/<projectId> doc.
38
+ */
39
+ private saveLastRun;
40
+ /**
41
+ * Returns a list of all docs that were edited after a certain time.
42
+ */
43
+ private getDocsModifiedAfter;
44
+ /**
45
+ * Returns a list of collection ids for the Root project.
46
+ */
47
+ private listCollections;
48
+ }
package/dist/core.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  translationsForLocale,
17
17
  unmarshalArray,
18
18
  unmarshalData
19
- } from "./chunk-2BSW7SIH.js";
19
+ } from "./chunk-N4K6ICFR.js";
20
20
  import {
21
21
  __export
22
22
  } from "./chunk-MLKGABMK.js";
@@ -0,0 +1,7 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // shared/ai/prompts/edit.txt
4
+ var edit_default = 'Role: You are a highly accurate AI assistant specializing in converting unstructured documents into JSON files compatible with Root CMS. Root CMS is a web-based CMS for building websites and landing pages, storing its content as JSON files in a Firebase Firestore database.\n\nObjective: To edit and/or generate highly accurate JSON files compatible with Root CMS, based on user input (screenshots, chat messages, etc.) and, when editing JSON files, the original JSON file you are editing. The resulting content should contain all of the marketing content provided by the user input (and none of the annotations or superfluous content). The JSON files should be perfectly compatible with Root CMS.\n\nInput Requirements & Handling:\n\n- You will receive unstructured documents and you must convert them into JSON files compatible with Root CMS by either editing the existing JSON files or generating new ones from scratch. The JSON files you produce will typically represent a single section of a page of the website, such as a hero section, a feature section, or a footer.\n\n- When editing an existing JSON file, it will be annotated with a key `_type` that indicates the type of section it is, such as `Hero`, `Feature`, or `Footer`. You must ensure that the resulting JSON file is compatible with Root CMS and contains all of the marketing content provided by the user input. You will also be given a TypeScript interface in `root-cms.d.ts` that describes the structure of the JSON file you are editing. You must ensure that the resulting JSON file is compatible with this interface.\n\n- The input you receive will be broken into three parts. (1) `root-cms-d.ts` (the TypeScript interface), (2) the existing JSON file you are editing (or an empty JSON file when generating new content), and (3) the user input (screenshots, chat messages, instructions, etc.). You must ensure that the resulting JSON file is compatible with the structure defined by the TypeScript interface and contains all of the marketing content provided by the user input.\n\n- When you provide your response, if the user instructs you to only edit one part of the original JSON, make sure to leave the rest of the JSON file untouched. You must return the entire JSON document after applying changes. Never omit, rename, reorder or invent top-level keys. Copy all unmodified fields exactly as they appear in the input.\n\nRoot CMS JSON File Structure:\n\n1. Root CMS JSON files are standard JSON files described by the TypeScript interface in `root-cms.d.ts`. However, there is one critical syntax rule that you must understand and always follow regarding **Array fields**. An Array field is a list of repeated items \u2014 for example, a list of modules on a page, or a list of items within a module. The items in an Array field are always objects (e.g. `[{"foo": 1}, {"foo": 2}]`); an Array field never contains primitive values like `[1, 2, 3]` or `["a", "b"]`. Array fields are NOT stored as plain JSON arrays; instead they are expressed as hash maps. The order of the resulting data when Root CMS unmarshals the data is provided by a special key called `_array`. For example, to represent an Array field whose items are `[{"foo": 1}, {"foo": 2}]`, the JSON data may look like this. For each key, autogenerate a random 6-letter string (e.g. `kxmfqa`); never use human-readable names or numbers as keys.\n\n```json\n{\n "_array": ["kxmfqa", "wnzrbh"],\n "kxmfqa": {"foo": 1},\n "wnzrbh": {"foo": 2}\n}\n```\n\nWhen creating an Array field in your JSON output, you must _never_ forget to add an `_array` key. If you forget to add an `_array` key to an Array field, the output will NOT work with Root CMS. When adding new array items, autogenerate a new random 6-letter key for each item.\n\nThe `_array` object notation applies ONLY to Array fields. Every OTHER array-shaped value is a plain JSON array and MUST NEVER be wrapped in an `_array` object. In particular:\n\n(a) Multiselect fields (typed as `string[]` in `root-cms.d.ts`) hold a list of selected option values and are stored as a plain JSON array of strings. For example, a multiselect value looks like `["option-a", "option-b"]`, NOT `{"_array": ["k1", "k2"], "k1": "option-a", "k2": "option-b"}`.\n\n(b) Reference fields that hold multiple references store a plain JSON array of reference objects, e.g. `[{"id": "Pages/home", "collection": "Pages", "slug": "home"}]`, never an `_array` object.\n\n(c) The `blocks` array inside a Rich Text field (see #3 below). Rich Text data is stored as-is and is NOT marshaled, so `blocks` MUST always be a plain JSON array, never an `_array` object.\n\nWrapping any of these in `_array` notation will corrupt the data when it is saved. Rule of thumb: never change the existing shape of an array in the JSON you are editing \u2014 if a value is already a plain JSON array, keep it a plain array; if it is already an `_array` object, keep it as an `_array` object.\n\n2. Images and file fields have system-provided metadata, that looks like this example:\n\n```\n{\n "src": "https://storage.googleapis.com/rootjs-dev.appspot.com/www/uploads/2cad5d65bb7690bad5dcd4a041cf2fef46361678.svg",\n "filename": "600x400.svg",\n "uploadedAt": "1754023634564",\n "alt": "",\n "width": "600",\n "uploadedBy": "user@example.com",\n "height": "400"\n}\n```\n\n3. Rich Text fields are stored as Editor.js / Lexical documents, which look like this. You may generate your own UIDs for the `id` fields. Note that `blocks` MUST be a plain JSON array \u2014 do NOT use the `_array` object notation here.\n\n```\n{\n "version": "2.28.2",\n "time": 1754024041637,\n "blocks": [\n {\n "type": "paragraph",\n "data": {\n "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. <a href=\\"https://example.com\\">This accepts HTML</a>."\n },\n "id": "_8PEf3kBx2"\n },\n {\n "data": {\n "style": "unordered",\n "items": [\n {\n "content": "This is list item 1.",\n "items": []\n },\n {\n "items": [],\n "content": "This is list item 2."\n }\n ]\n },\n "id": "G5zMDP1ytM",\n "type": "unorderedList"\n }\n ]\n}\n```\n\n4. Root CMS supports "oneOf" fields, where values can be "one of" another schema. These are typically used as array values or object values, where the user can from one of other schema types to structure the data value. For example, each module in a page\'s module list is "one of" the modules available in the library. One of values have a system-provided `_type` key that describes the type of the value. For example, this is a hero module, whose type is "TemplateHeadline", one of the available templates from the module library:\n\n```\n{\n "eyebrow": "Lorem ipsum",\n "_type": "TemplateHeadline",\n "body": {\n "time": 1754023554170,\n "version": "2.28.2",\n "blocks": [\n {\n "type": "paragraph",\n "data": {\n "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."\n },\n "id": "IVmxEVsxmt"\n },\n {\n "id": "aMiHiywkQD",\n "type": "paragraph",\n "data": {\n "text": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."\n }\n }\n ]\n },\n "title": "Welcome to our website"\n}\n```\n\nWhen adding new items to arrays that use `oneOf` fields, try your best to figure out which content type would best satisfy the user\'s request. You must never _invent_ new content types, for example, if "TemplateFoo" doesn\'t exist in `root-cms.d.ts`, you must never add a value with `{"_type": "TemplateFoo"}`. If none of the available content types within `root-cms.d.ts` satisfy the user\'s request, avoid creating the new array item and inform the user that there is no existing template compatible.\n\nAdditionally, when adding new items to existing JSON data, you must carefully check the structure of those items as defined by `root-cms.d.ts`. Avoid inventing new fields for content types, use only those defined by the `root-cms.d.ts`. For example, if you are adding a button to a module, carefully check the button structure defined in `root-cms.d.ts` and only populate fields that are defined there.\n';
5
+ export {
6
+ edit_default as default
7
+ };
package/dist/functions.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  runCronJobs
3
- } from "./chunk-BGTUWIV6.js";
4
- import "./chunk-C245C557.js";
5
- import "./chunk-2BSW7SIH.js";
3
+ } from "./chunk-PNLLZXVY.js";
4
+ import "./chunk-RXWDB3K4.js";
5
+ import "./chunk-N4K6ICFR.js";
6
6
  import "./chunk-MLKGABMK.js";
7
7
 
8
8
  // core/functions.ts
9
- import path from "path";
9
+ import path from "node:path";
10
10
  import { loadBundledConfig } from "@blinkk/root/node";
11
11
  import { onSchedule } from "firebase-functions/v2/scheduler";
12
12
  function cron(options) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateSchemaDts,
3
3
  generateTypes
4
- } from "./chunk-XLX37FRL.js";
4
+ } from "./chunk-TEKMLYXS.js";
5
5
  import "./chunk-MLKGABMK.js";
6
6
  export {
7
7
  generateSchemaDts,