czon 0.6.7 → 0.6.9

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.
@@ -60,6 +60,10 @@ async function applyConfig(options) {
60
60
  if (options.baseUrl !== undefined) {
61
61
  metadata_1.MetaData.options.baseUrl = options.baseUrl;
62
62
  }
63
+ if (options.siteTitle !== undefined) {
64
+ console.log(`🏷️ Site title: ${options.siteTitle}`);
65
+ metadata_1.MetaData.options.siteTitle = options.siteTitle;
66
+ }
63
67
  }
64
68
  /**
65
69
  * 构建管道(函数组合)
package/dist/cli.js CHANGED
@@ -169,12 +169,16 @@ class BuildCommand extends clipanion_1.Command {
169
169
  this.baseUrl = clipanion_1.Option.String('--baseUrl', {
170
170
  description: 'Base URL for sitemap generation (e.g., https://yoursite.com). If not provided, sitemap will not be generated.',
171
171
  });
172
+ this.siteTitle = clipanion_1.Option.String('--siteTitle', {
173
+ description: 'Site title to replace "CZON" in the header (e.g., "My Docs")',
174
+ });
172
175
  }
173
176
  async execute() {
174
177
  try {
175
178
  await (0, pipeline_1.buildSite)({
176
179
  langs: this.lang,
177
180
  baseUrl: this.baseUrl,
181
+ siteTitle: this.siteTitle,
178
182
  });
179
183
  return 0;
180
184
  }
@@ -191,10 +195,11 @@ BuildCommand.usage = clipanion_1.Command.Usage({
191
195
  This command builds a documentation site from Markdown files in the current directory.
192
196
  The output will be placed in the .czon/dist directory.
193
197
 
194
- Examples:
195
- $ czon build
196
- $ czon build --lang en-US --lang ja-JP (translate to English and Japanese)
197
- $ czon build --baseUrl https://yoursite.com (generate sitemap.xml)
198
+ Examples:
199
+ $ czon build
200
+ $ czon build --lang en-US --lang ja-JP (translate to English and Japanese)
201
+ $ czon build --baseUrl https://yoursite.com (generate sitemap.xml)
202
+ $ czon build --siteTitle "My Docs" (replace "CZON" in header)
198
203
  `,
199
204
  });
200
205
  // 创建 CLI 应用
@@ -32,9 +32,13 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.downloadCDNResources = exports.spiderStaticSiteGenerator = void 0;
37
40
  const fs = __importStar(require("fs/promises"));
41
+ const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
38
42
  const path = __importStar(require("path"));
39
43
  const sitemap_1 = require("../build/sitemap");
40
44
  const metadata_1 = require("../metadata");
@@ -43,12 +47,31 @@ const ssg_1 = require("../ssg");
43
47
  const resourceMap_1 = require("../ssg/resourceMap");
44
48
  const convertMarkdownToHtml_1 = require("../utils/convertMarkdownToHtml");
45
49
  const frontmatter_1 = require("../utils/frontmatter");
50
+ const isExists_1 = require("../utils/isExists");
46
51
  const writeFile_1 = require("../utils/writeFile");
52
+ const copyFavicon = async () => {
53
+ const faviconSource = path.join(paths_1.CZON_DIR, 'icons', 'favicon.ico');
54
+ const faviconTarget = path.join(paths_1.CZON_DIST_DIR, 'favicon.ico');
55
+ // 如果存在 .czon/icons/favicon.ico,则复制到输出目录
56
+ if (await (0, isExists_1.isExists)(faviconSource)) {
57
+ await fs.mkdir(path.dirname(faviconTarget), { recursive: true });
58
+ await fs.copyFile(faviconSource, faviconTarget);
59
+ console.info(`📄 Copied favicon from ${faviconSource} to: ${faviconTarget}`);
60
+ return;
61
+ }
62
+ // 否则,使用 CZON 源码目录下的默认图标
63
+ const defaultFaviconSource = path.join(__dirname, '../../templates/favicon.ico');
64
+ await fs.mkdir(path.dirname(faviconTarget), { recursive: true });
65
+ await fs.copyFile(defaultFaviconSource, faviconTarget);
66
+ console.info(`📄 Copied default favicon to: ${faviconTarget}`);
67
+ };
47
68
  /**
48
69
  * 使用简单的爬虫抓取生成的站点页面
49
70
  */
