@paroicms/site-generator-plugin
Version:
ParoiCMS Site Generator Plugin
251 lines (250 loc) • 9.36 kB
JavaScript
import { encodeLNodeId, } from "@paroicms/public-anywhere-lib";
import { getHandleOfFeaturedImage, getHandleOfFieldOnNode } from "@paroicms/public-server-lib";
import { dedupMessages, getTermTypeNames } from "./content-helpers.js";
import { generateFieldSetContent, generateMultipleFieldSetContents, } from "./generate-fake-content.js";
export async function updateRoutingDocument(ctx, report, nodeOptions) {
ctx.logger.debug(`[TASK] Updating routing document "${nodeOptions.nodeType.typeName}"…`);
const { clusterNode, nodeType } = nodeOptions;
const { siteSchema, schemaI18n, siteConnector } = ctx;
const content = await generateFieldSetContent(ctx, {
nodeType,
documentType: nodeType,
siteSchema,
schemaI18n,
withTitle: false,
llmTaskName: nodeType.kebabName,
}, report);
const [firstLanguage, ...otherLanguages] = siteSchema.languages;
// Update fields only for the first language
const firstLNodeId = encodeLNodeId({ nodeId: clusterNode.nodeId, language: firstLanguage });
await siteConnector.updateFields(firstLNodeId, content.fields);
report.addId({
typeName: nodeType.typeName,
id: {
nodeId: clusterNode.nodeId,
language: firstLanguage,
},
parentNodeId: undefined,
});
// For other languages, only report the ID (lNode exists but has no field values)
for (const language of otherLanguages) {
report.addId({
typeName: nodeType.typeName,
id: {
nodeId: clusterNode.nodeId,
language,
},
parentNodeId: undefined,
});
}
}
export async function addRegularDocuments(ctx, report, nodeOptions) {
// Special case: author terms are created without LLM
if (nodeOptions.nodeType.typeName === "author") {
await addSingleAuthor(ctx, report, nodeOptions);
return;
}
ctx.logger.debug(`[TASK] Adding regular documents "${nodeOptions.nodeType.typeName}"…`);
const { parentNodeId, nodeType, documentType } = nodeOptions;
const { siteSchema, schemaI18n, siteConnector } = ctx;
const termTypeNames = getTermTypeNames(siteSchema);
const isTaxonomyTerm = termTypeNames.has(nodeType.typeName);
const tolerateErrors = {
errorMessages: [],
};
const maxAttempts = 3;
let list = [];
for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
list = await generateMultipleFieldSetContents(ctx, {
siteSchema,
nodeType,
documentType,
schemaI18n,
count: getDefaultNodeContentCount(nodeType, siteSchema),
withTitle: true,
tolerateErrors,
llmTaskName: nodeType.kebabName,
isTaxonomyTerm,
}, report);
if (list.length > 0)
break;
if (attempt < maxAttempts) {
ctx.logger.debug(`[RETRY] Empty result for ${nodeType.typeName}, attempt ${attempt}/${maxAttempts}`);
}
}
const errorMessages = dedupMessages(tolerateErrors.errorMessages);
if (errorMessages.length > 0) {
ctx.logger.warn(`Error generating content for ${nodeType.typeName}:\n - ${errorMessages.join("\n - ")}`);
}
for (const content of list) {
const [firstLanguage, ...otherLanguages] = siteSchema.languages;
const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
const title = content.title?.[firstLanguage];
const info = await siteConnector.createDocument({
parentLNodeId,
typeName: nodeType.typeName,
title,
values: content.fields,
});
const nodeId = info.nodeId;
await uploadMediaEntries(ctx, nodeId, content.medias);
report.addId({
typeName: nodeType.typeName,
id: { nodeId, language: firstLanguage },
parentNodeId,
});
for (const language of otherLanguages) {
if (!content.title?.[language])
continue;
await siteConnector.createDocumentTranslation({
nodeId,
language,
title: content.title?.[language],
values: content.fields,
});
report.addId({
typeName: nodeType.typeName,
id: { nodeId, language },
parentNodeId,
});
}
}
}
const HERO_NAMES = [
"Mario",
"Luigi",
"Link",
"Zelda",
"Kirby",
"Pikachu",
"Yoshi",
"Sonic",
"Toad",
"Peach",
];
async function addSingleAuthor(ctx, report, nodeOptions) {
ctx.logger.debug("[TASK] Adding single author…");
const { parentNodeId, nodeType } = nodeOptions;
const { siteSchema, siteConnector } = ctx;
const title = HERO_NAMES[Math.floor(Math.random() * HERO_NAMES.length)];
const [firstLanguage, ...otherLanguages] = siteSchema.languages;
const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
const info = await siteConnector.createDocument({
parentLNodeId,
typeName: nodeType.typeName,
title,
values: {},
});
const nodeId = info.nodeId;
report.addId({
typeName: nodeType.typeName,
id: { nodeId, language: firstLanguage },
parentNodeId,
});
for (const language of otherLanguages) {
await siteConnector.createDocumentTranslation({
nodeId,
language,
title,
values: {},
});
report.addId({
typeName: nodeType.typeName,
id: { nodeId, language },
parentNodeId,
});
}
}
export async function addParts(ctx, report, nodeOptions) {
ctx.logger.debug(`[TASK] Adding parts "${nodeOptions.nodeType.typeName}"…`);
const { parentNodeId, nodeType, documentType } = nodeOptions;
const { siteSchema, schemaI18n, siteConnector } = ctx;
const tolerateErrors = {
errorMessages: [],
};
const list = await generateMultipleFieldSetContents(ctx, {
siteSchema,
nodeType,
documentType,
schemaI18n,
count: getDefaultNodeContentCount(nodeType, siteSchema),
withTitle: true,
tolerateErrors,
llmTaskName: nodeType.kebabName,
}, report);
const errorMessages = dedupMessages(tolerateErrors.errorMessages);
if (errorMessages.length > 0) {
ctx.logger.warn(`Error generating content for ${nodeType.typeName}:\n - ${errorMessages.join("\n - ")}`);
}
for (const content of list) {
const [firstLanguage, ..._otherLanguages] = siteSchema.languages;
const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
const info = await siteConnector.createPart({
parentLNodeId,
typeName: nodeType.typeName,
values: content.fields,
});
const nodeId = info.nodeId;
await uploadMediaEntries(ctx, nodeId, content.medias);
report.addId({
typeName: nodeType.typeName,
id: { nodeId, language: firstLanguage },
parentNodeId,
});
}
}
function getDefaultNodeContentCount(nodeType, siteSchema) {
if (nodeType.kind === "site")
throw new Error("Cannot generate content for site node type");
if (nodeType.kind === "document") {
if (nodeType.documentKind === "routing")
return 1;
const termTypeNames = getTermTypeNames(siteSchema);
if (termTypeNames.has(nodeType.typeName))
return 4;
if (nodeType.route === ":yyyy/:mm/:dd/:relativeId-:slug")
return 14;
return 8;
}
if (nodeType.kind === "part")
return 8;
throw new Error(`Unknown node type kind: ${nodeType.kind}`);
}
async function uploadMediaEntries(ctx, nodeId, medias) {
if (!medias || medias.length === 0)
return;
const { siteConnector, homeRoutingCluster } = ctx;
const siteNodeId = homeRoutingCluster.siteNodeId;
for (const entry of medias) {
switch (entry.kind) {
case "featuredImage": {
await siteConnector.setMedia({
handle: getHandleOfFeaturedImage(nodeId),
filePath: entry.filePath,
replace: true,
});
break;
}
case "mediaField": {
await siteConnector.setMedia({
handle: getHandleOfFieldOnNode({ siteNodeId, nodeId, fieldName: entry.fieldName }),
filePath: entry.filePath,
replace: true,
});
break;
}
case "mediaGallery": {
const handle = getHandleOfFieldOnNode({ siteNodeId, nodeId, fieldName: entry.fieldName });
// Gallery: first image replaces, subsequent ones add
for (let i = 0; i < entry.filePaths.length; i++) {
await siteConnector.setMedia({
handle,
filePath: entry.filePaths[i],
replace: i === 0,
});
}
break;
}
}
}
}