@paroicms/site-generator-plugin 0.27.9 → 0.28.1

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.
@@ -15,3 +15,16 @@ export function dedupMessages(messages) {
15
15
  return counter && counter > 1 ? `${m} (×${counter})` : m;
16
16
  });
17
17
  }
18
+ export function getTaxonomyTypeNames(siteSchema) {
19
+ const taxonomyTypeNames = new Set();
20
+ for (const nodeType of Object.values(siteSchema.nodeTypes)) {
21
+ if (!nodeType.fields)
22
+ continue;
23
+ for (const field of nodeType.fields) {
24
+ if (field.dataType === "labeling") {
25
+ taxonomyTypeNames.add(field.taxonomy);
26
+ }
27
+ }
28
+ }
29
+ return taxonomyTypeNames;
30
+ }
@@ -1,27 +1,16 @@
1
1
  import { getPartTypeByName, getRegularDocumentTypeByName, getRoutingDocumentTypeByName, } from "@paroicms/internal-anywhere-lib";
2
- import { createSimpleTranslator, } from "@paroicms/public-server-lib";
3
2
  import { updateGeneratedSiteStepSetAsCompleted, } from "../../db/db-write.queries.js";
4
3
  import { createTaskCollector } from "../lib/tasks.js";
5
4
  import { createGeneratedContentReport } from "./content-report.js";
6
5
  import { addParts, addRegularDocuments, updateRoutingDocument } from "./fill-lnodes.js";
7
6
  import { updateSiteFields } from "./fill-site-fields.js";
8
7
  import { updateLNodesWithTaxonomies } from "./fill-taxonomy-fields.js";
