@roottale/cms-renderer-next 0.47.0 → 0.51.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/blog-filter-BKCGM0qd.d.ts +29 -0
  3. package/dist/{chunk-6MFHA555.js → chunk-27RT6B3O.js} +1 -1
  4. package/dist/chunk-27RT6B3O.js.map +1 -0
  5. package/dist/chunk-2QU4JU46.js +3927 -0
  6. package/dist/chunk-2QU4JU46.js.map +1 -0
  7. package/dist/chunk-4RQ3SEDL.js +106 -0
  8. package/dist/chunk-4RQ3SEDL.js.map +1 -0
  9. package/dist/{chunk-OMXEU4LS.js → chunk-QYNEOFL7.js} +140 -136
  10. package/dist/chunk-QYNEOFL7.js.map +1 -0
  11. package/dist/{chunk-FUA5H6JE.js → chunk-U2IM6OCQ.js} +46 -2
  12. package/dist/chunk-U2IM6OCQ.js.map +1 -0
  13. package/dist/cms-public.css +137 -18
  14. package/dist/concerns-client.d.ts +1 -1
  15. package/dist/expertise-client.d.ts +1 -1
  16. package/dist/expertise-client.js +1 -1
  17. package/dist/hero-carousel-client.js +1 -1
  18. package/dist/routes.d.ts +377 -21
  19. package/dist/routes.js +273 -106
  20. package/dist/routes.js.map +1 -1
  21. package/dist/seo-asset-hosts.d.ts +36 -0
  22. package/dist/seo-asset-hosts.js +391 -0
  23. package/dist/seo-asset-hosts.js.map +1 -0
  24. package/dist/server.d.ts +34 -216
  25. package/dist/server.js +7 -7
  26. package/dist/{site-config-Dicv57oY.d.ts → site-config-DNpvlqA5.d.ts} +201 -201
  27. package/dist/spaces-client.d.ts +1 -1
  28. package/dist/team-expanding-stage-client.d.ts +1 -1
  29. package/dist/team-expanding-stage-client.js +1 -1
  30. package/dist/templates.d.ts +64 -25
  31. package/dist/templates.js +14 -7
  32. package/dist/templates.js.map +1 -1
  33. package/package.json +7 -2
  34. package/dist/chunk-6MFHA555.js.map +0 -1
  35. package/dist/chunk-FUA5H6JE.js.map +0 -1
  36. package/dist/chunk-FZK3EXYZ.js +0 -103
  37. package/dist/chunk-FZK3EXYZ.js.map +0 -1
  38. package/dist/chunk-OMXEU4LS.js.map +0 -1
  39. package/dist/chunk-ZHXYCABN.js +0 -3584
  40. package/dist/chunk-ZHXYCABN.js.map +0 -1
package/dist/routes.js CHANGED
@@ -1,65 +1,34 @@
1
1
  import {
2
- collectCategories,
3
- collectCollectionCategories
4
- } from "./chunk-FUA5H6JE.js";
2
+ collectCategoriesWithCounts,
3
+ collectCollectionCategories,
4
+ resolveCollections,
5
+ resolveCollectionsResult,
6
+ resolvePostCollection,
7
+ resolvePostUrl,
8
+ safeFetchPosts,
9
+ trimSlash
10
+ } from "./chunk-U2IM6OCQ.js";
5
11
 
6
12
  // src/routes.ts
7
13
  import { CMS_CLIENT_VERSION } from "@roottale/cms-client/server";
8
14
 
9
- // src/routes-shared.ts
10
- import { fetchPosts } from "@roottale/cms-client/server";
11
- import { resolvePostCollection, resolvePostUrl } from "@roottale/cms-core";
12
- async function resolveCollectionsResult(input) {
13
- if (typeof input === "function") {
14
- try {
15
- return { collections: await input(), failed: false };
16
- } catch (err) {
17
- console.error(
18
- "[@roottale/cms-renderer-next/routes] collections resolver \uC2E4\uD328:",
19
- err
20
- );
21
- return { collections: void 0, failed: true };
22
- }
23
- }
24
- return { collections: input, failed: false };
25
- }
26
- async function resolveCollections(input) {
27
- return (await resolveCollectionsResult(input)).collections;
28
- }
29
- function trimSlash(u) {
30
- return u.replace(/\/+$/, "");
31
- }
32
- async function safeFetchPosts(config, limit) {
33
- try {
34
- const page = await fetchPosts({
35
- apiKey: config.apiKey,
36
- baseUrl: config.apiBase,
37
- type: "post",
38
- limit
39
- });
40
- return page.items;
41
- } catch (err) {
42
- console.error("[@roottale/cms-renderer-next/routes] fetchPosts \uC2E4\uD328:", err);
43
- return [];
44
- }
45
- }
46
-
47
15
  // src/routes-feed-sitemap.ts
