From 95d119bbc9484c9b76f65cc298def8f3ee39fae0 Mon Sep 17 00:00:00 2001 From: PrinOrange Date: Mon, 12 Aug 2024 13:53:28 +0800 Subject: [PATCH] [update] update tool scripts --- consts/consts.ts | 11 ++- lib/date.ts | 19 ++++ lib/file.ts | 25 +++++ lib/post-process.ts | 8 +- package-lock.json | 141 ++++++++++++++++++++++++++++ package.json | 6 +- scripts/{archive.mjs => archive.ts} | 82 ++++++---------- scripts/newpost.mjs | 98 ------------------- scripts/newpost.ts | 92 ++++++++++++++++++ 9 files changed, 320 insertions(+), 162 deletions(-) create mode 100644 lib/file.ts rename scripts/{archive.mjs => archive.ts} (55%) delete mode 100644 scripts/newpost.mjs create mode 100644 scripts/newpost.ts diff --git a/consts/consts.ts b/consts/consts.ts index ccc70d9..eeb98db 100644 --- a/consts/consts.ts +++ b/consts/consts.ts @@ -1,14 +1,17 @@ import { Config } from "@/data/config"; +import { getCurrentTime } from "@/lib/date"; import path from "path"; import process from "process"; export const LatestPostCountInHomePage = 10; export const PostCountPerPagination = 10; -export const PostsRootDirectory = path.join(process.cwd(), "./data/posts"); +export const UserDataDirectory = path.join(process.cwd(), "./data"); +export const PostFilesDirectory = path.join(UserDataDirectory, "/posts"); export const RSSFeedURL = `https://${Config.SiteDomain}/rss.xml`; export const WebsiteURL = `https://${Config.SiteDomain}/`; +export const PostURL = (postId: string) => `https://${Config.SiteDomain}/blog/${postId}`; +export const SearchURL = (keyword: string) => `https://${Config.SiteDomain}/search/?q=${keyword}`; -export const CopyrightAnnouncement = `COPYRIGHT © ${Config.YearStart}-${new Date().getFullYear()} ${ - Config.AuthorName -} ALL RIGHTS RESERVED`; +const year = getCurrentTime().year; +export const CopyrightAnnouncement = `COPYRIGHT © ${Config.YearStart}-${year} ${Config.AuthorName} ALL RIGHTS RESERVED`; diff --git a/lib/date.ts b/lib/date.ts index 6807647..fb92802 100644 --- a/lib/date.ts +++ b/lib/date.ts @@ -24,3 +24,22 @@ export const normalizeDate = (date: string = "1970-01-01"): string => { }; return `${day_num} ${month_en[month_num]}, ${year}`; }; + +export const getCurrentTime = (): { + year: string; + month: string; + day: string; + hours: string; + minutes: string; + seconds: string; +} => { + const today = new Date(); + return { + year: today.getFullYear().toString(), + month: String(today.getMonth() + 1).padStart(2, "0"), + day: String(today.getDate()).padStart(2, "0"), + hours: String(today.getHours()).padStart(2, "0"), + minutes: String(today.getMinutes()).padStart(2, "0"), + seconds: String(today.getSeconds()).padStart(2, "0"), + }; +}; diff --git a/lib/file.ts b/lib/file.ts new file mode 100644 index 0000000..adce5f8 --- /dev/null +++ b/lib/file.ts @@ -0,0 +1,25 @@ +import * as fs from "fs"; + +export function checkAndCreateDirectory(dirPath: string) { + try { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK); + return true; + } catch (err) { + throw err; + } +} + +export function isDirectoryEmptySync(directory: string) { + try { + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + const files = fs.readdirSync(directory); + return files.length === 0; + } catch (err) { + throw err; + } +} diff --git a/lib/post-process.ts b/lib/post-process.ts index 61e5e7d..41d2f17 100644 --- a/lib/post-process.ts +++ b/lib/post-process.ts @@ -1,4 +1,4 @@ -import { PostsRootDirectory } from "@/consts/consts"; +import { PostFilesDirectory } from "@/consts/consts"; import { TFrontmatter } from "@/types/frontmatter.type"; import { TPostListItem, TPostsByTag } from "@/types/post-list"; import fs from "fs"; @@ -36,8 +36,8 @@ async function extractFrontmatters(filepath: string): Promise { function readPostsDirectory(): string[] { const result: string[] = []; - fs.readdirSync(PostsRootDirectory).forEach((fileName) => { - const filePath = path.join(PostsRootDirectory, fileName); + fs.readdirSync(PostFilesDirectory).forEach((fileName) => { + const filePath = path.join(PostFilesDirectory, fileName); const fileStat = fs.statSync(filePath); if (fileStat.isFile() && fileName.endsWith(".md")) { @@ -48,7 +48,7 @@ function readPostsDirectory(): string[] { } export const getPostFileContent = (postId: string): string | null => { - const filePath = path.join(PostsRootDirectory, `${postId}.md`); + const filePath = path.join(PostFilesDirectory, `${postId}.md`); if (!fs.existsSync(filePath)) return null; const content = fs.readFileSync(filePath, "utf-8"); return content; diff --git a/package-lock.json b/package-lock.json index 03bbd03..05cdadd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "tailwind-merge": "^2.0.0", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", + "title-case": "^4.3.1", "typescript": "5.2.2", "url-join": "^5.0.0", "zustand": "^4.4.7" @@ -87,6 +88,7 @@ "@types/tar": "^6.1.11", "archiver": "^7.0.1", "autocorrect-node": "^2.11.1", + "bun": "^1.1.22", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "feed": "^4.2.2", @@ -1203,6 +1205,110 @@ "node": ">= 8" } }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.1.22.tgz", + "integrity": "sha512-1sNo/mXEWmf/opHyQElIZwUUwamR97d/xreQ3rTypDdqObbWbTH7Y8EhpOwqMeP+WKW/+WgyXAks0dI+guFr8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-darwin-x64/-/bun-darwin-x64-1.1.22.tgz", + "integrity": "sha512-keEPJtDlvu/36J+NX1JUh6+u0PiyoRLVGdDNaVU5LsphnYIVL8A4VLAE+xA5FMPlvMyjVua8kYbgUuTTHJJGpg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.1.22.tgz", + "integrity": "sha512-r1IOBt7A3NVfM3/PYkfpef3fo1cIdznRzCpE/XwEquYBbMFcvRETWWRUUNK80MFttYaPQuhyHui/b0VLJhoaEw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.1.22.tgz", + "integrity": "sha512-qL7IVUIaCFzSYae1UhX0rSbG1I1ARH7t3d9EMUxG//nBOqdI5xgEmpFCWPh8Y1mdpPl3bPMT0EvmNx/HjFrUJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-linux-x64/-/bun-linux-x64-1.1.22.tgz", + "integrity": "sha512-y4ugmfIg9GlXgZPj2mE5D9g+ou8kwMgSraR4zWvaPPSGDB2nbULGAA9S5DCVEBAuTBxMgh3wXlEgZwQKPmDPuQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64-baseline": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-linux-x64-baseline/-/bun-linux-x64-baseline-1.1.22.tgz", + "integrity": "sha512-xTaKbyAxn4jI5CaL13mhkj/wCNphDVHrxMIEfPW4rkVbnY/4Ci2uG9dRrNAXCxwRmyflcp8t2KWmmAUBS95McQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-windows-x64/-/bun-windows-x64-1.1.22.tgz", + "integrity": "sha512-VNgJaK54MnyS4o47JBN0oMh+75kgaHrMt3HWS3Ree1zn2qYyAexeC9m6KPynHGtNFQrxuhHv5ULxJ0Z0pAU7+A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.1.22.tgz", + "integrity": "sha512-Xdf0ZgonVup+YgFSTaGkzDpgcxojc2whFcaNvV0BJtTsl2MeAwGFl98AziGJSCh0ooG80ipUEIUoxDJBWxaEYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3539,6 +3645,36 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "peer": true }, + "node_modules/bun": { + "version": "1.1.22", + "resolved": "https://registry.npmmirror.com/bun/-/bun-1.1.22.tgz", + "integrity": "sha512-G2HCPhzhjDc2jEDkZsO9vwPlpHrTm7a8UVwx9oNS5bZqo5OcSK5GPuWYDWjj7+37bRk5OVLfeIvUMtSrbKeIjQ==", + "cpu": [ + "arm64", + "x64" + ], + "dev": true, + "hasInstallScript": true, + "os": [ + "darwin", + "linux", + "win32" + ], + "bin": { + "bun": "bin/bun.exe", + "bunx": "bin/bun.exe" + }, + "optionalDependencies": { + "@oven/bun-darwin-aarch64": "1.1.22", + "@oven/bun-darwin-x64": "1.1.22", + "@oven/bun-darwin-x64-baseline": "1.1.22", + "@oven/bun-linux-aarch64": "1.1.22", + "@oven/bun-linux-x64": "1.1.22", + "@oven/bun-linux-x64-baseline": "1.1.22", + "@oven/bun-windows-x64": "1.1.22", + "@oven/bun-windows-x64-baseline": "1.1.22" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", @@ -18573,6 +18709,11 @@ "node": ">=0.8" } }, + "node_modules/title-case": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/title-case/-/title-case-4.3.1.tgz", + "integrity": "sha512-VnPxQ+/j0X2FZ4ceGq1oLruTLjtN5Ul4sam5ypd4mDZLm1eHwkwip1gLxqhON/j4qyTlUlfPKslE/t4NPSlxhg==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 1f0e3d9..e92d3a4 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "format": "npx prettier . --write && npx autocorrect --fix", "lint": "next lint", "lint:fix": "npx eslint --fix .", - "newpost": "node ./scripts/newpost.mjs", - "archive": "node ./scripts/archive.mjs" + "newpost": "bun ./scripts/newpost.ts", + "archive": "bun ./scripts/archive.ts" }, "dependencies": { "@giscus/react": "^3.0.0", @@ -83,6 +83,7 @@ "tailwind-merge": "^2.0.0", "tailwindcss": "^3.4.0", "tailwindcss-animate": "^1.0.7", + "title-case": "^4.3.1", "typescript": "5.2.2", "url-join": "^5.0.0", "zustand": "^4.4.7" @@ -103,6 +104,7 @@ "@types/tar": "^6.1.11", "archiver": "^7.0.1", "autocorrect-node": "^2.11.1", + "bun": "^1.1.22", "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.1", "feed": "^4.2.2", diff --git a/scripts/archive.mjs b/scripts/archive.ts similarity index 55% rename from scripts/archive.mjs rename to scripts/archive.ts index ab91bc8..cecce40 100644 --- a/scripts/archive.mjs +++ b/scripts/archive.ts @@ -1,37 +1,14 @@ +import { UserDataDirectory } from "@/consts/consts"; +import { getCurrentTime } from "@/lib/date"; +import { checkAndCreateDirectory, isDirectoryEmptySync } from "@/lib/file"; import archiver from "archiver"; import Color from "colors"; -import fs from "fs"; +import * as fs from "fs"; import inquirer from "inquirer"; -import path from "path"; -import tar from "tar"; +import * as path from "path"; +import * as tar from "tar"; -const UserDataDirectory = "./data"; - -function checkAndCreateDirectory(dirPath) { - try { - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { recursive: true }); - } - fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK); - return true; - } catch (err) { - throw err; - } -} - -function isDirectoryEmptySync(directory) { - try { - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }); - } - const files = fs.readdirSync(directory); - return files.length === 0; - } catch (err) { - throw err; - } -} - -function packageDirectory(sourceDir, outputFilePath) { +function packageDirectory(sourceDir: string, outputFilePath: string) { const output = fs.createWriteStream(outputFilePath); const archive = archiver("tar", { gzip: true, @@ -51,7 +28,7 @@ function packageDirectory(sourceDir, outputFilePath) { archive.finalize(); } -function extractTarGz(tarGzPath, targetDir) { +function extractTarGz(tarGzPath: string, targetDir: string) { tar .x({ file: tarGzPath, @@ -83,16 +60,13 @@ async function main() { }) .then((answers) => { const { OutputDirPath } = answers; - const date = new Date(); - const filename = `archive-${date.getFullYear()}-${ - date.getMonth() + 1 - }-${date.getDate()}-${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}.tar.gz`; + const { year, month, day, hours, minutes, seconds } = getCurrentTime(); + const filename = `archive-${year}-${month}-${day}-${hours}-${minutes}-${seconds}.tar.gz`; const outFilePath = path.join(OutputDirPath, filename); - if (checkAndCreateDirectory(OutputDirPath)) { - packageDirectory(UserDataDirectory, outFilePath); - } + checkAndCreateDirectory(OutputDirPath) && packageDirectory(UserDataDirectory, outFilePath); }); break; + case "Unpack and restore user data": if (!isDirectoryEmptySync(UserDataDirectory)) { const { confirm } = await inquirer.prompt([ @@ -106,25 +80,25 @@ async function main() { ), }, ]); - if (!confirm) { - console.log("Operation canceled."); + if (confirm) { + inquirer + .prompt({ + type: "input", + name: "ArchivedPackagePath", + message: "The archive package's path:", + }) + .then((answers) => { + const { ArchivedPackagePath } = answers; + if (fs.existsSync(ArchivedPackagePath)) { + extractTarGz(ArchivedPackagePath, UserDataDirectory); + } else { + console.log(Color.red("The archive package does not exist.")); + } + }); return; } } - inquirer - .prompt({ - type: "input", - name: "ArchiveFilePath", - message: "The archive package's path:", - }) - .then((answers) => { - const { ArchiveFilePath } = answers; - if (fs.existsSync(ArchiveFilePath)) { - extractTarGz(ArchiveFilePath, UserDataDirectory); - } else { - console.log(Color.red("The archive package does not exist.")); - } - }); + console.log("Operation canceled."); break; } } diff --git a/scripts/newpost.mjs b/scripts/newpost.mjs deleted file mode 100644 index a7a33e3..0000000 --- a/scripts/newpost.mjs +++ /dev/null @@ -1,98 +0,0 @@ -import { spawn } from "child_process"; -import Color from "colors"; -import fs from "fs"; -import inquirer from "inquirer"; -import path from "path"; -import process from "process"; - -let today = new Date(); - -let year = today.getFullYear(); -let month = today.getMonth() + 1; -let day = today.getDate(); - -month = month < 10 ? "0" + month : month; -day = day < 10 ? "0" + day : day; - -let formattedDate = year + "-" + month + "-" + day; - -const questions = [ - { - type: "input", - name: "title", - message: "What's the title?", - validate: function (input) { - if (input.trim() === "") { - return "Please enter a title."; - } - return true; - }, - }, - { - type: "input", - name: "subtitle", - message: "What's the subtitle?", - }, - { - type: "tags", - name: "tags", - message: "Assign tags for the posts and separate them with commas.", - }, - { - type: "confirm", - name: "noPrompt", - message: "Do NOT prompt this post? (D:false)", - default: false, - }, - { - type: "confirm", - name: "pin", - message: "Do you want to pin this post? (D:false)", - default: false, - }, - { - type: "confirm", - name: "allowShare", - message: "Do you allow everybody share this post? (D:true)", - default: true, - }, -]; - -const template = (title, subtitle, tags, noPrompt, pin, allowShare) => `--- -title: ${JSON.stringify(title.toLowerCase())} -subtitle: ${JSON.stringify(subtitle)} -summary: "" -coverURL: "" -time: "${formattedDate}" -tags: ${JSON.stringify(tags)} -noPrompt: ${noPrompt} -pin: ${pin} -allowShare: ${allowShare} -closed: false ---- -`; - -inquirer.prompt(questions).then((answers) => { - const tags = answers.tags - .split(",") - .map((tag) => tag.trim()) - .filter((tag) => tag !== ""); - const content = template(answers.title, answers.subtitle, tags, answers.noPrompt, answers.pin, answers.allowShare); - const sluggedTitle = answers.title.replace(/\s/g, "-"); - const postFileName = `${formattedDate}-${sluggedTitle}.md`; - const postFilePath = path.resolve(path.join("./data/posts", postFileName)); - fs.writeFile(postFilePath, content, "utf-8", (err) => { - if (err) console.log(err); - console.log(Color.green(Color.bold("Create Post Succeed."))); - console.log(`Open the file ${Color.cyan(postFilePath)} to write your blog now.`); - console.log("Some fields, such as summary, need to be filled in by yourself after opening the file."); - if (process.platform === "win32") { - spawn("cmd", ["/c", "start", postFilePath]); - return; - } - if (["darwin", "linux", "freebsd"].includes(process.platform)) { - spawn("open", [postFilePath]); - return; - } - }); -}); diff --git a/scripts/newpost.ts b/scripts/newpost.ts new file mode 100644 index 0000000..ca556fa --- /dev/null +++ b/scripts/newpost.ts @@ -0,0 +1,92 @@ +import { PostFilesDirectory } from "@/consts/consts"; +import { getCurrentTime } from "@/lib/date"; +import { ChildProcessWithoutNullStreams, SpawnSyncReturns, spawn, spawnSync } from "child_process"; +import colors from "colors"; +import fs from "fs"; +import inquirer, { QuestionCollection } from "inquirer"; +import _ from "lodash"; +import path from "path"; +import { titleCase } from "title-case"; + +type TAnswer = { + title: string; + subtitle: string; + tags: string; + noPrompt: boolean; + pin: boolean; + allowShare: boolean; +}; + +const questions: QuestionCollection = [ + { + type: "input", + name: "title", + message: "What's the title?", + validate: (input: string) => (input.trim() === "" ? "Please enter a title." : true), + }, + { type: "input", name: "subtitle", message: "What's the subtitle?" }, + { type: "input", name: "tags", message: "Assign tags for the posts and separate them with commas." }, + { type: "confirm", name: "noPrompt", message: "Do NOT prompt this post? (D:false)", default: false }, + { type: "confirm", name: "pin", message: "Do you want to pin this post? (D:false)", default: false }, + { type: "confirm", name: "allowShare", message: "Do you allow everybody share this post? (D:true)", default: true }, +]; + +const createTemplate = (answers: TAnswer): string => { + const { year, month, day } = getCurrentTime(); + const tags = JSON.stringify( + answers.tags + .split(",") + .map((tag) => tag.trim()) + .filter((tag) => tag !== ""), + ); + + return `--- +title: "${titleCase(answers.title)}" +subtitle: "${answers.subtitle}" +summary: "" +author: "" +coverURL: "" +time: "${year}-${month}-${day}" +tags: ${tags} +noPrompt: ${answers.noPrompt} +pin: ${answers.pin} +allowShare: ${answers.allowShare} +closed: false +---`; +}; + +const writePostFile = (filePath: string, content: string): void => { + fs.writeFile(filePath, content, "utf-8", (err) => { + if (err) { + console.error("Error writing file:", err); + return; + } + + console.log(colors.green(colors.bold("Create Post Succeed."))); + console.log(`Open the file ${colors.cyan(filePath)} to write your blog now.`); + console.log("Some fields, such as summary, need to be filled in by yourself after opening the file."); + + const openFileCommand: Record ChildProcessWithoutNullStreams | SpawnSyncReturns> = { + win32: () => spawn("cmd", ["/c", "start", filePath]), + darwin: () => spawnSync("open", [filePath]), + linux: () => spawnSync("xdg-open", [filePath]), + freebsd: () => spawnSync("xdg-open", [filePath]), + }; + + const command = openFileCommand[process.platform]; + if (command) command(); + else { + console.log("Unsupported platform. Open the file manually please."); + } + }); +}; + +inquirer.prompt(questions).then((answers) => { + const { year, month, day } = getCurrentTime(); + const content = createTemplate(answers); + const sluggedTitle = _.kebabCase(answers.title); + const postFileName = `${year}-${month}-${day}-${sluggedTitle}.md`; + const postFilePath = path.resolve(path.join(PostFilesDirectory, postFileName)); + + writePostFile(postFilePath, content); +});