@tnotesjs/core 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.

Potentially problematic release.


This version of @tnotesjs/core might be problematic. Click here for more details.

@@ -1,1532 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // utils/errorHandler.ts
4
- var TNotesError = class _TNotesError extends Error {
5
- constructor(message, code = "UNKNOWN" /* UNKNOWN */, context) {
6
- super(message);
7
- this.code = code;
8
- this.context = context;
9
- this.name = "TNotesError";
10
- if (Error.captureStackTrace) {
11
- Error.captureStackTrace(this, _TNotesError);
12
- }
13
- }
14
- };
15
- function handleError(error, exitOnError = false) {
16
- if (error instanceof TNotesError) {
17
- console.error(`\u274C TNotesError`);
18
- console.error(`\u9519\u8BEF\u7801\uFF1A${error.code}`);
19
- console.error(`\u9519\u8BEF\u4FE1\u606F\uFF1A${error.message}`);
20
- if (error.context && Object.keys(error.context).length > 0) {
21
- console.error("\u9519\u8BEF\u4E0A\u4E0B\u6587\u4FE1\u606F\uFF1A", error.context);
22
- }
23
- if (error.stack && process.env.DEBUG) {
24
- console.error("\u9519\u8BEF\u5806\u6808\u4FE1\u606F\uFF1A", error.stack);
25
- }
26
- } else if (error instanceof Error) {
27
- console.error(`\u274C Error`);
28
- console.error(`\u9519\u8BEF\u4FE1\u606F\uFF1A${error.message}`);
29
- if (error.stack && process.env.DEBUG) {
30
- console.error("\u9519\u8BEF\u5806\u6808\u4FE1\u606F\uFF1A", error.stack);
31
- }
32
- } else {
33
- console.error("\u274C \u672A\u77E5\u9519\u8BEF\uFF1A", error);
34
- }
35
- if (exitOnError) {
36
- process.exit(1);
37
- }
38
- }
39
- var createError = {
40
- fileNotFound: (path3) => new TNotesError(`\u6587\u4EF6\u672A\u627E\u5230\uFF1A${path3}`, "FILE_NOT_FOUND" /* FILE_NOT_FOUND */, {
41
- path: path3
42
- }),
43
- fileReadError: (path3, originalError) => new TNotesError(`\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25\uFF1A${path3}`, "FILE_READ_ERROR" /* FILE_READ_ERROR */, {
44
- path: path3,
45
- originalError: originalError?.message
46
- }),
47
- fileWriteError: (path3, originalError) => new TNotesError(`\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A${path3}`, "FILE_WRITE_ERROR" /* FILE_WRITE_ERROR */, {
48
- path: path3,
49
- originalError: originalError?.message
50
- }),
51
- gitNotRepo: (dir) => new TNotesError(`\u4E0D\u662F\u4E00\u4E2A Git \u4ED3\u5E93\uFF1A${dir}`, "GIT_NOT_REPO" /* GIT_NOT_REPO */, {
52
- dir
53
- }),
54
- gitCommandFailed: (command, dir, originalError) => new TNotesError(`Git \u547D\u4EE4\u5931\u8D25\uFF1A${command}`, "GIT_COMMAND_FAILED" /* GIT_COMMAND_FAILED */, {
55
- command,
56
- dir,
57
- originalError: originalError?.message
58
- }),
59
- noteIndexInvalid: (noteIndex) => new TNotesError(
60
- `\u65E0\u6548\u7684\u7B14\u8BB0\u7D22\u5F15\uFF1A${noteIndex}`,
61
- "NOTE_INDEX_INVALID" /* NOTE_INDEX_INVALID */,
62
- {
63
- noteIndex
64
- }
65
- ),
66
- noteConfigInvalid: (notePath, reason) => new TNotesError(
67
- `\u65E0\u6548\u7684\u7B14\u8BB0\u914D\u7F6E\uFF1A${notePath}`,
68
- "NOTE_CONFIG_INVALID" /* NOTE_CONFIG_INVALID */,
69
- { notePath, reason }
70
- ),
71
- configInvalid: (field, reason) => new TNotesError(`\u65E0\u6548\u7684\u914D\u7F6E\u5B57\u6BB5\uFF1A${field}`, "CONFIG_INVALID" /* CONFIG_INVALID */, {
72
- field,
73
- reason
74
- }),
75
- commandNotFound: (commandName) => new TNotesError(`\u672A\u627E\u5230\u547D\u4EE4\uFF1A${commandName}`, "COMMAND_NOT_FOUND" /* COMMAND_NOT_FOUND */, {
76
- commandName
77
- }),
78
- commandFailed: (commandName, exitCode, originalError) => new TNotesError(`\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${commandName}`, "COMMAND_FAILED" /* COMMAND_FAILED */, {
79
- commandName,
80
- exitCode,
81
- originalError: originalError?.message
82
- }),
83
- serverStartFailed: (port2, originalError) => new TNotesError(
84
- `\u542F\u52A8\u670D\u52A1\u5668\u5931\u8D25\uFF1A\u7AEF\u53E3 ${port2}`,
85
- "SERVER_START_FAILED" /* SERVER_START_FAILED */,
86
- { port: port2, originalError: originalError?.message }
87
- ),
88
- portInUse: (port2) => new TNotesError(`\u7AEF\u53E3 ${port2} \u5DF2\u88AB\u5360\u7528`, "PORT_IN_USE" /* PORT_IN_USE */, {
89
- port: port2
90
- })
91
- };
92
-
93
- // utils/logger.ts
94
- var Logger = class _Logger {
95
- constructor(config2 = {}) {
96
- this.level = config2.level ?? 1 /* INFO */;
97
- this.prefix = config2.prefix ?? "";
98
- this.timestamp = config2.timestamp ?? false;
99
- this.colors = config2.colors ?? true;
100
- }
101
- /**
102
- * 设置日志级别
103
- */
104
- setLevel(level) {
105
- this.level = level;
106
- }
107
- /**
108
- * 获取当前时间戳(精确到毫秒)
109
- */
110
- getTimestamp() {
111
- if (!this.timestamp) return "";
112
- const now = /* @__PURE__ */ new Date();
113
- const hours = String(now.getHours()).padStart(2, "0");
114
- const minutes = String(now.getMinutes()).padStart(2, "0");
115
- const seconds = String(now.getSeconds()).padStart(2, "0");
116
- const milliseconds = String(now.getMilliseconds()).padStart(3, "0");
117
- return `[${hours}:${minutes}:${seconds}.${milliseconds}] `;
118
- }
119
- /**
120
- * 格式化日志消息
121
- */
122
- formatMessage(message) {
123
- const timestamp = this.getTimestamp();
124
- const prefix = this.prefix ? `[${this.prefix}] ` : "";
125
- return `${timestamp}${prefix}${message}`;
126
- }
127
- /**
128
- * DEBUG 级别日志
129
- */
130
- debug(message, ...args) {
131
- if (this.level <= 0 /* DEBUG */) {
132
- console.log(`\u{1F41B} ${this.formatMessage(message)}`, ...args);
133
- }
134
- }
135
- /**
136
- * INFO 级别日志
137
- */
138
- info(message, ...args) {
139
- if (this.level <= 1 /* INFO */) {
140
- console.log(`\u2139\uFE0F ${this.formatMessage(message)}`, ...args);
141
- }
142
- }
143
- /**
144
- * 成功日志(特殊的 INFO 级别)
145
- */
146
- success(message, ...args) {
147
- if (this.level <= 1 /* INFO */) {
148
- console.log(`\u2705 ${this.formatMessage(message)}`, ...args);
149
- }
150
- }
151
- /**
152
- * 警告日志
153
- */
154
- warn(message, ...args) {
155
- if (this.level <= 2 /* WARN */) {
156
- console.warn(`\u26A0\uFE0F ${this.formatMessage(message)}`, ...args);
157
- }
158
- }
159
- /**
160
- * 错误日志
161
- */
162
- error(message, ...args) {
163
- if (this.level <= 3 /* ERROR */) {
164
- console.error(`\u274C ${this.formatMessage(message)}`, ...args);
165
- }
166
- }
167
- /**
168
- * 进度日志
169
- */
170
- progress(message, ...args) {
171
- if (this.level <= 1 /* INFO */) {
172
- console.log(`\u23F3 ${this.formatMessage(message)}`, ...args);
173
- }
174
- }
175
- /**
176
- * 启动日志
177
- */
178
- start(message, ...args) {
179
- if (this.level <= 1 /* INFO */) {
180
- console.log(`\u{1F680} ${this.formatMessage(message)}`, ...args);
181
- }
182
- }
183
- /**
184
- * 停止日志
185
- */
186
- stop(message, ...args) {
187
- if (this.level <= 1 /* INFO */) {
188
- console.log(`\u{1F6D1} ${this.formatMessage(message)}`, ...args);
189
- }
190
- }
191
- /**
192
- * 完成日志
193
- */
194
- done(message, duration, ...args) {
195
- if (this.level <= 1 /* INFO */) {
196
- const durationStr = duration ? ` (${duration}ms)` : "";
197
- console.log(`\u2728 ${this.formatMessage(message)}${durationStr}`, ...args);
198
- }
199
- }
200
- /**
201
- * 链接日志
202
- */
203
- link(message, url, ...args) {
204
- if (this.level <= 1 /* INFO */) {
205
- console.log(`\u{1F517} ${this.formatMessage(message)} ${url}`, ...args);
206
- }
207
- }
208
- /**
209
- * 文件操作日志
210
- */
211
- file(action, path3, ...args) {
212
- if (this.level <= 1 /* INFO */) {
213
- console.log(`\u{1F4C4} ${this.formatMessage(`${action}: ${path3}`)}`, ...args);
214
- }
215
- }
216
- /**
217
- * Git 操作日志
218
- */
219
- git(message, ...args) {
220
- if (this.level <= 1 /* INFO */) {
221
- console.log(`\u{1F4E6} ${this.formatMessage(message)}`, ...args);
222
- }
223
- }
224
- /**
225
- * 创建子 Logger
226
- */
227
- child(prefix) {
228
- return new _Logger({
229
- level: this.level,
230
- prefix: this.prefix ? `${this.prefix}:${prefix}` : prefix,
231
- timestamp: this.timestamp,
232
- colors: this.colors
233
- });
234
- }
235
- /**
236
- * 执行函数并记录耗时
237
- */
238
- async time(label, fn) {
239
- this.progress(`${label}...`);
240
- const startTime = Date.now();
241
- try {
242
- const result = await fn();
243
- const duration = Date.now() - startTime;
244
- this.done(label, duration);
245
- return result;
246
- } catch (error) {
247
- const duration = Date.now() - startTime;
248
- this.error(`${label} failed after ${duration}ms`);
249
- throw error;
250
- }
251
- }
252
- };
253
- var logger = new Logger({
254
- level: process.env.DEBUG ? 0 /* DEBUG */ : 1 /* INFO */,
255
- timestamp: true,
256
- // 启用时间戳(精确到毫秒)
257
- colors: true
258
- });
259
- function createLogger(prefix, config2) {
260
- return new Logger({
261
- ...config2,
262
- prefix
263
- });
264
- }
265
-
266
- // utils/parseArgs.ts
267
- function parseArgs(args) {
268
- const result = { _: [] };
269
- for (let i = 0; i < args.length; i++) {
270
- const arg = args[i];
271
- if (arg.startsWith("--") && arg.includes("=")) {
272
- const eqIndex = arg.indexOf("=");
273
- const key = arg.slice(2, eqIndex);
274
- const value = arg.slice(eqIndex + 1);
275
- result[key] = value;
276
- continue;
277
- }
278
- if (arg.startsWith("--no-")) {
279
- const key = arg.slice(5);
280
- result[key] = false;
281
- continue;
282
- }
283
- if (arg.startsWith("--")) {
284
- const key = arg.slice(2);
285
- const next = args[i + 1];
286
- if (next !== void 0 && !next.startsWith("-")) {
287
- if (next === "true") {
288
- result[key] = true;
289
- i++;
290
- } else if (next === "false") {
291
- result[key] = false;
292
- i++;
293
- } else {
294
- result[key] = true;
295
- }
296
- } else {
297
- result[key] = true;
298
- }
299
- continue;
300
- }
301
- if (arg.startsWith("-") && arg.length > 1 && !arg.startsWith("--")) {
302
- const flags = arg.slice(1);
303
- for (const flag of flags) {
304
- result[flag] = true;
305
- }
306
- continue;
307
- }
308
- result._.push(arg);
309
- }
310
- return result;
311
- }
312
-
313
- // config/ConfigManager.ts
314
- import fs2 from "fs";
315
- import path2 from "path";
316
- import { fileURLToPath } from "url";
317
-
318
- // config/defaultConfig.ts
319
- function getDefaultConfig(repoName2) {
320
- const name = repoName2 || "TNotes.default";
321
- const cleanName = name.replace("TNotes.", "");
322
- return {
323
- // 基础信息
324
- author: "tnotesjs",
325
- repoName: name,
326
- keywords: [name],
327
- id: "",
328
- // 由外部生成的 UUID
329
- // Sidebar 配置
330
- sidebarShowNoteId: true,
331
- // 忽略目录
332
- ignore_dirs: [
333
- ".vscode",
334
- "0000",
335
- "assets",
336
- "node_modules",
337
- "hidden",
338
- "demos"
339
- ],
340
- // 根目录项配置
341
- root_item: {
342
- icon: {
343
- src: `https://cdn.jsdelivr.net/gh/tnotesjs/imgs@main/assets/icon--${cleanName}.svg`
344
- },
345
- title: cleanName,
346
- completed_notes_count: {},
347
- details: `${name} \u77E5\u8BC6\u5E93`,
348
- link: `https://tnotesjs.github.io/${name}/`,
349
- created_at: Date.now(),
350
- updated_at: Date.now(),
351
- days_since_birth: 0
352
- },
353
- // 端口配置(根据仓库名生成)
354
- port: 8e3,
355
- // 菜单项
356
- menuItems: [
357
- {
358
- text: "\u{1F3E0} Home",
359
- link: "/"
360
- },
361
- {
362
- text: "\u2699\uFE0F Settings",
363
- link: "/Settings"
364
- },
365
- {
366
- text: "\u{1F4D2} TNotes",
367
- link: "https://tnotesjs.github.io/notes"
368
- },
369
- {
370
- text: "\u{1F4C2} TNotes.yuque",
371
- link: "https://www.yuque.com/tdahuyou/tnotes.yuque"
372
- }
373
- ],
374
- // 社交链接
375
- socialLinks: [
376
- {
377
- ariaLabel: "Tdahuyou \u8BED\u96C0\u4E3B\u9875\u94FE\u63A5",
378
- link: "https://www.yuque.com/tdahuyou",
379
- icon: {
380
- svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M17.28 2.955c2.97.203 3.756 2.342 3.84 2.597l1.297.096c.13 0 .169.18.054.236c-1.323.716-1.727 2.17-1.49 3.118c.09.358.254.69.412 1.02c.307.642.651 1.418.707 2.981c.117 3.24-2.51 6.175-5.789 6.593c1.17-1.187 1.815-2.444 2.12-3.375c.606-1.846.508-3.316.055-4.44a4.46 4.46 0 0 0-1.782-2.141c-1.683-1.02-3.22-1.09-4.444-.762c.465-.594.876-1.201 1.2-1.864c.584-1.65-.102-2.848-.704-3.519c-.192-.246-.061-.655.305-.655c1.41 0 2.813.02 4.22.115M3.32 19.107c1.924-2.202 4.712-5.394 7.162-8.15c.559-.63 2.769-2.338 5.748-.533c.878.532 2.43 2.165 1.332 5.51c-.803 2.446-4.408 7.796-15.76 5.844c-.227-.039-.511-.354-.218-.687z"/></svg>'
381
- }
382
- },
383
- {
384
- ariaLabel: "Tdahuyou B \u7AD9\u4E3B\u9875\u94FE\u63A5",
385
- link: "https://space.bilibili.com/407241004",
386
- icon: {
387
- svg: '<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024"><g fill="currentColor"><path d="M310.134 596.45c-7.999-4.463-16.498-8.43-24.997-11.9a274 274 0 0 0-26.996-7.438c-2.5-.992-2.5.991-2.5 1.487c0 7.934.5 18.843 1.5 27.768c1 7.438 2 15.372 4 22.81c0 .496 0 .991.5 1.487c.999.992 1.999 1.488 2.999.496c7.999-4.463 15.998-8.43 22.997-13.388c7.499-5.454 15.498-11.9 21.997-18.347c1.5-1.487 0-2.479.5-2.975m323.96-11.9a274 274 0 0 0-26.997-7.438c-2.5-.992-2.5.991-2.5 1.487c0 7.934.5 18.843 1.5 27.768c1 7.438 2 15.372 4 22.81c0 .496 0 .991.5 1.487c1 .992 2 1.488 3 .496c7.999-4.463 15.998-8.43 22.997-13.388c7.499-5.454 15.498-11.9 21.997-18.347c2-1.487.5-2.479.5-2.975c-7.5-4.463-16.498-8.43-24.997-11.9"/><path d="M741.496 112H283c-94.501 0-171 76.5-171 171.5v458c.5 94 77 170.5 170.999 170.5h457.997c94.5 0 171.002-76.5 171.002-170.5v-458c.497-95-76.002-171.5-170.502-171.5m95 343.5h15.5v48h-15.5zm-95.5-1.5l2 43l-13.5 1.5l-5-44.5zm-23.5 0l4 45.5l-14.5 1.5l-6.5-47.5h17zm-230.498 1.5h15v48h-15zm-96-1.5l2 43l-13.5 1.5l-5-44.5zm-23.5 0l4 45.5l-14.5 2l-6-47.5zm-3.5 149C343.498 668.5 216 662.5 204.5 660.5C195.5 499 181.5 464 170 385l54.5-22.5c1 71.5 9 185 9 185s108.5-15.5 132 47c.5 3 0 6-1.5 8.5m20.5 35.5l-23.5-124h35.5l13 123zm44.5-8l-27-235l33.5-1.5l21 236H429zm34-175h17.5v48H467zm41 190h-26.5l-9.5-126h36zm209.998-43C693.496 668 565.997 662 554.497 660c-9-161-23-196-34.5-275l54.5-22.5c1 71.5 9 185 9 185s108.5-15.5 132 46.5c.5 3 0 6-1.5 8.5m19.5 36l-23-124h35.5l13 123zm45.5-8l-27.5-235l33.5-1.5l21 236h-27zm33.5-175h17.5v48h-13zm41 190h-26.5l-9.5-126h36z"/></g></svg>'
388
- }
389
- },
390
- {
391
- ariaLabel: `${name} github \u4ED3\u5E93\u94FE\u63A5`,
392
- link: `https://github.com/tnotesjs/${name}`,
393
- icon: "github"
394
- }
395
- ]
396
- };
397
- }
398
- function mergeConfig(target, source) {
399
- const result = { ...target };
400
- for (const key in source) {
401
- if (!(key in result)) {
402
- result[key] = source[key];
403
- } else if (typeof source[key] === "object" && source[key] !== null && !Array.isArray(source[key])) {
404
- result[key] = mergeConfig(result[key] || {}, source[key]);
405
- }
406
- }
407
- return result;
408
- }
409
- function validateAndCompleteConfig(config2) {
410
- const repoName2 = config2.repoName || "TNotes.default";
411
- const defaultConfig = getDefaultConfig(repoName2);
412
- const mergedConfig = mergeConfig(config2, defaultConfig);
413
- const modified = JSON.stringify(config2) !== JSON.stringify(mergedConfig);
414
- return {
415
- config: mergedConfig,
416
- modified
417
- };
418
- }
419
-
420
- // utils/file.ts
421
- import fs from "fs";
422
- async function ensureDirectory(dir) {
423
- if (!fs.existsSync(dir)) {
424
- await fs.promises.mkdir(dir, { recursive: true });
425
- }
426
- }
427
-
428
- // utils/generateAnchor.ts
429
- import GithubSlugger from "github-slugger";
430
- var slugger = new GithubSlugger();
431
- var generateAnchor = (label) => {
432
- slugger.reset();
433
- return slugger.slug(label);
434
- };
435
-
436
- // utils/genHierarchicalSidebar.ts
437
- var genHierarchicalSidebar = (itemList, titles, titlesNotesCount, sidebarIsCollapsed) => {
438
- const stack = [];
439
- const root = [];
440
- titles.forEach((title, i) => {
441
- const match = title.match(/^#+/);
442
- if (!match) return;
443
- const level = match[0].length;
444
- const text = title.replace(/^#+\s*/, "");
445
- const noteItems = itemList.splice(0, titlesNotesCount[i]);
446
- const node = {
447
- text,
448
- collapsed: sidebarIsCollapsed,
449
- items: noteItems.length > 0 ? noteItems : []
450
- };
451
- if (i === 0 && level === 1) return;
452
- while (stack.length > 0 && stack[stack.length - 1].level >= level) {
453
- stack.pop();
454
- }
455
- if (stack.length === 0) {
456
- root.push(node);
457
- } else {
458
- const parent = stack[stack.length - 1].node;
459
- if (!parent.items) parent.items = [];
460
- parent.items.push(node);
461
- }
462
- stack.push({ level, node });
463
- });
464
- return root;
465
- };
466
-
467
- // utils/getChangedIds.ts
468
- import path from "path";
469
- import { execSync } from "child_process";
470
- function getChangedIds() {
471
- const changedFiles = execSync(
472
- `git diff --name-only HEAD -- "notes/[0-9][0-9][0-9][0-9]*/README.md"`
473
- // 根据当前仓库状态和最近一次提交之间的比较
474
- ).toString().split(/\r?\n/).filter(Boolean).map((fp) => fp.replace(/^"|"$/g, "")).map((fp) => fp.split("/").join(path.sep));
475
- const changedIds = changedFiles.map((fp) => {
476
- const parts = fp.split(path.sep);
477
- const dirName = parts.find((p, i) => parts[i - 1] === "notes");
478
- return dirName?.slice(0, 4);
479
- }).filter((id) => Boolean(id));
480
- return new Set(changedIds);
481
- }
482
-
483
- // utils/getTargetDirs.ts
484
- import { readdirSync } from "fs";
485
- import { join } from "path";
486
- var getTargetDirs = (baseDir, prefix, excludeDirs = []) => {
487
- try {
488
- const entries = readdirSync(baseDir, { withFileTypes: true });
489
- const targetDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith(prefix)).map((entry) => join(baseDir, entry.name)).filter((dir) => !excludeDirs.includes(dir));
490
- return targetDirs;
491
- } catch (error) {
492
- const errorMessage = error instanceof Error ? error.message : String(error);
493
- console.error(`\u8BFB\u53D6\u76EE\u5F55 ${baseDir} \u65F6\u51FA\u9519\uFF1A${errorMessage}`);
494
- return [];
495
- }
496
- };
497
-
498
- // utils/markdown.ts
499
- function createAddNumberToTitle() {
500
- const titleNumbers = Array(7).fill(0);
501
- return function addNumberToTitle(title) {
502
- const match = title.match(
503
- /^(\#+)\s*((\d+(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?(\.\d*)?)\.\s*)?(.*)/
504
- );
505
- const plainTitle = match ? match[9].trim() : title.trim();
506
- const level = title.indexOf(" ");
507
- const baseLevel = 2;
508
- if (level === 1) return [title, plainTitle];
509
- for (let i = level + 1; i < titleNumbers.length; i++) titleNumbers[i] = 0;
510
- titleNumbers[level] += 1;
511
- const newNumber = titleNumbers.slice(baseLevel, level + 1).join(".");
512
- const headerSymbol = title.slice(0, level).trim();
513
- const newTitle = `${headerSymbol} ${newNumber}. ${plainTitle}`;
514
- return [newTitle, plainTitle];
515
- };
516
- }
517
- function generateToc(titles, baseLevel = 2, eol = "\n") {
518
- const toc = titles.map((title) => {
519
- const level = title.indexOf(" ");
520
- const text = title.slice(level).trim();
521
- const anchor = generateAnchor(text);
522
- const indent = Math.max(0, (level - baseLevel) * 2);
523
- return " ".repeat(indent) + `- [${text}](#${anchor})`;
524
- }).join(eol);
525
- return `${eol}${toc}${eol}`;
526
- }
527
-
528
- // utils/parseReadmeCompletedNotes.ts
529
- function parseReadmeCompletedNotes(content) {
530
- const lines = content.split("\n");
531
- const noteMap = /* @__PURE__ */ new Map();
532
- const noteIndexRegex = /\[(\d{4})\./;
533
- for (const line of lines) {
534
- const match = line.match(noteIndexRegex);
535
- if (!match) continue;
536
- const noteIndex = match[1];
537
- let completed;
538
- if (line.includes("\u274C")) {
539
- completed = false;
540
- } else if (line.includes("\u23F0")) {
541
- completed = false;
542
- } else if (line.includes("\u2705")) {
543
- completed = true;
544
- } else if (line.trim().startsWith("- [ ]")) {
545
- completed = false;
546
- } else if (line.trim().startsWith("- [x]")) {
547
- completed = true;
548
- } else {
549
- continue;
550
- }
551
- if (noteMap.has(noteIndex)) {
552
- const existing = noteMap.get(noteIndex);
553
- if (existing.completed !== completed) {
554
- throw new Error(
555
- `\u53D1\u73B0\u76F8\u540C\u7F16\u53F7 ${noteIndex} \u7684\u7B14\u8BB0\u6709\u4E0D\u540C\u7684\u5B8C\u6210\u72B6\u6001:
556
- \u7B2C\u4E00\u6B21\u51FA\u73B0: ${existing.line}
557
- \u7B2C\u4E8C\u6B21\u51FA\u73B0: ${line}`
558
- );
559
- }
560
- continue;
561
- }
562
- noteMap.set(noteIndex, {
563
- noteIndex,
564
- completed,
565
- line: line.trim()
566
- });
567
- }
568
- const notes = Array.from(noteMap.values());
569
- const completedCount = notes.filter((note) => note.completed).length;
570
- const totalCount = notes.length;
571
- return {
572
- completedCount,
573
- totalCount,
574
- notes
575
- };
576
- }
577
-
578
- // utils/portUtils.ts
579
- import { execSync as execSync2 } from "child_process";
580
- function isPortInUse(port2) {
581
- try {
582
- if (process.platform === "win32") {
583
- const output = execSync2(
584
- `netstat -ano | findstr :${port2} | findstr LISTENING`,
585
- { encoding: "utf-8", stdio: "pipe" }
586
- );
587
- return output.trim().length > 0;
588
- } else {
589
- const output = execSync2(`lsof -i :${port2}`, {
590
- encoding: "utf-8",
591
- stdio: "pipe"
592
- });
593
- return output.trim().length > 0;
594
- }
595
- } catch (error) {
596
- return false;
597
- }
598
- }
599
- function getPortPid(port2) {
600
- try {
601
- if (process.platform === "win32") {
602
- const output = execSync2(`netstat -ano | findstr :${port2}`, {
603
- encoding: "utf-8",
604
- stdio: "pipe"
605
- });
606
- const lines = output.trim().split("\n");
607
- if (lines.length > 0) {
608
- const match = lines[0].match(/\s+(\d+)\s*$/);
609
- if (match) {
610
- return parseInt(match[1]);
611
- }
612
- }
613
- } else {
614
- const output = execSync2(`lsof -t -i :${port2}`, {
615
- encoding: "utf-8",
616
- stdio: "pipe"
617
- });
618
- const pid = parseInt(output.trim());
619
- if (!isNaN(pid)) {
620
- return pid;
621
- }
622
- }
623
- } catch (error) {
624
- }
625
- return null;
626
- }
627
- function killPortProcess(port2) {
628
- const pid = getPortPid(port2);
629
- if (!pid) {
630
- return false;
631
- }
632
- try {
633
- if (process.platform === "win32") {
634
- execSync2(`taskkill /F /PID ${pid}`, { stdio: "pipe" });
635
- } else {
636
- execSync2(`kill -9 ${pid}`, { stdio: "pipe" });
637
- }
638
- logger.info(`\u5DF2\u7EC8\u6B62\u5360\u7528\u7AEF\u53E3 ${port2} \u7684\u8FDB\u7A0B (PID: ${pid})`);
639
- return true;
640
- } catch (error) {
641
- logger.error(
642
- `\u7EC8\u6B62\u8FDB\u7A0B\u5931\u8D25 (PID: ${pid}): ${error instanceof Error ? error.message : String(error)}`
643
- );
644
- return false;
645
- }
646
- }
647
- async function waitForPort(port2, timeout = 5e3) {
648
- const startTime = Date.now();
649
- while (Date.now() - startTime < timeout) {
650
- if (!isPortInUse(port2)) {
651
- return true;
652
- }
653
- await new Promise((resolve2) => setTimeout(resolve2, 100));
654
- }
655
- return false;
656
- }
657
-
658
- // core/NoteManager.ts
659
- import { existsSync, readFileSync, readdirSync as readdirSync2, writeFileSync } from "fs";
660
- import { join as join2 } from "path";
661
-
662
- // config/constants.ts
663
- import { resolve } from "path";
664
- var configManager = getConfigManager();
665
- var config = configManager.getAll();
666
- var {
667
- author,
668
- ignore_dirs,
669
- menuItems,
670
- port,
671
- repoName,
672
- sidebarShowNoteId,
673
- socialLinks,
674
- root_item
675
- } = config;
676
- var rootPath = configManager.getRootPath();
677
- var TNOTES_BASE_DIR = resolve(rootPath, "..");
678
- var TNOTES_CORE_DIR = resolve(TNOTES_BASE_DIR, "TNotes.core");
679
- var EN_WORDS_DIR = resolve(TNOTES_BASE_DIR, "TNotes.en-words");
680
- var ROOT_DIR_PATH = rootPath;
681
- var ROOT_README_PATH = resolve(ROOT_DIR_PATH, "README.md");
682
- var ROOT_CONFIG_PATH = resolve(ROOT_DIR_PATH, ".tnotes.json");
683
- var NOTES_DIR_PATH = resolve(ROOT_DIR_PATH, "notes");
684
- var VP_DIR_PATH = resolve(ROOT_DIR_PATH, ".vitepress");
685
- var PUBLIC_PATH = resolve(ROOT_DIR_PATH, "public");
686
- var GITHUB_DIR_PATH = resolve(ROOT_DIR_PATH, ".github");
687
- var GITHUB_DEPLOY_YML_PATH = resolve(
688
- GITHUB_DIR_PATH,
689
- "workflows",
690
- "deploy.yml"
691
- );
692
- var VP_SIDEBAR_PATH = resolve(ROOT_DIR_PATH, "sidebar.json");
693
- var ROOT_PKG_PATH = resolve(ROOT_DIR_PATH, "package.json");
694
- var VSCODE_SETTINGS_PATH = resolve(
695
- ROOT_DIR_PATH,
696
- ".vscode",
697
- "settings.json"
698
- );
699
- var VSCODE_TASKS_PATH = resolve(ROOT_DIR_PATH, ".vscode", "tasks.json");
700
- var EOL = "\n";
701
- var CONSTANTS = {
702
- // 端口配置
703
- DEFAULT_PORT: 5173,
704
- // 笔记索引配置(文件夹前缀的 4 位数字)
705
- NOTE_INDEX_LENGTH: 4,
706
- NOTE_INDEX_PATTERN: /^\d{4}\./,
707
- NOTE_INDEX_PREFIX_PATTERN: /^\d{4}/,
708
- // Git 配置
709
- DEFAULT_BRANCH: "main",
710
- // 缓存配置
711
- CACHE_TTL: 5e3,
712
- // 终端输出颜色
713
- COLORS: {
714
- RESET: "\x1B[0m",
715
- BRIGHT: "\x1B[1m",
716
- DIM: "\x1B[2m",
717
- RED: "\x1B[31m",
718
- GREEN: "\x1B[32m",
719
- YELLOW: "\x1B[33m",
720
- BLUE: "\x1B[34m",
721
- MAGENTA: "\x1B[35m",
722
- CYAN: "\x1B[36m"
723
- },
724
- // Emoji
725
- EMOJI: {
726
- SUCCESS: "\u2705",
727
- ERROR: "\u274C",
728
- WARNING: "\u26A0\uFE0F",
729
- INFO: "\u2139\uFE0F",
730
- PROGRESS: "\u23F3",
731
- ROCKET: "\u{1F680}",
732
- STOP: "\u{1F6D1}",
733
- SPARKLES: "\u2728",
734
- LINK: "\u{1F517}",
735
- FILE: "\u{1F4C4}",
736
- GIT: "\u{1F4E6}",
737
- DEBUG: "\u{1F41B}"
738
- }
739
- };
740
- var NOTES_PATH = NOTES_DIR_PATH;
741
- var REPO_NOTES_URL = `https://github.com/${author}/${repoName}/tree/main/notes`;
742
-
743
- // core/NoteManager.ts
744
- var NoteManager = class _NoteManager {
745
- static {
746
- /** 笔记索引正则:4 位数字开头,后接小数点 */
747
- this.NOTE_INDEX_REGEX = /^(\d{4})\./;
748
- }
749
- constructor() {
750
- }
751
- /**
752
- * 从文件夹名称或文本中提取笔记索引
753
- *
754
- * @param text - 要解析的文本(通常是文件夹名称)
755
- * @returns 笔记索引(4 位数字字符串)或 null
756
- *
757
- * @example
758
- * NoteManager.extractNoteIndex('0001. TNotes 简介') // '0001'
759
- * NoteManager.extractNoteIndex('invalid-folder') // null
760
- */
761
- static extractNoteIndex(text) {
762
- const match = text.match(_NoteManager.NOTE_INDEX_REGEX);
763
- return match ? match[1] : null;
764
- }
765
- /**
766
- * 输出无效笔记名称的警告日志
767
- *
768
- * @param name - 无效的笔记名称
769
- */
770
- static warnInvalidNoteIndex(name) {
771
- logger.warn(`\u65E0\u6548\u7684\u7B14\u8BB0\u540D: ${name}`);
772
- logger.warn("\u7B14\u8BB0\u540D\u5FC5\u987B\u4EE5 4 \u4E2A\u6570\u5B57\u5F00\u5934");
773
- logger.warn("\u8303\u56F4\uFF1A0001-9999");
774
- }
775
- static getInstance() {
776
- if (!_NoteManager.instance) {
777
- _NoteManager.instance = new _NoteManager();
778
- }
779
- return _NoteManager.instance;
780
- }
781
- /**
782
- * 获取 notes 目录下所有合法的笔记目录名(已排序)
783
- * 合法条件:是目录、不以 . 开头、以 4 位数字 + . 开头
784
- */
785
- getNoteDirs() {
786
- if (!existsSync(NOTES_PATH)) return [];
787
- return readdirSync2(NOTES_PATH, { withFileTypes: true }).filter(
788
- (entry) => entry.isDirectory() && !entry.name.startsWith(".") && _NoteManager.NOTE_INDEX_REGEX.test(entry.name)
789
- ).map((entry) => entry.name).sort();
790
- }
791
- /**
792
- * 根据目录名构建单条 NoteInfo
793
- * @returns NoteInfo 或 undefined(README 不存在时)
794
- */
795
- buildNoteInfo(dirName) {
796
- const notePath = join2(NOTES_PATH, dirName);
797
- const readmePath = join2(notePath, "README.md");
798
- const configPath = join2(notePath, ".tnotes.json");
799
- if (!existsSync(readmePath)) {
800
- logger.warn(`README not found in note: ${dirName}`);
801
- return void 0;
802
- }
803
- let config2;
804
- if (existsSync(configPath)) {
805
- config2 = this.validateAndFixConfig(configPath) || void 0;
806
- }
807
- return {
808
- index: _NoteManager.extractNoteIndex(dirName),
809
- path: notePath,
810
- dirName,
811
- readmePath,
812
- configPath,
813
- config: config2
814
- };
815
- }
816
- /**
817
- * 扫描所有笔记并校验数据完整性
818
- *
819
- * @returns 笔记信息数组
820
- */
821
- scanNotes() {
822
- const noteDirs = this.getNoteDirs();
823
- if (noteDirs.length === 0) {
824
- logger.warn(`${NOTES_PATH} \u672A\u68C0\u6D4B\u5230\u7B14\u8BB0\u76EE\u5F55`);
825
- return [];
826
- }
827
- const notes = [];
828
- for (const dirName of noteDirs) {
829
- const note = this.buildNoteInfo(dirName);
830
- if (note) notes.push(note);
831
- }
832
- this.validateNotes(notes);
833
- return notes;
834
- }
835
- /**
836
- * 校验笔记数据完整性
837
- *
838
- * - 检查 noteIndex 冲突 + config id 缺失/重复
839
- * - 任一检查失败则终止进程
840
- */
841
- validateNotes(notes) {
842
- const errors = [];
843
- const L1 = " ".repeat(3);
844
- const L2 = " ".repeat(6);
845
- const indexMap = this.buildNoteIndexMap(notes.map((n) => n.dirName));
846
- for (const [index, dirNames] of indexMap.entries()) {
847
- if (dirNames.length > 1) {
848
- errors.push(`\u26A0\uFE0F \u68C0\u6D4B\u5230\u91CD\u590D\u7684\u7B14\u8BB0\u7F16\u53F7\uFF1A`);
849
- errors.push(`${L1}\u7D22\u5F15 ${index} \u88AB\u4EE5\u4E0B\u7B14\u8BB0\u91CD\u590D\u4F7F\u7528\uFF1A`);
850
- dirNames.forEach((dirName) => errors.push(`${L2}- ${dirName}`));
851
- }
852
- }
853
- const missingConfigId = [];
854
- for (const note of notes) {
855
- if (!note.config || !note.config.id) {
856
- missingConfigId.push(note.dirName);
857
- }
858
- }
859
- if (missingConfigId.length > 0) {
860
- errors.push(`\u26A0\uFE0F \u68C0\u6D4B\u5230\u7B14\u8BB0\u914D\u7F6E ID \u7F3A\u5931\uFF1A`);
861
- missingConfigId.forEach((dirName) => errors.push(`${L2}- ${dirName}`));
862
- }
863
- const configIdMap = /* @__PURE__ */ new Map();
864
- for (const note of notes) {
865
- if (note.config?.id) {
866
- if (!configIdMap.has(note.config.id))
867
- configIdMap.set(note.config.id, []);
868
- configIdMap.get(note.config.id).push(note.dirName);
869
- }
870
- }
871
- for (const [configId, dirNames] of configIdMap.entries()) {
872
- if (dirNames.length > 1) {
873
- errors.push(`\u26A0\uFE0F \u68C0\u6D4B\u5230\u91CD\u590D\u7684\u7B14\u8BB0\u914D\u7F6E ID\uFF1A`);
874
- errors.push(`${L1}\u914D\u7F6E ID ${configId} \u88AB\u4EE5\u4E0B\u7B14\u8BB0\u91CD\u590D\u4F7F\u7528\uFF1A`);
875
- dirNames.forEach((dirName) => errors.push(`${L2}- ${dirName}`));
876
- }
877
- }
878
- if (errors.length > 0) {
879
- for (const line of errors) {
880
- logger.error(line);
881
- }
882
- logger.error("\n\n\u8BF7\u4FEE\u590D\u4E0A\u8FF0\u95EE\u9898\u540E\u91CD\u65B0\u542F\u52A8\u670D\u52A1\u3002\n\n");
883
- process.exit(1);
884
- }
885
- }
886
- /**
887
- * 按 4 位数字编号对目录名分组
888
- * @param dirNames - 目录名数组
889
- * @returns 编号 -> 目录名数组 的映射
890
- */
891
- buildNoteIndexMap(dirNames) {
892
- const indexMap = /* @__PURE__ */ new Map();
893
- for (const name of dirNames) {
894
- const index = _NoteManager.extractNoteIndex(name);
895
- if (!indexMap.has(index)) indexMap.set(index, []);
896
- indexMap.get(index).push(name);
897
- }
898
- return indexMap;
899
- }
900
- static {
901
- /** 配置字段顺序 */
902
- this.FIELD_ORDER = [
903
- "bilibili",
904
- "tnotes",
905
- "yuque",
906
- "done",
907
- "category",
908
- "enableDiscussions",
909
- "description",
910
- "id",
911
- "created_at",
912
- "updated_at"
913
- ];
914
- }
915
- static {
916
- /** 默认配置字段 */
917
- this.DEFAULT_CONFIG_FIELDS = {
918
- bilibili: [],
919
- tnotes: [],
920
- yuque: [],
921
- done: false,
922
- enableDiscussions: false,
923
- description: ""
924
- };
925
- }
926
- static {
927
- /** 必需字段(不能缺失) */
928
- this.REQUIRED_FIELDS = ["id"];
929
- }
930
- /**
931
- * 验证并修复配置文件
932
- * @param configPath - 配置文件路径
933
- * @returns 修复后的配置对象,失败时返回 null
934
- */
935
- validateAndFixConfig(configPath) {
936
- const configContent = readFileSync(configPath, "utf-8");
937
- let config2;
938
- try {
939
- config2 = JSON.parse(configContent);
940
- } catch (error) {
941
- logger.error(`\u914D\u7F6E\u6587\u4EF6 JSON \u89E3\u6790\u5931\u8D25: ${configPath}`, error);
942
- return null;
943
- }
944
- let needsUpdate = false;
945
- for (const field of _NoteManager.REQUIRED_FIELDS) {
946
- if (!config2[field]) {
947
- return null;
948
- }
949
- }
950
- for (const [key, defaultValue] of Object.entries(
951
- _NoteManager.DEFAULT_CONFIG_FIELDS
952
- )) {
953
- if (!(key in config2)) {
954
- ;
955
- config2[key] = defaultValue;
956
- needsUpdate = true;
957
- logger.info(`\u8865\u5145\u7F3A\u5931\u5B57\u6BB5 "${key}": ${configPath}`);
958
- }
959
- }
960
- const now = Date.now();
961
- if (!config2.created_at) {
962
- config2.created_at = now;
963
- needsUpdate = true;
964
- logger.info(
965
- `\u68C0\u6D4B\u5230 ${configPath} \u7F3A\u5931 created_at \u5B57\u6BB5\uFF0C\u8BF7\u6267\u884C tn:fix-timestamps \u6821\u51C6\u4E3A\u7B14\u8BB0\u9996\u6B21 git commit \u7684\u65F6\u95F4\uFF09`
966
- );
967
- }
968
- if (!config2.updated_at) {
969
- config2.updated_at = now;
970
- needsUpdate = true;
971
- logger.info(
972
- `\u68C0\u6D4B\u5230 ${configPath} \u7F3A\u5931 updated_at \u5B57\u6BB5\uFF0C\u8BF7\u6267\u884C tn:fix-timestamps \u6821\u51C6\u4E3A\u7B14\u8BB0\u6700\u540E\u4E00\u6B21 git commit \u7684\u65F6\u95F4\uFF09`
973
- );
974
- }
975
- const sortedConfig = this.sortConfigKeys(config2);
976
- if (needsUpdate) {
977
- this.writeNoteConfig(configPath, sortedConfig);
978
- logger.info(`\u914D\u7F6E\u6587\u4EF6\u5DF2\u4FEE\u590D: ${configPath}`);
979
- }
980
- return sortedConfig;
981
- }
982
- /**
983
- * 按指定顺序排序配置对象的键
984
- */
985
- sortConfigKeys(config2) {
986
- const configRecord = config2;
987
- const sorted = {};
988
- for (const key of _NoteManager.FIELD_ORDER) {
989
- if (key in config2) {
990
- sorted[key] = configRecord[key];
991
- }
992
- }
993
- for (const key of Object.keys(config2)) {
994
- if (!(key in sorted)) {
995
- sorted[key] = configRecord[key];
996
- }
997
- }
998
- return sorted;
999
- }
1000
- /**
1001
- * 序列化 NoteConfig 为格式化的 JSON 字符串
1002
- * 保持字段顺序,使用 2 空格缩进,末尾含换行符
1003
- */
1004
- serializeNoteConfig(config2) {
1005
- const sorted = this.sortConfigKeys(config2);
1006
- return JSON.stringify(sorted, null, 2) + "\n";
1007
- }
1008
- /**
1009
- * 统一写入笔记配置文件
1010
- * @param configPath - 配置文件路径
1011
- * @param config - 笔记配置
1012
- */
1013
- writeNoteConfig(configPath, config2) {
1014
- writeFileSync(configPath, this.serializeNoteConfig(config2), "utf-8");
1015
- }
1016
- /**
1017
- * 验证笔记配置对象的结构合法性
1018
- * @param config - 笔记配置
1019
- * @returns 是否有效
1020
- */
1021
- validateConfig(config2) {
1022
- if (!config2.id) {
1023
- logger.error("Note config missing id");
1024
- return false;
1025
- }
1026
- if (!Array.isArray(config2.bilibili)) {
1027
- logger.error(`Invalid bilibili config in note: ${config2.id}`);
1028
- return false;
1029
- }
1030
- if (!Array.isArray(config2.tnotes)) {
1031
- logger.error(`Invalid tnotes config in note: ${config2.id}`);
1032
- return false;
1033
- }
1034
- if (!Array.isArray(config2.yuque)) {
1035
- logger.error(`Invalid yuque config in note: ${config2.id}`);
1036
- return false;
1037
- }
1038
- if (typeof config2.done !== "boolean") {
1039
- logger.error(`Invalid done status in note: ${config2.id}`);
1040
- return false;
1041
- }
1042
- if (typeof config2.enableDiscussions !== "boolean") {
1043
- logger.error(`Invalid enableDiscussions status in note: ${config2.id}`);
1044
- return false;
1045
- }
1046
- return true;
1047
- }
1048
- /**
1049
- * 更新笔记配置
1050
- * @param noteInfo - 笔记信息
1051
- * @param config - 新的配置
1052
- */
1053
- updateNoteConfig(noteInfo, config2) {
1054
- if (!this.validateConfig(config2)) {
1055
- throw new Error(`Invalid config for note: ${noteInfo.dirName}`);
1056
- }
1057
- config2.updated_at = Date.now();
1058
- this.writeNoteConfig(noteInfo.configPath, config2);
1059
- logger.info(`Updated config for note: ${noteInfo.dirName}`);
1060
- }
1061
- /**
1062
- * 获取笔记信息(通过索引)- 直接查找不扫描所有笔记
1063
- * @param noteIndex - 笔记索引
1064
- * @returns 笔记信息,未找到时返回 undefined
1065
- */
1066
- getNoteByIndex(noteIndex) {
1067
- const noteDirs = this.getNoteDirs();
1068
- for (const dirName of noteDirs) {
1069
- if (_NoteManager.extractNoteIndex(dirName) === noteIndex) {
1070
- return this.buildNoteInfo(dirName);
1071
- }
1072
- }
1073
- return void 0;
1074
- }
1075
- };
1076
-
1077
- // utils/readmeHelpers.ts
1078
- var NOTE_LINE_REGEX = /^( *)- \[.\] \[(\d{4}\. .+?)\]/;
1079
- function parseNoteLine(line) {
1080
- const noteMatch = line.match(NOTE_LINE_REGEX);
1081
- if (!noteMatch) {
1082
- return {
1083
- isMatch: false,
1084
- noteIndex: null
1085
- };
1086
- }
1087
- const [, , text] = noteMatch;
1088
- const noteIndex = NoteManager.extractNoteIndex(text);
1089
- return {
1090
- isMatch: true,
1091
- noteIndex
1092
- };
1093
- }
1094
- function buildNoteLink(note, repoOwner, repoName2) {
1095
- const encodedDirName = encodeURIComponent(note.dirName);
1096
- return `https://github.com/${repoOwner}/${repoName2}/tree/main/notes/${encodedDirName}/README.md`;
1097
- }
1098
- function updateNoteStatus(note) {
1099
- let status = " ";
1100
- let deprecatedMark = "";
1101
- if (note.config) {
1102
- if (note.config.done) {
1103
- status = "x";
1104
- }
1105
- }
1106
- return { status, deprecatedMark };
1107
- }
1108
- function buildNoteLineMarkdown(note, repoOwner, repoName2) {
1109
- const url = buildNoteLink(note, repoOwner, repoName2);
1110
- const { status, deprecatedMark } = updateNoteStatus(note);
1111
- return `- [${status}] [${note.dirName}](${url})${deprecatedMark}`;
1112
- }
1113
- function isNoteLine(line) {
1114
- return NOTE_LINE_REGEX.test(line);
1115
- }
1116
- function mergeConsecutiveEmptyLines(lines) {
1117
- const result = [];
1118
- let previousLineIsEmpty = false;
1119
- for (const line of lines) {
1120
- const isCurrentLineEmpty = line === "";
1121
- if (isCurrentLineEmpty) {
1122
- if (!previousLineIsEmpty) {
1123
- result.push(line);
1124
- previousLineIsEmpty = true;
1125
- }
1126
- } else {
1127
- result.push(line);
1128
- previousLineIsEmpty = false;
1129
- }
1130
- }
1131
- return result;
1132
- }
1133
- function removeEmptyLinesBetweenNotes(lines) {
1134
- const result = [];
1135
- for (let i = 0; i < lines.length; i++) {
1136
- const currentLine = lines[i];
1137
- const prevLine = i > 0 ? lines[i - 1] : null;
1138
- const nextLine = i < lines.length - 1 ? lines[i + 1] : null;
1139
- if (currentLine === "" && prevLine && nextLine) {
1140
- const isPrevLineNote = isNoteLine(prevLine);
1141
- const isNextLineNote = isNoteLine(nextLine);
1142
- if (isPrevLineNote && isNextLineNote) {
1143
- continue;
1144
- }
1145
- }
1146
- result.push(currentLine);
1147
- }
1148
- return result;
1149
- }
1150
- function processEmptyLines(lines) {
1151
- const stepOne = mergeConsecutiveEmptyLines(lines);
1152
- const stepTwo = removeEmptyLinesBetweenNotes(stepOne);
1153
- return stepTwo;
1154
- }
1155
-
1156
- // utils/runCommand.ts
1157
- import { exec } from "child_process";
1158
- async function runCommand(command, dir) {
1159
- return new Promise((resolve2, reject) => {
1160
- exec(command, { cwd: dir }, (error, stdout, stderr) => {
1161
- if (error) {
1162
- console.error(`\u5904\u7406 ${dir} \u65F6\u51FA\u9519\uFF1A${stderr}`);
1163
- reject(error);
1164
- } else {
1165
- resolve2(stdout.trim());
1166
- }
1167
- });
1168
- });
1169
- }
1170
-
1171
- // utils/syncRepo.ts
1172
- async function pushAllRepos(options) {
1173
- const {
1174
- parallel = true,
1175
- continueOnError = true,
1176
- force = false
1177
- } = options || {};
1178
- const targetDirs = getTargetDirs(TNOTES_BASE_DIR, "TNotes.", [EN_WORDS_DIR, TNOTES_CORE_DIR]);
1179
- logger.info(`\u6B63\u5728\u63A8\u9001 ${targetDirs.length} \u4E2A\u4ED3\u5E93...`);
1180
- if (force) {
1181
- logger.warn("\u4F7F\u7528\u5F3A\u5236\u63A8\u9001\u6A21\u5F0F");
1182
- }
1183
- const results = [];
1184
- const pushCmd = force ? "pnpm tn:push --force" : "pnpm tn:push";
1185
- if (parallel) {
1186
- const promises = targetDirs.map(async (dir, index) => {
1187
- try {
1188
- await runCommand(pushCmd, dir);
1189
- process.stdout.write(`\r \u8FDB\u5EA6: ~${index + 1}/${targetDirs.length}`);
1190
- return { dir, success: true };
1191
- } catch (error) {
1192
- const errorMessage = error instanceof Error ? error.message : String(error);
1193
- return { dir, success: false, error: errorMessage };
1194
- }
1195
- });
1196
- results.push(...await Promise.all(promises));
1197
- console.log();
1198
- } else {
1199
- for (let i = 0; i < targetDirs.length; i++) {
1200
- const dir = targetDirs[i];
1201
- try {
1202
- await runCommand(pushCmd, dir);
1203
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1204
- results.push({ dir, success: true });
1205
- } catch (error) {
1206
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1207
- const errorMessage = error instanceof Error ? error.message : String(error);
1208
- results.push({ dir, success: false, error: errorMessage });
1209
- if (!continueOnError) {
1210
- console.log();
1211
- throw error;
1212
- }
1213
- }
1214
- }
1215
- console.log();
1216
- }
1217
- const successCount = results.filter((r) => r.success).length;
1218
- const failCount = results.length - successCount;
1219
- if (failCount === 0) {
1220
- logger.success(`\u63A8\u9001\u5B8C\u6210: ${successCount}/${results.length} \u4E2A\u4ED3\u5E93\u6210\u529F`);
1221
- } else {
1222
- logger.warn(
1223
- `\u63A8\u9001\u5B8C\u6210: ${successCount} \u6210\u529F, ${failCount} \u5931\u8D25 (\u5171 ${results.length} \u4E2A)`
1224
- );
1225
- console.log("\n\u5931\u8D25\u7684\u4ED3\u5E93:");
1226
- results.filter((r) => !r.success).forEach((r, index) => {
1227
- const repoName2 = r.dir.split("\\").pop() || r.dir;
1228
- console.log(` ${index + 1}. ${repoName2}`);
1229
- console.log(` \u9519\u8BEF: ${r.error}`);
1230
- });
1231
- }
1232
- }
1233
- async function pullAllRepos(options) {
1234
- const { parallel = true, continueOnError = true } = options || {};
1235
- const targetDirs = getTargetDirs(TNOTES_BASE_DIR, "TNotes.", [EN_WORDS_DIR, TNOTES_CORE_DIR]);
1236
- logger.info(`\u6B63\u5728\u62C9\u53D6 ${targetDirs.length} \u4E2A\u4ED3\u5E93...`);
1237
- const results = [];
1238
- if (parallel) {
1239
- const promises = targetDirs.map(async (dir, index) => {
1240
- try {
1241
- await runCommand("pnpm tn:pull", dir);
1242
- process.stdout.write(`\r \u8FDB\u5EA6: ~${index + 1}/${targetDirs.length}`);
1243
- return { dir, success: true };
1244
- } catch (error) {
1245
- const errorMessage = error instanceof Error ? error.message : String(error);
1246
- return { dir, success: false, error: errorMessage };
1247
- }
1248
- });
1249
- results.push(...await Promise.all(promises));
1250
- console.log();
1251
- } else {
1252
- for (let i = 0; i < targetDirs.length; i++) {
1253
- const dir = targetDirs[i];
1254
- try {
1255
- await runCommand("pnpm tn:pull", dir);
1256
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1257
- results.push({ dir, success: true });
1258
- } catch (error) {
1259
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1260
- const errorMessage = error instanceof Error ? error.message : String(error);
1261
- results.push({ dir, success: false, error: errorMessage });
1262
- if (!continueOnError) {
1263
- console.log();
1264
- throw error;
1265
- }
1266
- }
1267
- }
1268
- console.log();
1269
- }
1270
- const successCount = results.filter((r) => r.success).length;
1271
- const failCount = results.length - successCount;
1272
- if (failCount === 0) {
1273
- logger.success(`\u62C9\u53D6\u5B8C\u6210: ${successCount}/${results.length} \u4E2A\u4ED3\u5E93\u6210\u529F`);
1274
- } else {
1275
- logger.warn(
1276
- `\u62C9\u53D6\u5B8C\u6210: ${successCount} \u6210\u529F, ${failCount} \u5931\u8D25 (\u5171 ${results.length} \u4E2A)`
1277
- );
1278
- console.log("\n\u5931\u8D25\u7684\u4ED3\u5E93:");
1279
- results.filter((r) => !r.success).forEach((r, index) => {
1280
- const repoName2 = r.dir.split("\\").pop() || r.dir;
1281
- console.log(` ${index + 1}. ${repoName2}`);
1282
- console.log(` \u9519\u8BEF: ${r.error}`);
1283
- });
1284
- }
1285
- }
1286
- async function syncAllRepos(options) {
1287
- const { parallel = true, continueOnError = true } = options || {};
1288
- const targetDirs = getTargetDirs(TNOTES_BASE_DIR, "TNotes.", [EN_WORDS_DIR, TNOTES_CORE_DIR]);
1289
- logger.info(`\u6B63\u5728\u540C\u6B65 ${targetDirs.length} \u4E2A\u4ED3\u5E93...`);
1290
- const results = [];
1291
- if (parallel) {
1292
- const promises = targetDirs.map(async (dir, index) => {
1293
- try {
1294
- await runCommand("pnpm tn:sync", dir);
1295
- process.stdout.write(`\r \u8FDB\u5EA6: ~${index + 1}/${targetDirs.length}`);
1296
- return { dir, success: true };
1297
- } catch (error) {
1298
- const errorMessage = error instanceof Error ? error.message : String(error);
1299
- return { dir, success: false, error: errorMessage };
1300
- }
1301
- });
1302
- results.push(...await Promise.all(promises));
1303
- } else {
1304
- for (let i = 0; i < targetDirs.length; i++) {
1305
- const dir = targetDirs[i];
1306
- try {
1307
- await runCommand("pnpm tn:sync", dir);
1308
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1309
- results.push({ dir, success: true });
1310
- } catch (error) {
1311
- process.stdout.write(`\r \u8FDB\u5EA6: ${i + 1}/${targetDirs.length}`);
1312
- const errorMessage = error instanceof Error ? error.message : String(error);
1313
- results.push({ dir, success: false, error: errorMessage });
1314
- if (!continueOnError) {
1315
- throw error;
1316
- }
1317
- }
1318
- }
1319
- }
1320
- console.log();
1321
- const successCount = results.filter((r) => r.success).length;
1322
- const failCount = results.length - successCount;
1323
- if (failCount === 0) {
1324
- logger.success(`\u540C\u6B65\u5B8C\u6210: ${successCount}/${results.length} \u4E2A\u4ED3\u5E93\u6210\u529F`);
1325
- } else {
1326
- logger.warn(
1327
- `\u540C\u6B65\u5B8C\u6210: ${successCount} \u6210\u529F, ${failCount} \u5931\u8D25 (\u5171 ${results.length} \u4E2A)`
1328
- );
1329
- console.log("\n\u5931\u8D25\u7684\u4ED3\u5E93:");
1330
- results.filter((r) => !r.success).forEach((r, index) => {
1331
- const repoName2 = r.dir.split("\\").pop() || r.dir;
1332
- console.log(` ${index + 1}. ${repoName2}`);
1333
- console.log(` \u9519\u8BEF: ${r.error}`);
1334
- });
1335
- }
1336
- }
1337
-
1338
- // utils/validators.ts
1339
- var INVALID_FILENAME_CHARS = /[<>:"/\\|?*\x00-\x1F]/;
1340
- var WINDOWS_RESERVED_NAMES = /* @__PURE__ */ new Set([
1341
- "CON",
1342
- "PRN",
1343
- "AUX",
1344
- "NUL",
1345
- "COM1",
1346
- "COM2",
1347
- "COM3",
1348
- "COM4",
1349
- "COM5",
1350
- "COM6",
1351
- "COM7",
1352
- "COM8",
1353
- "COM9",
1354
- "LPT1",
1355
- "LPT2",
1356
- "LPT3",
1357
- "LPT4",
1358
- "LPT5",
1359
- "LPT6",
1360
- "LPT7",
1361
- "LPT8",
1362
- "LPT9"
1363
- ]);
1364
- function validateNoteTitle(title) {
1365
- if (!title || title.trim().length === 0) {
1366
- return { valid: false, error: "\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A" };
1367
- }
1368
- const trimmedTitle = title.trim();
1369
- if (trimmedTitle.length > 200) {
1370
- return { valid: false, error: "\u6807\u9898\u8FC7\u957F(\u6700\u591A200\u4E2A\u5B57\u7B26)" };
1371
- }
1372
- if (INVALID_FILENAME_CHARS.test(trimmedTitle)) {
1373
- return {
1374
- valid: false,
1375
- error: '\u6807\u9898\u5305\u542B\u975E\u6CD5\u5B57\u7B26(\u4E0D\u5141\u8BB8: < > : " / \\ | ? *)'
1376
- };
1377
- }
1378
- if (/^[.\s]|[.\s]$/.test(trimmedTitle)) {
1379
- return {
1380
- valid: false,
1381
- error: "\u6807\u9898\u4E0D\u80FD\u4EE5\u70B9\u6216\u7A7A\u683C\u5F00\u5934/\u7ED3\u5C3E"
1382
- };
1383
- }
1384
- const upperTitle = trimmedTitle.toUpperCase();
1385
- if (WINDOWS_RESERVED_NAMES.has(upperTitle)) {
1386
- return {
1387
- valid: false,
1388
- error: `"${trimmedTitle}" \u662F Windows \u7CFB\u7EDF\u4FDD\u7559\u540D\u79F0`
1389
- };
1390
- }
1391
- const baseName = trimmedTitle.split(".")[0].toUpperCase();
1392
- if (WINDOWS_RESERVED_NAMES.has(baseName)) {
1393
- return {
1394
- valid: false,
1395
- error: `"${trimmedTitle}" \u5305\u542B Windows \u7CFB\u7EDF\u4FDD\u7559\u540D\u79F0`
1396
- };
1397
- }
1398
- return { valid: true };
1399
- }
1400
-
1401
- // config/ConfigManager.ts
1402
- var ConfigManager = class _ConfigManager {
1403
- constructor(rootPath2) {
1404
- this.config = null;
1405
- if (rootPath2) {
1406
- this.rootPath = rootPath2;
1407
- } else {
1408
- const __dirname = path2.dirname(fileURLToPath(import.meta.url));
1409
- this.rootPath = path2.resolve(__dirname, "..", "..", "..");
1410
- }
1411
- this.configPath = path2.normalize(
1412
- path2.resolve(this.rootPath, ".tnotes.json")
1413
- );
1414
- }
1415
- /**
1416
- * 使用指定的根路径初始化配置管理器
1417
- *
1418
- * 必须在 getInstance() 之前调用(如果需要指定 rootPath)。
1419
- * 如果已经初始化,则忽略重复调用。
1420
- */
1421
- static init({ rootPath: rootPath2 }) {
1422
- if (!_ConfigManager.instance) {
1423
- _ConfigManager.instance = new _ConfigManager(rootPath2);
1424
- }
1425
- return _ConfigManager.instance;
1426
- }
1427
- /**
1428
- * 获取单例实例
1429
- */
1430
- static getInstance() {
1431
- if (!_ConfigManager.instance) {
1432
- _ConfigManager.instance = new _ConfigManager();
1433
- }
1434
- return _ConfigManager.instance;
1435
- }
1436
- /**
1437
- * 加载配置文件
1438
- */
1439
- loadConfig(configPath) {
1440
- if (this.config) return this.config;
1441
- const path3 = configPath || this.configPath;
1442
- if (!fs2.existsSync(path3)) {
1443
- throw new Error(`\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${path3}`);
1444
- }
1445
- const configContent = fs2.readFileSync(path3, "utf-8");
1446
- const rawConfig = JSON.parse(configContent);
1447
- const { config: validatedConfig, modified } = validateAndCompleteConfig(rawConfig);
1448
- if (modified) {
1449
- logger.warn(`\u68C0\u6D4B\u5230\u77E5\u8BC6\u5E93\u914D\u7F6E\u7F3A\u5931\u5B57\u6BB5\uFF0C\u5DF2\u81EA\u52A8\u8865\u5168`);
1450
- fs2.writeFileSync(path3, JSON.stringify(validatedConfig, null, 2), "utf-8");
1451
- logger.info("\u77E5\u8BC6\u5E93\u914D\u7F6E\u6587\u4EF6\u5DF2\u66F4\u65B0");
1452
- logger.info(`\u77E5\u8BC6\u5E93\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${path3}`);
1453
- }
1454
- this.config = validatedConfig;
1455
- return this.config;
1456
- }
1457
- /**
1458
- * 获取配置项
1459
- */
1460
- get(key) {
1461
- if (!this.config) {
1462
- this.loadConfig();
1463
- }
1464
- return this.config[key];
1465
- }
1466
- /**
1467
- * 获取所有配置
1468
- */
1469
- getAll() {
1470
- if (!this.config) {
1471
- this.loadConfig();
1472
- }
1473
- return this.config;
1474
- }
1475
- /**
1476
- * 获取项目根路径
1477
- */
1478
- getRootPath() {
1479
- return this.rootPath;
1480
- }
1481
- /**
1482
- * @deprecated 使用 getRootPath() 替代
1483
- */
1484
- getDirname() {
1485
- return path2.resolve(this.rootPath, ".vitepress", "tnotes", "config");
1486
- }
1487
- };
1488
- function getConfigManager() {
1489
- return ConfigManager.getInstance();
1490
- }
1491
-
1492
- export {
1493
- handleError,
1494
- createError,
1495
- ensureDirectory,
1496
- genHierarchicalSidebar,
1497
- getChangedIds,
1498
- getTargetDirs,
1499
- Logger,
1500
- logger,
1501
- createLogger,
1502
- createAddNumberToTitle,
1503
- generateToc,
1504
- parseArgs,
1505
- parseReadmeCompletedNotes,
1506
- isPortInUse,
1507
- killPortProcess,
1508
- waitForPort,
1509
- ConfigManager,
1510
- getConfigManager,
1511
- TNOTES_BASE_DIR,
1512
- TNOTES_CORE_DIR,
1513
- EN_WORDS_DIR,
1514
- ROOT_DIR_PATH,
1515
- ROOT_README_PATH,
1516
- ROOT_CONFIG_PATH,
1517
- NOTES_DIR_PATH,
1518
- VP_SIDEBAR_PATH,
1519
- EOL,
1520
- CONSTANTS,
1521
- NOTES_PATH,
1522
- REPO_NOTES_URL,
1523
- NoteManager,
1524
- parseNoteLine,
1525
- buildNoteLineMarkdown,
1526
- processEmptyLines,
1527
- runCommand,
1528
- pushAllRepos,
1529
- pullAllRepos,
1530
- syncAllRepos,
1531
- validateNoteTitle
1532
- };