UNPKG

@paroicms/site-generator-plugin

Version:

ParoiCMS Site Generator Plugin

251 lines (250 loc) 9.36 kB
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; } } } }