50
71
  const spiderStaticSiteGenerator = async () => {
51
72
  (0, sitemap_1.clearSitemapCollection)();
73
+ // 复制 favicon 图标
74
+ await copyFavicon();
52
75
  const queue = ['/index.html', '/404.html'];
53
76
  // 将每个语言的首页加入队列
54
77
  for (const lang of metadata_1.MetaData.options.langs || []) {
@@ -114,6 +137,8 @@ const spiderStaticSiteGenerator = async () => {
114
137
  const resolvedPath = path.resolve('/', path.dirname(currentPath), link);
115
138
  if (resolvedPath.startsWith('/__raw__/'))
116
139
  continue; // 跳过原始内容目录
140
+ if (resolvedPath === '/favicon.ico')
141
+ continue; // 跳过 favicon.ico
117
142
  console.info(` ➕ Found link: ${link} -> ${resolvedPath} (${isVisited.has(resolvedPath) ? 'visited' : 'new'})`);
118
143
  if (!isVisited.has(resolvedPath)) {
119
144
  queue.push(resolvedPath);
@@ -126,7 +151,7 @@ const downloadCDNResources = async () => {
126
151
  for (const resource of resourceMap_1.EXTERNAL_RESOURCES) {
127
152
  const targetFilePath = path.join(paths_1.CZON_DIST_DIR, 'assets', resource.name);
128
153
  console.info(`⬇️ Downloading resource: ${resource.url} -> ${targetFilePath}`);
129
- const response = await fetch(resource.url);
154
+ const response = await (0, make_fetch_happen_1.default)(resource.url);
130
155
  if (!response.ok) {
131
156
  console.error(`❌ Failed to download resource: ${resource.url}, status: ${response.status}`);
132
157
  throw new Error(`Failed to download resource: ${resource.url}`);
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.completeMessages = void 0;
7
+ // 使用 make-fetch-happen 以支持代理选项 (支持环境变量 HTTP_PROXY, HTTPS_PROXY, PROXY, NO_PROXY)
8
+ const make_fetch_happen_1 = __importDefault(require("make-fetch-happen"));
4
9
  const startTime = Date.now();
5
10
  let totalContentGenerated = 0;
6
11
  const processingTaskIds = new Set();
@@ -70,7 +75,7 @@ const completeMessages = async (messages, options) => {
70
75
  // for (const msg of messages) {
71
76
  // console.info(`💬 [${msg.role}] ${msg.content}`);
72
77
  // }
73
- const response = await fetch(`${baseUrl}/chat/completions`, {
78
+ const response = await (0, make_fetch_happen_1.default)(`${baseUrl}/chat/completions`, {
74
79
  method: 'POST',
75
80
  headers: {
76
81
  'Content-Type': 'application/json',
@@ -83,17 +88,10 @@ const completeMessages = async (messages, options) => {
83
88
  throw new Error(`OpenAI API error (${response.status}): ${errorText}`);
84
89
  }
85
90
  // 处理流式响应
86
- const reader = response.body?.getReader();
87
- if (!reader) {
88
- throw new Error('No response body reader available');
89
- }
90
91
  const decoder = new TextDecoder();
91
92
  let buffer = '';
92
93
  let content = '';
93
- while (true) {
94
- const { done, value } = await reader.read();
95
- if (done)
96
- break;
94
+ const handleData = (value) => {
97
95
  buffer += decoder.decode(value, { stream: true });
98
96
  const lines = buffer.split('\n');
99
97
  buffer = lines.pop() || ''; // 保留未完成的行
@@ -134,7 +132,13 @@ const completeMessages = async (messages, options) => {
134
132
  }
135
133
  }
136
134
  }
137
- }
135
+ };
136
+ //
137
+ await new Promise((resolve, reject) => {
138
+ response.body.on('data', handleData);
139
+ response.body.on('end', () => resolve());
140
+ response.body.on('error', err => reject(err));
141
+ });
138
142
  // 确保所有剩余数据被解码
139
143
  if (buffer) {
140
144
  buffer += decoder.decode();
@@ -25,11 +25,13 @@ const ContentPage = props => {
25
25
  // 查找指向当前文章的其他文章
26
26
  const thisPath = (0, node_path_1.resolve)('/', props.file.path);
27
27
  const referencedFiles = props.ctx.site.files.filter(f => f.links.some(link => (0, node_path_1.resolve)('/', (0, node_path_1.dirname)(f.path), link) === thisPath));
28
+ const faviconUrl = (0, resourceMap_1.getFaviconUrlFrom)(props.ctx.path);
28
29
  return (react_1.default.createElement("html", { lang: props.lang },
29
30
  react_1.default.createElement("head", null,
30
31
  react_1.default.createElement("meta", { charSet: "UTF-8" }),
31
32
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
32
33
  react_1.default.createElement("title", null, title),
34
+ react_1.default.createElement("link", { rel: "icon", href: faviconUrl, type: "image/x-icon" }),
33
35
  react_1.default.createElement("meta", { name: "description", content: `tags: ${tags.join(', ')}` }),
34
36
  react_1.default.createElement("script", { src: (0, resourceMap_1.getResourceUrlFrom)(props.ctx.path, 'tailwindcss.js') }),
35
37
  react_1.default.createElement("style", null, style_1.style),
@@ -21,10 +21,12 @@ const IndexPage = props => {
21
21
  [x => x.metadata?.inferred_date || '', 'desc'],
22
22
  ]);
23
23
  const allCategories = Array.from(new Set([undefined].concat(props.ctx.site.files.map(f => f.category))));
24
+ const faviconUrl = (0, resourceMap_1.getFaviconUrlFrom)(props.ctx.path);
24
25
  return (react_1.default.createElement("html", null,
25
26
  react_1.default.createElement("head", null,
26
27
  react_1.default.createElement("meta", { charSet: "UTF-8" }),
27
28
  react_1.default.createElement("title", null, `Index of ${props.lang.toString()}`),
29
+ react_1.default.createElement("link", { rel: "icon", href: faviconUrl, type: "image/x-icon" }),
28
30
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
29
31
  react_1.default.createElement("meta", { name: "description", content: `Index page for language ${props.lang}` }),
30
32
  react_1.default.createElement("script", { src: (0, resourceMap_1.getResourceUrlFrom)(props.ctx.path, 'tailwindcss.js') }),
@@ -6,8 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.RedirectPage = void 0;
7
7
  const node_path_1 = require("node:path");
8
8
  const react_1 = __importDefault(require("react"));
9
+ const resourceMap_1 = require("./resourceMap");
9
10
  const RedirectPage = props => {
10
11
  const toURL = (0, node_path_1.relative)((0, node_path_1.dirname)(props.from), props.to);
12
+ const faviconUrl = (0, resourceMap_1.getFaviconUrlFrom)(props.from);
11
13
  return (react_1.default.createElement("html", { lang: "en" },
12
14
  react_1.default.createElement("head", null,
13
15
  react_1.default.createElement("meta", { charSet: "UTF-8" }),
@@ -15,6 +17,7 @@ const RedirectPage = props => {
15
17
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
16
18
  react_1.default.createElement("meta", { httpEquiv: "refresh", content: `0; url=${toURL}` }),
17
19
  react_1.default.createElement("title", null, "Redirecting..."),
20
+ react_1.default.createElement("link", { rel: "icon", href: faviconUrl, type: "image/x-icon" }),
18
21
  react_1.default.createElement("script", { dangerouslySetInnerHTML: {
19
22
  __html: `
20
23
  (function() {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.RootPage = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const languages_1 = require("../languages");
9
+ const resourceMap_1 = require("./resourceMap");
9
10
  // /index.html 的根页面
10
11
  // 需要实现多语言选择,自动重定向到用户浏览器语言对应的首页
11
12
  // 需要对 SEO 友好,提供适当的 meta 标签
@@ -18,11 +19,13 @@ const RootPage = props => {
18
19
  mapUserLangToSupported[langPrefix] = lang;
19
20
  }
20
21
  }
22
+ const faviconUrl = (0, resourceMap_1.getFaviconUrlFrom)(props.ctx.path);
21
23
  return (react_1.default.createElement("html", { lang: "en" },
22
24
  react_1.default.createElement("head", null,
23
25
  react_1.default.createElement("meta", { charSet: "UTF-8" }),
24
26
  react_1.default.createElement("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
25
27
  react_1.default.createElement("title", null, "CZON Multilingual Site Navigator"),
28
+ react_1.default.createElement("link", { rel: "icon", href: faviconUrl, type: "image/x-icon" }),
26
29
  react_1.default.createElement("meta", { name: "description", content: "Select your preferred language to explore our content." }),
27
30
  props.ctx.site.options.langs.map(lang => (react_1.default.createElement("link", { key: lang, rel: "alternate", hrefLang: lang, href: `${lang}/index.html` }))),
28
31
  react_1.default.createElement("link", { rel: "alternate", hrefLang: "x-default", href: `${props.ctx.site.options.langs[0]}/index.html` }),
@@ -10,7 +10,7 @@ const LanguageSwitch_1 = require("./LanguageSwitch");
10
10
  const CZONHeader = props => {
11
11
  return (react_1.default.createElement("header", { className: "czon-header py-4 border-b flex justify-between items-center px-6" },
12
12
  react_1.default.createElement("h1", { className: "text-2xl font-bold" },
13
- react_1.default.createElement("a", { href: "index.html" }, "CZON")),
13
+ react_1.default.createElement("a", { href: "index.html" }, props.ctx.site.options.siteTitle ?? 'CZON')),
14
14
  react_1.default.createElement("div", { className: "flex items-center gap-4" },
15
15
  react_1.default.createElement(DarkModeSwitch_1.DarkModeSwitch, null),
16
16
  props.lang && react_1.default.createElement(LanguageSwitch_1.LanguageSwitch, { ctx: props.ctx, lang: props.lang, file: props.file }))));
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getResourceUrlFrom = exports.EXTERNAL_RESOURCES = void 0;
3
+ exports.getFaviconUrlFrom = exports.getResourceUrlFrom = exports.EXTERNAL_RESOURCES = void 0;
4
4
  const path_1 = require("path");
5
5
  exports.EXTERNAL_RESOURCES = [
6
6
  {
@@ -20,4 +20,12 @@ const getResourceUrlFrom = (path, name) => {
20
20
  return (0, path_1.relative)((0, path_1.dirname)(path), `/assets/${resource.name}`);
21
21
  };
22
22
  exports.getResourceUrlFrom = getResourceUrlFrom;
23
+ /**
24
+ * 获取 favicon 的相对引用 URL
25
+ * @param path - 当前文件路径 (e.g. `/en-US/index.html`)
26
+ */
27
+ const getFaviconUrlFrom = (path) => {
28
+ return (0, path_1.relative)((0, path_1.dirname)(path), '/favicon.ico');
29
+ };
30
+ exports.getFaviconUrlFrom = getFaviconUrlFrom;
23
31
  //# sourceMappingURL=resourceMap.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "CZone - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,6 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@types/chokidar": "^1.7.5",
44
44
  "@types/express": "^5.0.6",
45
+ "@types/make-fetch-happen": "^10.0.4",
45
46
  "@types/node": "^25.0.3",
46
47
  "@types/yaml": "^1.9.6",
47
48
  "husky": "^9.1.7",
@@ -59,6 +60,7 @@
59
60
  "dotenv": "^16.4.7",
60
61
  "express": "^4.21.2",
61
62
  "highlight.js": "^11.11.1",
63
+ "make-fetch-happen": "^15.0.3",
62
64
  "marked": "^17.0.1",
63
65
  "marked-footnote": "^1.4.0",
64
66
  "marked-katex-extension": "^5.1.6",
Binary file