-
Notifications
You must be signed in to change notification settings - Fork 274
Scripts to add blog and authors #2414
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds two npm scripts to package.json: Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (11)
scripts/blog/utils.js (5)
213-214
: Fix multi-select value extraction (false/0 currently lost).Use an explicit undefined check;
||
mis-handles falsy option values.- const selected = Array.from(selectedItems).map(i => options[i].value || options[i]); + const selected = Array.from(selectedItems).map((i) => + options[i].value !== undefined ? options[i].value : options[i] + );
101-107
: Add non‑TTY fallback to avoid crashes when raw mode is unavailable.
process.stdin.setRawMode(true)
throws if stdin isn’t a TTY (CI, pipes). Provide a simple numeric fallback.render(); - process.stdin.setRawMode(true); - process.stdin.resume(); - process.stdin.setEncoding('utf8'); + if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') { + // Fallback: simple numeric selection + console.log('\n' + options.map((o, i) => ` ${i + 1}) ${o.label || o}`).join('\n')); + questionWithDefault('Enter choice number', '1').then((ans) => { + const idx = Math.min(options.length - 1, Math.max(0, (parseInt(ans, 10) || 1) - 1)); + resolve(options[idx].value !== undefined ? options[idx].value : options[idx]); + }); + return; + } + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8');
108-113
: Close readline on Ctrl+C to restore terminal cleanly.Ensure
rl
is closed before exit.if (key === '\u0003') { // Ctrl+C process.stdin.setRawMode(false); process.stdin.pause(); console.log('\n'); + try { rl.close(); } catch {} process.exit(); }
194-201
: Mirror the non‑TTY fallback in multi-select.Same raw‑mode concern applies here.
render(); - process.stdin.setRawMode(true); - process.stdin.resume(); - process.stdin.setEncoding('utf8'); + if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') { + console.log('\n' + options.map((o, i) => ` ${i + 1}) ${o.label || o}`).join('\n')); + questionWithDefault('Enter comma-separated numbers', '').then((ans) => { + const indices = (ans || '') + .split(',') + .map((s) => parseInt(s.trim(), 10) - 1) + .filter((i) => Number.isInteger(i) && i >= 0 && i < options.length); + const selected = (indices.length ? indices : [0]).map((i) => + options[i].value !== undefined ? options[i].value : options[i] + ); + resolve(selected); + }); + return; + } + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8');
201-206
: Also close readline on Ctrl+C here.if (key === '\u0003') { // Ctrl+C process.stdin.setRawMode(false); process.stdin.pause(); console.log('\n'); + try { rl.close(); } catch {} process.exit(); }
scripts/blog/create-author.js (2)
33-40
: Slugify: trim leading/trailing hyphens.Prevents slugs like
-foo-
if the name starts/ends with symbols.function slugify(text) { return text .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-') .replace(/--+/g, '-') - .trim(); + .replace(/^-+|-+$/g, '') + .trim(); }
277-278
: Command hint: pnpm script invocation.“pnpm create-blog” is fine with pnpm (alias to run). Consider “pnpm run create-blog” for clarity to npm users reading logs.
package.json (1)
25-27
: Unrelated: duplicate dependency key detected.
posthog-js
appears twice (Lines 32 and 91) with different versions; JSON keeps only the latter. Please dedupe to a single version.scripts/blog/create-blog.js (3)
33-43
: Robustly read author names/slugs from frontmatter (handle quoted values).Dequote matched scalars to avoid showing quotes in the picker.
- if (match) { - const frontmatter = match[1]; - const nameMatch = frontmatter.match(/name:\s*(.+)/); - const slugMatch = frontmatter.match(/slug:\s*(.+)/); - - if (nameMatch && slugMatch) { - authors.push({ - label: nameMatch[1].trim(), - value: slugMatch[1].trim() - }); - } - } + if (match) { + const frontmatter = match[1]; + const nameMatch = frontmatter.match(/^\s*name:\s*(.+)$/m); + const slugMatch = frontmatter.match(/^\s*slug:\s*(.+)$/m); + if (nameMatch && slugMatch) { + const dequote = (s) => s.trim().replace(/^['"]|['"]$/g, ''); + const name = dequote(nameMatch[1]); + const slug = dequote(slugMatch[1]); + authors.push({ label: name, value: slug }); + } + }
133-137
: Validate date format and coerce timeToRead to a positive integer.Prevents malformed frontmatter types.
- const today = new Date().toISOString().split('T')[0]; - blogInfo.date = await questionWithDefault('Date (YYYY-MM-DD)', today); - - blogInfo.timeToRead = await questionWithDefault('Time to read (minutes)', '5'); + const today = new Date().toISOString().split('T')[0]; + blogInfo.date = await questionWithDefault('Date (YYYY-MM-DD)', today); + const dateRe = /^\d{4}-\d{2}-\d{2}$/; + while (!dateRe.test(blogInfo.date)) { + console.log(`${COLORS.red}Invalid date. Use YYYY-MM-DD.${COLORS.reset}`); + blogInfo.date = await question('Date (YYYY-MM-DD): '); + } + + let ttrInput = await questionWithDefault('Time to read (minutes)', '5'); + let ttr = Number.parseInt(String(ttrInput), 10); + while (!Number.isInteger(ttr) || ttr <= 0) { + console.log(`${COLORS.red}Enter a positive integer.${COLORS.reset}`); + ttrInput = await question('Time to read (minutes): '); + ttr = Number.parseInt(String(ttrInput), 10); + } + blogInfo.timeToRead = ttr;
166-183
: Optional: omit cover field if no image was provided.Reduces broken image risk until the asset is added.
- const imageResult = await imagePathInput('Add Cover Image', fullCoverPath); - blogInfo.cover = coverPath; + const imageResult = await imagePathInput('Add Cover Image', fullCoverPath); + blogInfo.cover = coverPath; blogInfo.coverSourcePath = imageResult.sourcePath; blogInfo.coverTargetPath = imageResult.targetPath; + if (!blogInfo.coverSourcePath) { + delete blogInfo.cover; // handled in generateMarkdocContent if desired + }If you prefer this, I can adjust
generateMarkdocContent
to conditionally includecover
.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
package.json
(1 hunks)scripts/blog/create-author.js
(1 hunks)scripts/blog/create-blog.js
(1 hunks)scripts/blog/utils.js
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
scripts/blog/create-blog.js (1)
scripts/blog/utils.js (12)
path
(281-281)fs
(266-266)fs
(280-280)COLORS
(9-21)COLORS
(9-21)question
(37-43)questionWithDefault
(45-54)selectFromList
(56-139)imagePathInput
(244-275)copyImage
(277-298)printHeader
(23-35)closeReadline
(300-302)
scripts/blog/create-author.js (1)
scripts/blog/utils.js (11)
path
(281-281)COLORS
(9-21)COLORS
(9-21)fs
(266-266)fs
(280-280)question
(37-43)questionWithDefault
(45-54)imagePathInput
(244-275)copyImage
(277-298)selectFromList
(56-139)closeReadline
(300-302)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: tests
- GitHub Check: build
🔇 Additional comments (1)
package.json (1)
26-27
: LGTM: scripts wired for the new CLIs.
let content = `--- | ||
layout: author | ||
slug: ${authorInfo.slug} | ||
name: ${authorInfo.name} | ||
role: ${authorInfo.role} | ||
bio: ${authorInfo.bio} | ||
avatar: ${authorInfo.avatar}`; | ||
|
||
if (authorInfo.twitter) { | ||
content += `\ntwitter: ${authorInfo.twitter}`; | ||
} | ||
|
||
if (authorInfo.github) { | ||
content += `\ngithub: ${authorInfo.github}`; | ||
} | ||
|
||
if (authorInfo.linkedin) { | ||
content += `\nlinkedin: ${authorInfo.linkedin}`; | ||
} | ||
|
||
content += '\n---\n'; | ||
|
||
return content; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quote/escape YAML frontmatter to avoid parse breaks.
Unquoted user input (name/role/bio/links) will break on :
or special chars. Use JSON strings (valid YAML) for safety.
function generateAuthorMarkdoc(authorInfo) {
- let content = `---
-layout: author
-slug: ${authorInfo.slug}
-name: ${authorInfo.name}
-role: ${authorInfo.role}
-bio: ${authorInfo.bio}
-avatar: ${authorInfo.avatar}`;
+ let content = `---
+layout: author
+slug: ${JSON.stringify(authorInfo.slug)}
+name: ${JSON.stringify(authorInfo.name)}
+role: ${JSON.stringify(authorInfo.role)}
+bio: ${JSON.stringify(authorInfo.bio)}
+avatar: ${JSON.stringify(authorInfo.avatar)}`;
if (authorInfo.twitter) {
- content += `\ntwitter: ${authorInfo.twitter}`;
+ content += `\ntwitter: ${JSON.stringify(authorInfo.twitter)}`;
}
if (authorInfo.github) {
- content += `\ngithub: ${authorInfo.github}`;
+ content += `\ngithub: ${JSON.stringify(authorInfo.github)}`;
}
if (authorInfo.linkedin) {
- content += `\nlinkedin: ${authorInfo.linkedin}`;
+ content += `\nlinkedin: ${JSON.stringify(authorInfo.linkedin)}`;
}
content += '\n---\n';
return content;
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let content = `--- | |
layout: author | |
slug: ${authorInfo.slug} | |
name: ${authorInfo.name} | |
role: ${authorInfo.role} | |
bio: ${authorInfo.bio} | |
avatar: ${authorInfo.avatar}`; | |
if (authorInfo.twitter) { | |
content += `\ntwitter: ${authorInfo.twitter}`; | |
} | |
if (authorInfo.github) { | |
content += `\ngithub: ${authorInfo.github}`; | |
} | |
if (authorInfo.linkedin) { | |
content += `\nlinkedin: ${authorInfo.linkedin}`; | |
} | |
content += '\n---\n'; | |
return content; | |
} | |
function generateAuthorMarkdoc(authorInfo) { | |
let content = `--- | |
layout: author | |
slug: ${JSON.stringify(authorInfo.slug)} | |
name: ${JSON.stringify(authorInfo.name)} | |
role: ${JSON.stringify(authorInfo.role)} | |
bio: ${JSON.stringify(authorInfo.bio)} | |
avatar: ${JSON.stringify(authorInfo.avatar)}`; | |
if (authorInfo.twitter) { | |
content += `\ntwitter: ${JSON.stringify(authorInfo.twitter)}`; | |
} | |
if (authorInfo.github) { | |
content += `\ngithub: ${JSON.stringify(authorInfo.github)}`; | |
} | |
if (authorInfo.linkedin) { | |
content += `\nlinkedin: ${JSON.stringify(authorInfo.linkedin)}`; | |
} | |
content += '\n---\n'; | |
return content; | |
} |
🤖 Prompt for AI Agents
In scripts/blog/create-author.js around lines 161 to 184, the YAML frontmatter
is built by directly interpolating user-provided fields (name, role, bio,
avatar, twitter, github, linkedin), which can break parsing if values contain
":" or special characters; replace direct interpolation with a safe-quoting
strategy by wrapping each inserted user value with JSON.stringify(...) (or an
equivalent YAML-escaping helper) so values are emitted as valid quoted strings
(e.g., name: "..." role: "..." bio: "..." etc.), apply the same to optional
fields when present, and ensure the final content still ends with '\n---\n'.
function generateMarkdocContent(blogInfo) { | ||
const frontmatter = `--- | ||
layout: post | ||
title: ${blogInfo.title} | ||
description: ${blogInfo.description} | ||
date: ${blogInfo.date} | ||
cover: ${blogInfo.cover} | ||
timeToRead: ${blogInfo.timeToRead} | ||
author: ${blogInfo.author} | ||
category: ${blogInfo.category} | ||
featured: ${blogInfo.featured} | ||
--- | ||
|
||
# ${blogInfo.title} | ||
|
||
Start writing your blog post here... | ||
|
||
## Introduction | ||
|
||
Your introduction paragraph goes here. | ||
|
||
## Main Content | ||
|
||
Add your main content sections here. | ||
|
||
## Conclusion | ||
|
||
Wrap up your blog post with a conclusion. | ||
`; | ||
|
||
return frontmatter; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quote/escape string fields in post frontmatter.
Avoid YAML parse issues from :
and special chars; keep numbers/bools unquoted.
function generateMarkdocContent(blogInfo) {
const frontmatter = `---
layout: post
-title: ${blogInfo.title}
-description: ${blogInfo.description}
-date: ${blogInfo.date}
-cover: ${blogInfo.cover}
-timeToRead: ${blogInfo.timeToRead}
-author: ${blogInfo.author}
-category: ${blogInfo.category}
-featured: ${blogInfo.featured}
+title: ${JSON.stringify(blogInfo.title)}
+description: ${JSON.stringify(blogInfo.description)}
+date: ${JSON.stringify(blogInfo.date)}
+cover: ${JSON.stringify(blogInfo.cover)}
+timeToRead: ${blogInfo.timeToRead}
+author: ${JSON.stringify(blogInfo.author)}
+category: ${JSON.stringify(blogInfo.category)}
+featured: ${blogInfo.featured}
---
# ${blogInfo.title}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
function generateMarkdocContent(blogInfo) { | |
const frontmatter = `--- | |
layout: post | |
title: ${blogInfo.title} | |
description: ${blogInfo.description} | |
date: ${blogInfo.date} | |
cover: ${blogInfo.cover} | |
timeToRead: ${blogInfo.timeToRead} | |
author: ${blogInfo.author} | |
category: ${blogInfo.category} | |
featured: ${blogInfo.featured} | |
--- | |
# ${blogInfo.title} | |
Start writing your blog post here... | |
## Introduction | |
Your introduction paragraph goes here. | |
## Main Content | |
Add your main content sections here. | |
## Conclusion | |
Wrap up your blog post with a conclusion. | |
`; | |
return frontmatter; | |
} | |
function generateMarkdocContent(blogInfo) { | |
const frontmatter = `--- | |
layout: post | |
title: ${JSON.stringify(blogInfo.title)} | |
description: ${JSON.stringify(blogInfo.description)} | |
date: ${JSON.stringify(blogInfo.date)} | |
cover: ${JSON.stringify(blogInfo.cover)} | |
timeToRead: ${blogInfo.timeToRead} | |
author: ${JSON.stringify(blogInfo.author)} | |
category: ${JSON.stringify(blogInfo.category)} | |
featured: ${blogInfo.featured} | |
--- | |
# ${blogInfo.title} | |
Start writing your blog post here... | |
## Introduction | |
Your introduction paragraph goes here. | |
## Main Content | |
Add your main content sections here. | |
## Conclusion | |
Wrap up your blog post with a conclusion. | |
`; | |
return frontmatter; | |
} |
Blog creation
Blog creation flow allows the user to quickly generate folders and markdoc files and paste the cover into the correct location just by dragging and dropping the file into the CLI.
Author creation
Flow is pretty similar for author creation
Summary by CodeRabbit
New Features
Chores