@griddo/cx 11.9.7 → 11.9.8-rc.1

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 (222) hide show
  1. package/README.md +78 -193
  2. package/build/commands/end-render.js +31 -0
  3. package/build/commands/end-render.js.map +7 -0
  4. package/build/commands/prepare-assets-directory.js +9 -0
  5. package/build/commands/prepare-assets-directory.js.map +7 -0
  6. package/build/commands/prepare-domains-render.js +38 -0
  7. package/build/commands/prepare-domains-render.js.map +7 -0
  8. package/build/commands/reset-render.js +31 -0
  9. package/build/commands/reset-render.js.map +7 -0
  10. package/build/commands/start-embeddings.js +31 -0
  11. package/build/commands/start-embeddings.js.map +7 -0
  12. package/build/commands/start-render.js +66 -0
  13. package/build/commands/start-render.js.map +7 -0
  14. package/build/commands/upload-search-content.js +31 -0
  15. package/build/commands/upload-search-content.js.map +7 -0
  16. package/build/core/GriddoLog.d.ts +16 -0
  17. package/build/core/db.d.ts +4 -0
  18. package/build/core/dist-rollback.d.ts +2 -0
  19. package/build/{errors/index.d.ts → core/errors.d.ts} +5 -4
  20. package/build/core/fs.d.ts +69 -0
  21. package/build/core/logger.d.ts +18 -0
  22. package/build/index.d.ts +10 -29
  23. package/build/index.js +406 -73
  24. package/build/services/auth.d.ts +2 -5
  25. package/build/services/manage-store.d.ts +48 -0
  26. package/build/services/render.d.ts +70 -0
  27. package/build/shared/envs.d.ts +19 -0
  28. package/build/{errors/errors-data.d.ts → shared/errors.d.ts} +5 -3
  29. package/build/shared/npm-modules/brush.d.ts +18 -0
  30. package/build/shared/npm-modules/find-up-simple.d.ts +34 -0
  31. package/build/shared/npm-modules/pkg-dir.d.ts +7 -0
  32. package/build/{types → shared/types}/api.d.ts +18 -18
  33. package/build/{types → shared/types}/global.d.ts +15 -16
  34. package/build/{types → shared/types}/navigation.d.ts +5 -5
  35. package/build/{types → shared/types}/pages.d.ts +9 -9
  36. package/build/shared/types/render.d.ts +54 -0
  37. package/build/{types → shared/types}/sites.d.ts +18 -19
  38. package/cli.mjs +239 -0
  39. package/exporter/build-esbuild.noop +42 -0
  40. package/exporter/build.sh +17 -28
  41. package/exporter/commands/README.md +151 -0
  42. package/exporter/commands/end-render.ts +65 -86
  43. package/exporter/commands/prepare-assets-directory.ts +34 -0
  44. package/exporter/commands/prepare-domains-render.ts +143 -35
  45. package/exporter/commands/reset-render.ts +12 -7
  46. package/exporter/commands/single-domain-upload-search-content.noop +206 -0
  47. package/exporter/commands/start-embeddings.ts +29 -0
  48. package/exporter/commands/start-render.ts +26 -64
  49. package/exporter/commands/upload-search-content.ts +201 -26
  50. package/exporter/core/GriddoLog.ts +45 -0
  51. package/exporter/core/check-env-health.ts +200 -0
  52. package/exporter/core/db-class.ts +54 -0
  53. package/exporter/core/db.ts +33 -0
  54. package/exporter/core/dist-rollback.ts +40 -0
  55. package/exporter/core/errors.ts +82 -0
  56. package/exporter/core/fs.ts +385 -0
  57. package/exporter/{utils → core}/images.ts +1 -6
  58. package/exporter/{utils → core}/instance.ts +9 -13
  59. package/exporter/core/life-cycle.ts +73 -0
  60. package/exporter/core/logger.ts +141 -0
  61. package/exporter/core/objects.ts +37 -0
  62. package/exporter/core/print-logos.ts +21 -0
  63. package/exporter/index.ts +14 -56
  64. package/exporter/services/api.ts +306 -0
  65. package/exporter/services/auth.ts +8 -10
  66. package/exporter/services/domains.ts +23 -8
  67. package/exporter/services/manage-sites.ts +116 -0
  68. package/exporter/services/manage-store.ts +235 -0
  69. package/exporter/services/navigation.ts +12 -18
  70. package/exporter/{utils → services}/pages.ts +27 -92
  71. package/exporter/services/reference-fields.ts +14 -32
  72. package/exporter/services/render-artifacts.ts +44 -0
  73. package/exporter/services/render.ts +229 -0
  74. package/exporter/services/robots.ts +33 -61
  75. package/exporter/services/sitemaps.ts +129 -0
  76. package/exporter/services/sites.ts +40 -28
  77. package/exporter/services/store.ts +386 -319
  78. package/exporter/shared/context.ts +49 -0
  79. package/exporter/{constants → shared}/endpoints.ts +12 -11
  80. package/exporter/shared/envs.ts +62 -0
  81. package/exporter/{errors/errors-data.ts → shared/errors.ts} +24 -14
  82. package/exporter/shared/npm-modules/README.md +36 -0
  83. package/exporter/shared/npm-modules/brush.ts +34 -0
  84. package/exporter/shared/npm-modules/find-up-simple.ts +100 -0
  85. package/exporter/shared/npm-modules/pkg-dir.ts +17 -0
  86. package/exporter/shared/npm-modules/xml-parser.ts +57 -0
  87. package/exporter/{types → shared/types}/api.ts +40 -41
  88. package/exporter/{types → shared/types}/global.ts +17 -21
  89. package/exporter/{types → shared/types}/navigation.ts +3 -3
  90. package/exporter/{types → shared/types}/pages.ts +10 -11
  91. package/exporter/shared/types/render.ts +63 -0
  92. package/exporter/{types → shared/types}/sites.ts +18 -19
  93. package/exporter/ssg-adapters/gatsby/actions/clean.ts +26 -0
  94. package/exporter/ssg-adapters/gatsby/actions/close.ts +17 -0
  95. package/exporter/ssg-adapters/gatsby/actions/data.ts +22 -0
  96. package/exporter/ssg-adapters/gatsby/actions/healthCheck.ts +10 -0
  97. package/exporter/ssg-adapters/gatsby/actions/init.ts +12 -0
  98. package/exporter/ssg-adapters/gatsby/actions/logs.ts +10 -0
  99. package/exporter/ssg-adapters/gatsby/actions/meta.ts +13 -0
  100. package/exporter/ssg-adapters/gatsby/actions/prepare.ts +9 -0
  101. package/exporter/ssg-adapters/gatsby/actions/relocation.ts +15 -0
  102. package/exporter/ssg-adapters/gatsby/actions/restore.ts +21 -0
  103. package/exporter/ssg-adapters/gatsby/actions/ssg.ts +12 -0
  104. package/exporter/ssg-adapters/gatsby/actions/sync.ts +65 -0
  105. package/exporter/ssg-adapters/gatsby/index.ts +114 -0
  106. package/exporter/ssg-adapters/gatsby/shared/artifacts.ts +16 -0
  107. package/exporter/ssg-adapters/gatsby/shared/diff-assets.ts +128 -0
  108. package/exporter/ssg-adapters/gatsby/shared/extract-assets.ts +75 -0
  109. package/exporter/ssg-adapters/gatsby/shared/gatsby-build.ts +58 -0
  110. package/exporter/ssg-adapters/gatsby/shared/sync-render.ts +300 -0
  111. package/exporter/ssg-adapters/gatsby/shared/types.ts +35 -0
  112. package/exporter/ssg-adapters/gatsby/shared/utils.ts +33 -0
  113. package/gatsby-browser.tsx +41 -58
  114. package/gatsby-config.ts +10 -17
  115. package/gatsby-node.ts +20 -80
  116. package/gatsby-ssr.tsx +2 -1
  117. package/package.json +41 -92
  118. package/plugins/gatsby-plugin-svgr-loader/gatsby-node.js +55 -0
  119. package/plugins/gatsby-plugin-svgr-loader/package.json +8 -0
  120. package/react/DynamicScript/index.tsx +33 -0
  121. package/{exporter/react/Favicon → react/GriddoFavicon}/index.tsx +3 -9
  122. package/{exporter/react → react}/GriddoIntegrations/index.tsx +17 -23
  123. package/{exporter/react → react}/GriddoIntegrations/utils.ts +24 -12
  124. package/react/GriddoOpenGraph/index.tsx +39 -0
  125. package/src/components/Head.tsx +30 -73
  126. package/src/components/template.tsx +8 -30
  127. package/src/gatsby-node-utils.ts +76 -2
  128. package/src/html.tsx +2 -11
  129. package/src/types.ts +5 -5
  130. package/tsconfig.commands.json +36 -0
  131. package/tsconfig.exporter.json +20 -0
  132. package/tsconfig.json +5 -3
  133. package/build/adapters/gatsby/index.d.ts +0 -4
  134. package/build/adapters/gatsby/utils.d.ts +0 -22
  135. package/build/artifacts/index.d.ts +0 -6
  136. package/build/commands/end-render.d.ts +0 -2
  137. package/build/commands/move-assets.d.ts +0 -1
  138. package/build/commands/prepare-domains-render.d.ts +0 -1
  139. package/build/commands/reset-render.d.ts +0 -2
  140. package/build/commands/start-render.d.ts +0 -2
  141. package/build/commands/upload-search-content.d.ts +0 -2
  142. package/build/constants/envs.d.ts +0 -37
  143. package/build/constants/index.d.ts +0 -57
  144. package/build/end-render.js +0 -74
  145. package/build/end-render.js.map +0 -7
  146. package/build/index.js.map +0 -7
  147. package/build/prepare-domains-render.js +0 -73
  148. package/build/prepare-domains-render.js.map +0 -7
  149. package/build/react/Favicon/index.d.ts +0 -5
  150. package/build/react/Favicon/utils.d.ts +0 -9
  151. package/build/react/GriddoIntegrations/index.d.ts +0 -20
  152. package/build/react/GriddoIntegrations/utils.d.ts +0 -26
  153. package/build/react/index.d.ts +0 -3
  154. package/build/react/index.js +0 -3
  155. package/build/registers/api.d.ts +0 -9
  156. package/build/registers/gatsby.d.ts +0 -9
  157. package/build/registers/index.d.ts +0 -3
  158. package/build/reset-render.js +0 -74
  159. package/build/reset-render.js.map +0 -7
  160. package/build/services/domains.d.ts +0 -6
  161. package/build/services/navigation.d.ts +0 -50
  162. package/build/services/reference-fields.d.ts +0 -20
  163. package/build/services/register.d.ts +0 -36
  164. package/build/services/robots.d.ts +0 -19
  165. package/build/services/settings.d.ts +0 -4
  166. package/build/services/sites.d.ts +0 -29
  167. package/build/services/store.d.ts +0 -6
  168. package/build/start-render.js +0 -100
  169. package/build/start-render.js.map +0 -7
  170. package/build/types/templates.d.ts +0 -8
  171. package/build/upload-search-content.js +0 -74
  172. package/build/upload-search-content.js.map +0 -7
  173. package/build/utils/alerts.d.ts +0 -3
  174. package/build/utils/api.d.ts +0 -23
  175. package/build/utils/cache.d.ts +0 -35
  176. package/build/utils/core-utils.d.ts +0 -107
  177. package/build/utils/create-build-data.d.ts +0 -8
  178. package/build/utils/domains.d.ts +0 -13
  179. package/build/utils/folders.d.ts +0 -53
  180. package/build/utils/health-checks.d.ts +0 -7
  181. package/build/utils/images.d.ts +0 -16
  182. package/build/utils/loggin.d.ts +0 -51
  183. package/build/utils/pages.d.ts +0 -34
  184. package/build/utils/render.d.ts +0 -13
  185. package/build/utils/searches.d.ts +0 -15
  186. package/build/utils/sites.d.ts +0 -31
  187. package/build/utils/store.d.ts +0 -81
  188. package/cx.config.d.ts +0 -5
  189. package/cx.config.js +0 -36
  190. package/exporter/adapters/gatsby/index.ts +0 -162
  191. package/exporter/adapters/gatsby/utils.ts +0 -161
  192. package/exporter/artifacts/README.md +0 -34
  193. package/exporter/artifacts/index.ts +0 -33
  194. package/exporter/commands/move-assets.ts +0 -11
  195. package/exporter/constants/envs.ts +0 -94
  196. package/exporter/constants/index.ts +0 -129
  197. package/exporter/errors/index.ts +0 -40
  198. package/exporter/react/index.tsx +0 -11
  199. package/exporter/registers/api.ts +0 -14
  200. package/exporter/registers/gatsby.ts +0 -14
  201. package/exporter/registers/index.ts +0 -4
  202. package/exporter/services/register.ts +0 -113
  203. package/exporter/services/settings.ts +0 -17
  204. package/exporter/utils/alerts.ts +0 -29
  205. package/exporter/utils/api.ts +0 -243
  206. package/exporter/utils/cache.ts +0 -142
  207. package/exporter/utils/core-utils.ts +0 -458
  208. package/exporter/utils/create-build-data.ts +0 -17
  209. package/exporter/utils/domains.ts +0 -39
  210. package/exporter/utils/folders.ts +0 -320
  211. package/exporter/utils/health-checks.ts +0 -64
  212. package/exporter/utils/loggin.ts +0 -184
  213. package/exporter/utils/render.ts +0 -71
  214. package/exporter/utils/searches.ts +0 -156
  215. package/exporter/utils/sites.ts +0 -312
  216. package/exporter/utils/store.ts +0 -314
  217. package/src/README.md +0 -7
  218. package/start-render.js +0 -7
  219. /package/build/{utils → core}/instance.d.ts +0 -0
  220. /package/build/{constants → shared}/endpoints.d.ts +0 -0
  221. /package/exporter/{types → shared/types}/templates.ts +0 -0
  222. /package/{exporter/react/Favicon → react/GriddoFavicon}/utils.ts +0 -0
