From c633d86911ecc314b199d163f45b722818e176e7 Mon Sep 17 00:00:00 2001 From: Steve Date: Sat, 22 Aug 2026 11:49:42 -0400 Subject: [PATCH] fix: remove omit textContent instead of null Closes #54 --- packages/cli/src/lib/atproto.ts | 23 ++++-- packages/cli/test/atproto.test.ts | 117 ++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/lib/atproto.ts b/packages/cli/src/lib/atproto.ts index a3881d3..f3b4c7b 100644 --- a/packages/cli/src/lib/atproto.ts +++ b/packages/cli/src/lib/atproto.ts @@ -28,7 +28,9 @@ function isDocumentRecord(value: unknown): value is DocumentRecord { typeof v.title === "string" && typeof v.site === "string" && typeof v.path === "string" && - (v.textContent === undefined || typeof v.textContent === "string") && + (v.textContent === undefined || + v.textContent === null || + typeof v.textContent === "string") && typeof v.publishedAt === "string" && (v.updatedAt === undefined || typeof v.updatedAt === "string") ); @@ -264,7 +266,7 @@ export async function createDocument( } // Determine textContent (if enabled): use configured field from frontmatter, or fallback to markdown body - let textContent: string | null = null; + let textContent: string | undefined; if ( config.publishContent && config.textContentField && @@ -280,10 +282,13 @@ export async function createDocument( title: post.frontmatter.title, site: config.publicationUri, path: postPath, - textContent: textContent, publishedAt: publishDate.toISOString(), }; + if (textContent !== undefined) { + record.textContent = textContent; + } + if (updatedAt) { record.updatedAt = updatedAt.toISOString(); } @@ -341,7 +346,7 @@ export async function updateDocument( : undefined; // Determine textContent (if enabled): use configured field from frontmatter, or fallback to markdown body - let textContent: string | null = null; + let textContent: string | undefined; if ( config.publishContent && config.textContentField && @@ -361,6 +366,8 @@ export async function updateDocument( const existingRecord = existingResponse.data.value as Record; const recordBase = { ...existingRecord }; delete recordBase.canonicalUrl; + // Drop any previously published body so disabling publishContent removes it + delete recordBase.textContent; const record: Record = { ...recordBase, @@ -368,10 +375,13 @@ export async function updateDocument( title: post.frontmatter.title, site: config.publicationUri, path: postPath, - textContent: textContent, publishedAt: publishDate.toISOString(), }; + if (textContent !== undefined) { + record.textContent = textContent; + } + if (updatedAt) { record.updatedAt = updatedAt.toISOString(); } @@ -418,7 +428,8 @@ export interface DocumentRecord { title: string; site: string; path: string; - textContent?: string; + // Records published before textContent was omitted may carry an explicit null + textContent?: string | null; publishedAt: string; updatedAt?: string; description?: string; diff --git a/packages/cli/test/atproto.test.ts b/packages/cli/test/atproto.test.ts index 77df70e..96c6cce 100644 --- a/packages/cli/test/atproto.test.ts +++ b/packages/cli/test/atproto.test.ts @@ -130,6 +130,123 @@ describe("ATProto document publishing", () => { }); }); + it("omits textContent entirely when publishContent is disabled", async () => { + let capturedRecord: Record | undefined; + + const agent = { + did: "did:plc:publisher", + com: { + atproto: { + repo: { + createRecord: async (input: { + repo: string; + collection: string; + record: Record; + }) => { + capturedRecord = input.record; + return { + data: { + cid: "new-cid", + uri: "at://did:plc:publisher/site.standard.document/new", + }, + }; + }, + }, + }, + }, + } as Agent; + + await createDocument(agent, createPost(), createConfig()); + + expect(capturedRecord).toBeDefined(); + expect("textContent" in capturedRecord!).toBe(false); + }); + + it("includes textContent when publishContent is enabled", async () => { + let capturedRecord: Record | undefined; + + const agent = { + did: "did:plc:publisher", + com: { + atproto: { + repo: { + createRecord: async (input: { + repo: string; + collection: string; + record: Record; + }) => { + capturedRecord = input.record; + return { + data: { + cid: "new-cid", + uri: "at://did:plc:publisher/site.standard.document/new", + }, + }; + }, + }, + }, + }, + } as Agent; + + await createDocument( + agent, + createPost(), + createConfig({ publishContent: true }), + ); + + expect(capturedRecord?.textContent).toBe("Body content"); + }); + + it("strips existing textContent on update when publishContent is disabled", async () => { + let capturedRecord: Record | undefined; + + const agent = { + did: "did:plc:publisher", + com: { + atproto: { + repo: { + getRecord: async () => ({ + data: { + value: { + $type: "site.standard.document", + title: "Old title", + site: "at://did:plc:publisher/site.standard.publication/main", + path: "/posts/old", + publishedAt: "2024-01-01T00:00:00.000Z", + textContent: "Previously published body", + }, + }, + }), + putRecord: async (input: { + repo: string; + collection: string; + rkey: string; + record: Record; + }) => { + capturedRecord = input.record; + return { + data: { + cid: "updated-cid", + uri: "at://did:plc:publisher/site.standard.document/existing", + }, + }; + }, + }, + }, + }, + } as unknown as Agent; + + await updateDocument( + agent, + createPost(), + "at://did:plc:publisher/site.standard.document/existing", + createConfig(), + ); + + expect(capturedRecord).toBeDefined(); + expect("textContent" in capturedRecord!).toBe(false); + }); + it("still uses canonicalUrl for the Bluesky external embed URL", async () => { let capturedRecord: Record | undefined; -- 2.51.2