@mindbase/mindbase 1.0.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.
Files changed (45) hide show
  1. package/LICENSE +201 -0
  2. package/bin/gitlog.ts +3 -0
  3. package/bin/iconfont-editor.ts +3 -0
  4. package/bin/index.ts +29 -0
  5. package/bin/nodeclear.ts +3 -0
  6. package/bin/npmpublish.ts +3 -0
  7. package/bin/shared.ts +141 -0
  8. package/package.json +63 -0
  9. package/scripts/ensure-tsx.cjs +18 -0
  10. package/src/clear/app.ts +152 -0
  11. package/src/clear/config.ts +108 -0
  12. package/src/clear/file-cleaner.ts +137 -0
  13. package/src/clear/index.ts +28 -0
  14. package/src/clear/scanner.ts +154 -0
  15. package/src/gitlog/app.ts +272 -0
  16. package/src/gitlog/config.ts +91 -0
  17. package/src/gitlog/display.ts +178 -0
  18. package/src/gitlog/index.ts +5 -0
  19. package/src/gitlog/log-fetcher.ts +150 -0
  20. package/src/gitlog/pager.ts +178 -0
  21. package/src/gitlog/scanner.ts +60 -0
  22. package/src/iconfont/frontend/index.html +12 -0
  23. package/src/iconfont/frontend/src/App.vue +14 -0
  24. package/src/iconfont/frontend/src/main.ts +8 -0
  25. package/src/iconfont/frontend/src/views/IconEditor.vue +819 -0
  26. package/src/iconfont/frontend/vite.config.ts +15 -0
  27. package/src/iconfont/lib/css-generator.js +37 -0
  28. package/src/iconfont/lib/font-builder.js +82 -0
  29. package/src/iconfont/lib/glyph-extractor.js +69 -0
  30. package/src/iconfont/server/api.js +256 -0
  31. package/src/iconfont/server/index.js +64 -0
  32. package/src/index.ts +0 -0
  33. package/src/publish/app.ts +316 -0
  34. package/src/publish/builder.ts +120 -0
  35. package/src/publish/dependency.ts +144 -0
  36. package/src/publish/detector.ts +93 -0
  37. package/src/publish/index.ts +66 -0
  38. package/src/publish/npm-query.ts +244 -0
  39. package/src/publish/registry/adapters/npm.ts +128 -0
  40. package/src/publish/registry/registry-manager.ts +107 -0
  41. package/src/publish/scanner.ts +90 -0
  42. package/src/publish/types.ts +98 -0
  43. package/src/publish/version.ts +108 -0
  44. package/src/shared/config-manager.ts +57 -0
  45. package/src/shared/index.ts +1 -0
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "vite";
2
+ import vue from "@vitejs/plugin-vue";
3
+ import { resolve, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+
8
+ export default defineConfig({
9
+ plugins: [vue()],
10
+ root: __dirname,
11
+ build: {
12
+ outDir: resolve(__dirname, "../dist"),
13
+ emptyOutDir: true,
14
+ },
15
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * CSS 生成模块
3
+ * 根据 iconfont.json 生成对应的 CSS 文件
4
+ */
5
+
6
+ /**
7
+ * 生成 iconfont.css 内容
8
+ * @param {object} json - iconfont.json 数据
9
+ * @returns {string} CSS 内容
10
+ */
11
+ export function buildCSS(json) {
12
+ const t = Date.now();
13
+ const fontFamily = json.font_family || "iconfont";
14
+ const prefix = json.css_prefix_text || "icon-";
15
+
16
+ let css = `@font-face {\n`;
17
+ css += ` font-family: "${fontFamily}";\n`;
18
+ css += ` src: url('iconfont.woff2?t=${t}') format('woff2'),\n`;
19
+ css += ` url('iconfont.woff?t=${t}') format('woff'),\n`;
20
+ css += ` url('iconfont.ttf?t=${t}') format('truetype');\n`;
21
+ css += `}\n\n`;
22
+ css += `.${fontFamily} {\n`;
23
+ css += ` font-family: "${fontFamily}" !important;\n`;
24
+ css += ` font-size: 16px;\n`;
25
+ css += ` font-style: normal;\n`;
26
+ css += ` -webkit-font-smoothing: antialiased;\n`;
27
+ css += ` -moz-osx-font-smoothing: grayscale;\n`;
28
+ css += `}\n\n`;
29
+
30
+ for (const g of json.glyphs) {
31
+ css += `.${prefix}${g.font_class}:before {\n`;
32
+ css += ` content: "\\${g.unicode}";\n`;
33
+ css += `}\n\n`;
34
+ }
35
+
36
+ return css;
37
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 字体构建模块
3
+ * 从 SVG 文件集合构建完整的 iconfont 字体(TTF/WOFF/WOFF2)
4
+ */
5
+
6
+ import { Readable } from "stream";
7
+ import svgicons2svgfont from "svgicons2svgfont";
8
+ import svg2ttf from "svg2ttf";
9
+ import subsetFont from "subset-font";
10
+
11
+ /**
12
+ * 将 SVG 集合构建为 SVG font,再转为 TTF Buffer
13
+ * @param {Array<{unicode: string, svg: string, name: string}>} icons - 图标集合
14
+ * @param {object} options - 配置
15
+ * @param {string} options.fontFamily - 字体族名
16
+ * @param {string} options.cssPrefix - CSS 前缀
17
+ * @returns {Promise<Buffer>} TTF Buffer
18
+ */
19
+ export async function buildTtfFromIcons(icons, options) {
20
+ const fontFamily = options.fontFamily || "iconfont";
21
+
22
+ // 1. SVG → SVG font
23
+ const svgFont = await buildSvgFont(icons, fontFamily);
24
+
25
+ // 2. SVG font → TTF
26
+ const ttf = svg2ttf(svgFont, {});
27
+ return Buffer.from(ttf.buffer);
28
+ }
29
+
30
+ /**
31
+ * 从 TTF Buffer 生成 WOFF 和 WOFF2
32
+ * @param {Buffer} ttfBuffer - TTF Buffer
33
+ * @param {Array} glyphs - glyphs 数组(用于构建保留字符)
34
+ * @returns {Promise<{woff: Buffer, woff2: Buffer}>}
35
+ */
36
+ export async function buildWebFonts(ttfBuffer, glyphs) {
37
+ // 构建保留字符文本
38
+ const keepChars = glyphs
39
+ .map((g) => String.fromCodePoint(parseInt(g.unicode, 16)))
40
+ .join("");
41
+
42
+ const [woff, woff2] = await Promise.all([
43
+ subsetFont(ttfBuffer, keepChars, { targetFormat: "woff" }),
44
+ subsetFont(ttfBuffer, keepChars, { targetFormat: "woff2" }),
45
+ ]);
46
+
47
+ return {
48
+ woff: Buffer.from(woff),
49
+ woff2: Buffer.from(woff2),
50
+ };
51
+ }
52
+
53
+ /**
54
+ * 用 svgicons2svgfont 将 SVG 集合转为 SVG font
55
+ */
56
+ function buildSvgFont(icons, fontFamily) {
57
+ return new Promise((resolve, reject) => {
58
+ let output = "";
59
+
60
+ const stream = svgicons2svgfont({
61
+ fontName: fontFamily,
62
+ fontHeight: 1000,
63
+ normalize: true,
64
+ log: () => {},
65
+ });
66
+
67
+ stream.on("data", (chunk) => (output += chunk));
68
+ stream.on("end", () => resolve(output));
69
+ stream.on("error", reject);
70
+
71
+ for (const icon of icons) {
72
+ const glyph = Readable.from([icon.svg]);
73
+ glyph.metadata = {
74
+ unicode: [String.fromCodePoint(parseInt(icon.unicode, 16))],
75
+ name: icon.name || icon.unicode,
76
+ };
77
+ stream.write(glyph);
78
+ }
79
+
80
+ stream.end();
81
+ });
82
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 字形提取模块
3
+ * 从 TTF 字体中提取单个字形路径,转为 SVG
4
+ */
5
+
6
+ import opentype from "opentype.js";
7
+
8
+ /**
9
+ * 从 TTF Buffer 中提取指定 unicode 的字形,返回 SVG 字符串
10
+ * @param {Buffer} ttfBuffer - TTF 字体文件 Buffer
11
+ * @param {string} unicode - 十六进制 unicode(如 "e9e3")
12
+ * @returns {string} SVG 内容
13
+ */
14
+ export function extractGlyphToSvg(ttfBuffer, unicode) {
15
+ const font = opentype.parse(ttfBuffer.buffer);
16
+ const codePoint = parseInt(unicode, 16);
17
+ const glyph = font.glyphs.glyphs.find(
18
+ (g) => g.unicode === codePoint
19
+ );
20
+
21
+ if (!glyph) {
22
+ throw new Error(`未找到 unicode: ${unicode} 的字形`);
23
+ }
24
+
25
+ // 获取字形路径,使用 font 的 unitsPerEm 作为 viewBox
26
+ const unitsPerEm = font.unitsPerEm;
27
+ const ascender = font.ascender;
28
+ const path = glyph.getPath(0, ascender, unitsPerEm);
29
+
30
+ return [
31
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${unitsPerEm} ${unitsPerEm}">`,
32
+ ` <path d="${path.toPathData()}"/>`,
33
+ `</svg>`,
34
+ ].join("\n");
35
+ }
36
+
37
+ /**
38
+ * 从 TTF Buffer 中提取所有字形,每个转为 SVG 字符串
39
+ * @param {Buffer} ttfBuffer - TTF 字体文件 Buffer
40
+ * @param {Array} glyphs - iconfont.json 中的 glyphs 数组
41
+ * @returns {Map<string, string>} unicode → SVG 内容的映射
42
+ */
43
+ export function extractAllGlyphsToSvg(ttfBuffer, glyphs) {
44
+ const font = opentype.parse(ttfBuffer.buffer);
45
+ const result = new Map();
46
+
47
+ for (const g of glyphs) {
48
+ const codePoint = parseInt(g.unicode, 16);
49
+ const glyph = font.glyphs.glyphs.find(
50
+ (ft) => ft.unicode === codePoint
51
+ );
52
+
53
+ if (!glyph) continue;
54
+
55
+ const unitsPerEm = font.unitsPerEm;
56
+ const ascender = font.ascender;
57
+ const path = glyph.getPath(0, ascender, unitsPerEm);
58
+
59
+ const svg = [
60
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${unitsPerEm} ${unitsPerEm}">`,
61
+ ` <path d="${path.toPathData()}"/>`,
62
+ `</svg>`,
63
+ ].join("\n");
64
+
65
+ result.set(g.unicode, svg);
66
+ }
67
+
68
+ return result;
69
+ }
@@ -0,0 +1,256 @@
1
+ /**
2
+ * API 路由
3
+ * 处理 iconfont 读写、SVG 上传、字体构建等请求
4
+ */
5
+
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+ import express from "express";
9
+ import { optimize } from "svgo";
10
+ import { buildCSS } from "../lib/css-generator.js";
11
+ import { extractGlyphToSvg, extractAllGlyphsToSvg } from "../lib/glyph-extractor.js";
12
+ import { buildTtfFromIcons, buildWebFonts } from "../lib/font-builder.js";
13
+
14
+ /**
15
+ * 创建 API 路由
16
+ * @param {string} iconfontDir - iconfont 目录路径
17
+ * @returns {express.Router}
18
+ */
19
+ export async function createApiRouter(iconfontDir) {
20
+ const router = express.Router();
21
+
22
+ // 文件路径辅助
23
+ const filePath = (name) => path.join(iconfontDir, name);
24
+
25
+ // GET /api/read - 读取 iconfont.json
26
+ router.get("/read", async (req, res) => {
27
+ try {
28
+ const content = await fs.readFile(filePath("iconfont.json"), "utf-8");
29
+ res.json(JSON.parse(content));
30
+ } catch (err) {
31
+ res.status(500).json({ error: `读取 iconfont.json 失败: ${err.message}` });
32
+ }
33
+ });
34
+
35
+ // GET /api/font - 读取字体文件,支持 ?format=woff2|woff|ttf(保留用于动态加载)
36
+ router.get("/font", async (req, res) => {
37
+ try {
38
+ const format = req.query.format;
39
+ const searchOrder = format ? [format] : ["woff2", "woff", "ttf"];
40
+ const mimeMap = {
41
+ woff2: "font/woff2",
42
+ woff: "font/woff",
43
+ ttf: "font/ttf",
44
+ };
45
+
46
+ for (const ext of searchOrder) {
47
+ const p = filePath(`iconfont.${ext}`);
48
+ try {
49
+ const buf = await fs.readFile(p);
50
+ res.setHeader("Content-Type", mimeMap[ext] || "application/octet-stream");
51
+ res.setHeader("Cache-Control", "no-cache");
52
+ res.send(buf);
53
+ return;
54
+ } catch {
55
+ if (format) {
56
+ // 指定了格式但找不到,返回 404
57
+ return res.status(404).json({ error: `未找到 iconfont.${ext}` });
58
+ }
59
+ continue;
60
+ }
61
+ }
62
+ res.status(404).json({ error: "未找到字体文件" });
63
+ } catch (err) {
64
+ res.status(500).json({ error: err.message });
65
+ }
66
+ });
67
+
68
+ // POST /api/save - 保存配置
69
+ router.post("/save", async (req, res) => {
70
+ try {
71
+ const { json: iconJson, generateFont } = req.body;
72
+
73
+ // 保存 iconfont.json
74
+ await fs.writeFile(
75
+ filePath("iconfont.json"),
76
+ JSON.stringify(iconJson, null, 2),
77
+ "utf-8"
78
+ );
79
+
80
+ // 生成并保存 CSS
81
+ await fs.writeFile(
82
+ filePath("iconfont.css"),
83
+ buildCSS(iconJson),
84
+ "utf-8"
85
+ );
86
+
87
+ // 重建字体文件
88
+ if (generateFont) {
89
+ await rebuildFont(iconfontDir, iconJson);
90
+ }
91
+
92
+ res.json({ success: true });
93
+ } catch (err) {
94
+ console.error("保存失败:", err);
95
+ res.status(500).json({ error: err.message });
96
+ }
97
+ });
98
+
99
+ // POST /api/upload-svg - 上传 SVG 添加新图标
100
+ router.post("/upload-svg", async (req, res) => {
101
+ try {
102
+ const { svg: rawSvg, name, fontClass } = req.body;
103
+
104
+ if (!rawSvg || !rawSvg.includes("<svg")) {
105
+ return res.status(400).json({ error: "无效的 SVG 内容" });
106
+ }
107
+
108
+ // svgo 优化
109
+ const result = optimize(rawSvg, {
110
+ plugins: [
111
+ "removeDoctype",
112
+ "removeXMLProcInst",
113
+ "removeComments",
114
+ "removeMetadata",
115
+ "removeEditorsNSData",
116
+ "cleanupAttrs",
117
+ "removeStyleElement",
118
+ "removeEmptyContainers",
119
+ "convertShapeToPath",
120
+ ],
121
+ });
122
+ const svg = result.data;
123
+
124
+ // 读取当前 iconfont.json
125
+ const currentJson = JSON.parse(
126
+ await fs.readFile(filePath("iconfont.json"), "utf-8")
127
+ );
128
+
129
+ // 分配 unicode
130
+ const unicode = nextUnicode(currentJson.glyphs);
131
+ const iconId = `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
132
+
133
+ // 添加新图标到 glyphs
134
+ const newGlyph = {
135
+ icon_id: iconId,
136
+ name: name || fontClass || "new-icon",
137
+ font_class: fontClass || `icon-${unicode}`,
138
+ unicode,
139
+ unicode_decimal: parseInt(unicode, 16),
140
+ };
141
+
142
+ currentJson.glyphs.push(newGlyph);
143
+
144
+ // 读取当前 TTF,提取已有字形为 SVG
145
+ let existingSvgs = [];
146
+ try {
147
+ const ttfBuf = await fs.readFile(filePath("iconfont.ttf"));
148
+ const svgMap = extractAllGlyphsToSvg(ttfBuf, currentJson.glyphs.slice(0, -1));
149
+ existingSvgs = [...svgMap.entries()].map(([unicode, svg]) => ({
150
+ unicode,
151
+ svg,
152
+ name: currentJson.glyphs.find((g) => g.unicode === unicode)?.name || unicode,
153
+ }));
154
+ } catch {
155
+ // 没有 TTF 文件,跳过已有字形提取
156
+ }
157
+
158
+ // 合并:已有 + 新上传
159
+ const allIcons = [
160
+ ...existingSvgs,
161
+ { unicode, svg, name: newGlyph.name },
162
+ ];
163
+
164
+ // 重建字体
165
+ const ttfBuffer = await buildTtfFromIcons(allIcons, {
166
+ fontFamily: currentJson.font_family || "iconfont",
167
+ });
168
+
169
+ // 写入 TTF
170
+ await fs.writeFile(filePath("iconfont.ttf"), ttfBuffer);
171
+
172
+ // 生成 WOFF/WOFF2
173
+ const webFonts = await buildWebFonts(ttfBuffer, currentJson.glyphs);
174
+ await fs.writeFile(filePath("iconfont.woff"), webFonts.woff);
175
+ await fs.writeFile(filePath("iconfont.woff2"), webFonts.woff2);
176
+
177
+ // 保存 json 和 css
178
+ await fs.writeFile(
179
+ filePath("iconfont.json"),
180
+ JSON.stringify(currentJson, null, 2),
181
+ "utf-8"
182
+ );
183
+ await fs.writeFile(
184
+ filePath("iconfont.css"),
185
+ buildCSS(currentJson),
186
+ "utf-8"
187
+ );
188
+
189
+ res.json({ success: true, glyph: newGlyph, json: currentJson });
190
+ } catch (err) {
191
+ console.error("上传 SVG 失败:", err);
192
+ res.status(500).json({ error: err.message });
193
+ }
194
+ });
195
+
196
+ // GET /api/export-svg/:unicode - 导出单个图标为 SVG
197
+ router.get("/export-svg/:unicode", async (req, res) => {
198
+ try {
199
+ const { unicode } = req.params;
200
+ const ttfBuf = await fs.readFile(filePath("iconfont.ttf"));
201
+ const svg = extractGlyphToSvg(ttfBuf, unicode);
202
+
203
+ res.setHeader("Content-Type", "image/svg+xml");
204
+ res.setHeader(
205
+ "Content-Disposition",
206
+ `attachment; filename="icon-${unicode}.svg"`
207
+ );
208
+ res.send(svg);
209
+ } catch (err) {
210
+ res.status(500).json({ error: err.message });
211
+ }
212
+ });
213
+
214
+ return router;
215
+ }
216
+
217
+ /**
218
+ * 分配下一个可用的 PUA unicode(E000-F8FF 区段)
219
+ */
220
+ function nextUnicode(glyphs) {
221
+ const usedCodes = new Set(
222
+ glyphs.map((g) => parseInt(g.unicode, 16))
223
+ );
224
+ for (let code = 0xe000; code <= 0xf8ff; code++) {
225
+ if (!usedCodes.has(code)) return code.toString(16);
226
+ }
227
+ throw new Error("Unicode PUA 区段已满");
228
+ }
229
+
230
+ /**
231
+ * 从当前字形重建字体文件
232
+ */
233
+ async function rebuildFont(dir, iconJson) {
234
+ const ttfPath = path.join(dir, "iconfont.ttf");
235
+
236
+ // 读取当前 TTF,提取所有字形
237
+ const ttfBuf = await fs.readFile(ttfPath);
238
+ const svgMap = extractAllGlyphsToSvg(ttfBuf, iconJson.glyphs);
239
+
240
+ const icons = [...svgMap.entries()].map(([unicode, svg]) => ({
241
+ unicode,
242
+ svg,
243
+ name: iconJson.glyphs.find((g) => g.unicode === unicode)?.name || unicode,
244
+ }));
245
+
246
+ // 重建 TTF
247
+ const newTtf = await buildTtfFromIcons(icons, {
248
+ fontFamily: iconJson.font_family || "iconfont",
249
+ });
250
+ await fs.writeFile(ttfPath, newTtf);
251
+
252
+ // 生成 WOFF/WOFF2
253
+ const webFonts = await buildWebFonts(newTtf, iconJson.glyphs);
254
+ await fs.writeFile(path.join(dir, "iconfont.woff"), webFonts.woff);
255
+ await fs.writeFile(path.join(dir, "iconfont.woff2"), webFonts.woff2);
256
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Express 服务器
3
+ * 启动 HTTP 服务,提供 API 和前端静态文件
4
+ */
5
+
6
+ import express from "express";
7
+ import { resolve, dirname } from "path";
8
+ import { fileURLToPath } from "url";
9
+ import { promises as fs } from "fs";
10
+ import { createApiRouter } from "./api.js";
11
+ import open from "open";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+
15
+ /**
16
+ * 创建并启动服务器
17
+ * @param {{ dir: string, port: number }} options
18
+ */
19
+ export async function createServer (options) {
20
+ const { dir, port } = options;
21
+ const app = express();
22
+
23
+ // 解析 JSON body
24
+ app.use(express.json({ limit: "10mb" }));
25
+ app.use(express.text({ type: "text/plain", limit: "10mb" }));
26
+
27
+ // 静态文件:iconfont 目录挂载到 /public
28
+ app.use(express.static(dir));
29
+
30
+ // /css - 读取 iconfont.css
31
+ app.get("/css", async (_req, res) => {
32
+ try {
33
+ const content = await fs.readFile(`${dir}/iconfont.css`, "utf-8");
34
+ res.setHeader("Content-Type", "text/css");
35
+ res.send(content);
36
+ } catch (err) {
37
+ res.status(500).send(`读取 iconfont.css 失败: ${err.message}`);
38
+ }
39
+ });
40
+
41
+ // API 路由
42
+ const apiRouter = await createApiRouter(dir);
43
+ app.use("/api", apiRouter);
44
+
45
+ // 静态文件(前端构建产物)
46
+ const distPath = resolve(__dirname, "../dist");
47
+ app.use(express.static(distPath));
48
+
49
+ // SPA 回退
50
+ app.get("*", (_req, res) => {
51
+ res.sendFile(resolve(distPath, "index.html"));
52
+ });
53
+
54
+ // 启动
55
+ app.listen(port, () => {
56
+ const url = `http://localhost:${port}`;
57
+ console.log(`\n iconfont-editor 已启动`);
58
+ console.log(` 目录: ${dir}`);
59
+ console.log(` 地址: ${url}\n`);
60
+ open(url).catch(() => {
61
+ console.log(" 请手动打开浏览器访问上述地址");
62
+ });
63
+ });
64
+ }
package/src/index.ts ADDED
File without changes