@astro-paper/cli
Version:
A comprehensive CLI tool for AstroPaper blog theme management
135 lines (134 loc) • 4.42 kB
JavaScript
import dayjs from 'dayjs';
import { z } from 'zod/v4';
import { Command } from 'commander';
import kebabCase from 'lodash.kebabcase';
import { confirm, isCancel, log, text } from '@clack/prompts';
import { handleError } from '../utils/handle-error.js';
import { getSiteConfig } from '../utils/get-site-config.js';
import { getProjectRoot } from '../utils/get-project-root.js';
const optionsSchema = z.object({
title: z.string().optional(),
path: z.string().optional(),
yes: z.boolean().optional(),
draft: z.boolean().optional(),
featured: z.boolean().optional(),
});
export const addNewPost = new Command()
.name('new')
.argument('[title]', 'The title of the post')
.description('Add a new post to the blog')
.option('-p, --path <path>', 'The path to the post (e.g. "./examples/posts/")')
.option('-y, --yes', 'Create post with default values')
.option('-d,--draft', 'Create as draft')
.option('-f, --featured', 'Mark as featured')
.action(async (title, opts) => {
try {
log.info('Adding new post');
const projectRoot = await getProjectRoot();
if (!projectRoot) {
log.error('Not in an AstroPaper project. \nPlease run this command inside your AstroPaper project.');
process.exit(1);
}
const options = optionsSchema.parse({
title,
...opts,
});
const postInfo = await askPostInfo(projectRoot, options);
console.log(postInfo);
}
catch (error) {
console.log('');
handleError(error);
}
});
const DEFAULT_TITLE = 'New Post';
const DEFAULT_DESCRIPTION = 'A brief description of this blog post.';
const DEFAULT_PATH = './';
async function askPostInfo(projectRoot, options) {
const { yes, draft, featured } = options;
const siteConfig = await getSiteConfig(projectRoot);
if (!siteConfig) {
log.error('Could not find SITE config in config.ts');
process.exit(1);
}
if (yes) {
const { author } = siteConfig;
const title = options.title || DEFAULT_TITLE;
return {
title,
author,
description: DEFAULT_DESCRIPTION,
pubDatetime: dayjs().toISOString(),
fileName: kebabCase(title),
tags: ['others'],
draft: draft ?? true,
featured: featured ?? false,
};
}
let title = options.title;
let path = options.path;
if (!title) {
title = await text({
message: 'What is the title of the post?',
placeholder: DEFAULT_TITLE,
});
}
if (isCancel(title)) {
process.exit(0);
}
const description = await text({
message: 'What is the description of the post?',
placeholder: DEFAULT_DESCRIPTION,
});
if (isCancel(description)) {
process.exit(0);
}
if (!path) {
path = await text({
message: 'Where should the post be created? (e.g. "./examples/posts")',
placeholder: DEFAULT_PATH,
});
if (isCancel(path)) {
process.exit(0);
}
}
const defaultSlug = kebabCase(title ?? DEFAULT_TITLE);
const slug = await text({
message: 'What is the slug of the post? (e.g. "my-first-post")',
placeholder: defaultSlug,
});
if (isCancel(slug)) {
process.exit(0);
}
const author = await text({
message: 'What is the author of the post?',
placeholder: siteConfig.author,
});
const isDraft = await confirm({
message: 'Create as draft?',
initialValue: draft ?? true,
});
const isFeatured = await confirm({
message: 'Mark as featured?',
initialValue: featured ?? false,
});
const tags = await text({
message: 'Tags (eg: others, astro, React Query):',
placeholder: 'others',
});
if (isCancel(tags)) {
process.exit(0);
}
return {
title: title ?? DEFAULT_TITLE,
description: description ?? DEFAULT_DESCRIPTION,
path: path ?? DEFAULT_PATH,
slug: slug ?? defaultSlug,
author: author ?? siteConfig.author,
pubDatetime: dayjs().toISOString(),
fileName: kebabCase(title ?? DEFAULT_TITLE),
tags: tags ? tags.split(',').map(tag => tag.trim()) : ['others'],
draft: isDraft,
featured: isFeatured,
};
}