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 CHANGED
@@ -1,42 +1,24 @@
1
- # blog-vitepress
1
+ # notespress
2
2
 
3
- 一个用于托管个人笔记站的 VitePress 外壳仓库,同时也包含一个可发布的 `notespress` CLI。
3
+ `notespress` 是一个把 Markdown 笔记目录直接生成为 VitePress 博客站点的 CLI。
4
4
 
5
- Markdown 内容放在 `myBlog` 子模块中,当前仓库负责:
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
- - `myBlog/`:Markdown 内容仓库,使用 git submodule 管理
57
- - `.vitepress/`:VitePress 配置
58
- - `docs/`:项目说明页
59
- - `scripts/`:构建前生成代码示例页面
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
- 站点使用 `vitepress-auto-navigation` 根据 `myBlog` 目录自动生成 `nav` 和 `sidebar`。如果本地还没有拉取子模块,站点仍能启动,但不会显示笔记导航。
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
- 除了 Markdown 页面,站点还会在构建前扫描 `myBlog` 里的 `.js`、`.ts` 和 `.html` 文件,并自动生成 `snippets/` 下的包装页,让这些代码文件也能通过导航访问。
63
+ ## 适用目录
66
64
 
67
- ## 从 `myBlog` 直接生成网站
65
+ 适合这类目录结构:
68
66
 
69
- 根仓库现在提供了一个 `notespress`。如果你进入 `myBlog/`,可以直接运行:
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
- pnpm build
73
- pnpm dev
74
- pnpm preview
84
+ notespress init
75
85
  ```
76
86
 
77
- 这些脚本会调用上层仓库里的 `notespress`,临时创建一个独立的 VitePress 工作区,并把当前 `myBlog/` 目录生成为静态站点。
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
- - 输出目录默认是 `myBlog/dist`
80
- - 临时工作区在 `myBlog/.blog-cli`
81
- - 不会把 `.vitepress` 配置写回 `myBlog/`
82
- - `.js`、`.ts`、`.html` 文件会自动生成 `snippets/` 页面
96
+ ## 仓库说明
83
97
 
84
- ## 部署
98
+ 当前仓库除了发布 `notespress` 包,也保留了一个用于自用和演示的 `blog-vitepress` 站点壳。
85
99
 
86
- `.github/workflows/deploy.yml` 会在 `master` 分支收到 push 后自动:
100
+ 仓库结构和本地开发说明放在:
87
101
 
88
- 1. 拉取仓库和子模块
89
- 2. 安装依赖
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
+ }
@@ -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"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notespress",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Generate a VitePress blog site directly from a Markdown notes directory.",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@10.30.3",