tcdona_unilib 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/@ast-grep.ts ADDED
@@ -0,0 +1,18 @@
1
+ // -----Type Only Export!-----//
2
+ export type { FileOption, FindConfig, NapiConfig } from "@ast-grep/napi"
3
+ export type { DynamicLangRegistrations } from "@ast-grep/napi"
4
+ export type { Edit, Pos, Range } from "@ast-grep/napi"
5
+ export type { Rule } from "@ast-grep/napi"
6
+
7
+ // -----Runtime Value Export!-----//
8
+ export {
9
+ findInFiles,
10
+ kind,
11
+ parse,
12
+ parseAsync,
13
+ parseFiles,
14
+ pattern,
15
+ } from "@ast-grep/napi"
16
+ export { Lang } from "@ast-grep/napi"
17
+ export { registerDynamicLanguage } from "@ast-grep/napi"
18
+ export { SgNode, SgRoot } from "@ast-grep/napi"
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "tcdona_unilib",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Unified dependency aggregation layer for shared libraries",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "exports": {
8
+ "./astgrep": "./@ast-grep.ts",
9
+ "./staticMeta/eff": "./staticMeta/eff.ts",
10
+ "./staticMeta/file": "./staticMeta/file.yml.ts",
11
+ "./staticMeta/path": "./staticMeta/path.init.ts",
12
+ "./staticMeta/string": "./staticMeta/string.nanoid.ts",
13
+ "./staticMeta/url": "./staticMeta/url.ts",
14
+ "./staticMeta/iduniq": "./staticMeta/iduniq.ts",
15
+ "./staticMeta/idupdate": "./staticMeta/idupdate.ts",
16
+ "./staticMeta/pkgjson": "./staticMeta/pkg.json.ts",
8
17
  "./neverthrow": "./neverthrow.ts",
9
18
  "./zod": "./zod.ts",
10
19
  "./zx": "./zx.ts",
@@ -40,9 +49,11 @@
40
49
  "license": "ISC",
41
50
  "devDependencies": {
42
51
  "@types/bun": "^1.3.5",
43
- "@types/node": "^20.10.6"
52
+ "@types/node": "^20.10.6",
53
+ "vitest": "^4.0.16"
44
54
  },
