@griddo/cx 10.3.26 → 10.4.0

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.
@@ -2,7 +2,6 @@ import type { APIResponses } from "../types/api";
2
2
  import type { APIPageObject } from "../types/pages";
3
3
  import type { Site } from "../types/sites";
4
4
 
5
- import { Adapters } from "@exporter/adapters";
6
5
  import chalk from "chalk";
7
6
  import dotenv from "dotenv";
8
7
  import fs from "fs-extra";
@@ -10,14 +9,17 @@ import gradient from "gradient-string";
10
9
  import pkgDir from "pkg-dir";
11
10
 
12
11
  import { version } from "../../package.json";
12
+ import { Adapters } from "../adapters";
13
13
 
14
14
  dotenv.config();
15
15
 
16
+ const GRIDDO_DEBUG_LOGS =
17
+ !!process.env.GRIDDO_DEBUG_LOGS &&
18
+ !!JSON.parse(process.env.GRIDDO_DEBUG_LOGS);
16
19
  const GRIDDO_BUILD_LOGS =
17
20
  (!!process.env.GRIDDO_BUILD_LOGS &&
18
21
  !!JSON.parse(process.env.GRIDDO_BUILD_LOGS)) ||
19
22
  (!!process.env.LOGS && !!JSON.parse(process.env.LOGS));
20
-
21
23
  const CXRootFolder = pkgDir.sync(__dirname)!; // usually monorepo/packages/griddo-cx/
22
24
  const instanceRootFolder = pkgDir.sync()!; // instace root folder
23
25
 
