Commit ae84b5c
Changed files (5)
scaffolds/draft.md
@@ -0,0 +1,8 @@
+---
+title: {{ title }}
+slug:
+category:
+tags:
+cover:
+description:
+---
scaffolds/post.md
@@ -0,0 +1,9 @@
+---
+title: {{ title }}
+slug:
+category:
+tags:
+cover:
+description:
+published: {{ date }}
+---
scripts/new.mjs
@@ -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();
scripts/pub.mjs
@@ -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();
package.json
@@ -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",