45
55
  "dependencies": {
56
+ "@ast-grep/napi": "^0.40.3",
46
57
  "@dotenvx/dotenvx": "^1.51.2",
47
58
  "@inquirer/prompts": "^8.1.0",
48
59
  "@types/big.js": "^6.2.2",
@@ -0,0 +1,131 @@
1
+ /**
2
+ * 异步文件操作中心:提供统一的文件读写、AST 解析、扫描等功能
3
+ * 基于 Eff 框架和 neverthrow 处理错误
4
+ */
5
+
6
+ import { glob } from 'zx';
7
+ import { promises as fs } from 'node:fs';
8
+ import { fileInit } from './enum.api.js';
9
+ import { dirname, relative, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { Eff } from './eff.js';
12
+ import type { ResultAsync } from 'neverthrow';
13
+ import { err, ok } from 'neverthrow';
14
+ import { Lang, parse } from '@ast-grep/napi';
15
+ import type { AstNode } from './ast.js';
16
+
17
+ // ==================== 配置 ====================
18
+
19
+ const meta = dirname(fileURLToPath(import.meta.url));
20
+ const fileId = fileInit('staticMeta/ast.scan.ts');
21
+
22
+ export const projectRoot = process.cwd();
23
+
24
+ // ==================== 类型定义 ====================
25
+
26
+ export interface FileParseInfo {
27
+ readonly filePath: string;
28
+ readonly root: AstNode;
29
+ }
30
+
31
+ // ==================== 异步文件操作 ====================
32
+
33
+ /**
34
+ * 异步读取文件(UTF-8 编码)
35
+ */
36
+ export function readFileAsync(filePath: string, signal: AbortSignal): ResultAsync<string, Error> {
37
+ return Eff.create(async ({ signal: effSignal }) => {
38
+ if (effSignal.aborted) return err(new Error('操作已取消'));
39
+ try {
40
+ const content = await fs.readFile(filePath, 'utf-8');
41
+ return ok(content);
42
+ } catch (e) {
43
+ return err(e instanceof Error ? e : new Error(String(e)));
44
+ }
45
+ }, signal);
46
+ }
47
+
48
+ /**
49
+ * 异步写入文件(UTF-8 编码)
50
+ */
51
+ export function writeFileAsync(filePath: string, content: string, signal: AbortSignal): ResultAsync<void, Error> {
52
+ return Eff.create(async ({ signal: effSignal }) => {
53
+ if (effSignal.aborted) return err(new Error('操作已取消'));
54
+ try {
55
+ await fs.writeFile(filePath, content, 'utf-8');
56
+ return ok(undefined);
57
+ } catch (e) {
58
+ return err(e instanceof Error ? e : new Error(String(e)));
59
+ }
60
+ }, signal);
61
+ }
62
+
63
+ /**
64
+ * 异步读取并解析 TypeScript 文件为 AST
65
+ */
66
+ export function parseFileAsync(filePath: string, signal: AbortSignal): ResultAsync<FileParseInfo, Error> {
67
+ return Eff.create(async ({ signal: effSignal }) => {
68
+ if (effSignal.aborted) return err(new Error('操作已取消'));
69
+
70
+ return readFileAsync(filePath, effSignal)
71
+ .andThen((content) => {
72
+ try {
73
+ const parsed = parse(Lang.TypeScript, content);
74
+ const root = parsed.root();
75
+ return ok({ filePath, root });
76
+ } catch (e) {
77
+ return err(e instanceof Error ? e : new Error(String(e)));
78
+ }
79
+ });
80
+ }, signal);
81
+ }
82
+
83
+ /**
84
+ * 并行处理多个文件(通过 Eff.all)
85
+ */
86
+ export function processFilesAsync<T>(
87
+ files: readonly string[],
88
+ processor: (filePath: string, signal: AbortSignal) => ResultAsync<T, Error>,
89
+ signal: AbortSignal,
90
+ ): ResultAsync<T[], Error> {
91
+ if (files.length === 0) {
92
+ return Eff.create(async () => ok([]), signal);
93
+ }
94
+
95
+ const tasks = files.map((file) =>
96
+ (ctx: { signal: AbortSignal }) =>
97
+ processor(file, ctx.signal),
98
+ );
99
+
100
+ return Eff.all(tasks as any, signal) as ResultAsync<T[], Error>;
101
+ }
102
+
103
+ /**
104
+ * 获取相对于项目根目录的相对路径
105
+ */
106
+ export function getRelativePath(filePath: string): string {
107
+ return relative(projectRoot, filePath);
108
+ }
109
+
110
+ /**
111
+ * 扫描项目中所有 TS/TSX/Vue 文件
112
+ */
113
+ export async function scanTypeScriptFilesAsync(
114
+ ignorePatterns: string[] = [],
115
+ ): Promise<string[]> {
116
+ const files = await glob('**/*.{ts,tsx,vue}', {
117
+ cwd: projectRoot,
118
+ ignore: [
119
+ '**/node_modules/**',
120
+ '**/*.d.ts',
121
+ '**/*.test.ts',
122
+ '**/*.spec.ts',
123
+ 'dist/**',
124
+ '.git/**',
125
+ ...ignorePatterns,
126
+ ],
127
+ absolute: true,
128
+ onlyFiles: true,
129
+ });
130
+ return files;
131
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * AST 工具集:代码解析、遍历、分析和文本替换
3
+ */
4
+
5
+ import type { SgNode } from '@ast-grep/napi';
6
+ import { Lang, parse } from '@ast-grep/napi';
7
+ import { fromThrowable } from 'neverthrow';
8
+ import type { Result } from 'neverthrow';
9
+
10
+ // ==================== 类型定义 ====================
11
+
12
+ export type AstNode = InstanceType<typeof SgNode>;
13
+
14
+ export interface FileParseInfo {
15
+ readonly filePath: string;
16
+ readonly relativePath: string;
17
+ readonly root: AstNode;
18
+ }
19
+
20
+ // ==================== AST 遍历工具 ====================
21
+
22
+ /**
23
+ * 递归查找 AST 中所有匹配类型的节点
24
+ */
25
+ export function findAllByKind(
26
+ node: AstNode,
27
+ kind: string,
28
+ results: AstNode[] = [],
29
+ ): AstNode[] {
30
+ if (node.kind() === kind) {
31
+ results.push(node);
32
+ }
33
+ for (const child of node.children()) {
34
+ findAllByKind(child, kind, results);
35
+ }
36
+ return results;
37
+ }
38
+
39
+ // ==================== 字符串处理 ====================
40
+
41
+ /**
42
+ * 删除字符串的首尾引号
43
+ */
44
+ export function stripQuotes(str: string): string {
45
+ if (
46
+ (str.startsWith('"') && str.endsWith('"'))
47
+ || (str.startsWith('\'') && str.endsWith('\''))
48
+ || (str.startsWith('`') && str.endsWith('`'))
49
+ ) {
50
+ return str.slice(1, -1);
51
+ }
52
+ return str;
53
+ }
54
+
55
+ // ==================== 代码解析 ====================
56
+
57
+ /**
58
+ * 解析代码为 AST 根节点
59
+ */
60
+ export function parseCode(
61
+ content: string,
62
+ lang: Lang = Lang.TypeScript,
63
+ ): Result<AstNode, Error> {
64
+ return fromThrowable(
65
+ () => {
66
+ const parsed = parse(lang, content);
67
+ return parsed.root();
68
+ },
69
+ (error) => new Error(`代码解析失败: ${error}`),
70
+ )();
71
+ }
72
+
73
+ // ==================== 函数调用分析 ====================
74
+
75
+ export interface CallExpressionInfo {
76
+ readonly functionName: string;
77
+ readonly arguments: AstNode;
78
+ }
79
+
80
+ /**
81
+ * 从 arguments 节点提取第一个字符串参数值
82
+ */
83
+ export function extractArgumentValue(argsNode: AstNode): string | null {
84
+ const argChildren = argsNode.children();
85
+
86
+ for (const child of argChildren) {
87
+ const kind = child.kind();
88
+
89
+ // 跳过标点符号
90
+ if (kind === '(' || kind === ')' || kind === ',') continue;
91
+
92
+ // 处理字符串字面量
93
+ if (kind === 'string') {
94
+ // 优先查找 string_fragment 子节点
95
+ for (const frag of child.children()) {
96
+ if (frag.kind() === 'string_fragment') {
97
+ return frag.text();
98
+ }
99
+ }
100
+ // 降级:直接处理文本
101
+ const text = child.text();
102
+ if (text.length >= 2 && (text[0] === '"' || text[0] === '\'')) {
103
+ return text.slice(1, -1);
104
+ }
105
+ }
106
+
107
+ // 处理模板字符串(只支持纯字符串,不支持插值)
108
+ if (kind === 'template_string') {
109
+ const text = child.text();
110
+ if (!text.includes('${')) {
111
+ return text.slice(1, -1);
112
+ }
113
+ return null;
114
+ }
115
+
116
+ // 变量或其他类型
117
+ return null;
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * 从函数调用提取函数名(支持 identifier 和 member_expression)
125
+ */
126
+ export function extractFunctionName(call: AstNode): string | null {
127
+ const children = call.children();
128
+ if (children.length < 1) return null;
129
+
130
+ const callee = children[0];
131
+ if (callee.kind() === 'identifier') {
132
+ return callee.text();
133
+ }
134
+
135
+ if (callee.kind() === 'member_expression') {
136
+ const memberChildren = callee.children();
137
+ const lastIdentifier = memberChildren.findLast(
138
+ (c) => c.kind() === 'property_identifier' || c.kind() === 'identifier',
139
+ );
140
+ if (lastIdentifier) {
141
+ return lastIdentifier.text();
142
+ }
143
+ }
144
+
145
+ return null;
146
+ }
147
+
148
+ // ==================== 导入检测 ====================
149
+
150
+ /**
151
+ * 检查代码中是否导入了指定的名称
152
+ */
153
+ export function hasImport(root: AstNode, importName: string): boolean {
154
+ // 查找所有 import_statement
155
+ const importStatements = findAllByKind(root, 'import_statement');
156
+
157
+ for (const stmt of importStatements) {
158
+ // 查找 named_imports 中的标识符
159
+ const namedImports = findAllByKind(stmt, 'import_specifier');
160
+ for (const specifier of namedImports) {
161
+ const children = specifier.children();
162
+ // import_specifier 的结构: identifier | (identifier 'as' identifier)
163
+ if (children.length >= 1) {
164
+ const firstName = children[0].text();
165
+ if (firstName === importName) {
166
+ return true;
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * 查找代码中所有对指定函数的调用
177
+ */
178
+ export function findFunctionCalls(
179
+ root: AstNode,
180
+ functionName: string,
181
+ ): AstNode[] {
182
+ const calls = findAllByKind(root, 'call_expression');
183
+ return calls.filter((call) => {
184
+ const fname = extractFunctionName(call);
185
+ return fname === functionName;
186
+ });
187
+ }
188
+
189
+ /**
190
+ * 从函数调用提取第一个参数(key)和其他字符串参数(tags)
191
+ */
192
+ export function extractKeyFromCall(
193
+ call: AstNode,
194
+ fnName: string,
195
+ ): { key: string; tags?: string[] } | null {
196
+ const children = call.children();
197
+ if (children.length < 2) return null;
198
+
199
+ const callee = children[0];
200
+ const args = children[1];
201
+
202
+ if (callee?.kind() !== 'identifier' || callee.text() !== fnName) {
203
+ return null;
204
+ }
205
+
206
+ if (args?.kind() !== 'arguments') return null;
207
+
208
+ const argChildren = args.children().filter((c) => c.kind() === 'string');
209
+ if (argChildren.length === 0) return null;
210
+
211
+ const key = stripQuotes(argChildren[0].text());
212
+
213
+ let tags: string[] = [];
214
+ if (argChildren.length > 1) {
215
+ tags = argChildren.slice(1).map((c) => stripQuotes(c.text()));
216
+ }
217
+
218
+ return { key, tags };
219
+ }
220
+
221
+ /**
222
+ * 按指定范围替换文本内容(从后往前替换以避免位置偏移)
223
+ */
224
+ export function replaceRanges(
225
+ content: string,
226
+ replacements: {
227
+ startIndex: number;
228
+ endIndex: number;
229
+ replacement: string;
230
+ }[],
231
+ ): string {
232
+ // 从后往前排序,避免位置偏移
233
+ const sorted = [...replacements].sort((a, b) => b.startIndex - a.startIndex);
234
+
235
+ let result = content;
236
+ for (const { startIndex, endIndex, replacement } of sorted) {
237
+ const before = result.slice(0, startIndex);
238
+ const after = result.slice(endIndex);
239
+ result = before + replacement + after;
240
+ }
241
+
242
+ return result;
243
+ }
244
+
245
+ // ==================== 导出默认函数 ====================
246
+
247
+ // 统一导出所有 AST 工具函数
248
+ export const astTools = {
249
+ findAllByKind,
250
+ stripQuotes,
251
+ parseCode,
252
+ extractArgumentValue,
253
+ extractFunctionName,
254
+ hasImport,
255
+ findFunctionCalls,
256
+ replaceRanges,
257
+ };
258
+
259
+ export default astTools;
@@ -0,0 +1,17 @@
1
+ import { ok } from 'neverthrow';
2
+ import { Eff } from './eff';
3
+
4
+ /**
5
+ * 异步延迟函数:返回延迟是否完成(true)或被取消(false)
6
+ */
7
+ export const delayEff = (time: number, parentSignal: AbortSignal) => {
8
+ return Eff.create<boolean, never>(({ signal }) => {
9
+ return new Promise((resolve) => {
10
+ const timer = setTimeout(() => resolve(ok(true)), time);
11
+ signal.addEventListener('abort', () => {
12
+ clearTimeout(timer);
13
+ resolve(ok(false));
14
+ }, { once: true });
15
+ });
16
+ }, parentSignal);
17
+ };