48
16
  import {
49
- fetchAuthors,
50
- fetchBlogSettings,
51
- fetchPosts as fetchPosts2,
52
17
  generateRssXml,
53
18
  rssItemsFromPosts
54
19
  } from "@roottale/cms-client/server";
55
20
  import {
56
- isInCollectionMode,
57
- resolvePostCollection as resolvePostCollection2,
58
- resolvePostUrl as resolvePostUrl2
21
+ isInCollectionMode as isInCollectionMode2,
22
+ resolvePostCollection as resolvePostCollection3,
23
+ resolvePostUrl as resolvePostUrl3
59
24
  } from "@roottale/cms-core";
60
25
 
61
26
  // src/routes-metadata.ts
62
27
  import { resolvePostPath } from "@roottale/cms-core";
28
+ function resolveRobotsMeta(flags) {
29
+ if (!flags.noindex && !flags.nofollow) return void 0;
30
+ return { index: !flags.noindex, follow: !flags.nofollow };
31
+ }
63
32
  function buildPostMetadata(post, options = {}) {
64
33
  const seo = post.seo ?? void 0;
65
34
  const title = seo?.title || post.title;
@@ -83,6 +52,10 @@ function buildPostMetadata(post, options = {}) {
83
52
  if (description) openGraph.description = description;
84
53
  if (canonical) openGraph.url = canonical;
85
54
  if (post.date) openGraph.publishedTime = post.date;
55
+ if (post.modified) openGraph.modifiedTime = post.modified;
56
+ if (post.section) openGraph.section = post.section;
57
+ if (post.tags && post.tags.length > 0) openGraph.tags = [...post.tags];
58
+ if (post.authors && post.authors.length > 0) openGraph.authors = [...post.authors];
86
59
  if (ogImage) openGraph.images = [{ url: ogImage, alt: title }];
87
60
  const meta = { title, openGraph };
88
61
  if (description) meta.description = description;
@@ -97,10 +70,26 @@ function buildPostMetadata(post, options = {}) {
97
70
  if (languages) meta.alternates.languages = languages;
98
71
  }
99
72
  }
100
- if (seo?.noindex || seo?.nofollow) {
101
- meta.robots = { index: !seo.noindex, follow: !seo.nofollow };
73
+ if (options.siteUrl) {
74
+ const feedPath = options.feedPath ?? "/feed.xml";
75
+ const normalizedFeedPath = feedPath.startsWith("/") ? feedPath : `/${feedPath}`;
76
+ meta.alternates = {
77
+ ...meta.alternates,
78
+ types: {
79
+ "application/rss+xml": `${trimSlash(options.siteUrl)}${normalizedFeedPath}`
80
+ }
81
+ };
102
82
  }
83
+ const robots = resolveRobotsMeta({
84
+ ...seo?.noindex !== void 0 && { noindex: seo.noindex },
85
+ ...seo?.nofollow !== void 0 && { nofollow: seo.nofollow }
86
+ });
87
+ if (robots) meta.robots = robots;
103
88
  if (post.id) meta.other = { "rt:content-id": post.id };
89
+ const twitter = { card: "summary_large_image", title };
90
+ if (description) twitter.description = description;
91
+ if (ogImage) twitter.images = [ogImage];
92
+ meta.twitter = twitter;
104
93
  return meta;
105
94
  }
106
95
  function buildHreflangLanguages(translations, siteUrl, locales) {
@@ -120,40 +109,133 @@ function buildHreflangLanguages(translations, siteUrl, locales) {
120
109
  return languages;
121
110
  }
122
111
 
123
- // src/routes-feed-sitemap.ts
124
- function createFeedRoute(config) {
125
- return async function GET() {
126
- const siteUrl = trimSlash(config.siteUrl);
127
- const collections = await resolveCollections(config.collections);
128
- const allPosts = await safeFetchPosts(config, 50);
129
- const inCollectionMode = isInCollectionMode(collections);
130
- const blogBase = config.blogBasePath ?? "/blog";
131
- const posts = inCollectionMode ? allPosts.filter((p) => {
132
- const c = resolvePostCollection2(p, collections);
133
- return c ? c.feed === true && c.detail !== false && c.basePath !== "" : false;
134
- }) : allPosts;
135
- const xml = generateRssXml({
136
- title: config.title,
137
- link: inCollectionMode ? siteUrl : `${siteUrl}${blogBase}`,
138
- feedUrl: `${siteUrl}/feed.xml`,
139
- description: config.description,
140
- language: config.language,
141
- items: rssItemsFromPosts(posts, {
142
- postUrl: (p) => inCollectionMode ? resolvePostUrl2(siteUrl, p, collections) ?? `${siteUrl}${blogBase}/${p.slug}` : `${siteUrl}${blogBase}/${p.slug}`
143
- })
144
- });
145
- return new Response(xml, {
146
- headers: {
147
- "content-type": "application/rss+xml; charset=utf-8",
148
- "cache-control": "public, max-age=1800, s-maxage=1800"
149
- }
112
+ // src/archive-index-policy.ts
113
+ var ARCHIVE_POST_COUNT_LIMIT = 200;
114
+ var NOT_FOUND = {
115
+ render: "notFound",
116
+ index: false,
117
+ inSitemap: false
118
+ };
119
+ var NOINDEX = {
120
+ render: "ok",
121
+ index: false,
122
+ inSitemap: false
123
+ };
124
+ var INDEXED = {
125
+ render: "ok",
126
+ index: true,
127
+ inSitemap: true
128
+ };
129
+ var POLICY_NOT_APPLIED = INDEXED;
130
+ function isNonNegativeInteger(value) {
131
+ return Number.isInteger(value) && value >= 0;
132
+ }
133
+ function resolveCategoryPromotion(promotedSlugs, slug) {
134
+ if (!promotedSlugs) return "unknown";
135
+ return promotedSlugs.includes(slug) ? "promoted" : "not-promoted";
136
+ }
137
+ function resolveAuthorPromotion(authorsEnabled) {
138
+ if (authorsEnabled === void 0) return "unknown";
139
+ return authorsEnabled ? "promoted" : "not-promoted";
140
+ }
141
+ function decideArchiveIndex(input) {
142
+ const page = input.page ?? 1;
143
+ if (!isNonNegativeInteger(input.postCount)) return NOINDEX;
144
+ if (!isNonNegativeInteger(page) || page < 1) return NOINDEX;
145
+ if (input.postCount === 0 && input.kind !== "search") return NOT_FOUND;
146
+ if (page >= 2) return NOINDEX;
147
+ if (input.kind !== "category" && input.kind !== "author") return NOINDEX;
148
+ switch (input.promotion) {
149
+ case "promoted":
150
+ return INDEXED;
151
+ case "unknown":
152
+ return POLICY_NOT_APPLIED;
153
+ default:
154
+ return NOINDEX;
155
+ }
156
+ }
157
+ function buildArchiveMetadata(input, options = {}) {
158
+ const decision = decideArchiveIndex(input);
159
+ const meta = { title: input.title };
160
+ if (input.description) meta.description = input.description;
161
+ if (options.siteUrl && options.path) {
162
+ const normalized = options.path.startsWith("/") ? options.path : `/${options.path}`;
163
+ meta.alternates = { canonical: `${trimSlash(options.siteUrl)}${normalized}` };
164
+ }
165
+ const robots = resolveRobotsMeta({
166
+ noindex: !decision.index,
167
+ ...input.nofollow !== void 0 && { nofollow: input.nofollow }
168
+ });
169
+ if (robots) meta.robots = robots;
170
+ return meta;
171
+ }
172
+
173
+ // src/routes-sitemap-entries.ts
174
+ import {
175
+ fetchAuthors,
176
+ fetchBlogSettings,
177
+ fetchPosts as fetchPosts2
178
+ } from "@roottale/cms-client/server";
179
+ import { isInCollectionMode, resolvePostCollection as resolvePostCollection2, resolvePostUrl as resolvePostUrl2 } from "@roottale/cms-core";
180
+
181
+ // src/archive-categories.ts
182
+ import {
183
+ fetchCategoryCounts,
184
+ fetchPosts
185
+ } from "@roottale/cms-client/server";
186
+ async function fetchBlogArchivePosts(config, options = {}) {
187
+ const page = await fetchPosts({
188
+ apiKey: config.apiKey,
189
+ baseUrl: config.apiBase,
190
+ type: "post",
191
+ limit: ARCHIVE_POST_COUNT_LIMIT,
192
+ // 서버측 "미소속 글" 필터 — 페이지(`excludeCollections`)와 같은 정의.
193
+ excludeCollections: true,
194
+ ...options.revalidate !== void 0 && { revalidate: options.revalidate }
195
+ });
196
+ return page.items;
197
+ }
198
+ async function fetchServerCategoryCounts(config, options) {
199
+ try {
200
+ const result = await fetchCategoryCounts({
201
+ apiKey: config.apiKey,
202
+ baseUrl: config.apiBase,
203
+ // 레거시 `/blog` 계열의 모집단 = 미소속 글. 페이지(`excludeCollections`)·
204
+ // 사이트맵과 **같은 서버 판정**을 쓰기 위해 서버 필터로 지정한다.
205
+ excludeCollections: true,
206
+ ...options.revalidate !== void 0 && { revalidate: options.revalidate }
150
207
  });
151
- };
208
+ if (result.truncated) {
209
+ console.warn(
210
+ "[@roottale/cms-renderer-next/routes] \uBD84\uB958 \uC218\uAC00 \uC11C\uBC84 \uC0C1\uD55C\uC744 \uB118\uC5B4 \uC77C\uBD80\uAC00 \uC751\uB2F5\uC5D0\uC11C \uBE60\uC84C\uC2B5\uB2C8\uB2E4(\uC0C9\uC778\uC5D0\uC11C \uB204\uB77D\uB420 \uC218 \uC788\uC74C)."
211
+ );
212
+ }
213
+ return result.categories.filter((category) => category.publishedPostCount > 0).map((category) => ({
214
+ name: category.name,
215
+ slug: category.slug,
216
+ count: category.publishedPostCount
217
+ }));
218
+ } catch (err) {
219
+ console.warn(
220
+ "[@roottale/cms-renderer-next/routes] \uBD84\uB958\uBCC4 \uAE00 \uC218 \uC9D1\uACC4 API \uC2E4\uD328 \u2014 \uCD5C\uADFC \uAE00 \uBC30\uCE58 \uD30C\uC0DD\uC73C\uB85C \uD3F4\uBC31:",
221
+ err
222
+ );
223
+ return null;
224
+ }
225
+ }
226
+ async function fetchBlogArchiveCategories(config, options = {}) {
227
+ const counts = await fetchServerCategoryCounts(config, options);
228
+ if (counts) return counts;
229
+ return collectCategoriesWithCounts(await fetchBlogArchivePosts(config, options));
152
230
  }
231
+
232
+ // src/routes-sitemap-entries.ts
153
233
  var SITEMAP_DEFAULTS = {
154
234
  blogImages: true,
155
235
  locales: [],
156
236
  authors: false
237
+ // promotedCategorySlugs 는 의도적으로 기본값이 없다 — "모름"(undefined)과
238
+ // "승격 0건"([])을 구분해야 색인 정책을 fail-soft 로 적용할 수 있다.
157
239
  };
158
240
  async function safeFetchAuthors(config) {
159
241
  try {
@@ -165,20 +247,29 @@ async function safeFetchAuthors(config) {
165
247
  }
166
248
  async function resolveSitemapSettings(config, opts) {
167
249
  let remote = {};
250
+ let settingsFetchFailed = false;
168
251
  if (!opts.skipRemoteSettings) {
169
252
  try {
170
253
  remote = (await fetchBlogSettings({
171
254
  apiKey: config.apiKey,
172
255
  baseUrl: config.apiBase
173
256
  })).sitemap ?? {};
174
- } catch {
257
+ } catch (err) {
258
+ settingsFetchFailed = true;
259
+ console.error(
260
+ "[@roottale/cms-renderer-next/routes] fetchBlogSettings \uC2E4\uD328(\uAE30\uBCF8\uAC12\uC73C\uB85C \uC9C4\uD589):",
261
+ err
262
+ );
175
263
  }
176
264
  }
177
- return {
265
+ const settings = {
178
266
  blogImages: opts.blogImages ?? remote.blogImages ?? SITEMAP_DEFAULTS.blogImages,
179
267
  locales: opts.locales ?? remote.locales ?? SITEMAP_DEFAULTS.locales,
180
268
  authors: opts.authors ?? remote.authors ?? SITEMAP_DEFAULTS.authors
181
269
  };
270
+ const promoted = opts.promotedCategorySlugs ?? remote.promotedCategorySlugs;
271
+ if (promoted) settings.promotedCategorySlugs = promoted;
272
+ return { settings, settingsFetchFailed };
182
273
  }
183
274
  async function localesWithPublishedContent(config, locales) {
184
275
  if (locales.length === 0) return locales;
@@ -238,84 +329,152 @@ function postEntries(config, siteUrl, posts, collections, settings, defaultLocal
238
329
  return [entry];
239
330
  });
240
331
  }
241
- function categoryEntries(config, siteUrl, posts, collections) {
332
+ async function safeBlogArchiveCategories(config) {
333
+ try {
334
+ return await fetchBlogArchiveCategories(config);
335
+ } catch (err) {
336
+ console.error(
337
+ "[@roottale/cms-renderer-next/routes] \uC544\uCE74\uC774\uBE0C \uCE74\uD14C\uACE0\uB9AC \uC870\uD68C \uC2E4\uD328:",
338
+ err
339
+ );
340
+ return [];
341
+ }
342
+ }
343
+ async function categoryEntries(config, siteUrl, collections, promotedSlugs) {
344
+ const inSitemap = (category) => decideArchiveIndex({
345
+ kind: "category",
346
+ promotion: resolveCategoryPromotion(promotedSlugs, category.slug),
347
+ postCount: category.count
348
+ }).inSitemap;
242
349
  if (isInCollectionMode(collections)) {
350
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
243
351
  return collections.filter((c) => c.archives && c.basePath).flatMap((c) => {
244
352
  const cats = collectCollectionCategories(posts, collections, c.key);
245
353
  if (!cats.length) return [];
246
354
  const base = `${siteUrl}${trimSlash(c.basePath)}`;
247
355
  return [
248
356
  { url: `${base}/categories` },
249
- ...cats.map((category) => ({
357
+ ...cats.filter(inSitemap).map((category) => ({
250
358
  url: `${base}/categories/${category.slug}`
251
359
  }))
252
360
  ];
253
361
  });
254
362
  }
255
- const categories = collectCategories(posts);
363
+ const categories = await safeBlogArchiveCategories(config);
256
364
  if (!categories.length) return [];
257
365
  const blogBase = config.blogBasePath ?? "/blog";
258
366
  return [
259
367
  { url: `${siteUrl}${blogBase}/categories` },
260
- ...categories.map((category) => ({
368
+ ...categories.filter(inSitemap).map((category) => ({
261
369
  url: `${siteUrl}${blogBase}/categories/${category.slug}`
262
370
  }))
263
371
  ];
264
372
  }
373
+ async function authorEntries(config, siteUrl, settings, authorBasePath) {
374
+ if (!settings.authors) return [];
375
+ const authors = await safeFetchAuthors(config);
376
+ const base = trimSlash(authorBasePath ?? "/blog/author");
377
+ const promotion = resolveAuthorPromotion(settings.authors);
378
+ return authors.filter(
379
+ (a) => decideArchiveIndex({
380
+ kind: "author",
381
+ promotion,
382
+ postCount: a.postCount
383
+ }).inSitemap
384
+ ).map((a) => ({
385
+ url: `${siteUrl}${base}/${encodeURIComponent(a.slug)}`,
386
+ lastModified: a.lastUpdatedAt ?? void 0
387
+ }));
388
+ }
389
+
390
+ // src/routes-feed-sitemap.ts
391
+ function createFeedRoute(config) {
392
+ return async function GET() {
393
+ const siteUrl = trimSlash(config.siteUrl);
394
+ const collections = await resolveCollections(config.collections);
395
+ const allPosts = await safeFetchPosts(config, 50);
396
+ const inCollectionMode = isInCollectionMode2(collections);
397
+ const blogBase = config.blogBasePath ?? "/blog";
398
+ const posts = inCollectionMode ? allPosts.filter((p) => {
399
+ const c = resolvePostCollection3(p, collections);
400
+ return c ? c.feed === true && c.detail !== false && c.basePath !== "" : false;
401
+ }) : allPosts;
402
+ const xml = generateRssXml({
403
+ title: config.title,
404
+ link: inCollectionMode ? siteUrl : `${siteUrl}${blogBase}`,
405
+ feedUrl: `${siteUrl}/feed.xml`,
406
+ description: config.description,
407
+ language: config.language,
408
+ items: rssItemsFromPosts(posts, {
409
+ postUrl: (p) => inCollectionMode ? resolvePostUrl3(siteUrl, p, collections) ?? `${siteUrl}${blogBase}/${p.slug}` : `${siteUrl}${blogBase}/${p.slug}`
410
+ })
411
+ });
412
+ return new Response(xml, {
413
+ headers: {
414
+ "content-type": "application/rss+xml; charset=utf-8",
415
+ "cache-control": "public, max-age=1800, s-maxage=1800"
416
+ }
417
+ });
418
+ };
419
+ }
265
420
  function createSitemap(config, staticRoutes = []) {
266
421
  return async function sitemap() {
267
422
  const siteUrl = trimSlash(config.siteUrl);
268
423
  const collections = await resolveCollections(config.collections);
269
- const posts = await safeFetchPosts(config, 200);
270
- const settings = await resolveSitemapSettings(config, {
424
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
425
+ const { settings } = await resolveSitemapSettings(config, {
271
426
  skipRemoteSettings: true,
272
427
  blogImages: false
273
428
  });
274
429
  return [
275
430
  ...staticEntries(staticRoutes),
276
431
  ...postEntries(config, siteUrl, posts, collections, settings),
277
- ...categoryEntries(config, siteUrl, posts, collections)
432
+ // settings.promotedCategorySlugs 는 여기서 항상 undefined(위 skipRemoteSettings)
433
+ // → 색인 정책 미적용 = 레거시 동등. 정책이 필요하면 createSitemapIndex 로.
434
+ ...await categoryEntries(config, siteUrl, collections, void 0)
278
435
  ];
279
436
  };
280
437
  }
281
438
  function createSitemapIndex(config, staticRoutes = [], opts = {}) {
282
439
  async function generateSitemaps() {
283
- const settings = await resolveSitemapSettings(config, opts);
440
+ const { settings, settingsFetchFailed } = await resolveSitemapSettings(
441
+ config,
442
+ opts
443
+ );
284
444
  const ids = ["static", "blog", "categories"];
285
- if (settings.authors) ids.push("authors");
445
+ if (settings.authors || settingsFetchFailed) ids.push("authors");
286
446
  return ids.map((id) => ({ id }));
287
447
  }
288
448
  async function sitemap(props) {
289
449
  const id = await props.id;
290
450
  const siteUrl = trimSlash(config.siteUrl);
291
- const settings = await resolveSitemapSettings(config, opts);
451
+ const { settings } = await resolveSitemapSettings(config, opts);
292
452
  const activeLocales = await localesWithPublishedContent(config, settings.locales);
293
453
  const defaultLocale = activeLocales[0];
294
454
  if (id === "static") return staticEntries(staticRoutes);
295
455
  if (id === "authors") {
296
- if (!settings.authors) return [];
297
- const authors = await safeFetchAuthors(config);
298
- const base = trimSlash(opts.authorBasePath ?? "/blog/author");
299
- return authors.map((a) => ({
300
- url: `${siteUrl}${base}/${encodeURIComponent(a.slug)}`,
301
- lastModified: a.lastUpdatedAt ?? void 0
302
- }));
456
+ return authorEntries(config, siteUrl, settings, opts.authorBasePath);
303
457
  }
304
458
  const collections = await resolveCollections(config.collections);
305
- const posts = await safeFetchPosts(config, 200);
459
+ if (id === "categories") {
460
+ return categoryEntries(
461
+ config,
462
+ siteUrl,
463
+ collections,
464
+ settings.promotedCategorySlugs
465
+ );
466
+ }
306
467
  if (id === "blog") {
468
+ const posts = await safeFetchPosts(config, ARCHIVE_POST_COUNT_LIMIT);
307
469
  return postEntries(config, siteUrl, posts, collections, settings, defaultLocale);
308
470
  }
309
- if (id === "categories") {
310
- return categoryEntries(config, siteUrl, posts, collections);
311
- }
312
471
  return [];
313
472
  }
314
473
  return { generateSitemaps, sitemap };
315
474
  }
316
475
 
317
476
  // src/routes-llms.ts
318
- import { isInCollectionMode as isInCollectionMode2, resolvePostUrl as resolvePostUrl3 } from "@roottale/cms-core";
477
+ import { isInCollectionMode as isInCollectionMode3, resolvePostUrl as resolvePostUrl4 } from "@roottale/cms-core";
319
478
  function mdLinkSafe(text) {
320
479
  return text.replace(/[[\]()]/g, "").trim();
321
480
  }
@@ -340,10 +499,10 @@ function createLlmsTxtRoute(config) {
340
499
  }
341
500
  }
342
501
  if (posts.length > 0) {
343
- const inCollectionMode = isInCollectionMode2(collections);
502
+ const inCollectionMode = isInCollectionMode3(collections);
344
503
  lines.push("", inCollectionMode ? "## \uCF58\uD150\uCE20" : "## \uBE14\uB85C\uADF8");
345
504
  for (const p of posts) {
346
- const url = inCollectionMode ? resolvePostUrl3(siteUrl, p, collections) : `${siteUrl}${blogBase}/${p.slug}`;
505
+ const url = inCollectionMode ? resolvePostUrl4(siteUrl, p, collections) : `${siteUrl}${blogBase}/${p.slug}`;
347
506
  if (!url) continue;
348
507
  const excerpt = p.excerpt ? `: ${oneLine(p.excerpt)}` : "";
349
508
  lines.push(`- [${mdLinkSafe(p.title)}](${url})${excerpt}`);
@@ -364,7 +523,7 @@ function createLlmsTxtRoute(config) {
364
523
  import {
365
524
  verifyRootTaleWebhook
366
525
  } from "@roottale/cms-client/webhook";
367
- import { isInCollectionMode as isInCollectionMode3 } from "@roottale/cms-core";
526
+ import { isInCollectionMode as isInCollectionMode4 } from "@roottale/cms-core";
368
527
  function pathsFromBody(rawBody) {
369
528
  try {
370
529
  const body = JSON.parse(rawBody);
@@ -420,7 +579,7 @@ function createRevalidateRoute(config) {
420
579
  if (failed) {
421
580
  alsoRevalidate = INVARIANTS;
422
581
  warning = "collections_resolve_failed";
423
- } else if (isInCollectionMode3(collections)) {
582
+ } else if (isInCollectionMode4(collections)) {
424
583
  alsoRevalidate = [
425
584
  ...INVARIANTS,
426
585
  // 주소 없는 유형(basePath === "")은 라우트가 없으므로 revalidate 대상에서
@@ -671,7 +830,7 @@ function createFleetInfoRoute(config = {}) {
671
830
  site: config.site ?? env.VERCEL_PROJECT_PRODUCTION_URL ?? null,
672
831
  framework: "next",
673
832
  cms_client: CMS_CLIENT_VERSION,
674
- cms_renderer: true ? "0.47.0" : "dev",
833
+ cms_renderer: true ? "0.51.0" : "dev",
675
834
  build_sha: env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) ?? null
676
835
  },
677
836
  { headers: { "cache-control": "public, max-age=300" } }
@@ -679,8 +838,10 @@ function createFleetInfoRoute(config = {}) {
679
838
  };
680
839
  }
681
840
  export {
841
+ ARCHIVE_POST_COUNT_LIMIT,
682
842
  OG_IMAGE_CONTENT_TYPE,
683
843
  OG_IMAGE_SIZE,
844
+ buildArchiveMetadata,
684
845
  buildHreflangLanguages,
685
846
  buildPostMetadata,
686
847
  createFeedRoute,
@@ -691,13 +852,19 @@ export {
691
852
  createRevalidateRoute,
692
853
  createSitemap,
693
854
  createSitemapIndex,
855
+ decideArchiveIndex,
856
+ fetchBlogArchiveCategories,
857
+ fetchBlogArchivePosts,
694
858
  matchRedirect,
695
859
  normalizeRedirectPath,
696
860
  postRedirectPath,
861
+ resolveAuthorPromotion,
862
+ resolveCategoryPromotion,
697
863
  resolveCollections,
698
864
  resolveCollectionsResult,
699
865
  resolvePostCollection,
700
866
  resolvePostUrl,
867
+ resolveRobotsMeta,
701
868
  safeFetchPosts,
702
869
  trimSlash
703
870
  };