feat: add useful scripts

Add scripts for creating and publishing drafts, and update package.json
This commit is contained in:
HPCesia 2025-02-01 18:01:24 +08:00
parent 46d3b3c563
commit ae84b5c2cc
5 changed files with 307 additions and 1 deletions

View File

@ -8,7 +8,9 @@
"preview": "astro preview",
"astro": "astro",
"lint": "eslint ./src --fix && stylelint ./src/**/*.{scss,css,astro} --fix && astro check",
"format": "prettier --write ./src"
"format": "prettier --write ./src",
"new": "node scripts/new.mjs",
"pub": "node scripts/pub.mjs"
},
"dependencies": {
"@astrojs/rss": "^4.0.11",

8
scaffolds/draft.md Normal file
View File

@ -0,0 +1,8 @@
---
title: {{ title }}
slug:
category:
tags:
cover:
description:
---

9
scaffolds/post.md Normal file
View File

@ -0,0 +1,9 @@
---
title: {{ title }}
slug:
category:
tags:
cover:
description:
published: {{ date }}
---

144
scripts/new.mjs Normal file
View File

@ -0,0 +1,144 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import readline from 'node:readline';
const args = process.argv.slice(2);
let type,
name,
isDir = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--dir') {
isDir = true;
continue;
}
if (!type) {
type = args[i];
} else if (!name) {
name = args[i];
}
}
if (!type || !name) {
console.error('Usage: pnpm new [post|draft] [name] [--dir]');
process.exit(1);
}
if (type !== 'post' && type !== 'draft') {
console.error('Type must be either "post" or "draft"');
process.exit(1);
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
function sanitizeFilename(filename) {
const basename = filename.replace(/\.md$/, '');
return basename
.replace(/[<>:"/\\|?*.,\s]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, '-');
}
async function checkFileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function findAvailableFilename(basePath, baseName) {
let counter = 1;
let filePath = basePath;
while (await checkFileExists(filePath)) {
if (isDir) {
const dirName = `${baseName}-${counter}`;
filePath = path.join(path.dirname(path.dirname(basePath)), dirName, 'index.md');
} else {
filePath = path.join(path.dirname(basePath), `${baseName}-${counter}.md`);
}
counter++;
}
return filePath;
}
async function handleExistingFile(targetPath) {
console.log('\nFile already exists:', targetPath);
console.log('1. Overwrite');
console.log('2. Use a different name (auto-numbered)');
console.log('3. Cancel\n');
const answer = await question('Choose an option: ');
switch (answer.trim()) {
case '1':
return targetPath;
case '2':
return await findAvailableFilename(
targetPath,
path.basename(isDir ? path.dirname(targetPath) : targetPath, '.md')
);
case '3':
rl.close();
console.log('\nOperation cancelled');
process.exit(0);
break;
default:
console.log('\nInvalid option, operation cancelled');
rl.close();
process.exit(1);
}
}
async function main() {
try {
const templatePath = path.resolve('scaffolds', `${type}.md`);
const template = await fs.readFile(templatePath, 'utf-8');
const now = new Date().toISOString();
const variables = {
title: name,
date: now,
};
const content = template.replace(/\{\{\s*(\w+)\s*\}\}/g, (match, key) => {
return variables[key] || '';
});
const targetDir = path.resolve('src', 'content', `${type}s`);
const sanitizedName = sanitizeFilename(name);
let targetPath;
if (isDir) {
targetPath = path.join(targetDir, sanitizedName, 'index.md');
} else {
targetPath = path.join(targetDir, `${sanitizedName}.md`);
}
if (await checkFileExists(targetPath)) {
targetPath = await handleExistingFile(targetPath);
}
await fs.mkdir(path.dirname(targetPath), { recursive: true });
await fs.writeFile(targetPath, content, 'utf-8');
console.log(`\nSuccessfully created ${type}: ${targetPath}`);
rl.close();
} catch (error) {
console.error('Error:', error.message);
rl.close();
process.exit(1);
}
}
main();

143
scripts/pub.mjs Normal file
View File

@ -0,0 +1,143 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import readline from 'node:readline';
const [, , name] = process.argv;
if (!name) {
console.error('Usage: pnpm publish [name]');
process.exit(1);
}
function sanitizeFilename(filename) {
const basename = filename.replace(/\.md$/, '');
return basename
.replace(/[<>:"/\\|?*.,\s]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, '-');
}
async function checkFileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function findDraftPath(draftDir, sanitizedName) {
// 检查常规文件
const regularPath = path.join(draftDir, `${sanitizedName}.md`);
const dirPath = path.join(draftDir, sanitizedName, 'index.md');
// 记录找到的所有匹配路径
const foundPaths = [];
if (await checkFileExists(regularPath)) {
foundPaths.push(regularPath);
}
if (await checkFileExists(dirPath)) {
foundPaths.push(dirPath);
}
if (foundPaths.length === 0) {
return null;
}
if (foundPaths.length === 1) {
return foundPaths[0];
}
console.log('\nMultiple drafts found with the same name:');
foundPaths.forEach((p, i) => {
console.log(`${i + 1}. ${p}`);
});
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const answer = await new Promise((resolve) => {
rl.question('\nChoose which draft to publish (enter number): ', resolve);
});
rl.close();
const choice = parseInt(answer.trim()) - 1;
if (choice >= 0 && choice < foundPaths.length) {
return foundPaths[choice];
}
console.error('\nInvalid choice');
process.exit(1);
}
async function copyDirectory(src, dest) {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await copyDirectory(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
}
}
async function main() {
try {
const sanitizedName = sanitizeFilename(name);
const draftDir = path.resolve('src', 'content', 'drafts');
const postsDir = path.resolve('src', 'content', 'posts');
const draftPath = await findDraftPath(draftDir, sanitizedName);
if (!draftPath) {
console.error(`\nError: Draft not found: ${sanitizedName}`);
process.exit(1);
}
const isDirDraft = path.basename(draftPath) === 'index.md';
let targetPath;
if (isDirDraft) {
targetPath = path.join(postsDir, path.basename(path.dirname(draftPath)), 'index.md');
} else {
targetPath = path.join(postsDir, `${sanitizedName}.md`);
}
const content = await fs.readFile(draftPath, 'utf-8');
const now = new Date().toISOString();
const updatedContent = content.replace(/^(---\n(?:.*\n)*?)(---)/, (match, front, end) => {
if (!front.includes('published:')) {
return `${front}published: ${now}\n${end}`;
}
return match;
});
if (isDirDraft) {
const srcDir = path.dirname(draftPath);
const destDir = path.dirname(targetPath);
await copyDirectory(srcDir, destDir);
await fs.writeFile(targetPath, updatedContent, 'utf-8');
await fs.rm(srcDir, { recursive: true });
} else {
await fs.mkdir(postsDir, { recursive: true });
await fs.writeFile(targetPath, updatedContent, 'utf-8');
await fs.unlink(draftPath);
}
console.log(`\nSuccessfully published: ${targetPath}`);
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}
main();