Compare commits

..
3 Commits
Author SHA1 Message Date
dsh 8e08fc5094 内容 schema 兜住 YAML 空值,修掉手写偶得触发的构建失败
问题
新增的 src/content/thoughts/20260622-夏至已至.md 让 build 直接失败:
  InvalidContentEntryDataError: thoughts → 20260622-夏至已至
  tags:   Expected type "array", received "object"
  images: Expected type "array", received "object"
报错里那个 "object" 很误导 —— 它其实是 null(JS 里 typeof null === "object")。

根因
该文件的 frontmatter 写了两个空字段:
  images:
  tags:
YAML 把「只写 key、后面什么都不跟」解析成 null,而不是 undefined。
而 schema 用的是 z.array(z.string()).optional().default([]) ——
.optional() 只放行 undefined,.default() 也只对 undefined 生效,
所以 null 会直接被判为类型不符。

这不是那一个文件的问题,而是一类写法的问题:手写 frontmatter 时把不要的字段
留空太自然了。而且这类报错信息完全指不出真正的原因,很浪费时间。

改法
在 content.config.ts 顶部加三个 helper,把 null 当作「没填」处理:
  text(fallback = "")   文本,留空 → fallback
  optionalText()        可选文本,留空 → undefined(保持原类型)
  textList()            字符串数组,留空 → []
然后套用到 posts / thoughts / studies 里所有「留空应该算默认值」的字段
(description / image / tags / category / lang / password / prev* / next*、
thoughts 的 title / tags / location / weather / images、studies 的三个可选文本)。

注意:只改了「留空该走默认」的字段。像 posts.title、published、
studies.title / status / started 这些必填项保持原样 —— 它们留空本来就该报错。