@@ -40,17 +42,42 @@ function walk(dir: string) {
40
42
  /**
41
43
  * Custom log inside a line-box.
42
44
  *
43
- * @param str The string to be logged.
45
+ * @param stringValue The string to be logged.
46
+ * @param paddingInline The number of white spaces inside the box at left and right.
47
+ * @param paddingBlock The number of white spaces inside the box at top and bottom.
44
48
  */
45
- function logBox(str: string) {
46
- const len = str.length;
47
- const minWidth = str.length + 2;
48
- const margin = " ".repeat(Math.floor((minWidth - len) / 2));
49
- const borderTop = `╭${"─".repeat(minWidth)}╮\n`;
50
- const borderBottom = `\n╰${"─".repeat(minWidth)}╯`;
51
- const content = `│${margin}${str}${margin}│`;
52
-
53
- console.log(`${borderTop}${content}${borderBottom}`);
49
+ function logBox(
50
+ stringValue: string,
51
+ title = "",
52
+ paddingInline = 1,
53
+ paddingBlock = 1
54
+ ) {
55
+ const lines = stringValue
56
+ .split("\n") // lines
57
+ .map((line) => line.trim()); // remove extra spaces
58
+ const windowTitle = title ? ` ${title} ` : "";
59
+ const windowTitleLength = title ? windowTitle.length : 0;
60
+ const longerContent =
61
+ Math.max(...lines.map((l) => l.length)) + paddingInline * 2;
62
+ const longerLine = Math.max(longerContent, windowTitleLength);
63
+ const paddingBlockString = `│${" ".repeat(longerLine)}│\n`.repeat(
64
+ paddingBlock
65
+ );
66
+ const minWidth = longerLine;
67
+ const borderTop = `╭${windowTitle}${"─".repeat(
68
+ minWidth - windowTitleLength
69
+ )}╮\n`;
70
+ const borderBottom = `╰${"─".repeat(minWidth)}╯`;
71
+ const content = lines
72
+ .map((l) => {
73
+ const mr = " ".repeat(longerLine - l.length - paddingInline);
74
+ return `│${" ".repeat(paddingInline)}${l}${mr}│\n`;
75
+ })
76
+ .join("");
77
+
78
+ console.log(
79
+ `${borderTop}${paddingBlockString}${content}${paddingBlockString}${borderBottom}`
80
+ );
54
81
  }
55
82
 
56
83
  /**
@@ -65,6 +92,17 @@ function logInfo(str: string) {
65
92
  }
66
93
  }
67
94
 
95
+ /**
96
+ * Internal log
97
+ * @param values The values to be logged.
98
+ */
99
+ function debug(...values: Array<unknown>) {
100
+ if (!GRIDDO_DEBUG_LOGS) {
101
+ return;
102
+ }
103
+ console.log(...values);
104
+ }
105
+
68
106
  /**
69
107
  * Custom delay using the "promise hack",
70
108
  *
@@ -239,33 +277,69 @@ function sanitizeAPICacheFolder(folderPath: string) {
239
277
  console.log(`Sanitize apiCache folder for ${counter} files`);
240
278
  }
241
279
 
280
+ /**
281
+ * Measures the execution time of a series of sync or async functions.
282
+ * @param functions - Functions to be executed to measure their execution time.
283
+ * @returns A promise that resolves with the total execution time in seconds.
284
+ */
242
285
  async function measureExecutionTime(
243
- ...fns: Array<(...args: Array<unknown>) => unknown | Promise<any>>
244
- ) {
286
+ ...functions: Array<(...args: Array<unknown>) => unknown | Promise<any>>
287
+ ): Promise<number> {
245
288
  const start = process.hrtime();
246
289
 
247
- for (const f of fns) {
248
- await f();
290
+ for (const func of functions) {
291
+ await func();
249
292
  }
250
293
 
251
- const [s, ms] = process.hrtime(start);
252
- const timeInSeconds = s + ms / 1e9;
294
+ const [seconds, miliseconds] = process.hrtime(start);
295
+ const timeInSeconds = seconds + miliseconds / 1e9;
253
296
 
254
297
  return +timeInSeconds.toFixed(3);
255
298
  }
256
299
 
300
+ function pause(title: string) {
301
+ const isPauseEnabled = !!JSON.parse(
302
+ process.env.GRIDDO_RENDER_BREAKPOINTS_FEATURE || "false"
303
+ );
304
+
305
+ if (!isPauseEnabled) {
306
+ return;
307
+ }
308
+
309
+ return new Promise<void>((resolve) => {
310
+ console.log("\n");
311
+ logBox(title, "🥓", 1, 0);
312
+ process.stdin.once("data", () => {
313
+ resolve();
314
+ });
315
+ });
316
+ }
317
+
318
+ function startLifeCycle(lifeCyleName: string) {
319
+ console.info(`
320
+ ${chalk.blue(`info`)} start ${lifeCyleName} life-cycle`);
321
+ }
322
+
323
+ function successLifeCyle(value: string) {
324
+ console.info(`${chalk.green("success")} ${value}`);
325
+ }
326
+
257
327
  export {
258
328
  CXRootFolder,
329
+ debug,
259
330
  delay,
260
331
  instanceRootFolder,
261
332
  logBox,
262
333
  logInfo,
263
334
  logPageSize,
264
335
  measureExecutionTime,
336
+ pause,
265
337
  printExporterLogo,
266
338
  removeProperties,
267
339
  sanitizeAPICacheFolder,
268
340
  siteList,
269
341
  splash,
342
+ startLifeCycle,
343
+ successLifeCyle,
270
344
  walk,
271
345
  };
@@ -6,7 +6,6 @@ import path from "node:path";
6
6
  import fs from "fs-extra";
7
7
  import { parse } from "js2xmlparser";
8
8
 
9
- import { deleteSites } from "./folders";
10
9
  import { logInfo } from "./shared";
11
10
  import { AuthService } from "../services/auth";
12
11
  import { SitesService } from "../services/sites";
@@ -89,9 +88,9 @@ async function checkSites(domain: string) {
89
88
  /**
90
89
  * Unpublish an array of sites in two steps:
91
90
  * - Sending the information to the API
92
- * - Removing the files from the file system
93
91
  *
94
92
  * @param sites An array of sites
93
+ * @see https://griddoio.notion.site/Sites-d7bb0b7cb8d24894a5337e1139fc3d09#2019d3255bda4d219c7e19cf28d0c4fe
95
94
  */
96
95
  async function unpublishSites(sites: Array<Site>) {
97
96
  for (const site of sites) {
@@ -103,12 +102,8 @@ async function unpublishSites(sites: Array<Site>) {
103
102
  unpublishHashes: [],
104
103
  };
105
104
 
106
- logInfo(`Unpublish site(s): ${buildInfo.publishIds}`);
107
-
108
105
  await SitesService.endSiteRender(site.id, body);
109
106
  }
110
-
111
- await deleteSites(sites);
112
107
  }
113
108
 
114
109
  /**
@@ -1,11 +1,15 @@
1
+ import type { BuildMetaData } from "../types/api";
2
+ import type { RenderInfo } from "../types/global";
3
+ import type { GriddoPageObject } from "../types/pages";
4
+
5
+ import fs from "node:fs";
1
6
  import fsp from "node:fs/promises";
2
7
  import path from "node:path";
3
8
 
4
9
  import fsx from "fs-extra";
5
10
 
6
- import { walk } from "./shared";
7
- import { BuildMetaData } from "../types/api";
8
- import { GriddoPageObject } from "../types/pages";
11
+ import { removeProperties, walk } from "./shared";
12
+ import { Site } from "../types/sites";
9
13
 
10
14
  /**
11
15
  * Read all pages stored in `store` Griddo folder and return one by one with a
@@ -26,7 +30,10 @@ async function* getBuildPages<PageType extends GriddoPageObject>(
26
30
  encoding: "utf-8",
27
31
  })) as PageType;
28
32
  page.size = fileStats.size / 1024;
29
- yield page;
33
+ // SECURITY: Only returns valid page objects
34
+ if (page.path) {
35
+ yield page;
36
+ }
30
37
  } catch (error) {
31
38
  console.error(`Error: The file ${filePath} doesn't exist`, error);
32
39
  }
@@ -35,16 +42,11 @@ async function* getBuildPages<PageType extends GriddoPageObject>(
35
42
 
36
43
  /**
37
44
  * Get the build metadata from the Store.
45
+ * TODO: Refactorizar para leer un solo archivo: __metadata__.json
38
46
  */
39
47
  function getBuildMetadata(basePath: string): BuildMetaData {
40
- const buildProcessData = fsx.readJSONSync(
41
- path.resolve(basePath, "metadata", "buildProcessData.json")
42
- );
43
- const createdPages = fsx.readJSONSync(
44
- path.resolve(basePath, "metadata", "createdPages.json")
45
- );
46
- const sitesToPublish = fsx.readJSONSync(
47
- path.resolve(basePath, "metadata", "sitesToPublish.json")
48
+ const { sitesToPublish, createdPages, buildProcessData } = fsx.readJSONSync(
49
+ path.resolve(basePath, "metadata", "render-info.json")
48
50
  );
49
51
  return {
50
52
  buildProcessData,
@@ -53,4 +55,196 @@ function getBuildMetadata(basePath: string): BuildMetaData {
53
55
  };
54
56
  }
55
57
 
56
- export { getBuildMetadata, getBuildPages };
58
+ /**
59
+ * Creates an `store` folder to store pages transformed by createStore
60
+ */
61
+ function createStoreFolder(storeFolder: string) {
62
+ if (!fs.existsSync(storeFolder)) {
63
+ fs.mkdirSync(storeFolder);
64
+ fs.mkdirSync(path.resolve(storeFolder, "metadata"));
65
+ }
66
+
67
+ console.info("Store initialized");
68
+ }
69
+
70
+ /**
71
+ * Write render info into a file.
72
+ * @param basePath - Absolute path of the folder from which files will be saved.
73
+ * @param renderInfo - Data that will be saved related to the render process.
74
+ */
75
+ function saveRenderInfoInStore(basePath: string, renderInfo: RenderInfo) {
76
+ fs.writeFileSync(
77
+ path.resolve(basePath, "metadata", "render-info.json"),
78
+ JSON.stringify(renderInfo)
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Return an array of ids only from `.json` files (no dirs) in the `basePath` folder based in the file name and filtered by the pages ids in a concrete site.
84
+ * @param basePath - Absolute path of the folder from which files will be read.
85
+ * @returns A Array<number> of pages ids in `basePath` folder filtered by pages ids in a concrete site.
86
+ */
87
+ function getPagesInStoreFolderBySitePages(
88
+ basePath: string,
89
+ pages: Array<number>
90
+ ): Array<number> {
91
+ const allPagesInStore = getPageInStoreFolder(basePath);
92
+ const pagesBySite = allPagesInStore.filter((page) => pages.includes(page));
93
+
94
+ return pagesBySite;
95
+ }
96
+
97
+ /**
98
+ * Return an array of ids only from `.json` files (no dirs) in the `basePath` folder based in the file name.
99
+ * @param basePath - Absolute path of the folder from which files will be read.
100
+ * @returns A Array<number> of pages ids in `basePath` folder.
101
+ */
102
+ function getPageInStoreFolder(basePath: string): Array<number> {
103
+ const filesInStore = fs.readdirSync(basePath);
104
+
105
+ return (
106
+ filesInStore
107
+ .filter((file) => {
108
+ const fullPathFile = `${basePath}/${file}`;
109
+ const stat = fs.statSync(fullPathFile);
110
+ // Si es un directorio, no lo incluimos.
111
+ if (stat && stat.isDirectory()) {
112
+ return false;
113
+ }
114
+ // Si es un archivo pero no tiene la extensión `.json`, no lo incluimos
115
+ if (path.extname(file) !== ".json") {
116
+ return false;
117
+ }
118
+ // Si es un archivo con el nombre NO pasable a número (NaN), no lo incluimos.
119
+ const baseFilename = path.basename(file, path.extname(file));
120
+ if (isNaN(parseInt(baseFilename))) {
121
+ return false;
122
+ }
123
+
124
+ // no es dir, es json y con nombre pasable a número, ok :)
125
+ return true;
126
+ })
127
+ // Tenemos solo jsons donde el nombre es un "número": "21345.json"
128
+ .map((page) => {
129
+ const filename = path.basename(page, path.extname(page));
130
+ return parseInt(filename);
131
+ })
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Save the pages into the file system.
137
+ * @param basePath - Absolute path of the folder from which files will be saved.
138
+ * @param pages - An array of Griddo page objects to be saved.
139
+ */
140
+ function savePagesInStore(basePath: string, pages: Array<GriddoPageObject>) {
141
+ pages.forEach((page) => {
142
+ removeProperties(page, ["editorID", "parentEditorID"]);
143
+ const filename = `${page.context.page.id}.json`;
144
+ const filePath = path.resolve(basePath, filename);
145
+ fsx.writeJSONSync(filePath, page);
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Remove files from folder.
151
+ * @param basePath - Absolute path of the folder from which files will be removed.
152
+ * @param filenames - An array of ids representing file page names.
153
+ */
154
+ function removePagesFromStore(basePath: string, filenames: Array<number>) {
155
+ if (filenames.length === 0) {
156
+ return;
157
+ }
158
+
159
+ filenames.forEach((filename) => {
160
+ const filePath = path.resolve(basePath, `${filename}.json`);
161
+ try {
162
+ fs.unlinkSync(filePath);
163
+ } catch (error) {
164
+ console.log(`Error removing file ${filename}`, (error as Error).message);
165
+ }
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Returns pages that need to be created or deleted for a site in the store based on the provided arguments.
171
+ * @param sitePages - Properties for page retrieval.
172
+ * @param props.storeFolder - Absolute path of the Griddo store.
173
+ * @param props.pages - Exhaustive array of all pages in the site.
174
+ * @param props.validPagesIds - Array of valid pages in the site.
175
+ * @param props.changedPages - Array of pages that have been modified in the site.
176
+ * @returns - An object containing the pages to be created and deleted in the site.
177
+ */
178
+ async function getPagesToCreateOrDelete(
179
+ storeFolder: string,
180
+ sitePages: {
181
+ sitesToPublish: Array<Site>;
182
+ pages: Array<number>;
183
+ validPagesIds: Array<number>;
184
+ changedPages: Array<number>;
185
+ }
186
+ ) {
187
+ const { changedPages, pages, validPagesIds, sitesToPublish } = sitePages;
188
+ // Array<ids> de las páginas que están físicamente en el store en el
189
+ // site actual del bucle.
190
+ const pagesInStore = getPagesInStoreFolderBySitePages(storeFolder, pages);
191
+
192
+ // Array<ids> que están el `validPagesIds` pero no están por algún
193
+ // motivo en el store. Se incluyen porque deben ser creadas.
194
+ const pagesMissingInStore = validPagesIds.filter(
195
+ (page) => !pagesInStore.includes(page)
196
+ );
197
+
198
+ // Array<ids> para enviar al store, que son las que han cambiado y
199
+ // las missings.
200
+ const pagesToStore = pagesMissingInStore.concat(changedPages);
201
+
202
+ // Array<ids> de las páginas que han sido eliminadas para quitarlas
203
+ // físicamente de la carpeta store.
204
+ const pagesToDeleteFromStore = pagesInStore.filter((page) => {
205
+ // Las que no estén en validPages
206
+ return !validPagesIds.includes(page);
207
+ });
208
+
209
+ // 1 - Elimina del array final las páginas borradas.
210
+ // 2 - Elimina las posibles páginas (ids) duplicadas. (new Set)
211
+ const pagesToWriteToStore = Array.from(new Set(pagesToStore)).filter(
212
+ (page) => !pagesToDeleteFromStore.includes(page)
213
+ );
214
+
215
+ // 1 - Leer el store
216
+ // 2 - eliminar las páginas que tengan como site uno despublicado o borrado
217
+ const allPagesInStore = getBuildPages(path.resolve(__dirname, "../store"));
218
+
219
+ const pagesFromInvalidSites: Array<number> = [];
220
+ for await (const page of allPagesInStore) {
221
+ const pageSite = page.context.page.site;
222
+ const pageId = page.context.page.id!;
223
+ // Si el `site.id` de la página no es ninguno de los `sitesToPublish`
224
+ // añadimos la página a `pagesFromIvalidSites` para borrarla
225
+ // posteriormente.
226
+ if (!sitesToPublish.map((site) => site.id).includes(pageSite)) {
227
+ pagesFromInvalidSites.push(pageId);
228
+ }
229
+ }
230
+
231
+ return {
232
+ pagesInStore,
233
+ pagesMissingInStore,
234
+ pagesToDeleteFromStore: [
235
+ ...pagesToDeleteFromStore,
236
+ ...pagesFromInvalidSites,
237
+ ],
238
+ pagesToWriteToStore,
239
+ };
240
+ }
241
+
242
+ export {
243
+ createStoreFolder,
244
+ getBuildMetadata,
245
+ getBuildPages,
246
+ getPagesToCreateOrDelete,
247
+ removePagesFromStore,
248
+ savePagesInStore,
249
+ saveRenderInfoInStore,
250
+ };
package/gatsby-node.ts CHANGED
@@ -33,6 +33,10 @@ const createPages: GatsbyNode["createPages"] = async ({
33
33
  const pages = getBuildPages<GatsbyPageObject>(storeFolderPath);
34
34
 
35
35
  for await (const page of pages) {
36
+ // SECURITY
37
+ if (!page) {
38
+ return;
39
+ }
36
40
  const { domain, compose } = page.context.fullPath;
37
41
  const matchPath = getMatchPath(domain, compose);
38
42
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/cx",
3
3
  "description": "Griddo SSG based on Gatsby",
4
- "version": "10.3.26",
4
+ "version": "10.4.0",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -20,11 +20,11 @@
20
20
  "griddo-cx": "./index.js"
21
21
  },
22
22
  "scripts": {
23
- "build": "yarn build:exporter && yarn build:reset-render && yarn build:build-complete && yarn build:download-data",
23
+ "build": "yarn build:exporter && yarn build:reset-render && yarn build:build-complete && yarn build:create-build-data",
24
24
  "build:exporter": "esbuild ./exporter/index.ts --bundle --platform=node --minify --outfile=./build/index.js",
25
25
  "build:build-complete": "esbuild ./exporter/build-complete.ts --bundle --platform=node --minify --outfile=./build/build-complete.js",
26
26
  "build:reset-render": "esbuild ./exporter/reset-render.ts --bundle --platform=node --minify --outfile=./build/reset-render.js",
27
- "build:download-data": "esbuild ./exporter/download-data.ts --bundle --platform=node --minify --outfile=./build/download-data.js",
27
+ "build:create-build-data": "esbuild ./exporter/create-build-data.ts --bundle --platform=node --minify --outfile=./build/create-build-data.js",
28
28
  "clean": "gatsby clean; rm -rf .cache public assets dist build store",
29
29
  "export": "yarn build && yarn download:data && yarn gatsby-build",
30
30
  "gatsby-build": "gatsby telemetry --disable && gatsby build --prefix-paths",
@@ -34,7 +34,7 @@
34
34
  "watch:tscheck": "tsc --noEmit --watch",
35
35
  "complete-render": "node ./build/build-complete.js",
36
36
  "reset-render": "node ./build/reset-render.js",
37
- "download:data": "node ./build/download-data.js"
37
+ "download:data": "node ./build/create-build-data.js"
38
38
  },
39
39
  "dependencies": {
40
40
  "@babel/core": "^7.21.0",
@@ -70,7 +70,7 @@
70
70
  "react-helmet": "^6.0.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@griddo/eslint-config-back": "^10.3.26",
73
+ "@griddo/eslint-config-back": "^10.4.0",
74
74
  "@types/babel__core": "^7.20.0",
75
75
  "@types/babel__preset-env": "^7.9.2",
76
76
  "@types/csvtojson": "^2.0.0",
@@ -117,5 +117,5 @@
117
117
  "publishConfig": {
118
118
  "access": "public"
119
119
  },
120
- "gitHead": "8dd1e9969f77d2f1cbcd19f12eb236b51d804037"
120
+ "gitHead": "3b3b3de14cb767145da22162c196d8e706f7f7ad"
121
121
  }
@@ -16,7 +16,6 @@ export { Head } from "./Head";
16
16
  const Template = (data: TemplateProps) => {
17
17
  const {
18
18
  pageContext: {
19
- BUILD_MODE,
20
19
  cloudinaryName,
21
20
  locale,
22
21
  page,
@@ -49,7 +48,6 @@ const Template = (data: TemplateProps) => {
49
48
  return (
50
49
  <SiteProvider
51
50
  apiUrl={page.apiUrl}
52
- buildMode={BUILD_MODE}
53
51
  cloudinaryCloudName={cloudinaryName}
54
52
  instance={page.instance}
55
53
  linkComponent={Link}
package/src/types.ts CHANGED
@@ -56,7 +56,6 @@ export type GatsbyPageObject = {
56
56
  size?: number;
57
57
  context: {
58
58
  // Page
59
- BUILD_MODE?: string;
60
59
  cloudinaryName?: string;
61
60
  footer: Record<string, unknown> | null;
62
61
  fullPath: Core.Page["fullPath"];