A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing

Add Draft Post Feature #5

merged opened by stevedylan.dev targeting main from feat/draft-posts

Overview#

Simple change to add a draft field to the frontmatter config that will skip publishing posts that are marked draft: true, completing Issue #4 . Frontmatter config allows mapping this to another term like private and will ask the user during the init command. The sequoia.json config can be updated at any time to add or change this field.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/sh.tangled.repo.pull/3mdrah7dj7q22
+52 -1
Diff #0
+4 -1
docs/docs/pages/config.mdx
··· 51 | `publishDate` | `string` | Yes | `"publishDate"`, `"pubDate"`, `"date"`, `"createdAt"`, `"created_at"` | Publication date | 52 | `coverImage` | `string` | No | `"ogImage"` | Cover image filename | 53 | `tags` | `string[]` | No | `"tags"` | Post tags/categories | 54 55 ### Example 56 ··· 61 publishDate: 2024-01-15 62 ogImage: cover.jpg 63 tags: [welcome, intro] 64 --- 65 ``` 66 ··· 72 { 73 "frontmatter": { 74 "publishDate": "date", 75 - "coverImage": "thumbnail" 76 } 77 } 78 ```
··· 51 | `publishDate` | `string` | Yes | `"publishDate"`, `"pubDate"`, `"date"`, `"createdAt"`, `"created_at"` | Publication date | 52 | `coverImage` | `string` | No | `"ogImage"` | Cover image filename | 53 | `tags` | `string[]` | No | `"tags"` | Post tags/categories | 54 + | `draft` | `boolean` | No | `"draft"` | If `true`, post is skipped during publish | 55 56 ### Example 57 ··· 62 publishDate: 2024-01-15 63 ogImage: cover.jpg 64 tags: [welcome, intro] 65 + draft: false 66 --- 67 ``` 68 ··· 74 { 75 "frontmatter": { 76 "publishDate": "date", 77 + "coverImage": "thumbnail", 78 + "draft": "private" 79 } 80 } 81 ```
+21
docs/docs/pages/publishing.mdx
··· 45 46 The `maxAgeDays` setting prevents flooding your feed when first setting up Sequoia. For example, if you have 40 existing blog posts, only those published within the last 30 days will be posted to Bluesky. 47 48 ## Troubleshooting 49 50 - If you have files in your markdown directory that should be ignored, use the [`ignore` array in the config](/config#ignoring-files).
··· 45 46 The `maxAgeDays` setting prevents flooding your feed when first setting up Sequoia. For example, if you have 40 existing blog posts, only those published within the last 30 days will be posted to Bluesky. 47 48 + ## Draft Posts 49 + 50 + Posts with `draft: true` in their frontmatter are automatically skipped during publishing. This lets you work on content without accidentally publishing it. 51 + 52 + ```yaml 53 + --- 54 + title: Work in Progress 55 + draft: true 56 + --- 57 + ``` 58 + 59 + If your framework uses a different field name (like `private` or `hidden`), configure it in `sequoia.json`: 60 + 61 + ```json 62 + { 63 + "frontmatter": { 64 + "draft": "private" 65 + } 66 + } 67 + ``` 68 + 69 ## Troubleshooting 70 71 - If you have files in your markdown directory that should be ignored, use the [`ignore` array in the config](/config#ignoring-files).
+7
packages/cli/src/commands/init.ts
··· 138 defaultValue: "tags", 139 placeholder: "tags, categories, keywords, etc.", 140 }), 141 }, 142 { onCancel }, 143 ); ··· 149 ["publishDate", frontmatterConfig.dateField, "publishDate"], 150 ["coverImage", frontmatterConfig.coverField, "ogImage"], 151 ["tags", frontmatterConfig.tagsField, "tags"], 152 ]; 153 154 const builtMapping = fieldMappings.reduce<FrontmatterMapping>(
··· 138 defaultValue: "tags", 139 placeholder: "tags, categories, keywords, etc.", 140 }), 141 + draftField: () => 142 + text({ 143 + message: "Field name for draft status:", 144 + defaultValue: "draft", 145 + placeholder: "draft, private, hidden, etc.", 146 + }), 147 }, 148 { onCancel }, 149 ); ··· 155 ["publishDate", frontmatterConfig.dateField, "publishDate"], 156 ["coverImage", frontmatterConfig.coverField, "ogImage"], 157 ["tags", frontmatterConfig.tagsField, "tags"], 158 + ["draft", frontmatterConfig.draftField, "draft"], 159 ]; 160 161 const builtMapping = fieldMappings.reduce<FrontmatterMapping>(
+11
packages/cli/src/commands/publish.ts
··· 96 action: "create" | "update"; 97 reason: string; 98 }> = []; 99 100 for (const post of posts) { 101 const contentHash = await getContentHash(post.rawContent); 102 const relativeFilePath = path.relative(configDir, post.filePath); 103 const postState = state.posts[relativeFilePath]; ··· 125 } 126 } 127 128 if (postsToPublish.length === 0) { 129 log.success("All posts are up to date. Nothing to publish."); 130 return;
··· 96 action: "create" | "update"; 97 reason: string; 98 }> = []; 99 + const draftPosts: BlogPost[] = []; 100 101 for (const post of posts) { 102 + // Skip draft posts 103 + if (post.frontmatter.draft) { 104 + draftPosts.push(post); 105 + continue; 106 + } 107 + 108 const contentHash = await getContentHash(post.rawContent); 109 const relativeFilePath = path.relative(configDir, post.filePath); 110 const postState = state.posts[relativeFilePath]; ··· 132 } 133 } 134 135 + if (draftPosts.length > 0) { 136 + log.info(`Skipping ${draftPosts.length} draft post${draftPosts.length === 1 ? "" : "s"}`); 137 + } 138 + 139 if (postsToPublish.length === 0) { 140 log.success("All posts are up to date. Nothing to publish."); 141 return;
+7
packages/cli/src/lib/markdown.ts
··· 99 const tagsField = mapping?.tags || "tags"; 100 frontmatter.tags = raw[tagsField] || raw.tags; 101 102 // Always preserve atUri (internal field) 103 frontmatter.atUri = raw.atUri; 104
··· 99 const tagsField = mapping?.tags || "tags"; 100 frontmatter.tags = raw[tagsField] || raw.tags; 101 102 + // Draft mapping 103 + const draftField = mapping?.draft || "draft"; 104 + const draftValue = raw[draftField] ?? raw.draft; 105 + if (draftValue !== undefined) { 106 + frontmatter.draft = draftValue === true || draftValue === "true"; 107 + } 108 + 109 // Always preserve atUri (internal field) 110 frontmatter.atUri = raw.atUri; 111
+2
packages/cli/src/lib/types.ts
··· 4 publishDate?: string; // Field name for publish date (default: "publishDate", also checks "pubDate", "date", "createdAt", "created_at") 5 coverImage?: string; // Field name for cover image (default: "ogImage") 6 tags?: string; // Field name for tags (default: "tags") 7 } 8 9 // Strong reference for Bluesky post (com.atproto.repo.strongRef) ··· 46 tags?: string[]; 47 ogImage?: string; 48 atUri?: string; 49 } 50 51 export interface BlogPost {
··· 4 publishDate?: string; // Field name for publish date (default: "publishDate", also checks "pubDate", "date", "createdAt", "created_at") 5 coverImage?: string; // Field name for cover image (default: "ogImage") 6 tags?: string; // Field name for tags (default: "tags") 7 + draft?: string; // Field name for draft status (default: "draft") 8 } 9 10 // Strong reference for Bluesky post (com.atproto.repo.strongRef) ··· 47 tags?: string[]; 48 ogImage?: string; 49 atUri?: string; 50 + draft?: boolean; 51 } 52 53 export interface BlogPost {

History

1 round 0 comments
sign up or login to add to the discussion
stevedylan.dev submitted #0
1 commit
expand
feat: added draft field to frontmatter config
expand 0 comments
pull request successfully merged