fix(theme): payload 契约上提 contracts;空路由明错;ui.routeHrefFor;eslint astro ignore
审查修复(Plan 4 Task 3 review): - 载荷类型单一事实来源:ContentEntry/TaxonomyEntry/TaxonomyTerm/RouteEntry/ AssetEntry/RenderManifestMeta/SiteConfigPayload/BrandConfigPayload 上提 @sf/contracts/render-snapshot(纯新增、不动既有导出——冻结面加法无需升版); 物化端(apps/api build/render-snapshot.ts)与主题 load-render-data 同源引用, 双份手写镜像漂移即编译期破裂;主题侧 re-export 保持既有 import 面 - loader 空路由表前置明错(正常发布站点至少含首页路由——静默渲染会产零页站点) - ui.routeHrefFor:targetContentId → 纯路径 html 落点(redirect/查询行不参与, 无落点 undefined 不虚构——Task 4 分派器入口)+ 测试 - 具夹 assets.json copyPath 去机器相关绝对路径(改仓库相对) - eslint ignores 增 **/.astro/**(astro build 再生目录) 354 根测试 + 18 主题测试全绿;五段 typecheck;lint(.astro 存在态验证后清理)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
// 从 published 修订 + route_registry + media ready 集合物化冻结六文件
|
||||
// (RENDER_SNAPSHOT_FILES 契约清单),**db 只读、零写入**(磁盘仅做副本存在性探测)。
|
||||
// 构建编排(job 状态机/落盘/快照行)在 build.service.ts——本文件只负责"读出什么"。
|
||||
// 载荷类型(ContentEntry/RouteEntry/AssetEntry/TaxonomyEntry/RenderManifestMeta)与
|
||||
// 主题消费端同源于 @sf/contracts/render-snapshot(Plan 4 Task 3 审查上提——双份
|
||||
// 手写镜像漂移即编译期破裂)。
|
||||
//
|
||||
// 发布语义(ADR-010):只读 entry_locales.published_revision_id 指向的修订——current
|
||||
// 分叉(发布后再导入产新修订)不影响本快照;发布后的人工/导入变化须重新 publish +
|
||||
@@ -38,7 +41,15 @@ import {
|
||||
PARSER_VERSION,
|
||||
SANITIZER_VERSION,
|
||||
} from '@sf/adapter-wp'
|
||||
import { RENDER_SNAPSHOT_VERSION, type RenderSnapshotFile } from '@sf/contracts'
|
||||
import {
|
||||
RENDER_SNAPSHOT_VERSION,
|
||||
type AssetEntry,
|
||||
type ContentEntry,
|
||||
type RenderManifestMeta,
|
||||
type RenderSnapshotFile,
|
||||
type RouteEntry,
|
||||
type TaxonomyEntry,
|
||||
} from '@sf/contracts'
|
||||
import { canonicalJson } from '../../common/canonical.js'
|
||||
import {
|
||||
contentEntries,
|
||||
@@ -325,7 +336,7 @@ export async function materializeRenderSnapshot(
|
||||
}
|
||||
}
|
||||
|
||||
const contentPayload = published.map((row) => {
|
||||
const contentPayload: ContentEntry[] = published.map((row) => {
|
||||
const data = (row.data ?? {}) as Record<string, unknown>
|
||||
let entryData = data
|
||||
if (typeof data.bodyHtml === 'string') {
|
||||
@@ -337,14 +348,14 @@ export async function materializeRenderSnapshot(
|
||||
// slug 以修订 data.slug(发布时点冻结值)为准,缺席回退 entry_locales.slug(current 跟随面)
|
||||
const slug = typeof data.slug === 'string' && data.slug !== '' ? data.slug : row.localeSlug
|
||||
const media = usageMap.get(row.revisionId!)
|
||||
const entry: Record<string, unknown> = {
|
||||
const entry: ContentEntry = {
|
||||
contentId: row.contentId,
|
||||
typeKey: row.typeKey,
|
||||
locale: row.locale,
|
||||
slug,
|
||||
data: entryData,
|
||||
revisionId: row.revisionId,
|
||||
checksum: row.checksum,
|
||||
revisionId: row.revisionId!,
|
||||
checksum: row.checksum!,
|
||||
}
|
||||
if (media !== undefined && media.size > 0) {
|
||||
entry.media = Object.fromEntries([...media.entries()].sort(([a], [b]) => cmp(a, b)))
|
||||
@@ -352,10 +363,10 @@ export async function materializeRenderSnapshot(
|
||||
return entry
|
||||
})
|
||||
contentPayload.sort((a, b) =>
|
||||
cmp(a.typeKey as string, b.typeKey as string)
|
||||
|| cmp(a.slug as string, b.slug as string)
|
||||
|| cmp(a.locale as string, b.locale as string)
|
||||
|| cmp(a.contentId as string, b.contentId as string),
|
||||
cmp(a.typeKey, b.typeKey)
|
||||
|| cmp(a.slug, b.slug)
|
||||
|| cmp(a.locale, b.locale)
|
||||
|| cmp(a.contentId, b.contentId),
|
||||
)
|
||||
|
||||
// taxonomies.json:taxonomies × terms 全量 + content_terms 关联(auto: 自建行 source_id 原样)
|
||||
@@ -396,7 +407,7 @@ export async function materializeRenderSnapshot(
|
||||
list.push(t)
|
||||
termsByTaxonomy.set(t.taxonomyId, list)
|
||||
}
|
||||
const taxonomiesPayload = taxonomyRows.map((tax) => {
|
||||
const taxonomiesPayload: TaxonomyEntry[] = taxonomyRows.map((tax) => {
|
||||
const group = (termsByTaxonomy.get(tax.id) ?? []).map((t) => ({
|
||||
id: t.id,
|
||||
sourceId: t.sourceId,
|
||||
@@ -437,7 +448,9 @@ export async function materializeRenderSnapshot(
|
||||
`路由表含 ${unservicable.length} 条不可服务形态(dynamic/unresolved)——不进 routes.json(处置是 SP3+ 查询路由域)`,
|
||||
)
|
||||
}
|
||||
const routesPayload = routeRows.filter((r) => SERVICABLE.has(r.routeKind))
|
||||
// 契约载荷类型同源(Task 3 审查上提):filter 收窄 routeKind 枚举面(html/asset/redirect/gone)
|
||||
// TS 不可静态感知——as 是 Set 过滤的运行时保证,非断言放宽
|
||||
const routesPayload = routeRows.filter((r) => SERVICABLE.has(r.routeKind)) as RouteEntry[]
|
||||
routesPayload.sort(
|
||||
(a, b) => cmp(a.routePath, b.routePath) || cmpNull(a.queryKey, b.queryKey) || cmp(a.host, b.host),
|
||||
)
|
||||
@@ -452,7 +465,7 @@ export async function materializeRenderSnapshot(
|
||||
const bundleRootRaw = bundleRows[0]?.metadata as { storageRoot?: unknown } | undefined
|
||||
const bundleRoot = typeof bundleRootRaw?.storageRoot === 'string' ? bundleRootRaw.storageRoot : null
|
||||
|
||||
const assetsPayload = assetRows.map((a) => {
|
||||
const assetsPayload: AssetEntry[] = assetRows.map((a) => {
|
||||
const candidates: string[] = []
|
||||
if (bundleRoot !== null && bundleRoot !== '') candidates.push(join(bundleRoot, a.storagePath))
|
||||
const repoCopy = join(REPO_STORAGE, a.storagePath)
|
||||
@@ -496,7 +509,7 @@ export async function materializeRenderSnapshot(
|
||||
routes: routesPayload.length,
|
||||
assets: assetsPayload.length,
|
||||
}
|
||||
const manifestPayload = {
|
||||
const manifestPayload: RenderManifestMeta = {
|
||||
renderSnapshotVersion: RENDER_SNAPSHOT_VERSION,
|
||||
versions: {
|
||||
adapter: ADAPTER_VERSION,
|
||||
|
||||
Reference in New Issue
Block a user