Commit ae84b5c

HPCesia <me@hpcesia.com>
2025-02-01 10:01:24
feat: add useful scripts
Add scripts for creating and publishing drafts, and update package.json
1 parent 46d3b3c
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",