···1313import {
1414 scanContentDirectory,
1515 getContentHash,
1616+ getTextContent,
1617 updateFrontmatterWithAtUri,
1718 resolvePostPath,
1819} from "../lib/markdown";
···181182 log.message(` URI: ${doc.uri}`);
182183 log.message(` File: ${path.basename(localPost.filePath)}`);
183184184184- // Update state (use relative path from config directory)
185185- const contentHash = await getContentHash(localPost.rawContent);
185185+ // Compare local text content with PDS text content to detect changes.
186186+ // We must avoid storing the local rawContent hash blindly, because
187187+ // that would make publish think nothing changed even when content
188188+ // was modified since the last publish.
189189+ const localTextContent = getTextContent(
190190+ localPost,
191191+ config.textContentField,
192192+ );
193193+ const contentMatchesPDS =
194194+ localTextContent.slice(0, 10000) === doc.value.textContent;
195195+196196+ // If local content matches PDS, store the local hash (up to date).
197197+ // If it differs, store empty hash so publish detects the change.
198198+ const contentHash = contentMatchesPDS
199199+ ? await getContentHash(localPost.rawContent)
200200+ : "";
186201 const relativeFilePath = path.relative(configDir, localPost.filePath);
187202 state.posts[relativeFilePath] = {
188203 contentHash,
+3-23
packages/cli/src/lib/atproto.ts
···22import * as mimeTypes from "mime-types";
33import * as fs from "node:fs/promises";
44import * as path from "node:path";
55-import { stripMarkdownForText, resolvePostPath } from "./markdown";
55+import { getTextContent, resolvePostPath } from "./markdown";
66import { getOAuthClient } from "./oauth-client";
77import type {
88 BlobObject,
···251251 config.pathTemplate,
252252 );
253253 const publishDate = new Date(post.frontmatter.publishDate);
254254-255255- // Determine textContent: use configured field from frontmatter, or fallback to markdown body
256256- let textContent: string;
257257- if (
258258- config.textContentField &&
259259- post.rawFrontmatter?.[config.textContentField]
260260- ) {
261261- textContent = String(post.rawFrontmatter[config.textContentField]);
262262- } else {
263263- textContent = stripMarkdownForText(post.content);
264264- }
254254+ const textContent = getTextContent(post, config.textContentField);
265255266256 const record: Record<string, unknown> = {
267257 $type: "site.standard.document",
···316306 config.pathTemplate,
317307 );
318308 const publishDate = new Date(post.frontmatter.publishDate);
319319-320320- // Determine textContent: use configured field from frontmatter, or fallback to markdown body
321321- let textContent: string;
322322- if (
323323- config.textContentField &&
324324- post.rawFrontmatter?.[config.textContentField]
325325- ) {
326326- textContent = String(post.rawFrontmatter[config.textContentField]);
327327- } else {
328328- textContent = stripMarkdownForText(post.content);
329329- }
309309+ const textContent = getTextContent(post, config.textContentField);
330310331311 const record: Record<string, unknown> = {
332312 $type: "site.standard.document",