@griddo/cx 11.13.0 → 11.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/build/commands/end-render.js +150 -20
- package/build/commands/end-render.js.map +4 -4
- package/build/commands/prepare-assets-directory.js +146 -8
- package/build/commands/prepare-assets-directory.js.map +4 -4
- package/build/commands/prepare-domains-render.js +158 -28
- package/build/commands/prepare-domains-render.js.map +4 -4
- package/build/commands/reset-render.js +150 -20
- package/build/commands/reset-render.js.map +4 -4
- package/build/commands/start-render.js +179 -49
- package/build/commands/start-render.js.map +4 -4
- package/build/commands/upload-search-content.js +151 -21
- package/build/commands/upload-search-content.js.map +4 -4
- package/build/core/check-env-health.d.ts +47 -1
- package/build/core/http/create-adapter.d.ts +13 -0
- package/build/core/http/index.d.ts +6 -0
- package/build/core/http/types.d.ts +20 -0
- package/build/core/http/undici-adapter.d.ts +7 -0
- package/build/core/http/with-circuit-breaker.d.ts +24 -0
- package/build/core/http/with-retry.d.ts +32 -0
- package/build/index.js +25026 -42
- package/build/react/GriddoIntegrations/utils.d.ts +1 -1
- package/build/services/manage-store.d.ts +8 -1
- package/build/services/pages.d.ts +73 -2
- package/build/services/reference-fields.d.ts +1 -1
- package/build/shared/envs.d.ts +5 -2
- package/build/shared/types/api.d.ts +0 -2
- package/cli.mjs +28 -10
- package/exporter/commands/README.md +1 -1
- package/exporter/commands/end-render.ts +1 -1
- package/exporter/commands/prepare-domains-render.ts +1 -1
- package/exporter/commands/{single-domain-upload-search-content.ts → single-domain-upload-search-content.noop} +2 -4
- package/exporter/commands/upload-search-content.ts +1 -4
- package/exporter/core/check-env-health.ts +1 -1
- package/exporter/core/errors.ts +13 -13
- package/exporter/core/fs.ts +35 -31
- package/exporter/core/http/create-adapter.ts +58 -0
- package/exporter/core/http/index.ts +7 -0
- package/exporter/core/http/types.ts +22 -0
- package/exporter/core/http/undici-adapter.ts +53 -0
- package/exporter/core/http/with-circuit-breaker.ts +86 -0
- package/exporter/core/http/with-retry.ts +87 -0
- package/exporter/services/api.ts +22 -66
- package/exporter/services/auth.ts +11 -2
- package/exporter/services/domains.ts +6 -1
- package/exporter/services/llms.ts +1 -1
- package/exporter/services/manage-store.ts +16 -18
- package/exporter/services/pages.ts +7 -0
- package/exporter/services/reference-fields.ts +3 -5
- package/exporter/services/render.ts +3 -7
- package/exporter/services/store.ts +10 -4
- package/exporter/shared/envs.ts +20 -6
- package/exporter/shared/types/api.ts +0 -2
- package/exporter/ssg-adapters/gatsby/index.ts +4 -2
- package/exporter/ssg-adapters/gatsby/shared/sync-render.ts +5 -1
- package/package.json +15 -16
- package/tsconfig.commands.json +9 -22
- package/tsconfig.exporter.json +3 -4
- package/tsconfig.json +2 -3
- package/build/commands/single-domain-upload-search-content.d.ts +0 -1
|
@@ -8,7 +8,7 @@ declare function filterHeadIntegrations(integrations: Core.PageIntegration[]): {
|
|
|
8
8
|
content: string;
|
|
9
9
|
type: "addon" | "analytics" | "datalayer";
|
|
10
10
|
}[];
|
|
11
|
-
declare const filterPositionIntegrations: (integrations: Core.PageIntegration[], position: "
|
|
11
|
+
declare const filterPositionIntegrations: (integrations: Core.PageIntegration[], position: "head" | "start" | "end") => {
|
|
12
12
|
content: string;
|
|
13
13
|
type: "addon" | "analytics" | "datalayer";
|
|
14
14
|
}[];
|
|
@@ -29,4 +29,11 @@ declare function saveSitePagesInStore(siteDirName: string, pages: GriddoPageObje
|
|
|
29
29
|
*/
|
|
30
30
|
declare function removeOrphanSites(sitesToPublish: Site[], domain: string): Promise<void>;
|
|
31
31
|
declare function writeUniqueFileSync(filePath: string, content: string): Promise<void>;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Remove props from an object
|
|
34
|
+
*
|
|
35
|
+
* @param obj The object
|
|
36
|
+
* @param props An array of props to be removed
|
|
37
|
+
*/
|
|
38
|
+
declare function removeProperties(obj: Record<string, unknown>, propsToRemove: Set<string>): void;
|
|
39
|
+
export { getBuildMetadata, getPageInStoreDir, removeOrphanSites, removeProperties, saveRenderInfoInStore, saveSitePagesInStore, writeUniqueFileSync, };
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import type { Fields } from "@griddo/core";
|
|
1
|
+
import type { Core, Fields } from "@griddo/core";
|
|
2
2
|
import type { GriddoListPage, GriddoMultiPage, GriddoPageObject, GriddoSinglePage, MultiPageElements, PageAdditionalInfo } from "../shared/types/pages";
|
|
3
3
|
import type { TemplateWithReferenceField } from "../shared/types/templates";
|
|
4
|
+
/**
|
|
5
|
+
* Return an OpenGraph object.
|
|
6
|
+
*
|
|
7
|
+
* @param props.socialTitle The social title.
|
|
8
|
+
* @param props.socialDescription The social description.
|
|
9
|
+
* @param props.socialImage The social image in Cloudinary or DAM url format.
|
|
10
|
+
*/
|
|
11
|
+
declare function getOpenGraph({ socialTitle, socialDescription, socialImage }: Pick<Core.Page, "socialTitle" | "socialDescription" | "socialImage">): Core.Page["openGraph"];
|
|
12
|
+
/**
|
|
13
|
+
* Get the Page metadata object from a page Object.
|
|
14
|
+
*
|
|
15
|
+
* @param params A Page object
|
|
16
|
+
*/
|
|
17
|
+
declare function getPageMetadata(params: Core.Page): GriddoPageObject["context"]["pageMetadata"];
|
|
4
18
|
/**
|
|
5
19
|
* Create a single Griddo page object.
|
|
6
20
|
*
|
|
@@ -25,10 +39,67 @@ declare function createGriddoMultiPages(page: GriddoMultiPage, additionalInfo: P
|
|
|
25
39
|
* @param page The page to get the multipage parts.
|
|
26
40
|
*/
|
|
27
41
|
declare function getMultiPageElements(page: TemplateWithReferenceField): Promise<MultiPageElements> | null;
|
|
42
|
+
/**
|
|
43
|
+
* Get `itemsPerPage` elements from the `items` using as offset `page` number.
|
|
44
|
+
* It's a kind of paginator for an array.
|
|
45
|
+
*
|
|
46
|
+
* @param itemsPerPage The items per page.
|
|
47
|
+
* @param items An array of items, the queriedData.
|
|
48
|
+
* @param page The page to be returned.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* getPage(3, ["a", "b", "c", "d", "e", "f", "g", "h"], 2)
|
|
52
|
+
* // -> ["d", "e", "f"]
|
|
53
|
+
*/
|
|
54
|
+
declare function getPage(itemsPerPage: number, items: Fields.QueriedDataItem[], page: number): Fields.SimpleContentType<Omit<unknown, "__contentTypeKind">>[];
|
|
55
|
+
/**
|
|
56
|
+
* Takes an array of items and split it grouping in arrays of pages based on `itemsPerPage`.
|
|
57
|
+
*
|
|
58
|
+
* @param itemsPerPage The items per page.
|
|
59
|
+
* @param items An array of items, the queriedData.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* getPageCluster(3, ["a", "b", "c", "d", "e", "f", "g", "h"])
|
|
63
|
+
* // -> [["a", "b", "c"], ["d", "e", "f"], ["g", "h"]]
|
|
64
|
+
*/
|
|
65
|
+
declare function getPageCluster(itemsPerPage: number, items: Fields.QueriedDataItem[]): Fields.SimpleContentType<Omit<unknown, "__contentTypeKind">>[][];
|
|
28
66
|
/**
|
|
29
67
|
* Takes a template object and split the whole queriedItems into separated queriedItems to use in Griddo static list templates.
|
|
30
68
|
*
|
|
31
69
|
* @param listTemplate A template schema with the ReferenceField data included.
|
|
32
70
|
*/
|
|
33
71
|
declare function getPaginatedPages(listTemplate: TemplateWithReferenceField): Fields.SimpleContentType<Omit<unknown, "__contentTypeKind">>[][];
|
|
34
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Remove duplicate ending trailing character from a string
|
|
74
|
+
*
|
|
75
|
+
* @param url the strin url
|
|
76
|
+
* @example
|
|
77
|
+
* removeDuplicateTrailingSlash('http://griddo.com/foo/bar//')
|
|
78
|
+
* // -> http://griddo.com/foo/bar/
|
|
79
|
+
*/
|
|
80
|
+
declare function removeDuplicateTrailing(url: string): string;
|
|
81
|
+
/**
|
|
82
|
+
* Adds a number to an URL adding optionally an ending slash.
|
|
83
|
+
*
|
|
84
|
+
* @param url The url.
|
|
85
|
+
* @param pageNumber A number to be added to the url.
|
|
86
|
+
* @param options.addEndingSlash Boolean that indicates if return the final url with an end slash or not.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* addPageNumberToUrl("web.com", 3) // "web.com/3"
|
|
90
|
+
* addPageNumberToUrl("web.com", 3, { addEndingSlash: true }) // "web.com/3/"
|
|
91
|
+
*/
|
|
92
|
+
declare function addPageNumberToUrl(url: string, pageNumber: number, options?: {
|
|
93
|
+
addEndingSlash: boolean;
|
|
94
|
+
}): string;
|
|
95
|
+
/**
|
|
96
|
+
* Adds a number to an string with the format "string - number"
|
|
97
|
+
*
|
|
98
|
+
* @param title The title
|
|
99
|
+
* @param pageNumber A number to be added to the title.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* addPageNumberToTitle("The page", 3) // "The page - 3"
|
|
103
|
+
*/
|
|
104
|
+
declare function addPageNumberToTitle(title: string, pageNumber: number): string;
|
|
105
|
+
export { addPageNumberToTitle, addPageNumberToUrl, createGriddoListPages, createGriddoMultiPages, createGriddoSinglePage, getMultiPageElements, getOpenGraph, getPage, getPageCluster, getPageMetadata, getPaginatedPages, removeDuplicateTrailing, };
|
|
@@ -12,7 +12,7 @@ declare function getReferenceFieldData({ page, cacheKey }: {
|
|
|
12
12
|
cacheKey: string;
|
|
13
13
|
}): Promise<{
|
|
14
14
|
[key: string]: any;
|
|
15
|
-
type: "
|
|
15
|
+
type: "template" | "formTemplate";
|
|
16
16
|
templateType: string;
|
|
17
17
|
activeSectionSlug: string;
|
|
18
18
|
activeSectionBase: string;
|
package/build/shared/envs.d.ts
CHANGED
|
@@ -15,8 +15,11 @@ declare const GRIDDO_SSG_BUNDLE_ANALYZER: boolean;
|
|
|
15
15
|
declare const GRIDDO_SSG_VERBOSE_LOGS: boolean;
|
|
16
16
|
declare const GRIDDO_USE_DIST_BACKUP: boolean;
|
|
17
17
|
declare const GRIDDO_VERBOSE_LOGS: boolean;
|
|
18
|
+
declare const GRIDDO_RENDER_ENABLED_LLM_MD: boolean;
|
|
18
19
|
declare const GRIDDO_RENDER_API_FETCH_RETRY_WAIT_SECONDS: number;
|
|
19
20
|
declare const GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS: number;
|
|
21
|
+
declare const GRIDDO_RENDER_API_TIMEOUT_MS: number;
|
|
22
|
+
declare const GRIDDO_RENDER_CIRCUIT_BREAKER_FAILURE_THRESHOLD: number;
|
|
23
|
+
declare const GRIDDO_RENDER_CIRCUIT_BREAKER_COOLDOWN_MS: number;
|
|
20
24
|
declare const GRIDDO_RENDER_LIFECYCLE_RETRY_ATTEMPTS: number;
|
|
21
|
-
|
|
22
|
-
export { GRIDDO_AI_EMBEDDINGS, GRIDDO_API_CONCURRENCY_COUNT, GRIDDO_API_URL, GRIDDO_ASSET_PREFIX, GRIDDO_BOT_PASSWORD, GRIDDO_BOT_USER, GRIDDO_BUILD_LOGS, GRIDDO_BUILD_LOGS_BUFFER_SIZE, GRIDDO_PUBLIC_API_URL, GRIDDO_REACT_APP_INSTANCE, GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS, GRIDDO_RENDER_API_FETCH_RETRY_WAIT_SECONDS, GRIDDO_RENDER_DISABLE_LLMS_TXT, GRIDDO_RENDER_ENABLED_LLM_MD, GRIDDO_RENDER_LIFECYCLE_RETRY_ATTEMPTS, GRIDDO_SEARCH_FEATURE, GRIDDO_SKIP_BUILD_CHECKS, GRIDDO_SSG_BUNDLE_ANALYZER, GRIDDO_SSG_VERBOSE_LOGS, GRIDDO_USE_DIST_BACKUP, GRIDDO_VERBOSE_LOGS, };
|
|
25
|
+
export { GRIDDO_AI_EMBEDDINGS, GRIDDO_API_CONCURRENCY_COUNT, GRIDDO_API_URL, GRIDDO_ASSET_PREFIX, GRIDDO_BOT_PASSWORD, GRIDDO_BOT_USER, GRIDDO_BUILD_LOGS, GRIDDO_BUILD_LOGS_BUFFER_SIZE, GRIDDO_PUBLIC_API_URL, GRIDDO_REACT_APP_INSTANCE, GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS, GRIDDO_RENDER_API_FETCH_RETRY_WAIT_SECONDS, GRIDDO_RENDER_API_TIMEOUT_MS, GRIDDO_RENDER_CIRCUIT_BREAKER_COOLDOWN_MS, GRIDDO_RENDER_CIRCUIT_BREAKER_FAILURE_THRESHOLD, GRIDDO_RENDER_DISABLE_LLMS_TXT, GRIDDO_RENDER_ENABLED_LLM_MD, GRIDDO_RENDER_LIFECYCLE_RETRY_ATTEMPTS, GRIDDO_SEARCH_FEATURE, GRIDDO_SKIP_BUILD_CHECKS, GRIDDO_SSG_BUNDLE_ANALYZER, GRIDDO_SSG_VERBOSE_LOGS, GRIDDO_USE_DIST_BACKUP, GRIDDO_VERBOSE_LOGS, };
|
|
@@ -81,8 +81,6 @@ export interface APIRequest {
|
|
|
81
81
|
body?: any;
|
|
82
82
|
/** Reference id to manage cache between renders. */
|
|
83
83
|
cacheKey?: string;
|
|
84
|
-
/** Number of connection attempts (in case it fails on the first attempt). */
|
|
85
|
-
attempt?: number;
|
|
86
84
|
/**
|
|
87
85
|
* Headers for the post api fetch
|
|
88
86
|
*/
|
package/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -12,7 +12,7 @@ function getRoot(rootFlag) {
|
|
|
12
12
|
if (rootFlag) {
|
|
13
13
|
return path.resolve(rootFlag);
|
|
14
14
|
}
|
|
15
|
-
return
|
|
15
|
+
return process.cwd();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function getDBPath(rootFlag) {
|
|
@@ -96,8 +96,8 @@ export const AVAILABLE_COMMANDS = [
|
|
|
96
96
|
},
|
|
97
97
|
];
|
|
98
98
|
|
|
99
|
-
function showHelp() {
|
|
100
|
-
console.log("Griddo
|
|
99
|
+
export function showHelp() {
|
|
100
|
+
console.log("Griddo Render Engine CLI - Available commands:\n");
|
|
101
101
|
|
|
102
102
|
AVAILABLE_COMMANDS.forEach((cmd) => {
|
|
103
103
|
const domainArg = cmd.domainArgument ? " --domain=<domain>" : "";
|
|
@@ -114,11 +114,11 @@ function showHelp() {
|
|
|
114
114
|
console.log(" griddo-render --help");
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function findCommand(commandName) {
|
|
117
|
+
export function findCommand(commandName) {
|
|
118
118
|
return AVAILABLE_COMMANDS.find((cmd) => cmd.name === commandName);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
function validateCommand(commandName, config) {
|
|
121
|
+
export function validateCommand(commandName, config) {
|
|
122
122
|
const command = findCommand(commandName);
|
|
123
123
|
|
|
124
124
|
if (!command) {
|
|
@@ -154,7 +154,7 @@ function validateCommand(commandName, config) {
|
|
|
154
154
|
return { command, config: validatedConfig };
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function executeCommand(command, config) {
|
|
157
|
+
export function executeCommand(command, config) {
|
|
158
158
|
try {
|
|
159
159
|
const env = { ...process.env };
|
|
160
160
|
const scriptPath = path.join(COMMANDS_PATH, command.script);
|
|
@@ -164,9 +164,11 @@ function executeCommand(command, config) {
|
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
const
|
|
167
|
+
const args = [scriptPath];
|
|
168
|
+
if (config.domain) args.push(config.domain);
|
|
169
|
+
if (config.noStaticFiles) args.push("--dry-render");
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
execFileSync("node", args, {
|
|
170
172
|
stdio: "inherit",
|
|
171
173
|
env,
|
|
172
174
|
cwd: process.cwd(),
|
|
@@ -245,4 +247,20 @@ function main() {
|
|
|
245
247
|
executeCommand(command, config);
|
|
246
248
|
}
|
|
247
249
|
|
|
248
|
-
|
|
250
|
+
// Only run when executed directly, not when imported for testing
|
|
251
|
+
// fs.realpathSync is needed to resolve symlinks (e.g. node_modules/.bin/ entries)
|
|
252
|
+
const isDirectRun = (() => {
|
|
253
|
+
try {
|
|
254
|
+
return (
|
|
255
|
+
!!process.argv[1] &&
|
|
256
|
+
fs.realpathSync(path.resolve(process.argv[1])) ===
|
|
257
|
+
fs.realpathSync(path.resolve(fileURLToPath(import.meta.url)))
|
|
258
|
+
);
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
})();
|
|
263
|
+
|
|
264
|
+
if (isDirectRun) {
|
|
265
|
+
main();
|
|
266
|
+
}
|
|
@@ -41,7 +41,7 @@ async function endRender() {
|
|
|
41
41
|
const { renderMode, reason } = await getRenderModeFromDB(domain);
|
|
42
42
|
if (renderMode === RENDER_MODE.IDLE) {
|
|
43
43
|
GriddoLog.info(
|
|
44
|
-
`(
|
|
44
|
+
`(Pre-check) ${domain} skipped end-render as it is marked as <${RENDER_MODE.IDLE}> with the reason <${reason}>`,
|
|
45
45
|
);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
@@ -132,7 +132,7 @@ async function prepareDomains() {
|
|
|
132
132
|
|
|
133
133
|
// Log RenderModes/Reason
|
|
134
134
|
GriddoLog.info(
|
|
135
|
-
`(
|
|
135
|
+
`(Pre-check) ${domainSlug} marked as <${renderMode}> with the reason <${reason}>`,
|
|
136
136
|
);
|
|
137
137
|
|
|
138
138
|
db.domains[domainSlug] = db.domains[domainSlug] || {};
|
|
@@ -133,9 +133,7 @@ async function uploadRenderedSearchContentToAPI(options: {
|
|
|
133
133
|
const { htmlContentDir, jsonContentDir } = options;
|
|
134
134
|
|
|
135
135
|
if (!(await pathExists(jsonContentDir)) || !(await pathExists(htmlContentDir))) {
|
|
136
|
-
GriddoLog.info(
|
|
137
|
-
`(From Current Render) Skipping uploading content to the search endpoint because it has not exported sites.`,
|
|
138
|
-
);
|
|
136
|
+
GriddoLog.info(`(Pre-check) skipped uploading content because it has not exported sites`);
|
|
139
137
|
|
|
140
138
|
return;
|
|
141
139
|
}
|
|
@@ -184,7 +182,7 @@ async function uploadSearchContent() {
|
|
|
184
182
|
|
|
185
183
|
if (renderMode === RENDER_MODE.IDLE) {
|
|
186
184
|
GriddoLog.info(
|
|
187
|
-
`(
|
|
185
|
+
`(Pre-check) ${domain} skipped upload-search-content as it is marked as <${RENDER_MODE.IDLE}> with the reason <${reason}>`,
|
|
188
186
|
);
|
|
189
187
|
return;
|
|
190
188
|
}
|
|
@@ -73,10 +73,7 @@ async function uploadRenderedSearchContentToAPI(options: {
|
|
|
73
73
|
const { htmlContentDir, jsonContentDir } = options;
|
|
74
74
|
|
|
75
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
|
-
);
|
|
79
|
-
|
|
76
|
+
GriddoLog.info(`uploading content skipped because it has not exported sites`);
|
|
80
77
|
return;
|
|
81
78
|
}
|
|
82
79
|
|
package/exporter/core/errors.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { ErrorsType } from "../shared/errors";
|
|
2
2
|
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
3
|
import { brush } from "../shared/brush";
|
|
6
4
|
import { RENDER_MODE } from "../shared/types/render";
|
|
7
5
|
import { readDB, writeDB } from "./db";
|
|
@@ -66,26 +64,28 @@ async function withErrorHandler(fn: () => Promise<void>) {
|
|
|
66
64
|
GriddoLog.error(`An unexpected error occurred ${error}`);
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
// Try to rollback the exports directory if needed
|
|
70
67
|
try {
|
|
71
68
|
const data = await readDB();
|
|
72
|
-
const
|
|
73
|
-
if (data.needsRollbackOnError) {
|
|
74
|
-
GriddoLog.info("Cleaning exports dir...");
|
|
75
|
-
GriddoLog.verbose(`Deleting ${path.join(root, "exports")}...`);
|
|
69
|
+
const domain = data.currentRenderingDomain;
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
if (!domain) {
|
|
72
|
+
GriddoLog.warn("No currentRenderingDomain set, skipping cleanup");
|
|
78
73
|
} else {
|
|
79
|
-
|
|
74
|
+
if (data.needsRollbackOnError) {
|
|
75
|
+
GriddoLog.info("Cleaning exports dir...");
|
|
76
|
+
await distRollback(domain);
|
|
77
|
+
} else {
|
|
78
|
+
GriddoLog.info("No rollback needed, skipping...");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
data.domains[domain].isRendering = false;
|
|
82
|
+
data.domains[domain].renderMode = RENDER_MODE.ERROR;
|
|
83
|
+
await writeDB(data);
|
|
80
84
|
}
|
|
81
85
|
} catch (_e) {
|
|
82
86
|
GriddoLog.info("Early render stage, no db.json created yet...");
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
const data = await readDB();
|
|
86
|
-
data.domains[data.currentRenderingDomain!].isRendering = false;
|
|
87
|
-
data.domains[data.currentRenderingDomain!].renderMode = RENDER_MODE.ERROR;
|
|
88
|
-
await writeDB(data);
|
|
89
89
|
throw error;
|
|
90
90
|
}
|
|
91
91
|
}
|
package/exporter/core/fs.ts
CHANGED
|
@@ -39,10 +39,8 @@ async function deleteDisposableSiteDirs(baseDir: string) {
|
|
|
39
39
|
async function mkDirs(dirs: string[], options?: MakeDirectoryOptions) {
|
|
40
40
|
for (const dir of dirs) {
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
GriddoLog.verbose(`create directory: ${dir}`);
|
|
45
|
-
}
|
|
42
|
+
await fsp.mkdir(dir, { recursive: true, ...options });
|
|
43
|
+
GriddoLog.verbose(`create directory: ${dir}`);
|
|
46
44
|
} catch (error) {
|
|
47
45
|
throwError(ArtifactError, error);
|
|
48
46
|
}
|
|
@@ -96,10 +94,8 @@ async function cpDirs(
|
|
|
96
94
|
// Copy directory
|
|
97
95
|
try {
|
|
98
96
|
// First clean destination
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
GriddoLog.verbose(`clean destination: ${dstCompose}`);
|
|
102
|
-
}
|
|
97
|
+
await fsp.rm(dstCompose, { recursive: true, force: true });
|
|
98
|
+
GriddoLog.verbose(`clean destination: ${dstCompose}`);
|
|
103
99
|
|
|
104
100
|
// Then copy src to dst
|
|
105
101
|
await fsp.cp(srcCompose, dstCompose, {
|
|
@@ -153,7 +149,7 @@ async function mvDirs(
|
|
|
153
149
|
|
|
154
150
|
try {
|
|
155
151
|
// Clean destination
|
|
156
|
-
if (override
|
|
152
|
+
if (override) {
|
|
157
153
|
await fsp.rm(dstCompose, { recursive: true, force: true });
|
|
158
154
|
}
|
|
159
155
|
|
|
@@ -203,10 +199,6 @@ async function restoreBackup(src: string, suffix = "-BACKUP") {
|
|
|
203
199
|
async function deleteBackup(src: string, suffix = "-BACKUP") {
|
|
204
200
|
const dst = src + suffix;
|
|
205
201
|
|
|
206
|
-
if (!(await pathExists(dst))) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
202
|
try {
|
|
211
203
|
await fsp.rm(dst, { recursive: true, force: true });
|
|
212
204
|
GriddoLog.verbose(`Backup ${dst} has been deleted`);
|
|
@@ -252,6 +244,8 @@ async function siteIsEmpty(sitePath: string) {
|
|
|
252
244
|
if (siteFiles.length === xmlFiles.length) {
|
|
253
245
|
return true;
|
|
254
246
|
}
|
|
247
|
+
|
|
248
|
+
return false;
|
|
255
249
|
}
|
|
256
250
|
|
|
257
251
|
/**
|
|
@@ -334,14 +328,18 @@ async function pathExists(dir: string) {
|
|
|
334
328
|
*/
|
|
335
329
|
async function* findFilesBySuffix(dir: string, suffix: string): AsyncGenerator<string> {
|
|
336
330
|
const dirHandle = await fsp.opendir(dir);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
331
|
+
try {
|
|
332
|
+
for await (const item of dirHandle) {
|
|
333
|
+
const fullPath = path.join(dir, item.name);
|
|
334
|
+
if (item.isDirectory()) {
|
|
335
|
+
// yield* para encadenar otro generator.
|
|
336
|
+
yield* findFilesBySuffix(fullPath, suffix);
|
|
337
|
+
} else if (item.isFile() && item.name.endsWith(suffix)) {
|
|
338
|
+
yield fullPath;
|
|
339
|
+
}
|
|
344
340
|
}
|
|
341
|
+
} finally {
|
|
342
|
+
await dirHandle.close().catch(() => {});
|
|
345
343
|
}
|
|
346
344
|
}
|
|
347
345
|
|
|
@@ -354,20 +352,26 @@ async function* findFilesBySuffix(dir: string, suffix: string): AsyncGenerator<s
|
|
|
354
352
|
*/
|
|
355
353
|
async function* walkStore(storeDir: string): AsyncGenerator<string> {
|
|
356
354
|
const storeDirHandle = await fsp.opendir(storeDir);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
355
|
+
try {
|
|
356
|
+
for await (const siteDirent of storeDirHandle) {
|
|
357
|
+
if (siteDirent.isDirectory()) {
|
|
358
|
+
const siteDirPath = path.join(storeDir, siteDirent.name);
|
|
359
|
+
const siteDirHandle = await fsp.opendir(siteDirPath);
|
|
360
|
+
try {
|
|
361
|
+
for await (const fileDirent of siteDirHandle) {
|
|
362
|
+
const filePath = path.join(siteDirPath, fileDirent.name);
|
|
363
|
+
|
|
364
|
+
if (fileDirent.isFile() && path.extname(filePath) === ".json") {
|
|
365
|
+
yield filePath;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} finally {
|
|
369
|
+
await siteDirHandle.close().catch(() => {});
|
|
368
370
|
}
|
|
369
371
|
}
|
|
370
372
|
}
|
|
373
|
+
} finally {
|
|
374
|
+
await storeDirHandle.close().catch(() => {});
|
|
371
375
|
}
|
|
372
376
|
}
|
|
373
377
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HttpAdapter } from "./types";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS,
|
|
5
|
+
GRIDDO_RENDER_API_FETCH_RETRY_WAIT_SECONDS,
|
|
6
|
+
GRIDDO_RENDER_API_TIMEOUT_MS,
|
|
7
|
+
GRIDDO_RENDER_CIRCUIT_BREAKER_COOLDOWN_MS,
|
|
8
|
+
GRIDDO_RENDER_CIRCUIT_BREAKER_FAILURE_THRESHOLD,
|
|
9
|
+
} from "../../shared/envs";
|
|
10
|
+
import { GriddoLog } from "../GriddoLog";
|
|
11
|
+
import { createUndiciAdapter } from "./undici-adapter";
|
|
12
|
+
import { withCircuitBreaker } from "./with-circuit-breaker";
|
|
13
|
+
import { withRetry } from "./with-retry";
|
|
14
|
+
|
|
15
|
+
let adapter: HttpAdapter | undefined;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sync getter del adapter HTTP.
|
|
19
|
+
*
|
|
20
|
+
* Composición:
|
|
21
|
+
* circuitBreaker → retry(exponential + jitter) → undici(timeout nativo)
|
|
22
|
+
*/
|
|
23
|
+
function getHttpAdapter(): HttpAdapter {
|
|
24
|
+
if (!adapter) {
|
|
25
|
+
adapter = withCircuitBreaker(
|
|
26
|
+
withRetry(createUndiciAdapter({ timeoutMs: GRIDDO_RENDER_API_TIMEOUT_MS }), {
|
|
27
|
+
attempts: GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS,
|
|
28
|
+
delayMs: GRIDDO_RENDER_API_FETCH_RETRY_WAIT_SECONDS * 1000,
|
|
29
|
+
backoff: "exponential",
|
|
30
|
+
jitter: true,
|
|
31
|
+
retryOn: (res) => res.status >= 500,
|
|
32
|
+
onRetry: ({ request: req, attempt, delayMs, error, response }) => {
|
|
33
|
+
const cause = response ? `HTTP ${response.status}` : error?.message;
|
|
34
|
+
GriddoLog.warn(
|
|
35
|
+
`Retry ${attempt}/${GRIDDO_RENDER_API_FETCH_RETRY_ATTEMPTS}: ${req.method} ${req.url} — ${cause} (next in ${delayMs}ms)`,
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
{
|
|
40
|
+
failureThreshold: GRIDDO_RENDER_CIRCUIT_BREAKER_FAILURE_THRESHOLD,
|
|
41
|
+
cooldownMs: GRIDDO_RENDER_CIRCUIT_BREAKER_COOLDOWN_MS,
|
|
42
|
+
onOpen: () => GriddoLog.warn("Circuit breaker OPEN — requests will fail fast"),
|
|
43
|
+
onClose: () => GriddoLog.info("Circuit breaker CLOSED — recovered"),
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
GriddoLog.verbose("HTTP adapter: undici (timeout + retry + circuit breaker)");
|
|
47
|
+
}
|
|
48
|
+
return adapter;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reset del singleton (para tests).
|
|
53
|
+
*/
|
|
54
|
+
function resetHttpAdapter(): void {
|
|
55
|
+
adapter = undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { getHttpAdapter, resetHttpAdapter };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { HttpAdapter, HttpRequest, HttpResponse } from "./types";
|
|
2
|
+
export type { CircuitBreakerOptions } from "./with-circuit-breaker";
|
|
3
|
+
export type { RetryOptions } from "./with-retry";
|
|
4
|
+
|
|
5
|
+
export { getHttpAdapter, resetHttpAdapter } from "./create-adapter";
|
|
6
|
+
export { CircuitOpenError, withCircuitBreaker } from "./with-circuit-breaker";
|
|
7
|
+
export { withRetry } from "./with-retry";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Descriptor de request que recibe el adapter. */
|
|
2
|
+
export interface HttpRequest {
|
|
3
|
+
url: string;
|
|
4
|
+
method: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
body?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Descriptor de response que devuelve el adapter. */
|
|
10
|
+
export interface HttpResponse {
|
|
11
|
+
status: number;
|
|
12
|
+
statusText: string;
|
|
13
|
+
ok: boolean;
|
|
14
|
+
headers: Record<string, string>;
|
|
15
|
+
json: <T = unknown>() => Promise<T>;
|
|
16
|
+
text: () => Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Contrato del adapter. Un solo metodo, sin estado. */
|
|
20
|
+
export interface HttpAdapter {
|
|
21
|
+
request: (req: HttpRequest) => Promise<HttpResponse>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { HttpAdapter, HttpRequest, HttpResponse } from "./types";
|
|
2
|
+
|
|
3
|
+
import { request } from "undici";
|
|
4
|
+
|
|
5
|
+
export interface UndiciAdapterOptions {
|
|
6
|
+
/** Request timeout in milliseconds. Cancels the request if exceeded. */
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createUndiciAdapter(options: UndiciAdapterOptions = {}): HttpAdapter {
|
|
11
|
+
const { timeoutMs } = options;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
async request(req: HttpRequest): Promise<HttpResponse> {
|
|
15
|
+
const {
|
|
16
|
+
statusCode,
|
|
17
|
+
headers: rawHeaders,
|
|
18
|
+
body,
|
|
19
|
+
} = await request(req.url, {
|
|
20
|
+
method: req.method as "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
|
21
|
+
headers: req.headers,
|
|
22
|
+
body: req.body,
|
|
23
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const headers: Record<string, string> = {};
|
|
27
|
+
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
28
|
+
if (value !== undefined) {
|
|
29
|
+
headers[key] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let buffered: string | undefined;
|
|
34
|
+
const getText = async () => {
|
|
35
|
+
if (buffered === undefined) {
|
|
36
|
+
buffered = await body.text();
|
|
37
|
+
}
|
|
38
|
+
return buffered;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
status: statusCode,
|
|
43
|
+
statusText: "",
|
|
44
|
+
ok: statusCode >= 200 && statusCode < 300,
|
|
45
|
+
headers,
|
|
46
|
+
json: async <T>() => JSON.parse(await getText()) as T,
|
|
47
|
+
text: getText,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { createUndiciAdapter };
|