@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,91 @@
1
+ import { join } from 'path';
2
+ import { homedir } from 'os';
3
+ import { ConfigManager } from '../shared/index.js';
4
+
5
+ /**
6
+ * 获取配置文件路径
7
+ */
8
+ function getConfigPath(): string {
9
+ const appData = process.env.APPDATA || join(homedir(), '.config');
10
+ return join(appData, 'node-tools', 'git-log-config.json');
11
+ }
12
+
13
+ /**
14
+ * 默认配置
15
+ */
16
+ export const DEFAULT_CONFIG: GitLogConfig = {
17
+ filters: {
18
+ author: '',
19
+ since: '',
20
+ until: '',
21
+ selectedRepos: []
22
+ }
23
+ };
24
+
25
+ /**
26
+ * 日志筛选条件
27
+ */
28
+ export interface LogFilters {
29
+ /** 作者筛选 */
30
+ author: string;
31
+ /** 起始日期 */
32
+ since: string;
33
+ /** 结束日期 */
34
+ until: string;
35
+ /** 选中的仓库 */
36
+ selectedRepos: string[];
37
+ }
38
+
39
+ /**
40
+ * Git 日志配置
41
+ */
42
+ export interface GitLogConfig {
43
+ filters: LogFilters;
44
+ }
45
+
46
+ /**
47
+ * 配置管理器实例
48
+ */
49
+ const manager = new ConfigManager<GitLogConfig>(getConfigPath(), DEFAULT_CONFIG);
50
+
51
+ /**
52
+ * 读取配置
53
+ */
54
+ export function getConfig(): GitLogConfig {
55
+ return manager.getConfig();
56
+ }
57
+
58
+ /**
59
+ * 写入配置(内部使用)
60
+ */
61
+ function setConfig(config: GitLogConfig): void {
62
+ manager.setConfig(config);
63
+ }
64
+
65
+ /**
66
+ * 保存筛选条件
67
+ */
68
+ export function saveFilters(filters: LogFilters): void {
69
+ const config = getConfig();
70
+ config.filters = filters;
71
+ setConfig(config);
72
+ }
73
+
74
+ /**
75
+ * 获取保存的筛选条件,since 默认为7天前
76
+ */
77
+ export function getSavedFilters(): LogFilters {
78
+ const config = getConfig();
79
+ const filters = { ...config.filters };
80
+ // 如果 since 为空,默认7天前
81
+ if (!filters.since) {
82
+ const d = new Date();
83
+ d.setDate(d.getDate() - 7);
84
+ filters.since = d.toISOString().slice(0, 10);
85
+ }
86
+ // 如果 until 为空,默认今天
87
+ if (!filters.until) {
88
+ filters.until = new Date().toISOString().slice(0, 10);
89
+ }
90
+ return filters;
91
+ }
@@ -0,0 +1,178 @@
1
+ import pc from "picocolors";
2
+ import type { GitLogEntry } from "./log-fetcher.js";
3
+ import type { GitRepo } from "./scanner.js";
4
+ import type { LogFilters } from "./config.js";
5
+ import dayjs from "dayjs";
6
+ import zh from "dayjs/locale/zh-cn.js";
7
+ dayjs.locale(zh);
8
+
9
+ /**
10
+ * 计算字符串的终端显示宽度(CJK 字符占2列)
11
+ */
12
+ function displayWidth(str: string): number {
13
+ let w = 0;
14
+ for (const ch of str) {
15
+ w += ch.charCodeAt(0) > 0x7f ? 2 : 1;
16
+ }
17
+ return w;
18
+ }
19
+
20
+ /**
21
+ * 按终端显示宽度右填充空格
22
+ */
23
+ function padEndDisplay(str: string, width: number): string {
24
+ return str + " ".repeat(Math.max(0, width - displayWidth(str)));
25
+ }
26
+
27
+ /**
28
+ * 按终端显示宽度截断字符串
29
+ */
30
+ function truncateDisplay(str: string, maxWidth: number): string {
31
+ return splitAtWidth(str, maxWidth).first;
32
+ }
33
+
34
+ /**
35
+ * 按终端显示宽度将字符串拆分为两段
36
+ */
37
+ function splitAtWidth(str: string, maxWidth: number): { first: string; rest: string } {
38
+ maxWidth = maxWidth - 2;
39
+ let w = 0;
40
+ let i = 0;
41
+ for (; i < str.length; i++) {
42
+ const cw = str.charCodeAt(i) > 0x7f ? 2 : 1;
43
+ if (w + cw > maxWidth) break;
44
+ w += cw;
45
+ }
46
+ return { first: str.slice(0, i), rest: str.slice(i) };
47
+ }
48
+
49
+ /** 列宽计算结果 */
50
+ interface ColumnWidths {
51
+ time: number;
52
+ author: number;
53
+ repo: number;
54
+ hash: number;
55
+ subjectIndent: number;
56
+ }
57
+
58
+ /** 计算所有日志的列宽 */
59
+ function calcColumnWidths(logs: GitLogEntry[]): ColumnWidths {
60
+ let maxAuthor = 0;
61
+ let maxRepo = 0;
62
+
63
+ for (const entry of logs) {
64
+ const aw = displayWidth(entry.authorName);
65
+ if (aw > maxAuthor) maxAuthor = aw;
66
+
67
+ const rw = displayWidth(extractRepoName(entry.repo));
68
+ if (rw > maxRepo) maxRepo = rw;
69
+ }
70
+
71
+ // time 固定 21 (YYYY-MM-DD dd HH:mm),hash 固定 7
72
+ const time = 22;
73
+ const hash = 8;
74
+ return {
75
+ time,
76
+ author: maxAuthor,
77
+ repo: maxRepo,
78
+ hash,
79
+ subjectIndent: time + 1 + maxAuthor + 1 + maxRepo + 1 + hash + 1,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * 格式化单条日志为两行文本(带颜色)
85
+ */
86
+ function formatLogEntry(entry: GitLogEntry, widths: ColumnWidths, termWidth?: number): string {
87
+ const time = dayjs(entry.date).format("YYYY-MM-DD ddd HH:mm");
88
+ const hash = entry.hash.slice(0, 8);
89
+ const repoName = extractRepoName(entry.repo);
90
+
91
+ // 第一行前缀: 时间 作者 [仓库] hash
92
+ const prefix =
93
+ pc.dim(time) +
94
+ " " +
95
+ pc.cyan(padEndDisplay(entry.authorName, widths.author)) +
96
+ " " +
97
+ pc.yellow(padEndDisplay(`[${repoName}]`, widths.repo + 2)) +
98
+ " " +
99
+ pc.green(hash);
100
+
101
+ // subject 可用宽度 = termWidth - 前缀显示宽度
102
+ const maxSubjWidth = Math.max(20, (termWidth || 120) - widths.subjectIndent);
103
+ const { first, rest } = splitAtWidth(entry.subject, maxSubjWidth);
104
+
105
+ // 行1: 前缀 + 两个空格 + subject 前半段
106
+ const line1 = prefix + " " + first;
107
+
108
+ // 行2: 剩余 subject(无缩进),超过 termWidth 截断
109
+ const line2 = rest ? truncateDisplay(rest, termWidth || 120) : "";
110
+
111
+ return line1 + "\n" + line2;
112
+ }
113
+
114
+ /**
115
+ * 每条日志占几行(行1 + 行2)
116
+ */
117
+ export const LINES_PER_ENTRY = 2;
118
+
119
+ /**
120
+ * 格式化日志列表为分页用的行数组(每条日志占 LINES_PER_ENTRY 行)
121
+ */
122
+ export function formatLogEntries(logs: GitLogEntry[]): string[] {
123
+ if (logs.length === 0) return [];
124
+
125
+ const widths = calcColumnWidths(logs);
126
+ const termWidth = process.stdout.columns || 120;
127
+ const lines: string[] = [];
128
+
129
+ for (const entry of logs) {
130
+ const [line1, line2] = formatLogEntry(entry, widths, termWidth).split("\n");
131
+ lines.push(line1, line2);
132
+ }
133
+
134
+ return lines;
135
+ }
136
+
137
+ /**
138
+ * 格式化仓库概览
139
+ */
140
+ export function formatRepoOverview(repos: GitRepo[]): string {
141
+ if (repos.length === 0) return "未找到 Git 仓库";
142
+
143
+ const nameWidth = Math.max(...repos.map((r) => displayWidth(r.name))) + 2;
144
+ const branchWidth = Math.max(...repos.map((r) => displayWidth(r.currentBranch || "-"))) + 2;
145
+
146
+ let result = "";
147
+ result += padEndDisplay("名称", nameWidth) + padEndDisplay("分支", branchWidth) + "状态\n";
148
+ result += "\n";
149
+
150
+ for (const repo of repos) {
151
+ const status = repo.hasChanges ? pc.red("● 有更改") : pc.green(" 干净");
152
+ result +=
153
+ padEndDisplay(repo.name, nameWidth) + padEndDisplay(repo.currentBranch || "-", branchWidth) + status + "\n";
154
+ }
155
+
156
+ result += `\n共 ${repos.length} 个仓库`;
157
+ return result;
158
+ }
159
+
160
+ /**
161
+ * 格式化筛选条件展示
162
+ */
163
+ export function formatFilters(filters: LogFilters): string {
164
+ const lines: string[] = [];
165
+ lines.push(`作者: ${filters.author || "(未设置)"}`);
166
+ lines.push(`起始日期: ${filters.since || "(未设置)"}`);
167
+ lines.push(`结束日期: ${filters.until || "(未设置)"}`);
168
+ lines.push(`选中仓库: ${filters.selectedRepos.length > 0 ? filters.selectedRepos.join(", ") : "(全部)"}`);
169
+ return lines.join("\n");
170
+ }
171
+
172
+ /**
173
+ * 从仓库路径提取仓库名
174
+ */
175
+ function extractRepoName(repoPath: string): string {
176
+ const parts = repoPath.replace(/\\/g, "/").split("/");
177
+ return parts[parts.length - 1] || repoPath;
178
+ }
@@ -0,0 +1,5 @@
1
+ // gitlog 领域导出
2
+ export { runApp, showConfig } from './app.js';
3
+ export { scanRepos, type GitRepo } from './scanner.js';
4
+ export { fetchLogs, fetchLogsFromRepos, getRepoStatus, getRepoBranches, type GitLogEntry, type LogOptions } from './log-fetcher.js';
5
+ export { saveFilters, getSavedFilters, type LogFilters, type GitLogConfig } from './config.js';
@@ -0,0 +1,150 @@
1
+ import simpleGit, { SimpleGit } from 'simple-git';
2
+
3
+ /**
4
+ * Git 日志条目
5
+ */
6
+ export interface GitLogEntry {
7
+ /** 哈希值 */
8
+ hash: string;
9
+ /** 完整哈希值 */
10
+ hashFull: string;
11
+ /** 作者名称 */
12
+ authorName: string;
13
+ /** 作者邮箱 */
14
+ authorEmail: string;
15
+ /** 日期 */
16
+ date: Date;
17
+ /** 提交消息 */
18
+ subject: string;
19
+ /** 仓库路径 */
20
+ repo: string;
21
+ }
22
+
23
+ /**
24
+ * 日志查询选项
25
+ */
26
+ export interface LogOptions {
27
+ /** 作者筛选 */
28
+ author?: string;
29
+ /** 起始日期 */
30
+ since?: string | Date;
31
+ /** 结束日期 */
32
+ until?: string | Date;
33
+ /** 最大数量 */
34
+ maxCount?: number;
35
+ /** 分支 */
36
+ branch?: string;
37
+ }
38
+
39
+ /**
40
+ * 获取仓库的 Git 日志
41
+ */
42
+ export async function fetchLogs(
43
+ repoPath: string,
44
+ options: LogOptions = {}
45
+ ): Promise<GitLogEntry[]> {
46
+ const git: SimpleGit = simpleGit(repoPath);
47
+
48
+ const logOptions: string[] = [];
49
+
50
+ if (options.author) {
51
+ logOptions.push(`--author=${options.author}`);
52
+ }
53
+
54
+ if (options.since) {
55
+ const sinceStr = typeof options.since === 'string'
56
+ ? options.since
57
+ : options.since.toISOString();
58
+ logOptions.push(`--since=${sinceStr}`);
59
+ }
60
+
61
+ if (options.until) {
62
+ const untilStr = typeof options.until === 'string'
63
+ ? options.until
64
+ : options.until.toISOString();
65
+ logOptions.push(`--until=${untilStr}`);
66
+ }
67
+
68
+ if (options.maxCount) {
69
+ logOptions.push(`-n${options.maxCount}`);
70
+ }
71
+
72
+ if (options.branch) {
73
+ logOptions.push(options.branch);
74
+ }
75
+
76
+ try {
77
+ const result = await git.log([
78
+ '--all', // 获取所有分支的提交
79
+ ...logOptions,
80
+ ]);
81
+
82
+ return result.all.map(entry => ({
83
+ hash: entry.hash.slice(0, 8),
84
+ hashFull: entry.hash,
85
+ authorName: entry.author_name,
86
+ authorEmail: entry.author_email,
87
+ date: new Date(entry.date),
88
+ subject: entry.message,
89
+ repo: repoPath
90
+ }));
91
+ } catch (error) {
92
+ throw new Error(`获取 Git 日志失败: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * 批量获取多个仓库的日志
98
+ */
99
+ export async function fetchLogsFromRepos(
100
+ repos: string[],
101
+ options: LogOptions = {}
102
+ ): Promise<GitLogEntry[]> {
103
+ const allLogs: GitLogEntry[] = [];
104
+
105
+ for (const repoPath of repos) {
106
+ try {
107
+ const logs = await fetchLogs(repoPath, options);
108
+ allLogs.push(...logs);
109
+ } catch {
110
+ // 跳过获取失败的仓库
111
+ }
112
+ }
113
+
114
+ // 按日期排序(最新的在前)
115
+ return allLogs.sort((a, b) => b.date.getTime() - a.date.getTime());
116
+ }
117
+
118
+ /**
119
+ * 获取仓库状态
120
+ */
121
+ export async function getRepoStatus(repoPath: string): Promise<{
122
+ hasChanges: boolean;
123
+ currentBranch: string;
124
+ }> {
125
+ const git: SimpleGit = simpleGit(repoPath);
126
+
127
+ try {
128
+ const status = await git.status();
129
+ const branch = status.current || 'main';
130
+ const hasChanges = status.files.length > 0;
131
+
132
+ return { hasChanges, currentBranch: branch };
133
+ } catch {
134
+ return { hasChanges: false, currentBranch: 'unknown' };
135
+ }
136
+ }
137
+
138
+ /**
139
+ * 获取仓库分支列表
140
+ */
141
+ export async function getRepoBranches(repoPath: string): Promise<string[]> {
142
+ const git: SimpleGit = simpleGit(repoPath);
143
+
144
+ try {
145
+ const branches = await git.branch();
146
+ return branches.all;
147
+ } catch {
148
+ return [];
149
+ }
150
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * 终端分页器
3
+ * 类似 less 的键盘导航体验
4
+ */
5
+
6
+ /** 分页器退出动作 */
7
+ export type PagerAction = 'refilter' | 'quit';
8
+
9
+ /** 分页器配置 */
10
+ export interface PagerOptions {
11
+ /** 每页行数,默认根据终端高度动态计算 */
12
+ pageSize?: number;
13
+ /** 每个条目占几行,用于避免在条目中间断页 */
14
+ linesPerEntry?: number;
15
+ }
16
+
17
+ // ANSI 转义码
18
+ const CLEAR_SCREEN = '\x1b[2J\x1b[H';
19
+ const HIDE_CURSOR = '\x1b[?25l';
20
+ const SHOW_CURSOR = '\x1b[?25h';
21
+ const CLEAR_LINE = '\x1b[2K\r';
22
+ const MOVE_TO_BOTTOM = '\x1b[9999;1H';
23
+
24
+ /**
25
+ * 运行终端分页器
26
+ * @param lines 要显示的文本行
27
+ * @param options 分页配置
28
+ * @returns 用户的退出动作
29
+ */
30
+ export async function runPager(lines: string[], options?: PagerOptions): Promise<PagerAction> {
31
+ if (lines.length === 0) return 'quit';
32
+
33
+ const linesPerEntry = options?.linesPerEntry || 1;
34
+ const pageSize = getPageSize(options?.pageSize, linesPerEntry);
35
+ const totalPages = Math.max(1, Math.ceil(lines.length / pageSize));
36
+ const totalCount = Math.ceil(lines.length / linesPerEntry);
37
+ let currentPage = 0;
38
+
39
+ // 渲染当前页
40
+ renderPage(lines, currentPage, totalPages, pageSize, totalCount);
41
+
42
+ return new Promise<PagerAction>((resolve) => {
43
+ const stdin = process.stdin;
44
+
45
+ if (!stdin.isTTY) {
46
+ // 非 TTY 环境直接输出全部并退出
47
+ console.log(lines.join('\n'));
48
+ resolve('quit');
49
+ return;
50
+ }
51
+
52
+ // 进入 raw mode
53
+ stdin.setRawMode(true);
54
+ stdin.resume();
55
+ process.stdout.write(HIDE_CURSOR);
56
+
57
+ const cleanup = (action: PagerAction) => {
58
+ stdin.setRawMode(false);
59
+ stdin.pause();
60
+ stdin.removeListener('data', onKey);
61
+ process.stdout.write(SHOW_CURSOR);
62
+ // 清除提示行
63
+ process.stdout.write(CLEAR_LINE);
64
+ resolve(action);
65
+ };
66
+
67
+ const onKey = (data: Buffer) => {
68
+ const key = data.toString();
69
+
70
+ // q 或 Esc 或 Ctrl+C → 退出
71
+ if (key === 'q' || key === '\x1b' || key === '\x03') {
72
+ cleanup('quit');
73
+ return;
74
+ }
75
+
76
+ // r → 重新筛选
77
+ if (key === 'r') {
78
+ cleanup('refilter');
79
+ return;
80
+ }
81
+
82
+ // 解析方向键和功能键
83
+ const action = parseKey(key);
84
+
85
+ switch (action) {
86
+ case 'next':
87
+ if (currentPage < totalPages - 1) {
88
+ currentPage++;
89
+ renderPage(lines, currentPage, totalPages, pageSize, totalCount);
90
+ }
91
+ break;
92
+ case 'prev':
93
+ if (currentPage > 0) {
94
+ currentPage--;
95
+ renderPage(lines, currentPage, totalPages, pageSize, totalCount);
96
+ }
97
+ break;
98
+ case 'home':
99
+ if (currentPage !== 0) {
100
+ currentPage = 0;
101
+ renderPage(lines, currentPage, totalPages, pageSize, totalCount);
102
+ }
103
+ break;
104
+ case 'end':
105
+ if (currentPage !== totalPages - 1) {
106
+ currentPage = totalPages - 1;
107
+ renderPage(lines, currentPage, totalPages, pageSize, totalCount);
108
+ }
109
+ break;
110
+ }
111
+ };
112
+
113
+ stdin.on('data', onKey);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * 获取每页行数(按条目对齐,避免在条目中间断页)
119
+ */
120
+ function getPageSize(customSize?: number, linesPerEntry?: number): number {
121
+ if (customSize) return customSize;
122
+ const available = Math.max(3, (process.stdout.rows || 24) - 2);
123
+ if (!linesPerEntry || linesPerEntry <= 0) return available;
124
+ // 向下取整到 linesPerEntry 的倍数
125
+ return Math.floor(available / linesPerEntry) * linesPerEntry;
126
+ }
127
+
128
+ /**
129
+ * 渲染当前页
130
+ */
131
+ function renderPage(lines: string[], page: number, totalPages: number, pageSize: number, totalCount: number): void {
132
+ const start = page * pageSize;
133
+ const end = Math.min(start + pageSize, lines.length);
134
+ const pageLines = lines.slice(start, end);
135
+
136
+ process.stdout.write(CLEAR_SCREEN);
137
+
138
+ // 输出当前页日志
139
+ for (const line of pageLines) {
140
+ process.stdout.write(line + '\n');
141
+ }
142
+
143
+ // 填充空行到页面底部(确保提示行始终在最底部)
144
+ const remaining = pageSize - pageLines.length;
145
+ for (let i = 0; i < remaining; i++) {
146
+ process.stdout.write('\n');
147
+ }
148
+
149
+ // 页码信息行
150
+ const pageInfo = `第 ${page + 1}/${totalPages} 页(共 ${totalCount} 条)`;
151
+ process.stdout.write(`\x1b[2m${pageInfo}\x1b[0m\n`);
152
+
153
+ // 快捷键提示行
154
+ const hint = '↑↓/PgUp/PgDn 翻页 Space 下一页 Home/End 首尾页 r 重新筛选 q 返回';
155
+ process.stdout.write(`\x1b[7m ${hint} \x1b[0m`);
156
+ }
157
+
158
+ /**
159
+ * 解析按键为动作
160
+ */
161
+ function parseKey(key: string): 'next' | 'prev' | 'home' | 'end' | null {
162
+ // 空格 → 下一页
163
+ if (key === ' ') return 'next';
164
+
165
+ // 上箭头 / PgUp
166
+ if (key === '\x1b[A' || key === '\x1b[5~') return 'prev';
167
+
168
+ // 下箭头 / PgDn
169
+ if (key === '\x1b[B' || key === '\x1b[6~') return 'next';
170
+
171
+ // Home
172
+ if (key === '\x1b[H' || key === '\x1b[1~') return 'home';
173
+
174
+ // End
175
+ if (key === '\x1b[F' || key === '\x1b[4~') return 'end';
176
+
177
+ return null;
178
+ }
@@ -0,0 +1,60 @@
1
+ import { readdirSync, statSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ /**
5
+ * Git 仓库信息
6
+ */
7
+ export interface GitRepo {
8
+ /** 仓库名称 */
9
+ name: string;
10
+ /** 仓库路径 */
11
+ path: string;
12
+ /** 是否有未提交的更改 */
13
+ hasChanges?: boolean;
14
+ /** 当前分支 */
15
+ currentBranch?: string;
16
+ }
17
+
18
+ /**
19
+ * 检查目录是否是 Git 仓库
20
+ */
21
+ function isGitRepo(dirPath: string): boolean {
22
+ const gitPath = join(dirPath, '.git');
23
+ try {
24
+ const stat = statSync(gitPath);
25
+ return stat.isDirectory() || stat.isFile();
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 扫描指定目录的一级子目录,查找 Git 仓库
33
+ */
34
+ export function scanRepos(scanPath: string): GitRepo[] {
35
+ const results: GitRepo[] = [];
36
+
37
+ let entries;
38
+ try {
39
+ entries = readdirSync(scanPath);
40
+ } catch {
41
+ return results;
42
+ }
43
+
44
+ for (const entry of entries) {
45
+ if (entry === 'node_modules' || entry === '.git') continue;
46
+
47
+ const fullPath = join(scanPath, entry);
48
+
49
+ try {
50
+ const stat = statSync(fullPath);
51
+ if (stat.isDirectory() && isGitRepo(fullPath)) {
52
+ results.push({ name: entry, path: fullPath });
53
+ }
54
+ } catch {
55
+ // 跳过无法访问的目录
56
+ }
57
+ }
58
+
59
+ return results;
60
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>iconfont-editor</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,14 @@
1
+ <template>
2
+ <IconEditor />
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import IconEditor from "./views/IconEditor.vue";
7
+ </script>
8
+
9
+ <style>
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+ </style>
@@ -0,0 +1,8 @@
1
+ import { createApp } from "vue";
2
+ import ElementPlus from "element-plus";
3
+ import "element-plus/dist/index.css";
4
+ import App from "./App.vue";
5
+
6
+ const app = createApp(App);
7
+ app.use(ElementPlus);
8
+ app.mount("#app");