AstralHalo/scripts/pub.mjs
HPCesia 92e7cd5fcd fix(user scripts): pub time
Fix the bug that `pnpm pub` not add `published` key in the frontmatter of published post.
2025-03-19 16:04:25 +08:00

225 lines
6.1 KiB
JavaScript

import { t } from './locale/index.js';
import { checkFileExists, getFilePath } from './utils.mjs';
import { Command } from 'commander';
import dayjs from 'dayjs';
import { promises as fs } from 'fs';
import path from 'path';
import { createInterface } from 'readline/promises';
const program = new Command();
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
const DRAFTS_DIR = 'src/content/drafts';
const POSTS_DIR = 'src/content/posts';
// 格式化时间
function formatDateTime() {
return dayjs().format('YYYY-MM-DDTHH:mm:ssZ');
}
// 更新文章内容,添加发布时间
async function updateArticleContent(filePath) {
const content = await fs.readFile(filePath, 'utf-8');
const now = formatDateTime();
// 如果内容中已经包含 published 字段,则不修改
if (content.includes('published:')) {
return content;
}
// 在 frontmatter 的末尾(第一个 --- 之后,第二个 --- 之前)添加 published 字段
const parts = content.split('---');
if (parts.length < 3) {
return content; // 如果文件格式不正确,返回原内容
}
const [, frontmatter, ...bodyParts] = parts;
return `---${frontmatter.trimEnd()}\npublished: ${now}\n---${bodyParts.join('---')}`;
}
// 列出文件并分页显示
async function listDraftsWithPagination(drafts, page = 1, pageSize = 10) {
const start = (page - 1) * pageSize;
const end = start + pageSize;
const totalPages = Math.ceil(drafts.length / pageSize);
console.log(t('draftsTitle', { current: page, total: totalPages }));
drafts.slice(start, end).forEach((draft, index) => {
console.log(`${start + index + 1}. ${draft}`);
});
if (totalPages > 1) {
console.log(t('paginationTip'));
}
return totalPages;
}
// 处理文件冲突
async function handleFileConflict(title, isDir) {
console.log(t('fileExist', { path: title }));
console.log(t('chooseAction'));
console.log('1. ', t('actions.useNewName'));
console.log('2. ', t('actions.overwrite'));
console.log('3. ', t('actions.exit'));
const answer = await rl.question(t('inputOption', { countStart: 1, countEnd: 3 }));
switch (answer.trim()) {
case '1': {
let counter = 1;
while ((await checkFileExists(POSTS_DIR, title, counter)).exists) {
counter++;
}
return getFilePath(POSTS_DIR, `${title}-${counter}`, isDir);
}
case '2':
return getFilePath(POSTS_DIR, title, isDir);
case '3':
rl.close();
process.exit(0);
break;
default:
console.log(t('invalidOption'));
rl.close();
process.exit(1);
}
}
// 获取所有草稿文件
async function getAllDrafts() {
const drafts = [];
try {
const files = await fs.readdir(DRAFTS_DIR, { withFileTypes: true });
for (const file of files) {
const title = file.name.endsWith('.md') ? file.name.slice(0, -3) : file.name;
// 使用 checkFileExists 检查文件
const { exists, filePath } = await checkFileExists(DRAFTS_DIR, title);
if (exists && filePath) {
drafts.push(path.relative(DRAFTS_DIR, filePath));
}
}
return drafts;
} catch (error) {
console.error(t('readDraftsError'), error);
process.exit(1);
}
}
// 发布文章
async function publishDraft(draftPath) {
const fullDraftPath = path.join(DRAFTS_DIR, draftPath);
let destPath = path.join(POSTS_DIR, draftPath);
const isDir = draftPath.includes('index.md');
const title = isDir
? path.basename(path.dirname(draftPath))
: path.basename(draftPath, '.md');
const { exists } = await checkFileExists(POSTS_DIR, title);
if (exists) {
destPath = await handleFileConflict(title, isDir);
if (!destPath) {
console.log(t('invalidOption'));
process.exit(1);
}
}
try {
// 确保目标目录存在
await fs.mkdir(path.dirname(destPath), { recursive: true });
// 如果是目录形式,需要复制整个目录
if (isDir) {
const draftDir = path.dirname(fullDraftPath);
const destDir = path.dirname(destPath);
// 复制目录内所有文件
const files = await fs.readdir(draftDir);
for (const file of files) {
const srcFile = path.join(draftDir, file);
const destFile = path.join(destDir, file);
if (file === 'index.md') {
// 更新并写入文章内容
const updatedContent = await updateArticleContent(srcFile);
await fs.writeFile(destFile, updatedContent);
} else {
await fs.copyFile(srcFile, destFile);
}
}
// 删除源目录
await fs.rm(draftDir, { recursive: true });
} else {
// 更新并写入文章内容
const updatedContent = await updateArticleContent(fullDraftPath);
await fs.writeFile(destPath, updatedContent);
await fs.unlink(fullDraftPath);
}
console.log(t('publishSuccess', { path: destPath }));
} catch (error) {
console.error(t('publishError', { path: draftPath }), error);
process.exit(1);
}
}
async function main() {
// 获取所有草稿
const drafts = await getAllDrafts();
if (drafts.length === 0) {
console.log(t('noDrafts'));
rl.close();
return;
}
let currentPage = 1;
const totalPages = await listDraftsWithPagination(drafts, currentPage);
while (true) {
const answer = await rl.question(t('selectDraft'));
if (answer.toLowerCase() === 'n' && currentPage < totalPages) {
currentPage++;
await listDraftsWithPagination(drafts, currentPage);
continue;
}
if (answer.toLowerCase() === 'p' && currentPage > 1) {
currentPage--;
await listDraftsWithPagination(drafts, currentPage);
continue;
}
const selection = parseInt(answer);
if (isNaN(selection) || selection < 1 || selection > drafts.length) {
console.log(t('invalidSelection'));
continue;
}
const selectedDraft = drafts[selection - 1];
await publishDraft(selectedDraft);
break;
}
rl.close();
}
program
.name('pub')
.description(t('cli.pubDescription'))
.helpOption('-h, --help', t('cli.helpOption'))
.showHelpAfterError(t('cli.showHelp'));
program.parse();
main().catch((error) => {
console.error(t('cli.error'), error);
process.exit(1);
});