@@ -1,39 +1,214 @@
1
- #!/usr/bin/env node
1
+ import type { PostSearchInfoResponse } from "../shared/types/api";
2
+ import type { AIEmbeddingsResponse, PostSearchInfoProps } from "../shared/types/global";
3
+ import type { GatsbyPageData } from "../ssg-adapters/gatsby/shared/types";
2
4
 
3
- import fs from "node:fs";
5
+ import fsp from "node:fs/promises";
6
+ import path from "node:path";
4
7
 
5
- import { envs } from "../constants";
6
- import { getConfig } from "../utils/core-utils";
7
- import { getInstanceDomains } from "../utils/domains";
8
- import {
9
- startAIEmbeddings,
10
- uploadRenderedSearchContentToAPI,
11
- } from "../utils/searches";
8
+ import { throwError, withErrorHandler } from "../core/errors";
9
+ import { pathExists } from "../core/fs";
10
+ import { GriddoLog } from "../core/GriddoLog";
11
+ import { post } from "../services/api";
12
+ import { AuthService } from "../services/auth";
13
+ import { getInstanceDomains } from "../services/domains";
14
+ import { getRenderPathsHydratedWithDomainFromDB } from "../services/render";
15
+ import { AI_EMBEDDINGS, SEARCH } from "../shared/endpoints";
16
+ import { GRIDDO_AI_EMBEDDINGS, GRIDDO_SEARCH_FEATURE } from "../shared/envs";
17
+ import { ReadFromStoreError, UploadSearchError } from "../shared/errors";
18
+
19
+ /**
20
+ * Save in the BBDD the content of a page parsed without HTML tags.
21
+ *
22
+ * @param props Object with parts of the final page object to be saved in the BBDD.
23
+ */
24
+ async function postSearchInfo(props: PostSearchInfoProps) {
25
+ const { title, description, image, pageId, languageId, siteId, url, content, template } = props;
26
+
27
+ const response = await post<PostSearchInfoResponse>({
28
+ endpoint: SEARCH,
29
+ body: {
30
+ title,
31
+ description,
32
+ image,
33
+ pageId,
34
+ languageId,
35
+ siteId,
36
+ url,
37
+ template,
38
+ content,
39
+ },
40
+ useApiCacheDir: false,
41
+ logToFile: false,
42
+ });
43
+
44
+ return response;
45
+ }
46
+
47
+ function prepareHTMLContentForSearch(content: string): string {
48
+ // 1. Remove script, style, and other unwanted block tags and their content.
49
+ // The regex looks for <tag...>...</tag> where tag is one of the specified ones.
50
+ const tagsToRemove = ["meta", "link", "style", "script", "noscript", "nav", "header", "footer"];
51
+ const removeTagsRegex = new RegExp(`<(${tagsToRemove.join("|")})\\b[^>]*>.*?<\\/\\1>`, "gis");
52
+ let processedContent = content.replace(removeTagsRegex, "");
53
+
54
+ // 2. Strip all remaining HTML tags.
55
+ processedContent = processedContent.replace(/<[^>]+>/g, " ");
56
+
57
+ // 3. Normalize whitespace: replace multiple spaces/newlines with a single space and trim.
58
+ processedContent = processedContent.replace(/\s+/g, " ").trim();
59
+
60
+ return processedContent;
61
+ }
62
+
63
+ /**
64
+ * Function that search in the `/public` dir the content info of the pages and
65
+ * send it to the search table in the ddbb using the API.
66
+ * @todo Utilizar la carpeta page-data en lugar de la carpeta store puesto que
67
+ * esta ya no es persistente.
68
+ */
69
+ async function uploadRenderedSearchContentToAPI(options: {
70
+ htmlContentDir: string;
71
+ jsonContentDir: string;
72
+ }) {
73
+ const { htmlContentDir, jsonContentDir } = options;
74
+
75
+ if (!(await pathExists(jsonContentDir)) || !(await pathExists(htmlContentDir))) {
76
+ GriddoLog.info(
77
+ `Skipping uploading content to the search endpoint because it has not exported sites.`,
78
+ );
12
79
 
13
- async function main() {
14
- if (!envs.GRIDDO_SEARCH_FEATURE) {
15
80
  return;
16
81
  }
17
82
 
18
- const domains = await getInstanceDomains();
19
- const config = getConfig();
20
- for (const domain of domains) {
21
- const { __exports_dist } = config.paths(domain);
83
+ // Get pages from gatsby page-data dir
84
+ const gatsbyPageDataPages = getPageDataPagesFromExports(jsonContentDir);
85
+
86
+ for await (const pageData of gatsbyPageDataPages) {
87
+ const { result } = pageData;
88
+ const { pageContext } = result;
89
+ const { page, openGraph, pageMetadata } = pageContext;
90
+
91
+ const { compose } = page.fullPath;
92
+
93
+ const htmlPath = path.resolve(`${htmlContentDir}/${compose}/index.html`);
94
+ const htmlContent = await fsp.readFile(htmlPath, "utf-8");
22
95
 
23
- // If __exports_dist has not exported sites (directories), it does not
24
- // upload search content.
25
- if (fs.existsSync(__exports_dist)) {
26
- await uploadRenderedSearchContentToAPI(__exports_dist, domain);
96
+ const pageObject: PostSearchInfoProps = {
97
+ siteId: page.site,
98
+ pageId: page.id,
99
+ // `pageMetadata.title` has already a fallback `metatitle ||
100
+ // pageTitle` so probably `title` never will be take the
101
+ // `openGraph?.title` value. Only when the `metatitle` and
102
+ // `pageTitle` are empty.
103
+ title: pageMetadata?.title || openGraph?.title,
104
+ languageId: page.language,
105
+ url: page.fullUrl,
106
+ template: page.template.templateType || page.templateId,
107
+ description: pageMetadata?.description || openGraph?.description,
108
+ image: openGraph.image,
109
+ // _content: prepareHTMLContentForSearch(htmlContent),
110
+ content: prepareHTMLContentForSearch(htmlContent),
111
+ };
112
+
113
+ try {
114
+ await postSearchInfo(pageObject);
115
+ } catch (error) {
116
+ throwError(UploadSearchError, error);
27
117
  }
28
118
  }
119
+ }
120
+
121
+ /**
122
+ * Walk recursively in a basePath and return an array of pages with json
123
+ * extension with the full absolute path and the path includes "page-data".
124
+ *
125
+ * @param basePath The path to walk recursively.
126
+ * @returns An array of pages with json extension with the full absolute path
127
+ * and the path includes "page-data".
128
+ */
129
+ async function* walkRecursively(basePath: string): AsyncGenerator<string> {
130
+ const filesHandle = await fsp.opendir(basePath);
29
131
 
30
- // Solo una vez en cada render de todos los dominios...
31
- if (envs.GRIDDO_AI_EMBEDDINGS) {
32
- await startAIEmbeddings();
132
+ for await (const fileDirent of filesHandle) {
133
+ if (fileDirent.isDirectory()) {
134
+ yield* walkRecursively(path.join(basePath, fileDirent.name));
135
+ } else if (
136
+ fileDirent.isFile() &&
137
+ path.extname(fileDirent.name) === ".json" &&
138
+ fileDirent.name.includes("page-data")
139
+ ) {
140
+ yield path.join(basePath, fileDirent.name);
141
+ }
33
142
  }
34
143
  }
35
144
 
36
- main().catch((err) => {
37
- console.error("Error", err?.stdout?.toString() || err);
38
- process.exit(1);
39
- });
145
+ /**
146
+ * Walk recursively in a basePath and return an array of pages with json
147
+ * extension with the full absolute path and the path includes "page-data".
148
+ *
149
+ * @param basePath The path to walk recursively.
150
+ * @returns An array of pages with json extension with the full absolute path
151
+ * and the path includes "page-data".
152
+ */
153
+ async function* getPageDataPagesFromExports<PageType extends GatsbyPageData>(
154
+ basePath: string,
155
+ ): AsyncGenerator<PageType> {
156
+ const jsonFiles = walkRecursively(basePath);
157
+
158
+ for await (const filePath of jsonFiles) {
159
+ try {
160
+ const fileContent = await fsp.readFile(filePath, "utf8");
161
+ const page = JSON.parse(fileContent) as PageType;
162
+
163
+ if (page.path) {
164
+ yield page;
165
+ }
166
+ } catch (error) {
167
+ throwError(ReadFromStoreError, error);
168
+ }
169
+ }
170
+ }
171
+
172
+ async function getContentDirectories(domain: string) {
173
+ const { __exports } = await getRenderPathsHydratedWithDomainFromDB({ domain });
174
+
175
+ return {
176
+ htmlContentDir: path.join(__exports, "dist"),
177
+ jsonContentDir: path.join(__exports, "dist", "page-data"),
178
+ };
179
+ }
180
+
181
+ async function uploadSearchContent() {
182
+ if (GRIDDO_SEARCH_FEATURE) {
183
+ const domains = await getInstanceDomains();
184
+ for (const domain of domains) {
185
+ const { htmlContentDir, jsonContentDir } = await getContentDirectories(domain);
186
+
187
+ GriddoLog.info(
188
+ `Uploading search content for ${domain}: processing content for ${htmlContentDir.length} pages...`,
189
+ );
190
+ await uploadRenderedSearchContentToAPI({
191
+ htmlContentDir,
192
+ jsonContentDir,
193
+ });
194
+ }
195
+ }
196
+ }
197
+
198
+ async function aiEmbeddings() {
199
+ if (GRIDDO_SEARCH_FEATURE && GRIDDO_AI_EMBEDDINGS) {
200
+ GriddoLog.info(`Triggering AI embeddings...`);
201
+ await post<AIEmbeddingsResponse>({
202
+ endpoint: AI_EMBEDDINGS,
203
+ useApiCacheDir: false,
204
+ });
205
+ }
206
+ }
207
+
208
+ async function main() {
209
+ await AuthService.login();
210
+ await uploadSearchContent();
211
+ await aiEmbeddings();
212
+ }
213
+
214
+ withErrorHandler(main);
@@ -0,0 +1,45 @@
1
+ import { GRIDDO_BUILD_LOGS, GRIDDO_VERBOSE_LOGS } from "../shared/envs";
2
+ import { brush } from "../shared/npm-modules/brush";
3
+
4
+ /**
5
+ * Clase estática para gestionar los logs de la aplicación.
6
+ * No se puede instanciar, se usa directamente: GriddoLogs.info("mensaje").
7
+ */
8
+ class GriddoLog {
9
+ /** El constructor es privado para prevenir la instanciación de la clase. */
10
+ private constructor() {}
11
+
12
+ public static verbose(...str: unknown[]): void {
13
+ if (GRIDDO_VERBOSE_LOGS) {
14
+ console.log(brush.yellow("verbose"), brush.dim(str.join(" ")));
15
+ }
16
+ }
17
+
18
+ public static build(...str: unknown[]): void {
19
+ if (GRIDDO_BUILD_LOGS) {
20
+ GriddoLog.log(...str);
21
+ }
22
+ }
23
+
24
+ public static info(...str: unknown[]): void {
25
+ console.log(`${brush.blue("info")} ${str.join(" ")}`);
26
+ }
27
+
28
+ public static success(...str: unknown[]): void {
29
+ console.log(`${brush.green("success")} ${str.join(" ")}`);
30
+ }
31
+
32
+ public static error(...str: unknown[]): void {
33
+ console.error(`${brush.red("error")} ${str.join(" ")}`);
34
+ }
35
+
36
+ public static warn(...str: unknown[]): void {
37
+ console.warn(`${brush.yellow("warn")} ${str.join(" ")}`);
38
+ }
39
+
40
+ public static log(...args: Parameters<typeof console.log>): void {
41
+ console.log(...args);
42
+ }
43
+ }
44
+
45
+ export { GriddoLog };
@@ -0,0 +1,200 @@
1
+ import { GRIDDO_SKIP_BUILD_CHECKS } from "../shared/envs";
2
+ import { CheckHealthError } from "../shared/errors";
3
+ import { throwError } from "./errors";
4
+ import { GriddoLog } from "./GriddoLog";
5
+
6
+ /**
7
+ * Environment variables that must be present (but value doesn't matter)
8
+ */
9
+ const REQUIRED_ENV_VARS: readonly string[] = [];
10
+
11
+ /**
12
+ * Environment variables that must have specific values
13
+ */
14
+ const REQUIRED_ENV_VALUES: readonly {
15
+ key: string;
16
+ expected: string;
17
+ description?: string;
18
+ }[] = [
19
+ {
20
+ key: "GRIDDO_RENDER_BY_DOMAINS",
21
+ expected: "true",
22
+ description: "Must be set to 'true' for domain-based rendering",
23
+ },
24
+ ];
25
+
26
+ /**
27
+ * Environment variables that are recommended but not required
28
+ * These will show warnings if missing but won't fail the health check
29
+ */
30
+ const RECOMMENDED_ENV_VARS: readonly { key: string; description?: string }[] = [
31
+ {
32
+ key: "GRIDDO_BUILD_LOGS",
33
+ description: "Recommended for detailed render logs",
34
+ },
35
+ {
36
+ key: "GRIDDO_BUILD_LOGS_TO_FILE",
37
+ description: "Recommended for performance gain and simplify render logs",
38
+ },
39
+ {
40
+ key: "GRIDDO_API_CONCURRENCY_COUNT",
41
+ description: "Recommended for better performance and memory management",
42
+ },
43
+ ];
44
+
45
+ /**
46
+ * Environment variables that must be present (but value doesn't matter)
47
+ */
48
+ const DEPRECATED_ENV_VARS: readonly { key: string; description?: string }[] = [
49
+ {
50
+ key: "GRIDDO_RENDER_SITE",
51
+ description:
52
+ "This environment variable no longer has any effect because it is incompatible with incremental rendering.",
53
+ },
54
+ {
55
+ key: "GRIDDO_RENDER_PAGES",
56
+ description:
57
+ "This environment variable no longer has any effect because it is incompatible with incremental rendering.",
58
+ },
59
+ {
60
+ key: "REACT_APP_API_ENDPOINT",
61
+ description: "Use GRIDDO_API_URL",
62
+ },
63
+ {
64
+ key: "REACT_APP_PUBLIC_API_ENDPOINT",
65
+ description: "Use GRIDDO_PUBLIC_API_URL",
66
+ },
67
+ {
68
+ key: "GRIDDO_RENDER_ALL_SITES",
69
+ description: "This environment variable is deprecated, remove it",
70
+ },
71
+ ];
72
+
73
+ /**
74
+ * Check if required environment variables exist with any value.
75
+ */
76
+ function checkRequiredEnvVars(): { isValid: boolean; missing: string[] } {
77
+ const missing = REQUIRED_ENV_VARS.filter((envName) => !process.env[envName]);
78
+
79
+ if (missing.length > 0) {
80
+ GriddoLog.error(`Missing required environment variables: ${missing.join(", ")}`);
81
+ return { isValid: false, missing };
82
+ }
83
+
84
+ return { isValid: true, missing: [] };
85
+ }
86
+
87
+ /**
88
+ * Check if environment variables have the expected values.
89
+ */
90
+ function checkEnvVarValues(): {
91
+ isValid: boolean;
92
+ mismatches: { key: string; expected: string; actual: string }[];
93
+ } {
94
+ const mismatches: { key: string; expected: string; actual: string }[] = [];
95
+
96
+ for (const { key, expected } of REQUIRED_ENV_VALUES) {
97
+ const actual = process.env[key];
98
+
99
+ if (actual !== expected) {
100
+ mismatches.push({ key, expected, actual: actual || "(undefined)" });
101
+ }
102
+ }
103
+
104
+ if (mismatches.length > 0) {
105
+ return { isValid: false, mismatches };
106
+ }
107
+
108
+ return { isValid: true, mismatches: [] };
109
+ }
110
+
111
+ /**
112
+ * Check recommended environment variables and show warnings for missing ones.
113
+ */
114
+ function checkRecommendedEnvVars() {
115
+ const missing = RECOMMENDED_ENV_VARS.filter(({ key }) => !process.env[key]);
116
+
117
+ if (missing.length > 0) {
118
+ GriddoLog.warn("Recommended environment variables not set:");
119
+
120
+ for (const { key, description } of missing) {
121
+ const desc = description ? ` # ${description}` : "";
122
+ GriddoLog.log(` -> ${key}${desc}`);
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check if set enviroment variables have been deprecated
129
+ */
130
+ function checkDeprecatedEnvVars() {
131
+ const deprecated = DEPRECATED_ENV_VARS.filter(({ key }) => process.env[key]);
132
+
133
+ if (deprecated.length > 0) {
134
+ const deprecatedMessages = deprecated.map(({ key, description }) => {
135
+ const desc = description ? ` # ${description}` : "";
136
+ return ` ${key}${desc}`;
137
+ });
138
+
139
+ GriddoLog.warn("Deprecated environment variables set:");
140
+ GriddoLog.warn(`${deprecatedMessages.join("\n")}\n`);
141
+ }
142
+
143
+ return { deprecated: deprecated.map((key) => key) };
144
+ }
145
+
146
+ /**
147
+ * Check if the environment is secure to launch a render.
148
+ * If something fails then log an error message and exit from the process.
149
+ * Otherwise just return true.
150
+ */
151
+ export function checkEnvironmentHealth() {
152
+ // Bypass the check using this env var, handy for local renders.
153
+ if (GRIDDO_SKIP_BUILD_CHECKS) {
154
+ GriddoLog.info("Build health check skipped");
155
+ return true;
156
+ }
157
+
158
+ // Check required environment variables
159
+ const envVarsCheck = checkRequiredEnvVars();
160
+ const envValuesCheck = checkEnvVarValues();
161
+
162
+ // (warnings only)
163
+ checkRecommendedEnvVars();
164
+ checkDeprecatedEnvVars();
165
+
166
+ // Render is safe if all required checks pass
167
+ const isSafeToRender = envVarsCheck.isValid && envValuesCheck.isValid;
168
+
169
+ if (isSafeToRender) {
170
+ GriddoLog.info("Build health check passed");
171
+ return true;
172
+ }
173
+
174
+ // Generate consolidated error message
175
+ const allIssues: string[] = [];
176
+
177
+ if (!envVarsCheck.isValid) {
178
+ allIssues.push(`Missing: ${envVarsCheck.missing.join(", ")}`);
179
+ }
180
+
181
+ if (!envValuesCheck.isValid) {
182
+ const mismatchMessages = envValuesCheck.mismatches.map(
183
+ ({ key, expected, actual }) => `${key}="${actual || "undefined"}" (expected "${expected}")`,
184
+ );
185
+ allIssues.push(`Incorrect values:\n ${mismatchMessages.join("\n ")}`);
186
+ }
187
+
188
+ // Show what needs to be set
189
+ const requiredVarsMessage = REQUIRED_ENV_VALUES.map(({ key, expected, description }) => {
190
+ const desc = description ? ` # ${description}` : "";
191
+ return ` ${key}="${expected}"${desc}`;
192
+ }).join("\n");
193
+
194
+ GriddoLog.error(`Environment health check failed:`);
195
+ GriddoLog.error(` ${allIssues.join("\n ")}`);
196
+ GriddoLog.warn("\nRequired environment variables:");
197
+ GriddoLog.error(requiredVarsMessage);
198
+
199
+ throwError(CheckHealthError);
200
+ }
@@ -0,0 +1,54 @@
1
+ import type { RenderDB } from "../shared/types/render";
2
+
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ import { pkgDirSync } from "../shared/npm-modules/pkg-dir";
7
+ import { GriddoLog } from "./GriddoLog";
8
+
9
+ // Interfaz para abstraer la DB
10
+ export interface Database {
11
+ read(): Promise<RenderDB>;
12
+ write(renderDB: RenderDB): Promise<void>;
13
+ }
14
+
15
+ export class JsonDatabase implements Database {
16
+ private readonly dbFilePath: string;
17
+
18
+ constructor(customDBPath?: string) {
19
+ this.dbFilePath =
20
+ customDBPath ??
21
+ path.join(
22
+ pkgDirSync({ cwd: path.resolve(__dirname, "../../..") }) ?? "",
23
+ ".griddo/cache/db.json",
24
+ );
25
+ }
26
+
27
+ async read(customDBFilePath?: string): Promise<RenderDB> {
28
+ try {
29
+ const raw = await fsp.readFile(customDBFilePath || this.dbFilePath, "utf-8");
30
+ return JSON.parse(raw) as RenderDB;
31
+ } catch (error) {
32
+ if (error instanceof Error) {
33
+ GriddoLog.error(`Error reading DB file at ${this.dbFilePath}:`, error.message);
34
+ } else {
35
+ GriddoLog.error(`Unknown error reading DB file at ${this.dbFilePath}:`, error);
36
+ }
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ async write(renderDB: RenderDB): Promise<void> {
42
+ try {
43
+ await fsp.mkdir(path.dirname(this.dbFilePath), { recursive: true });
44
+ await fsp.writeFile(this.dbFilePath, JSON.stringify(renderDB, null, "\t"));
45
+ } catch (error) {
46
+ if (error instanceof Error) {
47
+ GriddoLog.error(`Error writing to DB file at ${this.dbFilePath}:`, error.message);
48
+ } else {
49
+ GriddoLog.error(`Unknown error writing to DB file at ${this.dbFilePath}:`, error);
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,33 @@
1
+ import type { RenderDB } from "../shared/types/render";
2
+
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ import { pkgDirSync } from "../shared/npm-modules/pkg-dir";
7
+ import { GriddoLog } from "./GriddoLog";
8
+
9
+ const root = pkgDirSync({ cwd: path.resolve(__dirname, "../../..") }) || "";
10
+ const cache = path.join(root, ".griddo/cache");
11
+ const dbFilePath = path.join(cache, "db.json");
12
+
13
+ async function readDB(customDBPath = "") {
14
+ const file = customDBPath || dbFilePath;
15
+ try {
16
+ return JSON.parse(await fsp.readFile(file, "utf-8")) as RenderDB;
17
+ } catch (error) {
18
+ GriddoLog.error(`Failed to read DB file at ${file}:`, error);
19
+ throw error;
20
+ }
21
+ }
22
+
23
+ async function writeDB(renderDB: RenderDB, customDBPath = "") {
24
+ const file = customDBPath || dbFilePath;
25
+ try {
26
+ await fsp.writeFile(file, JSON.stringify(renderDB, null, "\t"));
27
+ } catch (error) {
28
+ GriddoLog.error(`Failed to write DB file at ${file}:`, error);
29
+ throw error;
30
+ }
31
+ }
32
+
33
+ export { readDB, writeDB };
@@ -0,0 +1,40 @@
1
+ import fsp from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { readDB } from "./db";
5
+ import { pathExists } from "./fs";
6
+ import { GriddoLog } from "./GriddoLog";
7
+
8
+ async function distRollback(domain: string) {
9
+ const data = await readDB();
10
+ const { exportsDir, exportsDirBackup } = data.paths;
11
+
12
+ GriddoLog.info(`Cleaning exports dir for the domain ${domain}`);
13
+ GriddoLog.verbose(`Deleting ${path.join(exportsDir, domain)}...`);
14
+
15
+ // Probar rsync en lugar de borrar y copiar
16
+
17
+ // 1 - Borrar dist corrupto
18
+ await fsp.rm(path.join(exportsDir, domain), {
19
+ recursive: true,
20
+ force: true,
21
+ });
22
+
23
+ // 2 - Si hay backup, restaurar
24
+ if (await pathExists(path.join(exportsDirBackup, domain))) {
25
+ await fsp.cp(path.join(exportsDirBackup, domain), path.join(exportsDir, domain), {
26
+ recursive: true,
27
+ });
28
+
29
+ GriddoLog.info(`export-backup dir for the domain ${domain} found. Restoring before exit...`);
30
+ GriddoLog.verbose(
31
+ `Copying ${path.join(exportsDirBackup, domain)} -> ${path.join(exportsDir, domain)}...`,
32
+ );
33
+ } else {
34
+ GriddoLog.info(
35
+ "No export-backup found, skipping rollback. Next render will create a new exports dir from scratch...",
36
+ );
37
+ }
38
+ }
39
+
40
+ export { distRollback };
@@ -0,0 +1,82 @@
1
+ import type { ErrorsType } from "../shared/errors";
2
+
3
+ import path from "node:path";
4
+
5
+ import { getRenderPathsHydratedWithDomainFromDB } from "../services/render";
6
+ import { brush } from "../shared/npm-modules/brush";
7
+ import { readDB, writeDB } from "./db";
8
+ import { distRollback } from "./dist-rollback";
9
+ import { GriddoLog } from "./GriddoLog";
10
+
11
+ export type ErrorData = {
12
+ error: ErrorsType;
13
+ message: string;
14
+ expected?: string;
15
+ hint?: string;
16
+ };
17
+
18
+ export class RenderError extends Error {
19
+ constructor(originalError?: unknown) {
20
+ super(originalError instanceof Error ? originalError.message : String(originalError));
21
+
22
+ this.name = "InternalCXError";
23
+ this.stack = originalError instanceof Error ? originalError.stack : "";
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Throws an error with the provided error message, expected value, and hint.
29
+ */
30
+ function throwError(options: ErrorData, stack?: unknown): never {
31
+ const { error, message, expected, hint } = options;
32
+
33
+ const errorColor = GriddoLog.log(brush.red(`[ ${error} ]`));
34
+ const extraText = [expected, hint].filter(Boolean).join("\n");
35
+
36
+ GriddoLog.log(`
37
+ ${errorColor}
38
+ ${message}
39
+ ${extraText}
40
+
41
+ ${brush.red("stack")}
42
+ ${JSON.stringify(stack, null, 2)}`);
43
+
44
+ throw new RenderError(stack);
45
+ }
46
+
47
+ async function withErrorHandler<T>(fn: () => Promise<T>) {
48
+ try {
49
+ await fn();
50
+ } catch (error) {
51
+ if (error instanceof RenderError) {
52
+ GriddoLog.error("Internal Griddo RenderError");
53
+ } else if (error instanceof Error) {
54
+ GriddoLog.error(error.message);
55
+ } else {
56
+ GriddoLog.error(`An unexpected error occurred ${error}`);
57
+ }
58
+
59
+ try {
60
+ const { __root } = await getRenderPathsHydratedWithDomainFromDB();
61
+ const data = await readDB();
62
+ if (data.needsRollbackOnError) {
63
+ GriddoLog.info("Cleaning exports dir...");
64
+ GriddoLog.verbose(`Deleting ${path.join(__root, "exports")}...`);
65
+
66
+ await distRollback(data.currentRenderingDomain!);
67
+ } else {
68
+ GriddoLog.info("No rollback needed, skipping...");
69
+ }
70
+ } catch (_e) {
71
+ GriddoLog.info("Early render stage, no db.json created yet...");
72
+ }
73
+
74
+ const data = await readDB();
75
+ data.domains[data.currentRenderingDomain!].isRendering = false;
76
+ data.domains[data.currentRenderingDomain!].renderMode = "ERROR";
77
+ await writeDB(data);
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ export { throwError, withErrorHandler };