验证
- 清内容缓存重建:pnpm build 退出码 0,不再报 InvalidContentEntryDataError
- 那条偶得正常产出(tags / images 都成了空数组,location / weather 保留)
- 抽查其他条目:树莓派那篇的标签与任务列表、首页的偶得列表均正常
2026-09-17 20:35:56 +08:00
TimeCrackbot 90d24f8948 文章错误修改 2026-09-17 20:30:00 +08:00
TimeCrackbot 812140b2a5 文章更新 2026-09-17 20:29:37 +08:00
6 changed files with 62 additions and 31 deletions
+40 -18
View File
@@ -2,6 +2,28 @@ import { defineCollection } from "astro:content"
import { glob } from "astro/loaders" import { glob } from "astro/loaders"
import { z } from "astro/zod" import { z } from "astro/zod"
/*
* YAML 里只写 `tags:`(后面什么都不跟)会解析成 **null**,而不是 undefined。
*
* Zod 的 `.optional()` 只放行 undefined,`.default(x)` 也只对 undefined 生效,
* 所以 `z.array(z.string()).optional().default([])` 遇到 `tags:` 会直接报
* Expected type "array", received "object"
* —— 那个 "object" 是 JS 里 `typeof null === "object"` 造成的,很误导,
* 光看报错完全想不到是「字段留空」。
*
* 手写 frontmatter 时把不要的字段留空是很自然的写法,所以这里统一用下面两个
* helper 把 null 一并兜住,当作「没填」处理。
*/
/** 文本字段:留空 / 未填 → fallback(默认空字符串) */
const text = (fallback = "") => z.string().nullish().transform((v) => v ?? fallback)
/** 可选文本:留空 / 未填 → undefined(保持原来 `.optional()` 的类型) */
const optionalText = () => z.string().nullish().transform((v) => v ?? undefined)
/** 字符串数组:留空 / 未填 → 空数组 */
const textList = () => z.array(z.string()).nullish().transform((v) => v ?? [])
const posts = defineCollection({ const posts = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }), loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
schema: z.object({ schema: z.object({
@@ -9,36 +31,36 @@ const posts = defineCollection({
published: z.date(), published: z.date(),
updated: z.date().optional(), updated: z.date().optional(),
draft: z.boolean().optional().default(false), draft: z.boolean().optional().default(false),
description: z.string().optional().default(""), description: text(),
image: z.string().optional().default(""), image: text(),
tags: z.array(z.string()).optional().default([]), tags: textList(),
category: z.string().optional().nullable().default(""), category: z.string().nullish().transform((v) => v ?? ""),
lang: z.string().optional().default(""), lang: text(),
pinned: z.boolean().optional().default(false), pinned: z.boolean().optional().default(false),
encrypted: z.boolean().optional().default(false), encrypted: z.boolean().optional().default(false),
password: z.string().optional().default(""), password: text(),
disclaimer: z.union([z.string(), z.array(z.string())]).optional(), disclaimer: z.union([z.string(), z.array(z.string())]).optional(),
prevTitle: z.string().default(""), prevTitle: text(),
prevSlug: z.string().default(""), prevSlug: text(),
nextTitle: z.string().default(""), nextTitle: text(),
nextSlug: z.string().default(""), nextSlug: text(),
}), }),
}) })
const thoughts = defineCollection({ const thoughts = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/thoughts" }), loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/thoughts" }),
schema: z.object({ schema: z.object({
title: z.string().optional(), title: optionalText(),
published: z.date(), published: z.date(),
tags: z.array(z.string()).optional().default([]), tags: textList(),
/** 地点,显示在日期旁 */ /** 地点,显示在日期旁 */
location: z.string().optional(), location: optionalText(),
/** 天气,与地点一起显示 */ /** 天气,与地点一起显示 */
weather: z.string().optional(), weather: optionalText(),
/** 配图,public 下的绝对路径 */ /** 配图,public 下的绝对路径 */
images: z.array(z.string()).optional().default([]), images: textList(),
/** 置顶 */ /** 置顶 */
pinned: z.boolean().optional().default(false), pinned: z.boolean().optional().default(false),
}), }),
@@ -52,9 +74,9 @@ const studies = defineCollection({
title: z.string(), title: z.string(),
status: z.enum(["在读", "沉淀中", "暂搁", "已结"]), status: z.enum(["在读", "沉淀中", "暂搁", "已结"]),
started: z.string(), // "YYYY-MM" 或 "YYYY-MM-DD" started: z.string(), // "YYYY-MM" 或 "YYYY-MM-DD"
subtitle: z.string().optional(), subtitle: optionalText(),
epigraph: z.string().optional(), epigraph: optionalText(),
field: z.string().optional(), field: optionalText(),
}), }),
}) })
@@ -95,8 +95,7 @@ $$
$$ $$
行内模式(`$`)会让上下标显示在右上/右下: 行内模式(`$`)会让上下标显示在右上/右下:
$\sum_{i=1}^{n} i \quad \int_{a}^{b} f(x) , dx$
$ \sum_{i=1}^{n} i \quad \int_{a}^{b} f(x) \, dx $
### 希腊字母 ### 希腊字母
@@ -109,8 +108,8 @@ $ \sum_{i=1}^{n} i \quad \int_{a}^{b} f(x) \, dx $
| `\mu` | $\mu$ | `\pi` | $\pi$ | | `\mu` | $\mu$ | `\pi` | $\pi$ |
| `\sigma` | $\sigma$ | `\omega` | $\omega$ | | `\sigma` | $\sigma$ | `\omega` | $\omega$ |
| `\Gamma` | $\Gamma$ | `\Delta` | $\Delta$ | | `\Gamma` | $\Gamma$ | `\Delta` | $\Delta$ |
| `\Theta` | $\Theta$ | `\Pi$ | $\Pi$ | | `\Theta` | $\Theta$ | `\Pi$`| $\Pi$ |
| `\Sigma` | $\Sigma$ | `\Omega$ | $\Omega$ | | `\Sigma` | $\Sigma$ | `\Omega` | $\Omega$ |
### 常用运算符 ### 常用运算符
@@ -121,10 +120,10 @@ $ \sum_{i=1}^{n} i \quad \int_{a}^{b} f(x) \, dx $
| `\cdot` | $\cdot$ | `\circ` | $\circ$ | | `\cdot` | $\cdot$ | `\circ` | $\circ$ |
| `\leq` | $\leq$ | `\geq` | $\geq$ | | `\leq` | $\leq$ | `\geq` | $\geq$ |
| `\neq` | $\neq$ | `\approx` | $\approx$ | | `\neq` | $\neq$ | `\approx` | $\approx$ |
| `\subset` | $\subset$ | `\supset$ | $\supset$ | | `\subset` | $\subset$ | `\supset` | $\supset$ |
| `\in` | $\in$ | `\notin` | $\notin$ | | `\in` | $\in$ | `\notin` | $\notin$ |
| `\cup` | $\cup$ | `\cap$ | $\cap$ | | `\cup` | $\cup$ | `\cap` | $\cap$ |
| `\rightarrow` | $\rightarrow$ | `\Rightarrow$ | $\Rightarrow$ | | `\rightarrow` | $\rightarrow$ | `\Rightarrow` | $\Rightarrow$ |
| `\infty` | $\infty$ | `\partial` | $\partial$ | | `\infty` | $\infty$ | `\partial` | $\partial$ |
### 括号与定界符 ### 括号与定界符
@@ -13,7 +13,7 @@ draft: false
### 1. 重装了树莓派的操作系统 ### 1. 重装了树莓派的操作系统
通过官方烧录软件重新安装了新的 debian 操作系统,主要是根据的是[树莓派官方文档](https://pidoc.cn)指导,包括从系统 APT 换源、Docker 部署安装、基本的 linux,命令行语法,这个文档里都有比较详细的帮助。从简单的 ssh 登录入手,直接在命令行中操作文件复制、删除还是很高效的。特别是现在在 AI 的帮助下,很多代码指令可以让 AI 直接生成,不用自己再去查询,极大的降低了学习成本。 通过官方烧录软件重新安装了新的 debian 操作系统,主要是根据的是[树莓派官方文档](https://pidoc.cn)指导,包括从系统 APT 换源、Docker 部署安装、基本的 linux,命令行语法,这个文档里都有比较详细的帮助。从简单的 ssh 登录入手,直接在命令行中操作文件复制、删除还是很高效的。特别是现在在 AI 的帮助下,很多代码指令可以让 AI 直接生成,不用自己再去查询,极大的降低了学习成本。
![20260502103000658.png](http://81.70.45.191:40027/i/2026/05/02/69f561a9a3d61.png)
### 2. Docker 部署 ### 2. Docker 部署
Docker 容器化技术将各种依赖、运行环境打包成镜像,很多前后端分离的软件、需要长期在线的服务都非常适合。但是我现在只是会用一些基本的命令,很多还是靠 AI,特别是**端口映射**、**宿主机数据文件映射**、**环境配置**这些操作还是不懂。 Docker 容器化技术将各种依赖、运行环境打包成镜像,很多前后端分离的软件、需要长期在线的服务都非常适合。但是我现在只是会用一些基本的命令,很多还是靠 AI,特别是**端口映射**、**宿主机数据文件映射**、**环境配置**这些操作还是不懂。
@@ -0,0 +1,14 @@
---
published: 2026-06-21
title: 夏至已至
location: 张掖·甘州区
weather: 晴
images:
tags:
pinned: false
---
> 随笔,只是想写一些自己的想法
转眼之间已是夏至,太阳再次来到北回归线,时间就是这样,总是过得如此之快。突然之间就回想起十年前,也就是2016年那个夏天了,那时我刚参加完高考,面对自己的未来不知所措,踏上了前往军检的火车。人生关键的选择往往并不多,只不过当时是无法感受到其强劲的影响。
当时自己也是一个懵懂无知的少年,缺少自己的想法和主见。有的时候可能这就是命吧,谁也不知道自己的未来会怎样,但是依然要积极面对生活。
@@ -15,7 +15,3 @@ weather: 晴
## 我必须改变,这需要自己去行动起来 ## 我必须改变,这需要自己去行动起来
> - [ ] 每天至少阅读1小时(晚饭后20分钟,睡觉前30分钟,午睡前10分钟),每天坚持!
> - [ ] 不看视频(特别是抖音)
> - [ ] 今日的事情,尽心尽力了,无论结果如何,都应该高高兴兴地上床恬睡。
> - [ ] 利用空闲时间更新一下自己的文章,对积淀一些东西
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"updatedAt": "2026-09-17T12:15:45.676Z", "updatedAt": "2026-09-17T12:35:36.207Z",
"source": "weread", "source": "weread",
"books": [ "books": [
{ {