zengen 0.1.0
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 +138 -0
- package/demo/src/api.md +92 -0
- package/demo/src/best-practices.md +194 -0
- package/demo/src/config.md +160 -0
- package/demo/src/index.md +46 -0
- package/dist/builder.d.ts +37 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +298 -0
- package/dist/builder.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +297 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +34 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +209 -0
- package/dist/markdown.js.map +1 -0
- package/dist/navigation.d.ts +36 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +160 -0
- package/dist/navigation.js.map +1 -0
- package/dist/template.d.ts +29 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +407 -0
- package/dist/template.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
- package/src/builder.ts +292 -0
- package/src/cli.ts +296 -0
- package/src/index.ts +40 -0
- package/src/markdown.ts +196 -0
- package/src/navigation.ts +191 -0
- package/src/template.ts +387 -0
- package/src/types.ts +50 -0
- package/tsconfig.json +19 -0
package/src/builder.ts
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { BuildOptions, FileInfo, NavigationItem, ZenConfig } from './types';
|
|
2
|
+
import { MarkdownConverter } from './markdown';
|
|
3
|
+
import { TemplateEngine } from './template';
|
|
4
|
+
import { NavigationGenerator } from './navigation';
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as chokidar from 'chokidar';
|
|
8
|
+
|
|
9
|
+
export class ZenBuilder {
|
|
10
|
+
private markdownConverter: MarkdownConverter;
|
|
11
|
+
private templateEngine: TemplateEngine;
|
|
12
|
+
private navigationGenerator: NavigationGenerator;
|
|
13
|
+
private config: ZenConfig = {};
|
|
14
|
+
|
|
15
|
+
constructor(config: ZenConfig = {}) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.markdownConverter = new MarkdownConverter(config.processors || []);
|
|
18
|
+
this.templateEngine = new TemplateEngine();
|
|
19
|
+
this.navigationGenerator = new NavigationGenerator();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 构建文档站点
|
|
24
|
+
*/
|
|
25
|
+
async build(options: BuildOptions): Promise<void> {
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
const { srcDir, outDir, template, verbose = false } = options;
|
|
28
|
+
|
|
29
|
+
if (verbose) {
|
|
30
|
+
console.log(`🚀 Starting ZEN build...`);
|
|
31
|
+
console.log(`📁 Source: ${srcDir}`);
|
|
32
|
+
console.log(`📁 Output: ${outDir}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 验证源目录
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(srcDir);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Source directory does not exist: ${srcDir}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 确保输出目录存在
|
|
43
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// 读取并转换 Markdown 文件
|
|
46
|
+
if (verbose) console.log(`📄 Reading Markdown files...`);
|
|
47
|
+
const files = await this.markdownConverter.convertDirectory(srcDir);
|
|
48
|
+
|
|
49
|
+
if (files.length === 0) {
|
|
50
|
+
console.warn(`⚠️ No Markdown files found in ${srcDir}`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (verbose) console.log(`✅ Found ${files.length} Markdown files`);
|
|
55
|
+
|
|
56
|
+
// 生成导航
|
|
57
|
+
if (verbose) console.log(`🗺️ Generating navigation...`);
|
|
58
|
+
const navigation = this.navigationGenerator.generate(files);
|
|
59
|
+
|
|
60
|
+
// 处理每个文件
|
|
61
|
+
if (verbose) console.log(`⚡ Processing files...`);
|
|
62
|
+
let processedCount = 0;
|
|
63
|
+
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
try {
|
|
66
|
+
// 生成模板数据
|
|
67
|
+
const templateData = this.templateEngine.generateTemplateData(file, navigation);
|
|
68
|
+
|
|
69
|
+
// 渲染模板
|
|
70
|
+
const html = await this.templateEngine.render(templateData, template);
|
|
71
|
+
|
|
72
|
+
// 生成输出路径
|
|
73
|
+
const outputPath = this.templateEngine.getOutputPath(file, outDir);
|
|
74
|
+
|
|
75
|
+
// 保存文件
|
|
76
|
+
await this.templateEngine.saveToFile(html, outputPath);
|
|
77
|
+
|
|
78
|
+
processedCount++;
|
|
79
|
+
|
|
80
|
+
if (verbose && processedCount % 10 === 0) {
|
|
81
|
+
console.log(` Processed ${processedCount}/${files.length} files...`);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(`❌ Failed to process ${file.relativePath}:`, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 生成站点地图
|
|
89
|
+
if (verbose) console.log(`🗺️ Generating sitemap...`);
|
|
90
|
+
await this.generateSitemap(files, outDir);
|
|
91
|
+
|
|
92
|
+
// 生成导航 JSON 文件
|
|
93
|
+
if (verbose) console.log(`📊 Generating navigation data...`);
|
|
94
|
+
await this.generateNavigationJson(files, outDir);
|
|
95
|
+
|
|
96
|
+
// 复制静态资源(如果存在)
|
|
97
|
+
await this.copyStaticAssets(srcDir, outDir);
|
|
98
|
+
|
|
99
|
+
const duration = Date.now() - startTime;
|
|
100
|
+
if (verbose) {
|
|
101
|
+
console.log(`🎉 Build completed!`);
|
|
102
|
+
console.log(` Files processed: ${processedCount}/${files.length}`);
|
|
103
|
+
console.log(` Duration: ${duration}ms`);
|
|
104
|
+
console.log(` Output directory: ${outDir}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`✅ Built ${processedCount} files to ${outDir} in ${duration}ms`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 监听文件变化并自动重建
|
|
112
|
+
*/
|
|
113
|
+
async watch(options: BuildOptions): Promise<void> {
|
|
114
|
+
const { srcDir, outDir, template, verbose = false } = options;
|
|
115
|
+
|
|
116
|
+
console.log(`👀 Watching for changes in ${srcDir}...`);
|
|
117
|
+
console.log(`Press Ctrl+C to stop watching`);
|
|
118
|
+
|
|
119
|
+
// 初始构建
|
|
120
|
+
await this.build(options);
|
|
121
|
+
|
|
122
|
+
// 设置文件监听
|
|
123
|
+
const watcher = chokidar.watch(srcDir, {
|
|
124
|
+
ignored: /(^|[\/\\])\../, // 忽略隐藏文件
|
|
125
|
+
persistent: true,
|
|
126
|
+
ignoreInitial: true
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
let isBuilding = false;
|
|
130
|
+
let buildQueue: string[] = [];
|
|
131
|
+
|
|
132
|
+
const debouncedBuild = async () => {
|
|
133
|
+
if (isBuilding) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
isBuilding = true;
|
|
138
|
+
const changedFiles = [...buildQueue];
|
|
139
|
+
buildQueue = [];
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (verbose) {
|
|
143
|
+
console.log(`\n🔄 Rebuilding due to changes in: ${changedFiles.join(', ')}`);
|
|
144
|
+
} else {
|
|
145
|
+
console.log(`\n🔄 Rebuilding...`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await this.build(options);
|
|
149
|
+
console.log(`✅ Rebuild complete. Watching for changes...`);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`❌ Rebuild failed:`, error);
|
|
152
|
+
} finally {
|
|
153
|
+
isBuilding = false;
|
|
154
|
+
|
|
155
|
+
// 如果队列中有新文件,立即处理
|
|
156
|
+
if (buildQueue.length > 0) {
|
|
157
|
+
setTimeout(debouncedBuild, 100);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
watcher
|
|
163
|
+
.on('add', (filePath: string) => {
|
|
164
|
+
if (filePath.endsWith('.md')) {
|
|
165
|
+
if (verbose) console.log(`📄 File added: ${filePath}`);
|
|
166
|
+
buildQueue.push(filePath);
|
|
167
|
+
setTimeout(debouncedBuild, 300);
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.on('change', (filePath: string) => {
|
|
171
|
+
if (filePath.endsWith('.md')) {
|
|
172
|
+
if (verbose) console.log(`📄 File changed: ${filePath}`);
|
|
173
|
+
buildQueue.push(filePath);
|
|
174
|
+
setTimeout(debouncedBuild, 300);
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
.on('unlink', (filePath: string) => {
|
|
178
|
+
if (filePath.endsWith('.md')) {
|
|
179
|
+
if (verbose) console.log(`📄 File removed: ${filePath}`);
|
|
180
|
+
buildQueue.push(filePath);
|
|
181
|
+
setTimeout(debouncedBuild, 300);
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.on('error', (error: unknown) => {
|
|
185
|
+
console.error(`❌ Watcher error:`, error);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// 处理退出信号
|
|
189
|
+
process.on('SIGINT', () => {
|
|
190
|
+
console.log(`\n👋 Stopping watcher...`);
|
|
191
|
+
watcher.close();
|
|
192
|
+
process.exit(0);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 生成站点地图
|
|
198
|
+
*/
|
|
199
|
+
private async generateSitemap(files: FileInfo[], outDir: string): Promise<void> {
|
|
200
|
+
try {
|
|
201
|
+
const sitemapXml = this.navigationGenerator.generateSitemap(files);
|
|
202
|
+
const sitemapPath = path.join(outDir, 'sitemap.xml');
|
|
203
|
+
await fs.writeFile(sitemapPath, sitemapXml, 'utf-8');
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.warn(`⚠️ Failed to generate sitemap:`, error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 生成导航 JSON 文件
|
|
211
|
+
*/
|
|
212
|
+
private async generateNavigationJson(files: FileInfo[], outDir: string): Promise<void> {
|
|
213
|
+
try {
|
|
214
|
+
const navigationJson = this.navigationGenerator.generateJsonNavigation(files);
|
|
215
|
+
const navPath = path.join(outDir, 'navigation.json');
|
|
216
|
+
await fs.writeFile(navPath, navigationJson, 'utf-8');
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.warn(`⚠️ Failed to generate navigation JSON:`, error);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 复制静态资源
|
|
224
|
+
*/
|
|
225
|
+
private async copyStaticAssets(srcDir: string, outDir: string): Promise<void> {
|
|
226
|
+
const staticDir = path.join(srcDir, 'static');
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
await fs.access(staticDir);
|
|
230
|
+
|
|
231
|
+
// 简单的递归复制
|
|
232
|
+
async function copyDir(source: string, target: string) {
|
|
233
|
+
await fs.mkdir(target, { recursive: true });
|
|
234
|
+
const entries = await fs.readdir(source, { withFileTypes: true });
|
|
235
|
+
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
const srcPath = path.join(source, entry.name);
|
|
238
|
+
const destPath = path.join(target, entry.name);
|
|
239
|
+
|
|
240
|
+
if (entry.isDirectory()) {
|
|
241
|
+
await copyDir(srcPath, destPath);
|
|
242
|
+
} else {
|
|
243
|
+
await fs.copyFile(srcPath, destPath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await copyDir(staticDir, path.join(outDir, 'static'));
|
|
249
|
+
} catch (error) {
|
|
250
|
+
// 静态目录不存在是正常的,忽略错误
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 清理输出目录
|
|
256
|
+
*/
|
|
257
|
+
async clean(outDir: string): Promise<void> {
|
|
258
|
+
try {
|
|
259
|
+
await fs.rm(outDir, { recursive: true, force: true });
|
|
260
|
+
console.log(`🧹 Cleaned output directory: ${outDir}`);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error(`❌ Failed to clean output directory:`, error);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 验证配置
|
|
268
|
+
*/
|
|
269
|
+
validateConfig(config: ZenConfig): string[] {
|
|
270
|
+
const errors: string[] = [];
|
|
271
|
+
|
|
272
|
+
if (config.srcDir && !path.isAbsolute(config.srcDir)) {
|
|
273
|
+
errors.push('srcDir must be an absolute path');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (config.outDir && !path.isAbsolute(config.outDir)) {
|
|
277
|
+
errors.push('outDir must be an absolute path');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (config.i18n) {
|
|
281
|
+
if (!config.i18n.sourceLang) {
|
|
282
|
+
errors.push('i18n.sourceLang is required');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!config.i18n.targetLangs || config.i18n.targetLangs.length === 0) {
|
|
286
|
+
errors.push('i18n.targetLangs must have at least one language');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return errors;
|
|
291
|
+
}
|
|
292
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { ZenBuilder } from './builder';
|
|
5
|
+
import { ZenConfig } from './types';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('zengen')
|
|
13
|
+
.description('ZEN - A minimalist Markdown documentation site builder')
|
|
14
|
+
.version('0.1.0');
|
|
15
|
+
|
|
16
|
+
// 构建命令
|
|
17
|
+
program
|
|
18
|
+
.command('build')
|
|
19
|
+
.description('Build documentation site from Markdown files')
|
|
20
|
+
.argument('<src-dir>', 'Source directory containing Markdown files')
|
|
21
|
+
.option('-o, --out <dir>', 'Output directory for generated HTML', 'dist')
|
|
22
|
+
.option('-t, --template <file>', 'Custom template file')
|
|
23
|
+
.option('-w, --watch', 'Watch for changes and rebuild automatically')
|
|
24
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
25
|
+
.option('-c, --config <file>', 'Configuration file')
|
|
26
|
+
.option('--clean', 'Clean output directory before building')
|
|
27
|
+
.action(async (srcDir, options) => {
|
|
28
|
+
try {
|
|
29
|
+
// 加载配置文件
|
|
30
|
+
let config: ZenConfig = {};
|
|
31
|
+
if (options.config) {
|
|
32
|
+
try {
|
|
33
|
+
const configPath = path.resolve(options.config);
|
|
34
|
+
const configContent = await fs.readFile(configPath, 'utf-8');
|
|
35
|
+
config = JSON.parse(configContent);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(`❌ Failed to load config file:`, error);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 合并命令行参数和配置
|
|
43
|
+
const buildOptions = {
|
|
44
|
+
srcDir: path.resolve(srcDir),
|
|
45
|
+
outDir: path.resolve(options.out),
|
|
46
|
+
template: options.template ? path.resolve(options.template) : undefined,
|
|
47
|
+
watch: options.watch,
|
|
48
|
+
verbose: options.verbose
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const builder = new ZenBuilder(config);
|
|
52
|
+
|
|
53
|
+
// 验证配置
|
|
54
|
+
const errors = builder.validateConfig(config);
|
|
55
|
+
if (errors.length > 0) {
|
|
56
|
+
console.error('❌ Configuration errors:');
|
|
57
|
+
errors.forEach(error => console.error(` - ${error}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 清理输出目录
|
|
62
|
+
if (options.clean) {
|
|
63
|
+
await builder.clean(buildOptions.outDir);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 构建或监听
|
|
67
|
+
if (options.watch) {
|
|
68
|
+
await builder.watch(buildOptions);
|
|
69
|
+
} else {
|
|
70
|
+
await builder.build(buildOptions);
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('❌ Build failed:', error);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 清理命令
|
|
79
|
+
program
|
|
80
|
+
.command('clean')
|
|
81
|
+
.description('Clean output directory')
|
|
82
|
+
.argument('<dir>', 'Directory to clean')
|
|
83
|
+
.action(async (dir) => {
|
|
84
|
+
try {
|
|
85
|
+
const builder = new ZenBuilder();
|
|
86
|
+
await builder.clean(path.resolve(dir));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('❌ Clean failed:', error);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 初始化命令
|
|
94
|
+
program
|
|
95
|
+
.command('init')
|
|
96
|
+
.description('Initialize a new ZEN project')
|
|
97
|
+
.option('-d, --dir <directory>', 'Target directory', '.')
|
|
98
|
+
.action(async (options) => {
|
|
99
|
+
try {
|
|
100
|
+
const targetDir = path.resolve(options.dir);
|
|
101
|
+
|
|
102
|
+
// 创建目录结构
|
|
103
|
+
await fs.mkdir(path.join(targetDir, 'docs'), { recursive: true });
|
|
104
|
+
await fs.mkdir(path.join(targetDir, 'static'), { recursive: true });
|
|
105
|
+
|
|
106
|
+
// 创建示例文档
|
|
107
|
+
const exampleDoc = `# Welcome to ZEN
|
|
108
|
+
|
|
109
|
+
This is an example documentation page generated by ZEN.
|
|
110
|
+
|
|
111
|
+
## Getting Started
|
|
112
|
+
|
|
113
|
+
1. Write your documentation in Markdown format
|
|
114
|
+
2. Run \`zengen build docs --out dist\`
|
|
115
|
+
3. Open the generated HTML files in your browser
|
|
116
|
+
|
|
117
|
+
## Features
|
|
118
|
+
|
|
119
|
+
- **Minimal configuration**: Focus on writing, not configuration
|
|
120
|
+
- **Smart navigation**: Automatic navigation generation
|
|
121
|
+
- **Beautiful templates**: Clean, responsive design
|
|
122
|
+
- **Code highlighting**: Syntax highlighting for code blocks
|
|
123
|
+
|
|
124
|
+
## Example Code
|
|
125
|
+
|
|
126
|
+
\`\`\`javascript
|
|
127
|
+
// This is a JavaScript example
|
|
128
|
+
console.log('Hello ZEN!');
|
|
129
|
+
\`\`\`
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
*Happy documenting!*`;
|
|
134
|
+
|
|
135
|
+
await fs.writeFile(
|
|
136
|
+
path.join(targetDir, 'docs', 'index.md'),
|
|
137
|
+
exampleDoc,
|
|
138
|
+
'utf-8'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// 创建配置文件
|
|
142
|
+
const config = {
|
|
143
|
+
srcDir: './docs',
|
|
144
|
+
outDir: './dist',
|
|
145
|
+
template: undefined,
|
|
146
|
+
i18n: {
|
|
147
|
+
sourceLang: 'en-US',
|
|
148
|
+
targetLangs: ['zh-CN', 'ja-JP']
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await fs.writeFile(
|
|
153
|
+
path.join(targetDir, 'zen.config.json'),
|
|
154
|
+
JSON.stringify(config, null, 2),
|
|
155
|
+
'utf-8'
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// 创建 package.json 脚本(如果不存在)
|
|
159
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
160
|
+
try {
|
|
161
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
162
|
+
|
|
163
|
+
if (!packageJson.scripts) {
|
|
164
|
+
packageJson.scripts = {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
packageJson.scripts.build = 'zengen build docs --out dist';
|
|
168
|
+
packageJson.scripts['build:watch'] = 'zengen build docs --out dist --watch';
|
|
169
|
+
packageJson.scripts.clean = 'zengen clean dist';
|
|
170
|
+
|
|
171
|
+
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
172
|
+
} catch (error) {
|
|
173
|
+
// package.json 不存在,创建简单的版本
|
|
174
|
+
const simplePackageJson = {
|
|
175
|
+
name: 'zen-docs',
|
|
176
|
+
version: '1.0.0',
|
|
177
|
+
scripts: {
|
|
178
|
+
build: 'zengen build docs --out dist',
|
|
179
|
+
'build:watch': 'zengen build docs --out dist --watch',
|
|
180
|
+
clean: 'zengen clean dist'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
await fs.writeFile(
|
|
185
|
+
packageJsonPath,
|
|
186
|
+
JSON.stringify(simplePackageJson, null, 2),
|
|
187
|
+
'utf-8'
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`
|
|
192
|
+
🎉 ZEN project initialized successfully!
|
|
193
|
+
|
|
194
|
+
Next steps:
|
|
195
|
+
1. Add your Markdown files to the 'docs' directory
|
|
196
|
+
2. Run 'npm run build' to generate the site
|
|
197
|
+
3. Run 'npm run build:watch' for development with auto-reload
|
|
198
|
+
|
|
199
|
+
Project structure:
|
|
200
|
+
${targetDir}/
|
|
201
|
+
├── docs/ # Your documentation files
|
|
202
|
+
│ └── index.md # Example document
|
|
203
|
+
├── static/ # Static assets (images, CSS, JS)
|
|
204
|
+
├── zen.config.json # Configuration file
|
|
205
|
+
└── package.json # npm scripts
|
|
206
|
+
|
|
207
|
+
For more information, visit: https://github.com/yourusername/zen
|
|
208
|
+
`);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.error('❌ Initialization failed:', error);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// 信息命令
|
|
216
|
+
program
|
|
217
|
+
.command('info')
|
|
218
|
+
.description('Show information about ZEN')
|
|
219
|
+
.action(() => {
|
|
220
|
+
console.log(`
|
|
221
|
+
🤖 ZEN - A minimalist Markdown documentation site builder
|
|
222
|
+
|
|
223
|
+
Version: 0.1.0
|
|
224
|
+
Description: Build beautiful documentation sites from Markdown files
|
|
225
|
+
|
|
226
|
+
Features:
|
|
227
|
+
• Minimal configuration required
|
|
228
|
+
• Smart navigation generation
|
|
229
|
+
• Beautiful, responsive templates
|
|
230
|
+
• Code syntax highlighting
|
|
231
|
+
• Watch mode for development
|
|
232
|
+
• Sitemap generation
|
|
233
|
+
• Static asset support
|
|
234
|
+
|
|
235
|
+
Commands:
|
|
236
|
+
build Build documentation site
|
|
237
|
+
clean Clean output directory
|
|
238
|
+
init Initialize new project
|
|
239
|
+
info Show this information
|
|
240
|
+
|
|
241
|
+
Examples:
|
|
242
|
+
$ zengen build ./docs --out ./dist
|
|
243
|
+
$ zengen build ./docs --out ./dist --watch
|
|
244
|
+
$ zengen init --dir ./my-docs
|
|
245
|
+
$ zengen clean ./dist
|
|
246
|
+
|
|
247
|
+
For more help, run: zengen --help
|
|
248
|
+
`);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 默认命令(兼容旧格式)
|
|
252
|
+
program
|
|
253
|
+
.argument('[src-dir]', 'Source directory')
|
|
254
|
+
.option('-o, --out <dir>', 'Output directory')
|
|
255
|
+
.option('-t, --template <file>', 'Custom template file')
|
|
256
|
+
.option('-w, --watch', 'Watch for changes')
|
|
257
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
258
|
+
.action(async (srcDir, options) => {
|
|
259
|
+
if (!srcDir) {
|
|
260
|
+
program.help();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const builder = new ZenBuilder();
|
|
266
|
+
|
|
267
|
+
const buildOptions = {
|
|
268
|
+
srcDir: path.resolve(srcDir),
|
|
269
|
+
outDir: path.resolve(options.out || 'dist'),
|
|
270
|
+
template: options.template ? path.resolve(options.template) : undefined,
|
|
271
|
+
watch: options.watch,
|
|
272
|
+
verbose: options.verbose
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
if (options.watch) {
|
|
276
|
+
await builder.watch(buildOptions);
|
|
277
|
+
} else {
|
|
278
|
+
await builder.build(buildOptions);
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error('❌ Build failed:', error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// 错误处理
|
|
287
|
+
program.showHelpAfterError();
|
|
288
|
+
program.showSuggestionAfterError();
|
|
289
|
+
|
|
290
|
+
// 解析命令行参数
|
|
291
|
+
program.parse(process.argv);
|
|
292
|
+
|
|
293
|
+
// 如果没有参数,显示帮助
|
|
294
|
+
if (process.argv.length <= 2) {
|
|
295
|
+
program.help();
|
|
296
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { ZenBuilder } from './builder';
|
|
2
|
+
export { MarkdownConverter } from './markdown';
|
|
3
|
+
export { TemplateEngine } from './template';
|
|
4
|
+
export { NavigationGenerator } from './navigation';
|
|
5
|
+
export type {
|
|
6
|
+
BuildOptions,
|
|
7
|
+
FileInfo,
|
|
8
|
+
NavigationItem,
|
|
9
|
+
TemplateData,
|
|
10
|
+
MarkdownProcessor,
|
|
11
|
+
ZenConfig
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ZEN 文档构建工具
|
|
16
|
+
*
|
|
17
|
+
* 一个极简主义的 Markdown 文档站点生成器
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { ZenBuilder } from 'zengen';
|
|
22
|
+
*
|
|
23
|
+
* const builder = new ZenBuilder();
|
|
24
|
+
* await builder.build({
|
|
25
|
+
* srcDir: './docs',
|
|
26
|
+
* outDir: './dist'
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import { ZenBuilder } from './builder';
|
|
31
|
+
import { MarkdownConverter } from './markdown';
|
|
32
|
+
import { TemplateEngine } from './template';
|
|
33
|
+
import { NavigationGenerator } from './navigation';
|
|
34
|
+
|
|
35
|
+
export default {
|
|
36
|
+
Builder: ZenBuilder,
|
|
37
|
+
MarkdownConverter: MarkdownConverter,
|
|
38
|
+
TemplateEngine: TemplateEngine,
|
|
39
|
+
NavigationGenerator: NavigationGenerator
|
|
40
|
+
};
|