9
- export async function fillSiteWithFakeContent(ctx, stepHandle, { regSite, localizedValues }) {
10
- const { service } = ctx;
11
- const { fqdn } = regSite;
8
+ export async function fillSiteWithFakeContent(ctx, stepHandle, { homeRoutingCluster, localizedValues, }) {
9
+ const { siteSchema } = ctx;
12
10
  const report = createGeneratedContentReport();
13
- const { siteSchema, homeRoutingCluster } = await service.connector.loadSiteSchemaAndIds(fqdn);
14
- const schemaI18n = createSimpleTranslator({
15
- labels: siteSchema.l10n,
16
- logger: ctx.logger,
17
- });
18
- await updateSiteFields(ctx, report, { fqdn, siteSchema, siteTitle: localizedValues.siteTitle });
11
+ await updateSiteFields(ctx, report, { siteTitle: localizedValues.siteTitle });
19
12
  const tasks = createTaskCollector(ctx);
20
13
  fillRoutingDocumentAndAddChildren(ctx, tasks, report, {
21
- fqdn,
22
- siteSchema,
23
- schemaI18n,
24
- }, {
25
14
  clusterNode: homeRoutingCluster,
26
15
  nodeType: siteSchema.nodeTypes.home,
27
16
  });
@@ -29,9 +18,7 @@ export async function fillSiteWithFakeContent(ctx, stepHandle, { regSite, locali
29
18
  const { doneCount, errorMessages } = await promise;
30
19
  const results = report.getResults();
31
20
  await updateLNodesWithTaxonomies(ctx, {
32
- siteSchema,
33
21
  idPicker: results.idPicker,
34
- fqdn,
35
22
  });
36
23
  if (errorMessages.length > 0) {
37
24
  ctx.logger.warn(`Failed to generate documents:\n - ${errorMessages.join("\n - ")}`);
@@ -45,14 +32,14 @@ export async function fillSiteWithFakeContent(ctx, stepHandle, { regSite, locali
45
32
  contentErrors: errorMessages.length > 0 ? errorMessages.join("\n - ") : null,
46
33
  });
47
34
  }
48
- function fillRoutingDocumentAndAddChildren(ctx, tasks, report, siteOptions, nodeOptions) {
35
+ function fillRoutingDocumentAndAddChildren(ctx, tasks, report, nodeOptions) {
49
36
  const { clusterNode, nodeType } = nodeOptions;
50
- const { siteSchema } = siteOptions;
51
- tasks.add(() => updateRoutingDocument(ctx, report, siteOptions, nodeOptions));
37
+ const { siteSchema } = ctx;
38
+ tasks.add(() => updateRoutingDocument(ctx, report, nodeOptions));
52
39
  for (const listType of nodeType.lists ?? []) {
53
40
  for (const typeName of listType.parts) {
54
41
  const partType = getPartTypeByName(siteSchema, typeName);
55
- tasks.add(() => addParts(ctx, report, siteOptions, {
42
+ tasks.add(() => addParts(ctx, report, {
56
43
  parentNodeId: clusterNode.nodeId,
57
44
  nodeType: partType,
58
45
  documentType: nodeType,
@@ -63,15 +50,15 @@ function fillRoutingDocumentAndAddChildren(ctx, tasks, report, siteOptions, node
63
50
  const childType = getRoutingDocumentTypeByName(siteSchema, typeName);
64
51
  const childIds = clusterNode.children?.[typeName];
65
52
  if (!childIds)
66
- throw new Error(`Missing childIds for ${typeName}`);
67
- fillRoutingDocumentAndAddChildren(ctx, tasks, report, siteOptions, {
53
+ continue;
54
+ fillRoutingDocumentAndAddChildren(ctx, tasks, report, {
68
55
  clusterNode: childIds,
69
56
  nodeType: childType,
70
57
  });
71
58
  }
72
59
  for (const typeName of nodeType.regularChildren ?? []) {
73
60
  const childType = getRegularDocumentTypeByName(siteSchema, typeName);
74
- tasks.add(() => addRegularDocuments(ctx, report, siteOptions, {
61
+ tasks.add(() => addRegularDocuments(ctx, report, {
75
62
  parentNodeId: clusterNode.nodeId,
76
63
  nodeType: childType,
77
64
  documentType: childType,
@@ -1,4 +1,3 @@
1
- import { convertMarkdownToTiptap } from "@paroicms/markdown-to-tiptap-json";
2
1
  import { getRandomImagePath } from "../lib/images-lib.js";
3
2
  export function createNodeContents(options) {
4
3
  const { ctx, nodeType, count, generatedContents, outputTags, language } = options;
@@ -21,30 +20,57 @@ export function createNodeContents(options) {
21
20
  function createNodeContent(options) {
22
21
  const { ctx, nodeType, language, generatedContent, outputTags } = options;
23
22
  const fieldTypes = nodeType.fields ?? [];
23
+ const medias = [];
24
24
  if (nodeType.kind === "document") {
25
25
  const { title, ...generatedFields } = generatedContent;
26
- const fields = toRiFieldSetContent(ctx, generatedFields, outputTags, fieldTypes, language);
27
- const featuredImage = nodeType.withFeaturedImage
28
- ? {
29
- file: getRandomImagePath(),
26
+ const fields = toUpdateFieldValues(ctx, generatedFields, outputTags, fieldTypes, language);
27
+ if (nodeType.withFeaturedImage) {
28
+ medias.push({
29
+ kind: "featuredImage",
30
+ filePath: getRandomImagePath(),
31
+ });
32
+ }
33
+ collectMediaFieldEntries(fieldTypes, medias);
34
+ return { title, fields, medias: medias.length > 0 ? medias : undefined };
35
+ }
36
+ const fields = toUpdateFieldValues(ctx, generatedContent, outputTags, fieldTypes, language);
37
+ collectMediaFieldEntries(fieldTypes, medias);
38
+ return { fields, medias: medias.length > 0 ? medias : undefined };
39
+ }
40
+ function collectMediaFieldEntries(fieldTypes, medias) {
41
+ for (const fieldType of fieldTypes) {
42
+ if (fieldType.storedAs !== "mediaHandle")
43
+ continue;
44
+ if (fieldType.withGallery || fieldType.dataType === "gallery") {
45
+ // Gallery field: generate 3-5 random images
46
+ const count = 3 + Math.floor(Math.random() * 3);
47
+ const filePaths = [];
48
+ for (let i = 0; i < count; i++) {
49
+ filePaths.push(getRandomImagePath());
30
50
  }
31
- : undefined;
32
- return {
33
- title,
34
- featuredImage,
35
- fields,
36
- };
51
+ medias.push({
52
+ kind: "mediaGallery",
53
+ fieldName: fieldType.name,
54
+ filePaths,
55
+ });
56
+ }
57
+ else {
58
+ // Single media field
59
+ medias.push({
60
+ kind: "mediaField",
61
+ fieldName: fieldType.name,
62
+ filePath: getRandomImagePath(),
63
+ });
64
+ }
37
65
  }
38
- const fields = toRiFieldSetContent(ctx, generatedContent, outputTags, fieldTypes, language);
39
- return { fields };
40
66
  }
41
- function toRiFieldSetContent(ctx, content, outputTags, fieldTypes, language) {
67
+ function toUpdateFieldValues(ctx, content, outputTags, fieldTypes, language) {
42
68
  const result = {};
43
69
  for (const fieldType of fieldTypes) {
44
70
  const isMarkdown = outputTags.find((tag) => tag.key === fieldType.name)?.format === "markdown";
45
71
  const localized = fieldType.localized;
46
72
  const value = content[fieldType.name];
47
- const fieldContent = toRiFieldContent({
73
+ const fieldValue = toUpdateFieldValue({
48
74
  ctx,
49
75
  fieldType,
50
76
  generatedValue: value,
@@ -52,98 +78,37 @@ function toRiFieldSetContent(ctx, content, outputTags, fieldTypes, language) {
52
78
  language,
53
79
  isMarkdown,
54
80
  });
55
- if (fieldContent !== undefined) {
56
- result[fieldType.name] = fieldContent;
81
+ if (fieldValue !== undefined) {
82
+ result[fieldType.name] = fieldValue;
57
83
  }
58
84
  }
59
85
  return result;
60
86
  }
61
- function toRiFieldContent(options) {
62
- const { ctx, fieldType, generatedValue, localized, language, isMarkdown } = options;
87
+ function toUpdateFieldValue(options) {
88
+ const { fieldType, generatedValue, localized, language, isMarkdown } = options;
89
+ // Return the markdown string directly for markdown fields
63
90
  if (generatedValue !== undefined && localized) {
64
91
  if (fieldType.dataType === "string") {
65
- return {
66
- dataType: "string",
67
- localized: true,
68
- value: generatedValue,
69
- };
92
+ // For localized strings, return the value for the language
93
+ return generatedValue[language];
70
94
  }
71
- if (fieldType.dataType === "json" &&
72
- fieldType.renderAs === "html" &&
73
- fieldType.pluginName === "@paroicms/tiptap-editor-plugin" &&
74
- isMarkdown) {
75
- return toTiptapRiJsonContent(ctx, generatedValue);
76
- }
77
- }
78
- if (fieldType.storedAs === "mediaHandle") {
79
- if (fieldType.dataType === "media") {
80
- const file = getRandomImagePath();
81
- return localized
82
- ? {
83
- dataType: "media",
84
- localized: true,
85
- value: { [language]: { file } },
86
- }
87
- : {
88
- dataType: "media",
89
- localized: false,
90
- value: { file },
91
- };
92
- }
93
- if (fieldType.dataType === "gallery") {
94
- const files = [
95
- getRandomImagePath(),
96
- getRandomImagePath(),
97
- getRandomImagePath(),
98
- getRandomImagePath(),
99
- getRandomImagePath(),
100
- getRandomImagePath(),
101
- ];
102
- return localized
103
- ? {
104
- dataType: "gallery",
105
- localized: true,
106
- value: { [language]: { files } },
107
- }
108
- : {
109
- dataType: "gallery",
110
- localized: false,
111
- value: { files },
112
- };
95
+ if (fieldType.dataType === "json" && fieldType.renderAs === "html" && isMarkdown) {
96
+ // Return the markdown string for the language
97
+ return generatedValue[language];
113
98
  }
114
99
  }
100
+ // Skip media fields - they are handled separately via setMedia
101
+ if (fieldType.storedAs === "mediaHandle")
102
+ return undefined;
115
103
  if (fieldType.dataType === "string") {
116
104
  const value = getDefaultStringValueForField(fieldType.name);
117
105
  if (value !== undefined) {
118
- return localized
119
- ? {
120
- dataType: "string",
121
- localized: true,
122
- value: { [language]: value },
123
- }
124
- : {
125
- dataType: "string",
126
- localized: false,
127
- value,
128
- };
106
+ return value;
129
107
  }
130
108
  }
131
109
  if (fieldType.storedAs === "labeling")
132
110
  return; // TODO: Generate values for labeling fields
133
111
  }
134
- function toTiptapRiJsonContent(ctx, content) {
135
- return {
136
- dataType: "json",
137
- localized: true,
138
- value: Object.fromEntries(Object.entries(content).map(([language, text]) => {
139
- const { result, issues } = convertMarkdownToTiptap(text);
140
- if (issues && issues.length > 0) {
141
- ctx.logger.warn(`Markdown conversion issues for language ${language}:`, issues);
142
- }
143
- return [language, { j: result }];
144
- })),
145
- };
146
- }
147
112
  function getDefaultStringValueForField(fieldName) {
148
113
  if (fieldName === "phone" || fieldName === "phone2")
149
114
  return "0123456789";
@@ -153,15 +118,9 @@ function getDefaultStringValueForField(fieldName) {
153
118
  return new Date().toISOString();
154
119
  return;
155
120
  }
156
- export function generateLocalizedFooterMention(ctx, siteSchema) {
121
+ export function generateLocalizedFooterMention(siteSchema) {
157
122
  const { languages } = siteSchema;
158
- return Object.fromEntries(languages.map((language) => {
159
- const { result, issues } = convertMarkdownToTiptap(getPoweredByLabel(language));
160
- if (issues && issues.length > 0) {
161
- ctx.logger.warn(`Markdown conversion issues for footer mention (${language}):`, issues);
162
- }
163
- return [language, { j: result }];
164
- }));
123
+ return Object.fromEntries(languages.map((language) => [language, getPoweredByLabel(language)]));
165
124
  }
166
125
  function getPoweredByLabel(language) {
167
126
  const translations = {
@@ -1,9 +1,11 @@
1
- import { dedupMessages } from "./content-helpers.js";
1
+ import { encodeLNodeId, } from "@paroicms/public-anywhere-lib";
2
+ import { getHandleOfFeaturedImage, getHandleOfFieldOnNode } from "@paroicms/public-server-lib";
3
+ import { dedupMessages, getTaxonomyTypeNames } from "./content-helpers.js";
2
4
  import { generateFieldSetContent, generateMultipleFieldSetContents, } from "./generate-fake-content.js";
3
- export async function updateRoutingDocument(ctx, report, siteOptions, nodeOptions) {
5
+ export async function updateRoutingDocument(ctx, report, nodeOptions) {
4
6
  ctx.logger.debug(`[TASK] Updating routing document "${nodeOptions.nodeType.typeName}"…`);
5
7
  const { clusterNode, nodeType } = nodeOptions;
6
- const { fqdn, siteSchema, schemaI18n } = siteOptions;
8
+ const { siteSchema, schemaI18n, siteConnector } = ctx;
7
9
  const content = await generateFieldSetContent(ctx, {
8
10
  nodeType,
9
11
  documentType: nodeType,
@@ -12,11 +14,10 @@ export async function updateRoutingDocument(ctx, report, siteOptions, nodeOption
12
14
  withTitle: false,
13
15
  llmTaskName: nodeType.kebabName,
14
16
  }, report);
15
- await ctx.service.connector.updateNodeContent(fqdn, {
16
- nodeId: clusterNode.nodeId,
17
- content: toRiDocumentContent(content, nodeType),
18
- });
17
+ // Use updateFields for each language
19
18
  for (const language of siteSchema.languages) {
19
+ const lNodeId = encodeLNodeId({ nodeId: clusterNode.nodeId, language });
20
+ await siteConnector.updateFields(lNodeId, content.fields);
20
21
  report.addId({
21
22
  typeName: nodeType.typeName,
22
23
  id: {
@@ -27,37 +28,127 @@ export async function updateRoutingDocument(ctx, report, siteOptions, nodeOption
27
28
  });
28
29
  }
29
30
  }
30
- export async function addRegularDocuments(ctx, report, siteOptions, nodeOptions) {
31
+ export async function addRegularDocuments(ctx, report, nodeOptions) {
32
+ // Special case: author terms are created without LLM
33
+ if (nodeOptions.nodeType.typeName === "author") {
34
+ await addSingleAuthor(ctx, report, nodeOptions);
35
+ return;
36
+ }
31
37
  ctx.logger.debug(`[TASK] Adding regular documents "${nodeOptions.nodeType.typeName}"…`);
32
38
  const { parentNodeId, nodeType, documentType } = nodeOptions;
33
- const { fqdn, siteSchema, schemaI18n } = siteOptions;
39
+ const { siteSchema, schemaI18n, siteConnector } = ctx;
40
+ const taxonomyTypeNames = getTaxonomyTypeNames(siteSchema);
41
+ const isTaxonomyTerm = taxonomyTypeNames.has(nodeType.typeName);
34
42
  const tolerateErrors = {
35
43
  errorMessages: [],
36
44
  };
37
- const list = await generateMultipleFieldSetContents(ctx, {
38
- siteSchema,
39
- nodeType,
40
- documentType,
41
- schemaI18n,
42
- count: getDefaultNodeContentCount(nodeType),
43
- withTitle: true,
44
- tolerateErrors,
45
- llmTaskName: nodeType.kebabName,
46
- }, report);
45
+ const maxAttempts = 3;
46
+ let list = [];
47
+ for (let attempt = 1; attempt <= maxAttempts; ++attempt) {
48
+ list = await generateMultipleFieldSetContents(ctx, {
49
+ siteSchema,
50
+ nodeType,
51
+ documentType,
52
+ schemaI18n,
53
+ count: getDefaultNodeContentCount(nodeType, siteSchema),
54
+ withTitle: true,
55
+ tolerateErrors,
56
+ llmTaskName: nodeType.kebabName,
57
+ isTaxonomyTerm,
58
+ }, report);
59
+ if (list.length > 0)
60
+ break;
61
+ if (attempt < maxAttempts) {
62
+ ctx.logger.debug(`[RETRY] Empty result for ${nodeType.typeName}, attempt ${attempt}/${maxAttempts}`);
63
+ }
64
+ }
47
65
  const errorMessages = dedupMessages(tolerateErrors.errorMessages);
48
66
  if (errorMessages.length > 0) {
49
67
  ctx.logger.warn(`Error generating content for ${nodeType.typeName}:\n - ${errorMessages.join("\n - ")}`);
50
68
  }
51
- const insertedIds = await ctx.service.connector.addMultipleDocumentContents(fqdn, {
69
+ for (const content of list) {
70
+ const [firstLanguage, ...otherLanguages] = siteSchema.languages;
71
+ const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
72
+ const title = content.title?.[firstLanguage];
73
+ const info = await siteConnector.createDocument({
74
+ parentLNodeId,
75
+ typeName: nodeType.typeName,
76
+ title,
77
+ values: content.fields,
78
+ });
79
+ const nodeId = info.nodeId;
80
+ await uploadMediaEntries(ctx, nodeId, content.medias);
81
+ report.addId({
82
+ typeName: nodeType.typeName,
83
+ id: { nodeId, language: firstLanguage },
84
+ parentNodeId,
85
+ });
86
+ for (const language of otherLanguages) {
87
+ if (!content.title?.[language])
88
+ continue;
89
+ await siteConnector.createDocumentTranslation({
90
+ nodeId,
91
+ language,
92
+ title: content.title?.[language],
93
+ values: content.fields,
94
+ });
95
+ report.addId({
96
+ typeName: nodeType.typeName,
97
+ id: { nodeId, language },
98
+ parentNodeId,
99
+ });
100
+ }
101
+ }
102
+ }
103
+ const HERO_NAMES = [
104
+ "Mario",
105
+ "Luigi",
106
+ "Link",
107
+ "Zelda",
108
+ "Kirby",
109
+ "Pikachu",
110
+ "Yoshi",
111
+ "Sonic",
112
+ "Toad",
113
+ "Peach",
114
+ ];
115
+ async function addSingleAuthor(ctx, report, nodeOptions) {
116
+ ctx.logger.debug("[TASK] Adding single author…");
117
+ const { parentNodeId, nodeType } = nodeOptions;
118
+ const { siteSchema, siteConnector } = ctx;
119
+ const title = HERO_NAMES[Math.floor(Math.random() * HERO_NAMES.length)];
120
+ const [firstLanguage, ...otherLanguages] = siteSchema.languages;
121
+ const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
122
+ const info = await siteConnector.createDocument({
123
+ parentLNodeId,
124
+ typeName: nodeType.typeName,
125
+ title,
126
+ values: {},
127
+ });
128
+ const nodeId = info.nodeId;
129
+ report.addId({
130
+ typeName: nodeType.typeName,
131
+ id: { nodeId, language: firstLanguage },
52
132
  parentNodeId,
53
- contents: list.map((content) => toRiDocumentContent(content, nodeType)),
54
133
  });
55
- report.addId({ typeName: nodeType.typeName, id: insertedIds, parentNodeId });
134
+ for (const language of otherLanguages) {
135
+ await siteConnector.createDocumentTranslation({
136
+ nodeId,
137
+ language,
138
+ title,
139
+ values: {},
140
+ });
141
+ report.addId({
142
+ typeName: nodeType.typeName,
143
+ id: { nodeId, language },
144
+ parentNodeId,
145
+ });
146
+ }
56
147
  }
57
- export async function addParts(ctx, report, siteOptions, nodeOptions) {
148
+ export async function addParts(ctx, report, nodeOptions) {
58
149
  ctx.logger.debug(`[TASK] Adding parts "${nodeOptions.nodeType.typeName}"…`);
59
150
  const { parentNodeId, nodeType, documentType } = nodeOptions;
60
- const { fqdn, siteSchema, schemaI18n } = siteOptions;
151
+ const { siteSchema, schemaI18n, siteConnector } = ctx;
61
152
  const tolerateErrors = {
62
153
  errorMessages: [],
63
154
  };
@@ -66,7 +157,7 @@ export async function addParts(ctx, report, siteOptions, nodeOptions) {
66
157
  nodeType,
67
158
  documentType,
68
159
  schemaI18n,
69
- count: getDefaultNodeContentCount(nodeType),
160
+ count: getDefaultNodeContentCount(nodeType, siteSchema),
70
161
  withTitle: true,
71
162
  tolerateErrors,
72
163
  llmTaskName: nodeType.kebabName,
@@ -75,41 +166,75 @@ export async function addParts(ctx, report, siteOptions, nodeOptions) {
75
166
  if (errorMessages.length > 0) {
76
167
  ctx.logger.warn(`Error generating content for ${nodeType.typeName}:\n - ${errorMessages.join("\n - ")}`);
77
168
  }
78
- const insertedIds = await ctx.service.connector.addMultiplePartContents(fqdn, {
79
- parentNodeId,
80
- contents: list.map((content) => toRiPartContent(content, nodeType)),
81
- });
82
- report.addId({ typeName: nodeType.typeName, id: insertedIds, parentNodeId });
83
- }
84
- function toRiDocumentContent(content, nodeType) {
85
- const { title, fields, featuredImage } = content;
86
- return {
87
- kind: "document",
88
- typeName: nodeType.typeName,
89
- title,
90
- featuredImage,
91
- fields,
92
- };
93
- }
94
- function toRiPartContent(content, nodeType) {
95
- const { fields } = content;
96
- return {
97
- kind: "part",
98
- typeName: nodeType.typeName,
99
- fields,
100
- };
169
+ for (const content of list) {
170
+ const [firstLanguage, ..._otherLanguages] = siteSchema.languages;
171
+ const parentLNodeId = encodeLNodeId({ nodeId: parentNodeId, language: firstLanguage });
172
+ const info = await siteConnector.createPart({
173
+ parentLNodeId,
174
+ typeName: nodeType.typeName,
175
+ values: content.fields,
176
+ });
177
+ const nodeId = info.nodeId;
178
+ await uploadMediaEntries(ctx, nodeId, content.medias);
179
+ report.addId({
180
+ typeName: nodeType.typeName,
181
+ id: { nodeId, language: firstLanguage },
182
+ parentNodeId,
183
+ });
184
+ }
101
185
  }
102
- function getDefaultNodeContentCount(nodeType) {
186
+ function getDefaultNodeContentCount(nodeType, siteSchema) {
103
187
  if (nodeType.kind === "site")
104
188
  throw new Error("Cannot generate content for site node type");
105
189
  if (nodeType.kind === "document") {
106
190
  if (nodeType.documentKind === "routing")
107
191
  return 1;
192
+ const taxonomyTypeNames = getTaxonomyTypeNames(siteSchema);
193
+ if (taxonomyTypeNames.has(nodeType.typeName))
194
+ return 4;
108
195
  if (nodeType.route === ":yyyy/:mm/:dd/:relativeId-:slug")
109
- return 14; // 4
110
- return 8; // 2
196
+ return 14;
197
+ return 8;
111
198
  }
112
199
  if (nodeType.kind === "part")
113
- return 8; // 2
200
+ return 8;
114
201
  throw new Error(`Unknown node type kind: ${nodeType.kind}`);
115
202
  }
203
+ async function uploadMediaEntries(ctx, nodeId, medias) {
204
+ if (!medias || medias.length === 0)
205
+ return;
206
+ const { siteConnector, homeRoutingCluster } = ctx;
207
+ const siteNodeId = homeRoutingCluster.siteNodeId;
208
+ for (const entry of medias) {
209
+ switch (entry.kind) {
210
+ case "featuredImage": {
211
+ await siteConnector.setMedia({
212
+ handle: getHandleOfFeaturedImage(nodeId),
213
+ filePath: entry.filePath,
214
+ replace: true,
215
+ });
216
+ break;
217
+ }
218
+ case "mediaField": {
219
+ await siteConnector.setMedia({
220
+ handle: getHandleOfFieldOnNode({ siteNodeId, nodeId, fieldName: entry.fieldName }),
221
+ filePath: entry.filePath,
222
+ replace: true,
223
+ });
224
+ break;
225
+ }
226
+ case "mediaGallery": {
227
+ const handle = getHandleOfFieldOnNode({ siteNodeId, nodeId, fieldName: entry.fieldName });
228
+ // Gallery: first image replaces, subsequent ones add
229
+ for (let i = 0; i < entry.filePaths.length; i++) {
230
+ await siteConnector.setMedia({
231
+ handle,
232
+ filePath: entry.filePaths[i],
233
+ replace: i === 0,
234
+ });
235
+ }
236
+ break;
237
+ }
238
+ }
239
+ }
240
+ }
@@ -1,63 +1,31 @@
1
+ import { getHandleOfSiteField } from "@paroicms/public-server-lib";
1
2
  import { getRandomImagePath } from "../lib/images-lib.js";
2
3
  import { generateLocalizedFooterMention } from "./create-node-contents.js";
3
4
  export async function updateSiteFields(ctx, _report, options) {
4
- const { service, logger } = ctx;
5
+ const { logger, siteSchema, siteConnector } = ctx;
5
6
  logger.debug("Updating site fields…");
6
- const { fqdn, siteSchema, siteTitle } = options;
7
+ const { siteTitle } = options;
7
8
  const siteType = siteSchema.nodeTypes._site;
8
- const logoImageFile = getRandomImagePath();
9
- const fields = [
10
- [
11
- "title",
12
- {
13
- dataType: "string",
14
- localized: true,
15
- value: siteTitle,
16
- },
17
- ],
18
- [
19
- "favicon",
20
- {
21
- dataType: "media",
22
- localized: false,
23
- value: {
24
- file: logoImageFile,
25
- },
26
- },
27
- ],
28
- [
29
- "ogImage",
30
- {
31
- dataType: "media",
32
- localized: false,
33
- value: {
34
- file: logoImageFile,
35
- },
36
- },
37
- ],
38
- ];
39
- if (siteType.fields.some((f) => f.name === "logo")) {
40
- fields.push([
41
- "logo",
42
- {
43
- dataType: "media",
44
- localized: false,
45
- value: {
46
- file: logoImageFile,
47
- },
48
- },
49
- ]);
9
+ const footerMention = generateLocalizedFooterMention(siteSchema);
10
+ // Update site fields for each language
11
+ for (const language of siteSchema.languages) {
12
+ const values = {
13
+ title: siteTitle[language],
14
+ };
15
+ if (siteType.fields.some((f) => f.name === "footerMention")) {
16
+ values.footerMention = footerMention[language];
17
+ }
18
+ await siteConnector.updateSiteFields(language, values);
50
19
  }
51
- if (siteType.fields.some((f) => f.name === "footerMention")) {
52
- fields.push([
53
- "footerMention",
54
- {
55
- dataType: "json",
56
- localized: true,
57
- value: generateLocalizedFooterMention(ctx, siteSchema),
58
- },
59
- ]);
20
+ for (const fieldName of ["favicon", "ogImage", "logo"]) {
21
+ const fieldType = siteType.fields.find((f) => f.name === fieldName);
22
+ if (!fieldType || fieldType.storedAs !== "mediaHandle")
23
+ continue;
24
+ await siteConnector.setMedia({
25
+ handle: getHandleOfSiteField(fieldName),
26
+ filePath: getRandomImagePath(),
27
+ replace: true,
28
+ });
60
29
  }
61
- await service.connector.updateSiteFields(fqdn, Object.fromEntries(fields));
62
30
  logger.debug("… Site fields updated");
63
31
  }