fix: 0-byte index.html day archives

This commit is contained in:
2026-02-22 15:34:16 +01:00
parent b4109d7210
commit a7e7ae5b1a
8 changed files with 211 additions and 7 deletions

View File

@@ -18,6 +18,7 @@ interface RenderContext {
menu?: MenuDocument;
skipContextSetup?: boolean;
maxPostsPerPage?: number;
allowEmptyArchiveRender?: boolean;
}
export function createGenerationRouteRenderer(params: {
@@ -111,6 +112,10 @@ export function createPreviewBackedGenerationRouteRenderer(params: {
const serializeFilter = (filter: unknown): string => {
const normalizeValue = (value: unknown): unknown => {
if (value instanceof Date) {
return value.toISOString();
}
if (Array.isArray(value)) {
return value.map((entry) => normalizeValue(entry));
}
@@ -244,6 +249,7 @@ export function createPreviewBackedGenerationRouteRenderer(params: {
menu,
skipContextSetup: true,
maxPostsPerPage: params.maxPostsPerPage,
allowEmptyArchiveRender: true,
},
});
}

View File

@@ -1105,10 +1105,11 @@ export class PageRenderer {
html_theme_attribute?: string;
pagination?: PaginationContext;
categorySettings?: Record<string, CategoryRenderSettings>;
renderEmptyState?: boolean;
},
postEngine?: PostEngineContract,
): Promise<string> {
if (posts.length === 0) {
if (posts.length === 0 && !options.renderEmptyState) {
return '';
}

View File

@@ -179,6 +179,7 @@ export class PreviewServer {
maxPostsPerPage?: number;
requestTheme?: string | null;
htmlThemeAttribute?: string;
allowEmptyArchiveRender?: boolean;
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
},
): Promise<string | null> {

View File

@@ -30,6 +30,7 @@ export interface SharedRouteRenderOptions {
maxPostsPerPage?: number;
requestTheme?: string | null;
htmlThemeAttribute?: string;
allowEmptyArchiveRender?: boolean;
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string };
}
@@ -97,6 +98,7 @@ async function resolveRouteWithSharedServices(
tagColorByName: Record<string, string>,
listExcludedCategories: string[],
services: SharedRouteRenderServices<CategoryMetadata>,
allowEmptyArchiveRender: boolean,
singlePostOptions?: { useDraftContent?: boolean; draftPostId?: string },
): Promise<string | null> {
const routePagination = parseRoutePagination(pathname);
@@ -125,6 +127,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -144,6 +147,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -164,6 +168,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -206,6 +211,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -227,6 +233,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -246,6 +253,7 @@ async function resolveRouteWithSharedServices(
menu_items: pageContext.menuItems,
pico_stylesheet_href: pageContext.picoStylesheetHref,
html_theme_attribute: pageContext.htmlThemeAttribute,
renderEmptyState: allowEmptyArchiveRender,
}, services.postEngineForMacros);
}
@@ -310,5 +318,5 @@ export async function renderRouteWithSharedContext<TCategoryMetadata>(
menuItems,
picoStylesheetHref,
htmlThemeAttribute: options.htmlThemeAttribute,
}, categorySettings, categoryMetadata as Record<string, CategoryMetadata>, tagColorByName, listExcludedCategories, services as SharedRouteRenderServices<CategoryMetadata>, options.singlePostOptions);
}, categorySettings, categoryMetadata as Record<string, CategoryMetadata>, tagColorByName, listExcludedCategories, services as SharedRouteRenderServices<CategoryMetadata>, options.allowEmptyArchiveRender === true, options.singlePostOptions);
}

View File

@@ -55,8 +55,12 @@ function extractSitemapLocs(sitemapXml: string): string[] {
return locs;
}
async function collectHtmlIndexPaths(htmlDir: string): Promise<Set<string>> {
async function collectHtmlIndexPaths(htmlDir: string): Promise<{
existingHtmlPathSet: Set<string>;
zeroByteHtmlPathSet: Set<string>;
}> {
const existingHtmlPathSet = new Set<string>();
const zeroByteHtmlPathSet = new Set<string>();
const collectIndexPaths = async (dir: string, relativePrefix = ''): Promise<void> => {
let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }>;
@@ -80,12 +84,28 @@ async function collectHtmlIndexPaths(htmlDir: string): Promise<Set<string>> {
}
const normalizedRelative = nextRelative.replace(/(^|\/)index\.html$/, '');
existingHtmlPathSet.add(normalizeUrlPath(normalizedRelative ? `/${normalizedRelative}` : '/'));
const normalizedUrlPath = normalizeUrlPath(normalizedRelative ? `/${normalizedRelative}` : '/');
try {
const stats = await fs.stat(nextPath);
if (stats.size <= 0) {
zeroByteHtmlPathSet.add(normalizedUrlPath);
continue;
}
} catch {
zeroByteHtmlPathSet.add(normalizedUrlPath);
continue;
}
existingHtmlPathSet.add(normalizedUrlPath);
}
};
await collectIndexPaths(htmlDir);
return existingHtmlPathSet;
return {
existingHtmlPathSet,
zeroByteHtmlPathSet,
};
}
export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams): Promise<SiteValidationDiffResult> {
@@ -95,7 +115,7 @@ export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams):
.map((value) => normalizeUrlPath(value)),
);
const existingHtmlPathSet = await collectHtmlIndexPaths(params.htmlDir);
const { existingHtmlPathSet, zeroByteHtmlPathSet } = await collectHtmlIndexPaths(params.htmlDir);
const missingUrlPaths = Array.from(expectedPathSet)
.filter((value) => !existingHtmlPathSet.has(value))
@@ -103,6 +123,8 @@ export async function compareSitemapToHtml(params: CompareSitemapToHtmlParams):
const extraUrlPaths = Array.from(existingHtmlPathSet)
.filter((value) => !expectedPathSet.has(value))
.concat(Array.from(zeroByteHtmlPathSet).filter((value) => !expectedPathSet.has(value)))
.filter((value, index, array) => array.indexOf(value) === index)
.sort();
return {