@griddo/cx 11.9.7 → 11.9.8-rc.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.
- package/README.md +78 -193
- package/build/commands/end-render.js +31 -0
- package/build/commands/end-render.js.map +7 -0
- package/build/commands/prepare-assets-directory.js +9 -0
- package/build/commands/prepare-assets-directory.js.map +7 -0
- package/build/commands/prepare-domains-render.js +38 -0
- package/build/commands/prepare-domains-render.js.map +7 -0
- package/build/commands/reset-render.js +31 -0
- package/build/commands/reset-render.js.map +7 -0
- package/build/commands/start-embeddings.js +31 -0
- package/build/commands/start-embeddings.js.map +7 -0
- package/build/commands/start-render.js +66 -0
- package/build/commands/start-render.js.map +7 -0
- package/build/commands/upload-search-content.js +31 -0
- package/build/commands/upload-search-content.js.map +7 -0
- package/build/core/GriddoLog.d.ts +16 -0
- package/build/core/db.d.ts +4 -0
- package/build/core/dist-rollback.d.ts +2 -0
- package/build/{errors/index.d.ts → core/errors.d.ts} +5 -4
- package/build/core/fs.d.ts +69 -0
- package/build/core/logger.d.ts +18 -0
- package/build/index.d.ts +10 -29
- package/build/index.js +406 -73
- package/build/services/auth.d.ts +2 -5
- package/build/services/manage-store.d.ts +48 -0
- package/build/services/render.d.ts +70 -0
- package/build/shared/envs.d.ts +19 -0
- package/build/{errors/errors-data.d.ts → shared/errors.d.ts} +5 -3
- package/build/shared/npm-modules/brush.d.ts +18 -0
- package/build/shared/npm-modules/find-up-simple.d.ts +34 -0
- package/build/shared/npm-modules/pkg-dir.d.ts +7 -0
- package/build/{types → shared/types}/api.d.ts +18 -18
- package/build/{types → shared/types}/global.d.ts +15 -16
- package/build/{types → shared/types}/navigation.d.ts +5 -5
- package/build/{types → shared/types}/pages.d.ts +9 -9
- package/build/shared/types/render.d.ts +54 -0
- package/build/{types → shared/types}/sites.d.ts +18 -19
- package/cli.mjs +239 -0
- package/exporter/build-esbuild.noop +42 -0
- package/exporter/build.sh +17 -28
- package/exporter/commands/README.md +151 -0
- package/exporter/commands/end-render.ts +65 -86
- package/exporter/commands/prepare-assets-directory.ts +34 -0
- package/exporter/commands/prepare-domains-render.ts +143 -35
- package/exporter/commands/reset-render.ts +12 -7
- package/exporter/commands/single-domain-upload-search-content.noop +206 -0
- package/exporter/commands/start-embeddings.ts +29 -0
- package/exporter/commands/start-render.ts +26 -64
- package/exporter/commands/upload-search-content.ts +201 -26
- package/exporter/core/GriddoLog.ts +45 -0
- package/exporter/core/check-env-health.ts +200 -0
- package/exporter/core/db-class.ts +54 -0
- package/exporter/core/db.ts +33 -0
- package/exporter/core/dist-rollback.ts +40 -0
- package/exporter/core/errors.ts +82 -0
- package/exporter/core/fs.ts +385 -0
- package/exporter/{utils → core}/images.ts +1 -6
- package/exporter/{utils → core}/instance.ts +9 -13
- package/exporter/core/life-cycle.ts +73 -0
- package/exporter/core/logger.ts +141 -0
- package/exporter/core/objects.ts +37 -0
- package/exporter/core/print-logos.ts +21 -0
- package/exporter/index.ts +14 -56
- package/exporter/services/api.ts +306 -0
- package/exporter/services/auth.ts +8 -10
- package/exporter/services/domains.ts +23 -8
- package/exporter/services/manage-sites.ts +116 -0
- package/exporter/services/manage-store.ts +235 -0
- package/exporter/services/navigation.ts +12 -18
- package/exporter/{utils → services}/pages.ts +27 -92
- package/exporter/services/reference-fields.ts +14 -32
- package/exporter/services/render-artifacts.ts +44 -0
- package/exporter/services/render.ts +229 -0
- package/exporter/services/robots.ts +33 -61
- package/exporter/services/sitemaps.ts +129 -0
- package/exporter/services/sites.ts +40 -28
- package/exporter/services/store.ts +386 -319
- package/exporter/shared/context.ts +49 -0
- package/exporter/{constants → shared}/endpoints.ts +12 -11
- package/exporter/shared/envs.ts +62 -0
- package/exporter/{errors/errors-data.ts → shared/errors.ts} +24 -14
- package/exporter/shared/npm-modules/README.md +36 -0
- package/exporter/shared/npm-modules/brush.ts +34 -0
- package/exporter/shared/npm-modules/find-up-simple.ts +100 -0
- package/exporter/shared/npm-modules/pkg-dir.ts +17 -0
- package/exporter/shared/npm-modules/xml-parser.ts +57 -0
- package/exporter/{types → shared/types}/api.ts +40 -41
- package/exporter/{types → shared/types}/global.ts +17 -21
- package/exporter/{types → shared/types}/navigation.ts +3 -3
- package/exporter/{types → shared/types}/pages.ts +10 -11
- package/exporter/shared/types/render.ts +63 -0
- package/exporter/{types → shared/types}/sites.ts +18 -19
- package/exporter/ssg-adapters/gatsby/actions/clean.ts +26 -0
- package/exporter/ssg-adapters/gatsby/actions/close.ts +17 -0
- package/exporter/ssg-adapters/gatsby/actions/data.ts +22 -0
- package/exporter/ssg-adapters/gatsby/actions/healthCheck.ts +10 -0
- package/exporter/ssg-adapters/gatsby/actions/init.ts +12 -0
- package/exporter/ssg-adapters/gatsby/actions/logs.ts +10 -0
- package/exporter/ssg-adapters/gatsby/actions/meta.ts +13 -0
- package/exporter/ssg-adapters/gatsby/actions/prepare.ts +9 -0
- package/exporter/ssg-adapters/gatsby/actions/relocation.ts +15 -0
- package/exporter/ssg-adapters/gatsby/actions/restore.ts +21 -0
- package/exporter/ssg-adapters/gatsby/actions/ssg.ts +12 -0
- package/exporter/ssg-adapters/gatsby/actions/sync.ts +65 -0
- package/exporter/ssg-adapters/gatsby/index.ts +114 -0
- package/exporter/ssg-adapters/gatsby/shared/artifacts.ts +16 -0
- package/exporter/ssg-adapters/gatsby/shared/diff-assets.ts +128 -0
- package/exporter/ssg-adapters/gatsby/shared/extract-assets.ts +75 -0
- package/exporter/ssg-adapters/gatsby/shared/gatsby-build.ts +58 -0
- package/exporter/ssg-adapters/gatsby/shared/sync-render.ts +300 -0
- package/exporter/ssg-adapters/gatsby/shared/types.ts +35 -0
- package/exporter/ssg-adapters/gatsby/shared/utils.ts +33 -0
- package/gatsby-browser.tsx +41 -58
- package/gatsby-config.ts +10 -17
- package/gatsby-node.ts +20 -80
- package/gatsby-ssr.tsx +2 -1
- package/package.json +41 -92
- package/plugins/gatsby-plugin-svgr-loader/gatsby-node.js +55 -0
- package/plugins/gatsby-plugin-svgr-loader/package.json +8 -0
- package/react/DynamicScript/index.tsx +33 -0
- package/{exporter/react/Favicon → react/GriddoFavicon}/index.tsx +3 -9
- package/{exporter/react → react}/GriddoIntegrations/index.tsx +17 -23
- package/{exporter/react → react}/GriddoIntegrations/utils.ts +24 -12
- package/react/GriddoOpenGraph/index.tsx +39 -0
- package/src/components/Head.tsx +30 -73
- package/src/components/template.tsx +8 -30
- package/src/gatsby-node-utils.ts +76 -2
- package/src/html.tsx +2 -11
- package/src/types.ts +5 -5
- package/tsconfig.commands.json +36 -0
- package/tsconfig.exporter.json +20 -0
- package/tsconfig.json +5 -3
- package/build/adapters/gatsby/index.d.ts +0 -4
- package/build/adapters/gatsby/utils.d.ts +0 -22
- package/build/artifacts/index.d.ts +0 -6
- package/build/commands/end-render.d.ts +0 -2
- package/build/commands/move-assets.d.ts +0 -1
- package/build/commands/prepare-domains-render.d.ts +0 -1
- package/build/commands/reset-render.d.ts +0 -2
- package/build/commands/start-render.d.ts +0 -2
- package/build/commands/upload-search-content.d.ts +0 -2
- package/build/constants/envs.d.ts +0 -37
- package/build/constants/index.d.ts +0 -57
- package/build/end-render.js +0 -74
- package/build/end-render.js.map +0 -7
- package/build/index.js.map +0 -7
- package/build/prepare-domains-render.js +0 -73
- package/build/prepare-domains-render.js.map +0 -7
- package/build/react/Favicon/index.d.ts +0 -5
- package/build/react/Favicon/utils.d.ts +0 -9
- package/build/react/GriddoIntegrations/index.d.ts +0 -20
- package/build/react/GriddoIntegrations/utils.d.ts +0 -26
- package/build/react/index.d.ts +0 -3
- package/build/react/index.js +0 -3
- package/build/registers/api.d.ts +0 -9
- package/build/registers/gatsby.d.ts +0 -9
- package/build/registers/index.d.ts +0 -3
- package/build/reset-render.js +0 -74
- package/build/reset-render.js.map +0 -7
- package/build/services/domains.d.ts +0 -6
- package/build/services/navigation.d.ts +0 -50
- package/build/services/reference-fields.d.ts +0 -20
- package/build/services/register.d.ts +0 -36
- package/build/services/robots.d.ts +0 -19
- package/build/services/settings.d.ts +0 -4
- package/build/services/sites.d.ts +0 -29
- package/build/services/store.d.ts +0 -6
- package/build/start-render.js +0 -100
- package/build/start-render.js.map +0 -7
- package/build/types/templates.d.ts +0 -8
- package/build/upload-search-content.js +0 -74
- package/build/upload-search-content.js.map +0 -7
- package/build/utils/alerts.d.ts +0 -3
- package/build/utils/api.d.ts +0 -23
- package/build/utils/cache.d.ts +0 -35
- package/build/utils/core-utils.d.ts +0 -107
- package/build/utils/create-build-data.d.ts +0 -8
- package/build/utils/domains.d.ts +0 -13
- package/build/utils/folders.d.ts +0 -53
- package/build/utils/health-checks.d.ts +0 -7
- package/build/utils/images.d.ts +0 -16
- package/build/utils/loggin.d.ts +0 -51
- package/build/utils/pages.d.ts +0 -34
- package/build/utils/render.d.ts +0 -13
- package/build/utils/searches.d.ts +0 -15
- package/build/utils/sites.d.ts +0 -31
- package/build/utils/store.d.ts +0 -81
- package/cx.config.d.ts +0 -5
- package/cx.config.js +0 -36
- package/exporter/adapters/gatsby/index.ts +0 -162
- package/exporter/adapters/gatsby/utils.ts +0 -161
- package/exporter/artifacts/README.md +0 -34
- package/exporter/artifacts/index.ts +0 -33
- package/exporter/commands/move-assets.ts +0 -11
- package/exporter/constants/envs.ts +0 -94
- package/exporter/constants/index.ts +0 -129
- package/exporter/errors/index.ts +0 -40
- package/exporter/react/index.tsx +0 -11
- package/exporter/registers/api.ts +0 -14
- package/exporter/registers/gatsby.ts +0 -14
- package/exporter/registers/index.ts +0 -4
- package/exporter/services/register.ts +0 -113
- package/exporter/services/settings.ts +0 -17
- package/exporter/utils/alerts.ts +0 -29
- package/exporter/utils/api.ts +0 -243
- package/exporter/utils/cache.ts +0 -142
- package/exporter/utils/core-utils.ts +0 -458
- package/exporter/utils/create-build-data.ts +0 -17
- package/exporter/utils/domains.ts +0 -39
- package/exporter/utils/folders.ts +0 -320
- package/exporter/utils/health-checks.ts +0 -64
- package/exporter/utils/loggin.ts +0 -184
- package/exporter/utils/render.ts +0 -71
- package/exporter/utils/searches.ts +0 -156
- package/exporter/utils/sites.ts +0 -312
- package/exporter/utils/store.ts +0 -314
- package/src/README.md +0 -7
- package/start-render.js +0 -7
- /package/build/{utils → core}/instance.d.ts +0 -0
- /package/build/{constants → shared}/endpoints.d.ts +0 -0
- /package/exporter/{types → shared/types}/templates.ts +0 -0
- /package/{exporter/react/Favicon → react/GriddoFavicon}/utils.ts +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { SSG } from "./shared/types";
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { readDB, writeDB } from "../../core/db";
|
|
6
|
+
import { pathExists } from "../../core/fs";
|
|
7
|
+
import { GriddoLog } from "../../core/GriddoLog";
|
|
8
|
+
import { doLifeCycle } from "../../core/life-cycle";
|
|
9
|
+
import { initializeLogFile } from "../../core/logger";
|
|
10
|
+
import {
|
|
11
|
+
getRenderMetadataFromDB,
|
|
12
|
+
getRenderModeFromDB,
|
|
13
|
+
getRenderPathsHydratedWithDomainFromDB,
|
|
14
|
+
} from "../../services/render";
|
|
15
|
+
import { getRenderArtifacts } from "../../services/render-artifacts";
|
|
16
|
+
import { RenderContext } from "../../shared/context";
|
|
17
|
+
import { GRIDDO_ASSET_PREFIX, GRIDDO_BUILD_LOGS } from "../../shared/envs";
|
|
18
|
+
import { RENDER_MODE } from "../../shared/types/render";
|
|
19
|
+
import { cleanAction } from "./actions/clean";
|
|
20
|
+
import { closeAction } from "./actions/close";
|
|
21
|
+
import { dataAction } from "./actions/data";
|
|
22
|
+
import { healthCheckAction } from "./actions/healthCheck";
|
|
23
|
+
import { initAction } from "./actions/init";
|
|
24
|
+
import { logsAction } from "./actions/logs";
|
|
25
|
+
import { metaAction } from "./actions/meta";
|
|
26
|
+
import { prepareAction } from "./actions/prepare";
|
|
27
|
+
import { relocationAction } from "./actions/relocation";
|
|
28
|
+
import { restoreAction } from "./actions/restore";
|
|
29
|
+
import { ssgAction } from "./actions/ssg";
|
|
30
|
+
import { syncAction } from "./actions/sync";
|
|
31
|
+
import { getGatsbyArtifacts } from "./shared/artifacts";
|
|
32
|
+
import { disableRollbackOnError, enableRollbackOnError } from "./shared/utils";
|
|
33
|
+
|
|
34
|
+
const FILE_LOGS_ENABLED = GRIDDO_BUILD_LOGS; // && GRIDDO_BUILD_LOGS_TO_FILE;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Executes a complete Gatsby render process for a given domain.
|
|
38
|
+
*
|
|
39
|
+
* The render process consists of multiple lifecycle steps that handle cleaning,
|
|
40
|
+
* preparing, building and syncing the site. It can run in two modes:
|
|
41
|
+
* - FROM_SCRATCH: Performs a complete rebuild of the domains sites.
|
|
42
|
+
* - INCREMENTAL: Only rebuilds changed pages while preserving the rest.
|
|
43
|
+
*/
|
|
44
|
+
export async function gatsbyRenderDomain(domain: string) {
|
|
45
|
+
// Init render log
|
|
46
|
+
await initializeLogFile();
|
|
47
|
+
|
|
48
|
+
// Render data
|
|
49
|
+
const { renderMode, reason } = await getRenderModeFromDB(domain);
|
|
50
|
+
const renderMetadata = await getRenderMetadataFromDB();
|
|
51
|
+
const pathsHydratedWithDomain = await getRenderPathsHydratedWithDomainFromDB({ domain });
|
|
52
|
+
const { __ssg } = pathsHydratedWithDomain;
|
|
53
|
+
|
|
54
|
+
// Artifacts
|
|
55
|
+
const renderArtifacts = await getRenderArtifacts(domain);
|
|
56
|
+
const ssgArtifacts = await getGatsbyArtifacts(domain);
|
|
57
|
+
|
|
58
|
+
// SSG Gatsby assetPrefix
|
|
59
|
+
const assetPrefix = GRIDDO_ASSET_PREFIX ? `${GRIDDO_ASSET_PREFIX}/${domain}` : "";
|
|
60
|
+
|
|
61
|
+
// Previous render error management
|
|
62
|
+
const domainSentinelFile = await pathExists(path.join(__ssg, `.render-sentinel-${domain}`));
|
|
63
|
+
const derivedRenderMode = domainSentinelFile ? RENDER_MODE.FROM_SCRATCH : renderMode;
|
|
64
|
+
const derivedRenderModeReason = domainSentinelFile ? "previous render error" : reason;
|
|
65
|
+
|
|
66
|
+
const data = await readDB();
|
|
67
|
+
data.domains[domain].renderMode = derivedRenderMode;
|
|
68
|
+
data.domains[domain].renderModeReason = derivedRenderModeReason;
|
|
69
|
+
data.domains[domain].isRendering = false;
|
|
70
|
+
|
|
71
|
+
if (renderMode === RENDER_MODE.IDLE && derivedRenderMode === RENDER_MODE.IDLE) {
|
|
72
|
+
GriddoLog.info(
|
|
73
|
+
`(From Current Render) [${domain}]: Skipping start-render as it is marked as IDLE with the reason ${reason}.`,
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
data.domains[domain].isRendering = true;
|
|
79
|
+
data.currentRenderingDomain = domain;
|
|
80
|
+
await writeDB(data);
|
|
81
|
+
|
|
82
|
+
// Render mode reason to log information to the terminal
|
|
83
|
+
const renderModeReason = derivedRenderModeReason ? ` <${derivedRenderModeReason}>` : "";
|
|
84
|
+
GriddoLog.info(`Init render (${derivedRenderMode})${renderModeReason} for domain ${domain}\n`);
|
|
85
|
+
|
|
86
|
+
// Render context
|
|
87
|
+
const context = new RenderContext<SSG>({
|
|
88
|
+
domain,
|
|
89
|
+
pathsHydratedWithDomain,
|
|
90
|
+
renderArtifacts,
|
|
91
|
+
renderMetadata,
|
|
92
|
+
renderMode: derivedRenderMode,
|
|
93
|
+
ssg: { assetPrefix, ssgArtifacts },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// A las actions se les pasa siempre `context` y cada action ya utiliza lo que necesita.
|
|
97
|
+
// Si una action necesita devolver algo para otra, se guarda en el context.
|
|
98
|
+
// Ejemplo: el dataAction obtiene los ids de páginas para renderizar y
|
|
99
|
+
// borrar y los guarda en context.pathsToRender y context.pathsToDelete.
|
|
100
|
+
await doLifeCycle("Init", async () => initAction(context));
|
|
101
|
+
await doLifeCycle("Clean", async () => cleanAction(context));
|
|
102
|
+
await doLifeCycle("Prepare", async () => prepareAction(context));
|
|
103
|
+
await doLifeCycle("Restore", async () => restoreAction(context));
|
|
104
|
+
await enableRollbackOnError();
|
|
105
|
+
await doLifeCycle("Data", async () => dataAction(context));
|
|
106
|
+
await doLifeCycle("SSG", async () => ssgAction(context));
|
|
107
|
+
await doLifeCycle("Relocation", async () => relocationAction(context));
|
|
108
|
+
await doLifeCycle("Meta", async () => metaAction(context));
|
|
109
|
+
await doLifeCycle("Sync", async () => syncAction(context));
|
|
110
|
+
await doLifeCycle("HealthCheck", async () => healthCheckAction(context));
|
|
111
|
+
await doLifeCycle("Logs", async () => logsAction(context), { skip: !FILE_LOGS_ENABLED ? "Build logs to file are disabled" : false }); // biome-ignore format: oneliner
|
|
112
|
+
await doLifeCycle("Close", async () => closeAction(context));
|
|
113
|
+
await disableRollbackOnError();
|
|
114
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { getRenderPathsHydratedWithDomainFromDB } from "../../../services/render";
|
|
4
|
+
|
|
5
|
+
export async function getGatsbyArtifacts(domain: string) {
|
|
6
|
+
const { __ssg } = await getRenderPathsHydratedWithDomainFromDB({ domain });
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
disposables: [
|
|
10
|
+
path.join(__ssg, "public"),
|
|
11
|
+
path.join(__ssg, "static"),
|
|
12
|
+
path.join(__ssg, ".cache"),
|
|
13
|
+
],
|
|
14
|
+
cacheables: [".cache"],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { GriddoLog } from "../../../core/GriddoLog";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Patrones de archivos de assets críticos para la comparación de builds.
|
|
9
|
+
* Estos archivos son los que realmente afectan el webpackCompilationHash:
|
|
10
|
+
* - app-*.js: Código principal de la aplicación
|
|
11
|
+
* - framework-*.js: Framework de Gatsby/React
|
|
12
|
+
* - webpack-runtime-*.js: Runtime de webpack
|
|
13
|
+
*
|
|
14
|
+
* Si estos archivos no cambian entre renders, el hash debería ser el mismo.
|
|
15
|
+
*/
|
|
16
|
+
const _KEY_ASSET_PATTERNS = [/^app-.*\.js$/, /^framework-.*\.js$/, /^webpack-runtime-.*\.js$/];
|
|
17
|
+
|
|
18
|
+
const KEY_ASSET_PATTERNS = [/\.js$/];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Calcula los hashes SHA256 del contenido de los assets clave en un directorio.
|
|
22
|
+
* Solo procesa archivos que coincidan con KEY_ASSET_PATTERNS.
|
|
23
|
+
*
|
|
24
|
+
* @param dir La ruta al directorio (ej. './public')
|
|
25
|
+
* @returns Un Map donde la clave es el nombre del fichero y el valor es su hash.
|
|
26
|
+
* Si el directorio no existe o hay errores, retorna un Map vacío.
|
|
27
|
+
*/
|
|
28
|
+
async function getAssetHashes(dir: string): Promise<Map<string, string>> {
|
|
29
|
+
const assetHashes = new Map<string, string>();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const allFiles = await fsp.readdir(dir);
|
|
33
|
+
const keyAssetFiles = allFiles.filter((fileName) =>
|
|
34
|
+
KEY_ASSET_PATTERNS.some((pattern) => pattern.test(fileName)),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
for (const fileName of keyAssetFiles) {
|
|
38
|
+
const filePath = join(dir, fileName);
|
|
39
|
+
const fileContent = await fsp.readFile(filePath);
|
|
40
|
+
const hash = createHash("sha256").update(fileContent).digest("hex");
|
|
41
|
+
assetHashes.set(fileName, hash);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return assetHashes;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
GriddoLog.build(`ERROR: Error processing directory ${dir}: ${error}`);
|
|
47
|
+
// Si un directorio no existe (ej. el de producción la primera vez),
|
|
48
|
+
// lo tratamos como si no tuviera assets.
|
|
49
|
+
return assetHashes;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compara los assets clave entre dos directorios de build de Gatsby.
|
|
55
|
+
* Solo compara los archivos definidos en KEY_ASSET_PATTERNS.
|
|
56
|
+
*
|
|
57
|
+
* @param dir1 Ruta al primer directorio (ej. el de producción)
|
|
58
|
+
* @param dir2 Ruta al segundo directorio (ej. el del nuevo build)
|
|
59
|
+
* @returns `true` si hay diferencias en los assets clave, `false` si son idénticos.
|
|
60
|
+
*/
|
|
61
|
+
export async function getAssetsDiffBetweenRenders(dir1: string, dir2: string): Promise<boolean> {
|
|
62
|
+
const [hashes1, hashes2] = await Promise.all([getAssetHashes(dir1), getAssetHashes(dir2)]);
|
|
63
|
+
|
|
64
|
+
// Si el número de assets clave es diferente, hay cambios.
|
|
65
|
+
if (hashes1.size !== hashes2.size) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Comparamos los hashes de cada fichero.
|
|
70
|
+
for (const [fileName, hash1] of hashes1.entries()) {
|
|
71
|
+
const hash2 = hashes2.get(fileName);
|
|
72
|
+
|
|
73
|
+
// Si un fichero existe en el primero pero no en el segundo, hay cambios.
|
|
74
|
+
if (!hash2) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Si el contenido (hash) de un fichero ha cambiado.
|
|
79
|
+
if (hash1 !== hash2) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Actualiza el webpackCompilationHash en un único fichero HTML.
|
|
89
|
+
* @param filePath Ruta al fichero HTML.
|
|
90
|
+
* @param newHash El nuevo hash a insertar.
|
|
91
|
+
*/
|
|
92
|
+
export async function patchHtmlFile(filePath: string, newHash: string): Promise<void> {
|
|
93
|
+
try {
|
|
94
|
+
const content = await fsp.readFile(filePath, "utf-8");
|
|
95
|
+
|
|
96
|
+
// Esta expresión regular captura tres grupos:
|
|
97
|
+
// 1. La parte ANTES del hash (window.___webpackCompilationHash=")
|
|
98
|
+
// 2. El hash ANTIGUO ([^"]*)
|
|
99
|
+
// 3. La parte DESPUÉS del hash (";</script>)
|
|
100
|
+
const regex = /(window\.___webpackCompilationHash=")([^"]*)(";<\/script>)/;
|
|
101
|
+
|
|
102
|
+
// Solo reemplazamos el segundo grupo (el hash)
|
|
103
|
+
const newContent = content.replace(regex, `$1${newHash}$3`);
|
|
104
|
+
|
|
105
|
+
if (content === newContent) {
|
|
106
|
+
// GriddoLog.warn(`WARN: No se encontró el hash en ${filePath}. El fichero no fue modificado.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await fsp.writeFile(filePath, newContent, "utf-8");
|
|
111
|
+
} catch (error) {
|
|
112
|
+
GriddoLog.error(`Failed to process file ${filePath}: ${error}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Ejemplo de uso ---
|
|
117
|
+
// async function miScriptDeDeploy() {
|
|
118
|
+
// const dirProduccion = '/ruta/a/produccion/public';
|
|
119
|
+
// const dirNuevoBuild = './public';
|
|
120
|
+
|
|
121
|
+
// const hayDiferencias = await getAssetsDiffBetweenRenders(dirProduccion, dirNuevoBuild);
|
|
122
|
+
|
|
123
|
+
// if (hayDiferencias) {
|
|
124
|
+
// // Lógica para el CAMINO A (cambio de código)
|
|
125
|
+
// } else {
|
|
126
|
+
// // Lógica para el CAMINO B (solo contenido)
|
|
127
|
+
// }
|
|
128
|
+
// }
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { pathExists } from "../../../core/fs";
|
|
5
|
+
import { getRenderPathsHydratedWithDomainFromDB } from "../../../services/render";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Update the Griddo's `/dist` dir with the contents from `public` dir only
|
|
9
|
+
* with files of type: js, json and css.
|
|
10
|
+
* TODO: Explicar que el static se copia a assets porque el js va en el subdominio de assets.
|
|
11
|
+
*/
|
|
12
|
+
async function extractAssetsFromDist(domain: string) {
|
|
13
|
+
const { __root, __exports } = await getRenderPathsHydratedWithDomainFromDB({ domain });
|
|
14
|
+
|
|
15
|
+
// Archivos (no carpetas) válidos de public
|
|
16
|
+
const filesFromDist = (await fsp.readdir(path.join(__exports, "dist"))).filter(
|
|
17
|
+
(file) =>
|
|
18
|
+
path.extname(file) === ".js" ||
|
|
19
|
+
path.extname(file) === ".json" ||
|
|
20
|
+
path.extname(file) === ".css",
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Creamos assets si es necesario (needsAssetPrefix)
|
|
24
|
+
await fsp.mkdir(path.join(__root, "assets"), { recursive: true });
|
|
25
|
+
// page-data folder
|
|
26
|
+
await fsp.cp(
|
|
27
|
+
path.join(__exports, "dist", "page-data"),
|
|
28
|
+
path.join(__root, "assets", "page-data"),
|
|
29
|
+
{
|
|
30
|
+
preserveTimestamps: true,
|
|
31
|
+
recursive: true,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
// static folder si existe
|
|
35
|
+
if (await pathExists(path.join(__root, "static"))) {
|
|
36
|
+
await fsp.cp(path.join(__root, "static"), path.join(__root, "assets"), {
|
|
37
|
+
force: false,
|
|
38
|
+
preserveTimestamps: true,
|
|
39
|
+
recursive: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// dist/static -> assets/static
|
|
43
|
+
if (await pathExists(path.join(__exports, "dist", "static"))) {
|
|
44
|
+
await fsp.cp(path.join(__exports, "dist", "static"), path.join(__root, "assets", "static"), {
|
|
45
|
+
force: false,
|
|
46
|
+
preserveTimestamps: true,
|
|
47
|
+
recursive: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// otro static...
|
|
51
|
+
if (await pathExists(path.join(__root, "static"))) {
|
|
52
|
+
await fsp.cp(path.join(__root, "static"), path.join(__exports, "dist", domain), {
|
|
53
|
+
preserveTimestamps: true,
|
|
54
|
+
recursive: true,
|
|
55
|
+
force: false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Copia el resto de archivos...
|
|
60
|
+
// for (const file of filesFromDist) {
|
|
61
|
+
// const fileSrc = path.join(__exports, "dist", file);
|
|
62
|
+
// const fileDest = path.join(__root, "assets", file);
|
|
63
|
+
// await fsp.cp(fileSrc, fileDest, { preserveTimestamps: true, recursive: true });
|
|
64
|
+
// }
|
|
65
|
+
|
|
66
|
+
const arraysOfPromises: any[] = filesFromDist.map((file) => {
|
|
67
|
+
const fileSrc = path.join(__exports, "dist", file);
|
|
68
|
+
const fileDest = path.join(__root, "assets", file);
|
|
69
|
+
return fsp.cp(fileSrc, fileDest, { preserveTimestamps: true, recursive: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await Promise.all(arraysOfPromises);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { extractAssetsFromDist };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { GriddoLog } from "../../../core/GriddoLog";
|
|
5
|
+
import { getRenderPathsHydratedWithDomainFromDB } from "../../../services/render";
|
|
6
|
+
import { GRIDDO_SSG_VERBOSE_LOGS } from "../../../shared/envs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Spawn a new node process `gatsby build`
|
|
10
|
+
* @note This proccess can not access to the custom Griddo `process.env` so it
|
|
11
|
+
* needs variables passed to it via the `env` prop.
|
|
12
|
+
*/
|
|
13
|
+
async function gatsbyBuild(assetPrefixWithDomain: string): Promise<void> {
|
|
14
|
+
GriddoLog.verbose(`read assetPrefixWithDomain, ${assetPrefixWithDomain}`);
|
|
15
|
+
GriddoLog.verbose(`using this NODE_OPTIONS in gatsby command: ${process.env.NODE_OPTIONS}`);
|
|
16
|
+
|
|
17
|
+
const { __ssg } = await getRenderPathsHydratedWithDomainFromDB();
|
|
18
|
+
|
|
19
|
+
// Remove `--openssl-legacy-provider` from NODE_OPTIONS because this value
|
|
20
|
+
// break Gatsby render.
|
|
21
|
+
const nodeOptionsWithoutLegacyOpenSSL = process.env.NODE_OPTIONS
|
|
22
|
+
? process.env.NODE_OPTIONS.replace(/--openssl-legacy-provider\s*/g, "").trim()
|
|
23
|
+
: "";
|
|
24
|
+
|
|
25
|
+
// Ruta al ejecutable de Gatsby
|
|
26
|
+
const gatsbyExecutable = path.join(__ssg, "node_modules", ".bin", "gatsby");
|
|
27
|
+
|
|
28
|
+
const args = ["build", "--prefix-paths"];
|
|
29
|
+
if (GRIDDO_SSG_VERBOSE_LOGS) {
|
|
30
|
+
args.push("--verbose");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const child = spawn(gatsbyExecutable, args, {
|
|
35
|
+
cwd: __ssg,
|
|
36
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
SPAWN_ASSET_PREFIX_WITH_DOMAIN: assetPrefixWithDomain,
|
|
40
|
+
NODE_OPTIONS: nodeOptionsWithoutLegacyOpenSSL,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on("close", (code) => {
|
|
45
|
+
if (code === 0) {
|
|
46
|
+
resolve();
|
|
47
|
+
} else {
|
|
48
|
+
reject(new Error(`Gatsby build failed with exit code ${code}`));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
child.on("error", (error) => {
|
|
53
|
+
reject(new Error(`Failed to start Gatsby build process: ${error.message}`));
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { gatsbyBuild };
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import pLimit from "p-limit";
|
|
5
|
+
|
|
6
|
+
import { findFilesBySuffix } from "../../../core/fs";
|
|
7
|
+
import { GriddoLog } from "../../../core/GriddoLog";
|
|
8
|
+
import { GRIDDO_SSG_BUNDLE_ANALYZER } from "../../../shared/envs";
|
|
9
|
+
import { getAssetsDiffBetweenRenders, patchHtmlFile } from "./diff-assets";
|
|
10
|
+
|
|
11
|
+
interface SyncRenderConfig {
|
|
12
|
+
src: string;
|
|
13
|
+
dst: string;
|
|
14
|
+
pagesToCreate: number[];
|
|
15
|
+
pagesToDelete: number[];
|
|
16
|
+
artifactsToCopyToExports: string[];
|
|
17
|
+
}
|
|
18
|
+
interface CopyFilePath {
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
}
|
|
22
|
+
interface SyncState {
|
|
23
|
+
htmlToAdd: CopyFilePath[];
|
|
24
|
+
jsonToAdd: CopyFilePath[];
|
|
25
|
+
htmlToDelete: string[];
|
|
26
|
+
jsonToDelete: string[];
|
|
27
|
+
}
|
|
28
|
+
interface PageInfo {
|
|
29
|
+
id: number;
|
|
30
|
+
composePath: string; // p.ej. '/about-us' o '/'
|
|
31
|
+
htmlPath: string; // p.ej. '/path/to/public/about-us/index.html'
|
|
32
|
+
jsonPath: string; // p.ej. '/path/to/public/page-data/about-us/page-data.json'
|
|
33
|
+
}
|
|
34
|
+
interface GatsbyPageData {
|
|
35
|
+
result: {
|
|
36
|
+
pageContext: {
|
|
37
|
+
id: number;
|
|
38
|
+
fullPath: {
|
|
39
|
+
site: string;
|
|
40
|
+
domain: string;
|
|
41
|
+
domainUrl: string;
|
|
42
|
+
language: string;
|
|
43
|
+
page: string;
|
|
44
|
+
compose: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class SyncRender {
|
|
51
|
+
private bundleDir: string;
|
|
52
|
+
private currentRenderDir: string;
|
|
53
|
+
private pagesToCreate: number[];
|
|
54
|
+
private pagesToDelete: number[];
|
|
55
|
+
private assetArtifacts: string[];
|
|
56
|
+
|
|
57
|
+
private state: SyncState = {
|
|
58
|
+
htmlToAdd: [],
|
|
59
|
+
jsonToAdd: [],
|
|
60
|
+
htmlToDelete: [],
|
|
61
|
+
jsonToDelete: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
constructor(config: SyncRenderConfig) {
|
|
65
|
+
this.bundleDir = config.dst;
|
|
66
|
+
this.currentRenderDir = config.src;
|
|
67
|
+
this.pagesToCreate = config.pagesToCreate;
|
|
68
|
+
this.pagesToDelete = config.pagesToDelete;
|
|
69
|
+
this.assetArtifacts = config.artifactsToCopyToExports;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async execute() {
|
|
73
|
+
await this.setPagesToDelete();
|
|
74
|
+
await this.setPagesToAdd();
|
|
75
|
+
await this.sync();
|
|
76
|
+
if (GRIDDO_SSG_BUNDLE_ANALYZER) {
|
|
77
|
+
await this.assertAssetsAreValid();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async assertAssetsAreValid() {
|
|
82
|
+
const isDifferent = await getAssetsDiffBetweenRenders(this.currentRenderDir, this.bundleDir);
|
|
83
|
+
if (isDifferent) {
|
|
84
|
+
GriddoLog.info(
|
|
85
|
+
"Warning: JavaScript and CSS bundles must have the same files and content. You can ignore this warning if the rendering process only has pages to unpublish.",
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
GriddoLog.info("Previous and current JavaScript and CSS packages successfully matched");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async setPagesToDelete() {
|
|
93
|
+
// INCLUIMOS LAS PÁGINAS A CREAR EN LAS CANDIDATAS A BORRAR
|
|
94
|
+
// =======================================================
|
|
95
|
+
// El set de ids incluye tanto las páginas a borrar como las que se van
|
|
96
|
+
// a crear para manejar correctamente los cambios de slug.
|
|
97
|
+
//
|
|
98
|
+
// Ejemplo: Si la página `id=3` tiene slug `about` y después viene con
|
|
99
|
+
// `slug=about-us`, necesitamos borrar la página `id=3` del render previo
|
|
100
|
+
// y volver a crearla con la nueva ruta.
|
|
101
|
+
//
|
|
102
|
+
// ¿Por qué? Porque el slug determina dónde se guarda la página:
|
|
103
|
+
// - /about/index.html -> /about-us/index.html
|
|
104
|
+
//
|
|
105
|
+
// Si no la borrásemos previamente, tendríamos ambas páginas:
|
|
106
|
+
// `/about/index.html` y `/about-us/index.html`
|
|
107
|
+
const candidateIdsToDelete = new Set<number>([...this.pagesToDelete, ...this.pagesToCreate]);
|
|
108
|
+
const existingPreviousPages = await this.scanPages(this.bundleDir);
|
|
109
|
+
|
|
110
|
+
for (const page of existingPreviousPages) {
|
|
111
|
+
if (candidateIdsToDelete.has(page.id)) {
|
|
112
|
+
this.state.htmlToDelete.push(page.htmlPath);
|
|
113
|
+
this.state.jsonToDelete.push(page.jsonPath);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
GriddoLog.verbose(`${this.state.htmlToDelete.length} pages HTML to delete`);
|
|
118
|
+
GriddoLog.verbose(`${this.state.jsonToDelete.length} pages JSON to delete`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async setPagesToAdd() {
|
|
122
|
+
const candidateIdsToCreate = new Set<number>(this.pagesToCreate);
|
|
123
|
+
const newPages = await this.scanPages(this.currentRenderDir);
|
|
124
|
+
|
|
125
|
+
for (const page of newPages) {
|
|
126
|
+
if (candidateIdsToCreate.has(page.id)) {
|
|
127
|
+
const htmlTo = path.join(this.bundleDir, page.composePath, "index.html");
|
|
128
|
+
const normalizedCompose = page.composePath === "/" ? "index" : page.composePath;
|
|
129
|
+
const jsonTo = path.join(this.bundleDir, "page-data", normalizedCompose, "page-data.json");
|
|
130
|
+
|
|
131
|
+
this.state.htmlToAdd.push({ from: page.htmlPath, to: htmlTo });
|
|
132
|
+
this.state.jsonToAdd.push({ from: page.jsonPath, to: jsonTo });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
GriddoLog.verbose(`${this.state.htmlToAdd.length} pages HTML to create`);
|
|
137
|
+
GriddoLog.verbose(`${this.state.jsonToAdd.length} pages JSON to create`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private async sync() {
|
|
141
|
+
// Delete...
|
|
142
|
+
const allFilesToDelete = [...this.state.htmlToDelete, ...this.state.jsonToDelete];
|
|
143
|
+
for (const file of allFilesToDelete) {
|
|
144
|
+
try {
|
|
145
|
+
// Usar `force: true` para no fallar si el archivo ya no existe.
|
|
146
|
+
await fsp.rm(file, { force: true });
|
|
147
|
+
GriddoLog.verbose(`Sync (remove) : ${file}`);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// El error solo se registraría si es un problema de permisos, etc.
|
|
150
|
+
GriddoLog.error(`Failed to remove ${file}:`, e);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await this.restoreWebpackCompilationHash();
|
|
155
|
+
|
|
156
|
+
// Copy...
|
|
157
|
+
const allFilesToAdd = [...this.state.htmlToAdd, ...this.state.jsonToAdd];
|
|
158
|
+
for (const file of allFilesToAdd) {
|
|
159
|
+
try {
|
|
160
|
+
await fsp.mkdir(path.dirname(file.to), { recursive: true });
|
|
161
|
+
await fsp.copyFile(file.from, file.to);
|
|
162
|
+
GriddoLog.verbose(`Sync (copy) : ${file.from} -> ${file.to}`);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
GriddoLog.error(`Failed to copy ${file.from} to ${file.to}:`, e);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// AVOID TO RESTORE webpackCompilationHash
|
|
169
|
+
// await this.copyAppDataJsonFile();
|
|
170
|
+
await this.copyAssetArtifacts();
|
|
171
|
+
await this.copySitemaps();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// TODO: Optimizar esto a lo bestia...
|
|
175
|
+
private async restoreWebpackCompilationHash() {
|
|
176
|
+
const appDataJson = path.join(this.bundleDir, "page-data", "app-data.json");
|
|
177
|
+
const webpackCompilationHash = JSON.parse(
|
|
178
|
+
await fsp.readFile(appDataJson, "utf8"),
|
|
179
|
+
).webpackCompilationHash;
|
|
180
|
+
GriddoLog.build(
|
|
181
|
+
`Reading webpackCompilationHash from ${appDataJson} = ${webpackCompilationHash}`,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const changedHtmlFilePaths = this.state.htmlToAdd.map(({ from }) => from);
|
|
185
|
+
|
|
186
|
+
for (const htmlFilePath of changedHtmlFilePaths) {
|
|
187
|
+
await patchHtmlFile(htmlFilePath, webpackCompilationHash);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// private async copyAppDataJsonFile() {
|
|
192
|
+
// try {
|
|
193
|
+
// const src = path.join(this.currentRenderDir, "page-data", "app-data.json");
|
|
194
|
+
// const dst = path.join(this.bundleDir, "page-data", "app-data.json");
|
|
195
|
+
// await fsp.copyFile(src, dst);
|
|
196
|
+
// GriddoLog.verbose("Copied app-data.json");
|
|
197
|
+
// } catch (e) {
|
|
198
|
+
// GriddoLog.error("Failed to copy app-data.json:", e);
|
|
199
|
+
// }
|
|
200
|
+
// }
|
|
201
|
+
|
|
202
|
+
private async copyAssetArtifacts() {
|
|
203
|
+
for (const assetArtifact of this.assetArtifacts) {
|
|
204
|
+
const src = path.join(this.currentRenderDir, assetArtifact);
|
|
205
|
+
const dst = path.join(this.bundleDir, assetArtifact);
|
|
206
|
+
try {
|
|
207
|
+
await fsp.mkdir(path.dirname(dst), { recursive: true });
|
|
208
|
+
await fsp.copyFile(src, dst);
|
|
209
|
+
GriddoLog.verbose(`Copied artifact: ${assetArtifact}`);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
GriddoLog.error(`Failed to copy artifact ${src} to ${dst}:`, e);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async copySitemaps() {
|
|
217
|
+
const renderSrc = path.resolve(this.currentRenderDir);
|
|
218
|
+
const renderDst = path.resolve(this.bundleDir);
|
|
219
|
+
|
|
220
|
+
// Primero, elimina los sitemaps existentes en el destino
|
|
221
|
+
const xmlFilesInDst = findFilesBySuffix(renderDst, ".xml");
|
|
222
|
+
for await (const xmlFile of xmlFilesInDst) {
|
|
223
|
+
const basename = path.basename(xmlFile);
|
|
224
|
+
if (basename.startsWith("sitemap-") || basename === "sitemap.xml") {
|
|
225
|
+
await fsp.rm(xmlFile, { force: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Segundo, copia los nuevos sitemaps desde el origen
|
|
230
|
+
const xmlFilesInSrc = findFilesBySuffix(renderSrc, ".xml");
|
|
231
|
+
for await (const src of xmlFilesInSrc) {
|
|
232
|
+
// Reconstruye la ruta de destino de forma más robusta
|
|
233
|
+
const relativePath = path.relative(renderSrc, src);
|
|
234
|
+
const dst = path.join(renderDst, relativePath);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await fsp.mkdir(path.dirname(dst), { recursive: true });
|
|
238
|
+
await fsp.copyFile(src, dst);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
GriddoLog.error(`Failed to copy sitemap ${src} to ${dst}:`, e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
GriddoLog.verbose(`Copied sitemap files.`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Escanea un directorio de build de Gatsby y extrae la información esencial de cada página.
|
|
249
|
+
* Lee cada page-data.json una sola vez para optimizar el rendimiento.
|
|
250
|
+
* @param dir Directorio a escanear (src o dst).
|
|
251
|
+
* @returns Un array con la información de cada página encontrada.
|
|
252
|
+
*/
|
|
253
|
+
private async scanPages(dir: string): Promise<PageInfo[]> {
|
|
254
|
+
// 1. Crea una instancia de p-limit con 128 lecturas a la vez...
|
|
255
|
+
// Como ejemplo: macOS tiene un límite que suele empezar en 256.
|
|
256
|
+
const limit = pLimit(128);
|
|
257
|
+
|
|
258
|
+
// 2. Recolecta todos los paths de los archivos.
|
|
259
|
+
const gatsbyPageDataGenerator = findFilesBySuffix(
|
|
260
|
+
path.join(dir, "page-data"),
|
|
261
|
+
"page-data.json",
|
|
262
|
+
);
|
|
263
|
+
const jsonFilePaths: string[] = [];
|
|
264
|
+
for await (const file of gatsbyPageDataGenerator) {
|
|
265
|
+
jsonFilePaths.push(file);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 3. Mapea cada path a una promesa envuelta por pLimit.
|
|
269
|
+
const processingPromises = jsonFilePaths.map((jsonFile) => {
|
|
270
|
+
return limit(async (): Promise<PageInfo | null> => {
|
|
271
|
+
try {
|
|
272
|
+
const fileContent = await fsp.readFile(jsonFile, "utf-8");
|
|
273
|
+
const content = JSON.parse(fileContent) as GatsbyPageData;
|
|
274
|
+
|
|
275
|
+
const id = content.result.pageContext.id;
|
|
276
|
+
const composePath = content.result.pageContext.fullPath.compose;
|
|
277
|
+
const normalizedCompose = composePath === "/" ? "index" : composePath;
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
id,
|
|
281
|
+
composePath,
|
|
282
|
+
htmlPath: path.join(dir, composePath, "index.html"),
|
|
283
|
+
jsonPath: path.join(dir, "page-data", normalizedCompose, "page-data.json"),
|
|
284
|
+
};
|
|
285
|
+
} catch (e) {
|
|
286
|
+
GriddoLog.error(`Error reading or parsing page data from ${jsonFile}:`, e);
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// 4. Go :)
|
|
293
|
+
const pagesFoundOrNull = await Promise.all(processingPromises);
|
|
294
|
+
|
|
295
|
+
// 5. Filtra los nulos y devuelve solo las páginas válidas.
|
|
296
|
+
return pagesFoundOrNull.filter((page): page is PageInfo => page !== null);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { SyncRender };
|