notespress 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -51
- package/bin/blog-cli.mjs +29 -0
- package/lib/init.mjs +162 -0
- package/lib/standalone-site.mjs +29 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,42 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# notespress
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`notespress` 是一个把 Markdown 笔记目录直接生成为 VitePress 博客站点的 CLI。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
它默认把当前目录当作内容目录,自动生成导航、侧边栏和代码片段页面,不要求你手工维护 `.vitepress` 配置。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- 自动生成导航
|
|
9
|
-
- 代码示例包装页生成
|
|
10
|
-
- GitHub Pages 部署
|
|
11
|
-
|
|
12
|
-
## 快速开始
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
git clone <repo-url>
|
|
16
|
-
cd blog-vitepress
|
|
17
|
-
git submodule update --init --recursive
|
|
18
|
-
pnpm install
|
|
19
|
-
pnpm dev
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## 作为 npm 包使用
|
|
23
|
-
|
|
24
|
-
CLI 的目标用法是:在任意 Markdown 笔记目录下直接生成博客站点。
|
|
25
|
-
|
|
26
|
-
本地安装:
|
|
7
|
+
## 安装
|
|
27
8
|
|
|
28
9
|
```bash
|
|
29
10
|
npm install -D notespress
|
|
30
|
-
npx notespress build
|
|
31
11
|
```
|
|
32
12
|
|
|
33
|
-
|
|
13
|
+
一次性运行也可以:
|
|
34
14
|
|
|
35
15
|
```bash
|
|
36
16
|
pnpm dlx notespress build
|
|
37
17
|
```
|
|
38
18
|
|
|
39
|
-
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
在你的 Markdown 笔记目录里执行:
|
|
40
22
|
|
|
41
23
|
```bash
|
|
42
24
|
notespress dev
|
|
@@ -44,48 +26,78 @@ notespress build
|
|
|
44
26
|
notespress preview
|
|
45
27
|
```
|
|
46
28
|
|
|
47
|
-
|
|
29
|
+
如果你没有全局安装,可以改用:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx notespress dev
|
|
33
|
+
npx notespress build
|
|
34
|
+
npx notespress preview
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 默认行为
|
|
48
38
|
|
|
49
39
|
- 当前目录作为内容目录
|
|
50
40
|
- 构建产物输出到 `dist/`
|
|
51
41
|
- 临时工作区输出到 `.blog-cli/`
|
|
52
42
|
- `.js`、`.ts`、`.html` 会自动生成 `snippets/` 页面
|
|
43
|
+
- 不会把 `.vitepress` 配置写回你的笔记目录
|
|
53
44
|
|
|
54
|
-
##
|
|
45
|
+
## 常用命令
|
|
55
46
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
47
|
+
```bash
|
|
48
|
+
notespress dev [content-dir]
|
|
49
|
+
notespress build [content-dir]
|
|
50
|
+
notespress preview [content-dir]
|
|
51
|
+
notespress prepare-content [content-dir]
|
|
52
|
+
```
|
|
60
53
|
|
|
61
|
-
|
|
54
|
+
示例:
|
|
62
55
|
|
|
63
|
-
|
|
56
|
+
```bash
|
|
57
|
+
notespress build .
|
|
58
|
+
notespress build ./notes --out-dir ./site-dist
|
|
59
|
+
notespress dev ./notes --port 3000
|
|
60
|
+
notespress preview ./notes --port 4173
|
|
61
|
+
```
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
## 适用目录
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
适合这类目录结构:
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
```text
|
|
68
|
+
notes
|
|
69
|
+
├─ README.md
|
|
70
|
+
├─ frontend
|
|
71
|
+
│ ├─ react.md
|
|
72
|
+
│ └─ vue.md
|
|
73
|
+
└─ code
|
|
74
|
+
└─ demo.ts
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`notespress` 会根据目录结构生成导航,并把代码文件挂到 `/snippets/...` 路由下。
|
|
78
|
+
|
|
79
|
+
## 高级用法
|
|
80
|
+
|
|
81
|
+
如果你希望把当前目录初始化成一个长期维护的本地项目,再使用:
|
|
70
82
|
|
|
71
83
|
```bash
|
|
72
|
-
|
|
73
|
-
pnpm dev
|
|
74
|
-
pnpm preview
|
|
84
|
+
notespress init
|
|
75
85
|
```
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
它会尽量保守地初始化当前目录:
|
|
88
|
+
|
|
89
|
+
- 创建或合并 `package.json`
|
|
90
|
+
- 写入 `dev`、`build`、`preview` 脚本
|
|
91
|
+
- 添加 `.gitignore` 中的 `node_modules`、`dist`、`.blog-cli`
|
|
92
|
+
- 如果根目录没有 `README.md` 或 `index.md`,创建一个起始首页
|
|
93
|
+
|
|
94
|
+
如果要覆盖已有脚本或首页,可以加 `--force`。
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
- 临时工作区在 `myBlog/.blog-cli`
|
|
81
|
-
- 不会把 `.vitepress` 配置写回 `myBlog/`
|
|
82
|
-
- `.js`、`.ts`、`.html` 文件会自动生成 `snippets/` 页面
|
|
96
|
+
## 仓库说明
|
|
83
97
|
|
|
84
|
-
|
|
98
|
+
当前仓库除了发布 `notespress` 包,也保留了一个用于自用和演示的 `blog-vitepress` 站点壳。
|
|
85
99
|
|
|
86
|
-
|
|
100
|
+
仓库结构和本地开发说明放在:
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
3. 构建 VitePress 站点
|
|
91
|
-
4. 发布到 GitHub Pages
|
|
102
|
+
- [docs/index.md](/home/dev/workplace/github-repositories/blog-vitepress/docs/index.md)
|
|
103
|
+
- [docs/repository.md](/home/dev/workplace/github-repositories/blog-vitepress/docs/repository.md)
|
package/bin/blog-cli.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { createServer, build, serve } from "vitepress"
|
|
4
4
|
|
|
5
|
+
import { initNotespressProject } from "../lib/init.mjs"
|
|
5
6
|
import { generateSnippetPages } from "../lib/snippets.mjs"
|
|
6
7
|
import { prepareStandaloneWorkspace, resolveRepositoryRoot } from "../lib/standalone-site.mjs"
|
|
7
8
|
|
|
@@ -13,6 +14,7 @@ Usage:
|
|
|
13
14
|
notespress dev [content-dir] [--port 5173] [--host 0.0.0.0]
|
|
14
15
|
notespress preview [content-dir] [--port 4173]
|
|
15
16
|
notespress prepare-content [content-dir] [--output-dir dir] [--route-base /myBlog]
|
|
17
|
+
notespress init [content-dir] [--force] # optional project setup
|
|
16
18
|
|
|
17
19
|
Aliases:
|
|
18
20
|
build-site -> build
|
|
@@ -83,6 +85,30 @@ async function runPrepareContent(positionals, options) {
|
|
|
83
85
|
process.stdout.write(`Generated ${result.count} snippet pages in ${result.outputDir}\n`)
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
async function runInit(positionals, options) {
|
|
89
|
+
const result = initNotespressProject(resolveContentDir(positionals, options), {
|
|
90
|
+
force: options.force === true,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
process.stdout.write(`Initialized notespress in ${result.targetDir}\n`)
|
|
94
|
+
|
|
95
|
+
if (result.packageResult.changes.length > 0) {
|
|
96
|
+
process.stdout.write(`package.json: ${result.packageResult.changes.join(", ")}\n`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (result.gitignoreResult.additions.length > 0) {
|
|
100
|
+
process.stdout.write(`.gitignore: added ${result.gitignoreResult.additions.join(", ")}\n`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (result.readmeResult.created) {
|
|
104
|
+
process.stdout.write("README.md: created starter page\n")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
process.stdout.write("\nNext steps:\n")
|
|
108
|
+
process.stdout.write(" 1. Run `npm install` or `pnpm install`\n")
|
|
109
|
+
process.stdout.write(" 2. Run `npx notespress dev` or `pnpm dev`\n")
|
|
110
|
+
}
|
|
111
|
+
|
|
86
112
|
async function runBuild(positionals, options) {
|
|
87
113
|
const workspace = prepareStandaloneWorkspace({
|
|
88
114
|
contentDir: resolveContentDir(positionals, options),
|
|
@@ -144,6 +170,9 @@ async function main() {
|
|
|
144
170
|
const { command, options, positionals } = parseArgs(process.argv.slice(2))
|
|
145
171
|
|
|
146
172
|
switch (command) {
|
|
173
|
+
case "init":
|
|
174
|
+
await runInit(positionals, options)
|
|
175
|
+
return
|
|
147
176
|
case "build":
|
|
148
177
|
case "build-site":
|
|
149
178
|
await runBuild(positionals, options)
|
package/lib/init.mjs
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { basename, join, resolve } from "node:path"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
const GENERATED_SCRIPTS = {
|
|
6
|
+
dev: "notespress dev .",
|
|
7
|
+
build: "notespress build .",
|
|
8
|
+
preview: "notespress preview .",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const GITIGNORE_ENTRIES = [".blog-cli", "dist", "node_modules"]
|
|
12
|
+
|
|
13
|
+
function packageRoot() {
|
|
14
|
+
return resolve(fileURLToPath(new URL("..", import.meta.url)))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readOwnPackageMeta() {
|
|
18
|
+
const packageJsonPath = join(packageRoot(), "package.json")
|
|
19
|
+
return JSON.parse(readFileSync(packageJsonPath, "utf8"))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ensureDirectory(targetDir) {
|
|
23
|
+
if (!existsSync(targetDir)) {
|
|
24
|
+
mkdirSync(targetDir, { recursive: true })
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!statSync(targetDir).isDirectory()) {
|
|
29
|
+
throw new Error(`Target is not a directory: ${targetDir}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readJson(filePath) {
|
|
34
|
+
return JSON.parse(readFileSync(filePath, "utf8"))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeJson(filePath, value) {
|
|
38
|
+
writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function upsertPackageJson(targetDir, options) {
|
|
42
|
+
const packageJsonPath = join(targetDir, "package.json")
|
|
43
|
+
const ownPackage = readOwnPackageMeta()
|
|
44
|
+
const force = options.force === true
|
|
45
|
+
const changes = []
|
|
46
|
+
let packageJson
|
|
47
|
+
|
|
48
|
+
if (existsSync(packageJsonPath)) {
|
|
49
|
+
packageJson = readJson(packageJsonPath)
|
|
50
|
+
} else {
|
|
51
|
+
packageJson = {
|
|
52
|
+
name: basename(targetDir),
|
|
53
|
+
private: true,
|
|
54
|
+
}
|
|
55
|
+
changes.push("created package.json")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (packageJson.private == null) {
|
|
59
|
+
packageJson.private = true
|
|
60
|
+
changes.push("set package.json private=true")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
packageJson.scripts ??= {}
|
|
64
|
+
|
|
65
|
+
for (const [name, command] of Object.entries(GENERATED_SCRIPTS)) {
|
|
66
|
+
if (packageJson.scripts[name] == null || force) {
|
|
67
|
+
packageJson.scripts[name] = command
|
|
68
|
+
changes.push(`set script ${name}`)
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
packageJson.devDependencies ??= {}
|
|
74
|
+
|
|
75
|
+
if (packageJson.devDependencies.notespress == null || force) {
|
|
76
|
+
packageJson.devDependencies.notespress = `^${ownPackage.version}`
|
|
77
|
+
changes.push(`set devDependency notespress=^${ownPackage.version}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
writeJson(packageJsonPath, packageJson)
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
packageJsonPath,
|
|
84
|
+
changes,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function upsertGitignore(targetDir) {
|
|
89
|
+
const gitignorePath = join(targetDir, ".gitignore")
|
|
90
|
+
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf8") : ""
|
|
91
|
+
const normalized = existing.replace(/\r\n/g, "\n")
|
|
92
|
+
const lines = normalized === "" ? [] : normalized.split("\n")
|
|
93
|
+
const existingEntries = new Set(lines.filter(Boolean))
|
|
94
|
+
const additions = []
|
|
95
|
+
|
|
96
|
+
for (const entry of GITIGNORE_ENTRIES) {
|
|
97
|
+
if (existingEntries.has(entry)) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
additions.push(entry)
|
|
102
|
+
lines.push(entry)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const output = `${lines.filter((line, index) => index < lines.length - 1 || line !== "").join("\n")}\n`
|
|
106
|
+
writeFileSync(gitignorePath, output)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
gitignorePath,
|
|
110
|
+
additions,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hasRootIndexPage(targetDir) {
|
|
115
|
+
return existsSync(join(targetDir, "README.md")) || existsSync(join(targetDir, "index.md"))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createStarterReadme(targetDir, options) {
|
|
119
|
+
const readmePath = join(targetDir, "README.md")
|
|
120
|
+
|
|
121
|
+
if (hasRootIndexPage(targetDir) && options.force !== true) {
|
|
122
|
+
return {
|
|
123
|
+
readmePath,
|
|
124
|
+
created: false,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const title = basename(targetDir)
|
|
129
|
+
writeFileSync(
|
|
130
|
+
readmePath,
|
|
131
|
+
`# ${title}
|
|
132
|
+
|
|
133
|
+
Welcome to your notes site.
|
|
134
|
+
|
|
135
|
+
- Run \`notespress dev\` for local preview
|
|
136
|
+
- Run \`notespress build\` to generate \`dist/\`
|
|
137
|
+
- Put Markdown files into this directory and organize them by folders
|
|
138
|
+
`,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
readmePath,
|
|
143
|
+
created: true,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function initNotespressProject(targetDir, options = {}) {
|
|
148
|
+
const resolvedDir = resolve(targetDir)
|
|
149
|
+
|
|
150
|
+
ensureDirectory(resolvedDir)
|
|
151
|
+
|
|
152
|
+
const packageResult = upsertPackageJson(resolvedDir, options)
|
|
153
|
+
const gitignoreResult = upsertGitignore(resolvedDir)
|
|
154
|
+
const readmeResult = createStarterReadme(resolvedDir, options)
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
targetDir: resolvedDir,
|
|
158
|
+
packageResult,
|
|
159
|
+
gitignoreResult,
|
|
160
|
+
readmeResult,
|
|
161
|
+
}
|
|
162
|
+
}
|
package/lib/standalone-site.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
|
-
import { mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path"
|
|
4
|
+
import { createRequire } from "node:module"
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
5
6
|
|
|
6
7
|
import { generateSnippetPages, IGNORED_DIRS } from "./snippets.mjs"
|
|
7
8
|
|
|
8
9
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
|
10
|
+
const require = createRequire(import.meta.url)
|
|
9
11
|
|
|
10
12
|
function workspaceIdFor(contentDir) {
|
|
11
13
|
const hash = createHash("sha1").update(contentDir).digest("hex").slice(0, 8)
|
|
@@ -55,6 +57,30 @@ function mirrorContentEntries(contentDir, workspaceRoot) {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
function resolvePackageDir(packageName) {
|
|
61
|
+
return dirname(require.resolve(`${packageName}/package.json`))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function linkRuntimeDependencies(workspaceRoot) {
|
|
65
|
+
const nodeModulesDir = join(workspaceRoot, "node_modules")
|
|
66
|
+
|
|
67
|
+
ensureDir(nodeModulesDir)
|
|
68
|
+
symlinkSync(resolvePackageDir("vitepress"), join(nodeModulesDir, "vitepress"), "dir")
|
|
69
|
+
symlinkSync(resolvePackageDir("vue"), join(nodeModulesDir, "vue"), "dir")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function ensureContentRuntimeDependencies(contentDir) {
|
|
73
|
+
const nodeModulesDir = join(contentDir, "node_modules")
|
|
74
|
+
|
|
75
|
+
if (existsSync(nodeModulesDir)) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ensureDir(nodeModulesDir)
|
|
80
|
+
symlinkSync(resolvePackageDir("vitepress"), join(nodeModulesDir, "vitepress"), "dir")
|
|
81
|
+
symlinkSync(resolvePackageDir("vue"), join(nodeModulesDir, "vue"), "dir")
|
|
82
|
+
}
|
|
83
|
+
|
|
58
84
|
export function resolveRepositoryRoot() {
|
|
59
85
|
return REPO_ROOT
|
|
60
86
|
}
|
|
@@ -72,7 +98,9 @@ export function prepareStandaloneWorkspace(options = {}) {
|
|
|
72
98
|
ensureDir(workspaceRoot)
|
|
73
99
|
ensureDir(dirname(cacheDir))
|
|
74
100
|
|
|
101
|
+
ensureContentRuntimeDependencies(contentDir)
|
|
75
102
|
mirrorContentEntries(contentDir, workspaceRoot)
|
|
103
|
+
linkRuntimeDependencies(workspaceRoot)
|
|
76
104
|
generateSnippetPages({
|
|
77
105
|
contentDir,
|
|
78
106
|
outputDir: join(workspaceRoot, "snippets"),
|