A CLI for publishing standard.site documents to ATProto

feat: add option to disable publishing (text) content

+37 -9
+5
packages/cli/src/commands/init.ts
··· 98 98 message: "URL path prefix for posts:", 99 99 placeholder: "/posts, /blog, /articles, etc.", 100 100 }), 101 + publishContent: () => confirm({ 102 + message: 'Publish the post content on the standard.site document?', 103 + initialValue: true 104 + }) 101 105 }, 102 106 { onCancel }, 103 107 ); ··· 341 345 pdsUrl, 342 346 frontmatter: frontmatterMapping, 343 347 bluesky: blueskyConfig, 348 + publishContent: siteConfig.publishContent 344 349 }); 345 350 346 351 const configPath = path.join(process.cwd(), "sequoia.json");
+9
packages/cli/src/commands/update.ts
··· 162 162 stripDatePrefix: configUpdated.stripDatePrefix, 163 163 pathTemplate: configUpdated.pathTemplate, 164 164 textContentField: configUpdated.textContentField, 165 + publishContent: configUpdated.publishContent, 165 166 bluesky: configUpdated.bluesky, 166 167 }); 167 168 ··· 373 374 }), 374 375 ); 375 376 377 + const publishContent = exitOnCancel( 378 + await confirm({ 379 + message: 'Publish the post content on the standard.site document?', 380 + initialValue: config.publishContent ?? true 381 + }) 382 + ) 383 + 376 384 const textContentField = exitOnCancel( 377 385 await text({ 378 386 message: ··· 397 405 removeIndexFromSlug: removeIndexFromSlug || undefined, 398 406 stripDatePrefix: stripDatePrefix || undefined, 399 407 textContentField: textContentField || undefined, 408 + publishContent: publishContent ?? true 400 409 }; 401 410 } 402 411
+11 -9
packages/cli/src/lib/atproto.ts
··· 252 252 ); 253 253 const publishDate = new Date(post.frontmatter.publishDate); 254 254 255 - // Determine textContent: use configured field from frontmatter, or fallback to markdown body 256 - let textContent: string; 255 + // Determine textContent (if enabled): use configured field from frontmatter, or fallback to markdown body 256 + let textContent: string | null = null; 257 257 if ( 258 + config.publishContent && 258 259 config.textContentField && 259 260 post.rawFrontmatter?.[config.textContentField] 260 261 ) { 261 262 textContent = String(post.rawFrontmatter[config.textContentField]); 262 - } else { 263 + } else if (config.publishContent) { 263 264 textContent = stripMarkdownForText(post.content); 264 265 } 265 266 ··· 268 269 title: post.frontmatter.title, 269 270 site: config.publicationUri, 270 271 path: postPath, 271 - textContent: textContent.slice(0, 10000), 272 + textContent: textContent?.slice(0, 10000), 272 273 publishedAt: publishDate.toISOString(), 273 274 canonicalUrl: `${config.siteUrl}${postPath}`, 274 275 }; ··· 317 318 ); 318 319 const publishDate = new Date(post.frontmatter.publishDate); 319 320 320 - // Determine textContent: use configured field from frontmatter, or fallback to markdown body 321 - let textContent: string; 321 + // Determine textContent (if enabled): use configured field from frontmatter, or fallback to markdown body 322 + let textContent: string | null = null; 322 323 if ( 324 + config.publishContent && 323 325 config.textContentField && 324 326 post.rawFrontmatter?.[config.textContentField] 325 327 ) { 326 328 textContent = String(post.rawFrontmatter[config.textContentField]); 327 - } else { 329 + } else if (config.publishContent) { 328 330 textContent = stripMarkdownForText(post.content); 329 331 } 330 332 ··· 342 344 title: post.frontmatter.title, 343 345 site: config.publicationUri, 344 346 path: postPath, 345 - textContent: textContent.slice(0, 10000), 347 + textContent: textContent?.slice(0, 10000), 346 348 publishedAt: publishDate.toISOString(), 347 349 canonicalUrl: `${config.siteUrl}${postPath}`, 348 350 }; ··· 384 386 title: string; 385 387 site: string; 386 388 path: string; 387 - textContent: string; 389 + textContent?: string; 388 390 publishedAt: string; 389 391 canonicalUrl?: string; 390 392 description?: string;
+6
packages/cli/src/lib/config.ts
··· 85 85 stripDatePrefix?: boolean; 86 86 pathTemplate?: string; 87 87 textContentField?: string; 88 + publishContent?: boolean; 88 89 bluesky?: BlueskyConfig; 89 90 }): string { 90 91 const config: Record<string, unknown> = { ··· 138 139 if (options.textContentField) { 139 140 config.textContentField = options.textContentField; 140 141 } 142 + 143 + if (options.publishContent) { 144 + config.publishContent = options.publishContent 145 + } 146 + 141 147 if (options.bluesky) { 142 148 config.bluesky = options.bluesky; 143 149 }
+1
packages/cli/src/lib/types.ts
··· 41 41 stripDatePrefix?: boolean; // Remove YYYY-MM-DD- prefix from filenames (Jekyll-style, default: false) 42 42 pathTemplate?: string; // URL path template with tokens like {year}/{month}/{day}/{slug} (overrides pathPrefix + slug) 43 43 textContentField?: string; // Frontmatter field to use for textContent instead of markdown body 44 + publishContent?: boolean; // Whether or not to publish the documents content on the standard.site document (default: true) 44 45 bluesky?: BlueskyConfig; // Optional Bluesky posting configuration 45 46 ui?: UIConfig; // Optional UI components configuration 46 47 }
+5
sequoia.schema.json
··· 116 116 "type": "string", 117 117 "description": "Frontmatter field to use for textContent instead of markdown body" 118 118 }, 119 + "publishContent": { 120 + "type": "boolean", 121 + "description": "Whether or not to publish the documents content on the standard.site document", 122 + "default": true 123 + }, 119 124 "bluesky": { 120 125 "type": "object", 121 126 "additionalProperties": false,