tcdona_unilib 1.0.2 → 1.0.3

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 (2) hide show
  1. package/package.json +1 -1
  2. package/staticMeta/idupdate.ts +302 -262
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tcdona_unilib",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Unified dependency aggregation layer for shared libraries",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -2,60 +2,76 @@
2
2
  * 自动更新所有 fileInit() 调用的路径参数为正确的相对路径
3
3
  */
4
4
 
5
- import { fileURLToPath } from 'node:url';
6
- import { err, ok } from 'neverthrow';
7
- import type { EffContext } from './eff.js';
8
- import { Eff } from './eff.js';
9
- import type { Result } from 'neverthrow';
10
- import type { SgNode } from '@ast-grep/napi';
11
- import { fileInit } from './enum.api.js';
12
- import { extractArgumentValue, findFunctionCalls, hasImport, replaceRanges } from './ast.js';
13
- import { getRelativePath, parseFileAsync, readFileAsync, scanTypeScriptFilesAsync, writeFileAsync } from './ast.scan.js';
14
-
15
- type NonEmptyArray<T> = readonly [T, ...T[]];
5
+ import { fileURLToPath } from "node:url"
6
+ import { err, ok } from "neverthrow"
7
+ import type { EffContext } from "./eff.js"
8
+ import { Eff } from "./eff.js"
9
+ import type { Result } from "neverthrow"
10
+ import type { SgNode } from "@ast-grep/napi"
11
+ import { fileInit } from "./enum.api.js"
12
+ import {
13
+ extractArgumentValue,
14
+ findFunctionCalls,
15
+ hasImport,
16
+ replaceRanges,
17
+ } from "./ast.js"
18
+ import {
19
+ getRelativePath,
20
+ parseFileAsync,
21
+ readFileAsync,
22
+ scanTypeScriptFilesAsync,
23
+ writeFileAsync,
24
+ } from "./ast.scan.js"
25
+
26
+ type NonEmptyArray<T> = readonly [T, ...T[]]
16
27
 
17
28
  // ==================== 日志配置 ====================
18
29
 
19
- const fileId = fileInit('staticMeta/idupdate.ts');
30
+ const fileId = fileInit("staticMeta/idupdate.ts")
20
31
 
21
32
  // ==================== 类型定义 ====================
22
33
 
23
- type UpdateError
24
- = | { type: 'VALIDATION_FILENAME_FAILED'; message: string }
25
- | { type: 'FILE_ERROR'; message: string; filePath: string };
34
+ type UpdateError =
35
+ | { type: "VALIDATION_FILENAME_FAILED"; message: string }
36
+ | { type: "FILE_ERROR"; message: string; filePath: string }
26
37
 
27
38
  interface StaticMetaCallInfo {
28
- startOffset: number;
29
- endOffset: number;
30
- currentValue: string | null;
39
+ startOffset: number
40
+ endOffset: number
41
+ currentValue: string | null
31
42
  }
32
43
 
33
44
  interface InvalidCall {
34
- reason: 'NO_ARGUMENT' | 'VARIABLE_ARGUMENT' | 'TEMPLATE_STRING';
35
- line: number;
36
- text: string;
45
+ reason: "NO_ARGUMENT" | "VARIABLE_ARGUMENT" | "TEMPLATE_STRING"
46
+ line: number
47
+ text: string
37
48
  }
38
49
 
39
50
  // ==================== 初始化 ====================
40
51
 
41
- const __filename = fileURLToPath(import.meta.url);
52
+ const __filename = fileURLToPath(import.meta.url)
42
53
 
43
54
  // ==================== 验证 ====================
44
55
 
45
- function validate(): { isErr: () => boolean; isOk: () => boolean; value?: void; error?: UpdateError } {
46
- // 验证 staticMeta 功能
47
- const currentFileAbsPath = fileURLToPath(import.meta.url);
48
- const expectedRelativePath = getRelativePath(currentFileAbsPath);
49
-
50
- const conf = fileId('validate1').conf;
51
- if (conf.srcFile !== expectedRelativePath) {
52
- return err({
53
- type: 'VALIDATION_FILENAME_FAILED',
54
- message: `__filename 不正确:\n 期望: ${expectedRelativePath}\n 实际: ${conf.srcFile}`,
55
- });
56
- }
57
-
58
- return ok(undefined);
56
+ function validate(): {
57
+ isErr: () => boolean
58
+ isOk: () => boolean
59
+ value?: void
60
+ error?: UpdateError
61
+ } {
62
+ // 验证 staticMeta 功能
63
+ const currentFileAbsPath = fileURLToPath(import.meta.url)
64
+ const expectedRelativePath = getRelativePath(currentFileAbsPath)
65
+
66
+ const conf = fileId("validate1").conf
67
+ if (conf.srcFile !== expectedRelativePath) {
68
+ return err({
69
+ type: "VALIDATION_FILENAME_FAILED",
70
+ message: `__filename 不正确:\n 期望: ${expectedRelativePath}\n 实际: ${conf.srcFile}`,
71
+ })
72
+ }
73
+
74
+ return ok(undefined)
59
75
  }
60
76
 
61
77
  // ==================== 文件处理 ====================
@@ -64,120 +80,120 @@ function validate(): { isErr: () => boolean; isOk: () => boolean; value?: void;
64
80
  * 找出文件中所有 fileInit 调用(有效和无效)
65
81
  */
66
82
  function findFileInitCalls(
67
- root: SgNode,
83
+ root: SgNode
68
84
  ): [StaticMetaCallInfo[], InvalidCall[]] {
69
- const validCalls: StaticMetaCallInfo[] = [];
70
- const invalidCalls: InvalidCall[] = [];
71
-
72
- const allCalls = findFunctionCalls(root, 'fileInit');
73
-
74
- for (const call of allCalls) {
75
- const children = call.children();
76
- if (children.length < 2) continue;
77
-
78
- const args = children[1];
79
-
80
- if (args?.kind() !== 'arguments') continue;
81
-
82
- const value = extractArgumentValue(args);
83
- const range = call.range();
84
-
85
- if (value === null) {
86
- const argText = args.text();
87
- let reason: InvalidCall['reason'] = 'NO_ARGUMENT';
88
-
89
- if (argText.includes('`')) {
90
- reason = 'TEMPLATE_STRING';
91
- } else if (
92
- argText.length > 2
93
- && !argText.includes('"')
94
- && !argText.includes('\'')
95
- ) {
96
- reason = 'VARIABLE_ARGUMENT';
97
- }
98
-
99
- invalidCalls.push({
100
- reason,
101
- line: range.start.line,
102
- text: call.text(),
103
- });
104
- continue;
105
- }
106
-
107
- validCalls.push({
108
- startOffset: range.start.index,
109
- endOffset: range.end.index,
110
- currentValue: value,
111
- });
85
+ const validCalls: StaticMetaCallInfo[] = []
86
+ const invalidCalls: InvalidCall[] = []
87
+
88
+ const allCalls = findFunctionCalls(root, "fileInit")
89
+
90
+ for (const call of allCalls) {
91
+ const children = call.children()
92
+ if (children.length < 2) continue
93
+
94
+ const args = children[1]
95
+
96
+ if (args?.kind() !== "arguments") continue
97
+
98
+ const value = extractArgumentValue(args)
99
+ const range = call.range()
100
+
101
+ if (value === null) {
102
+ const argText = args.text()
103
+ let reason: InvalidCall["reason"] = "NO_ARGUMENT"
104
+
105
+ if (argText.includes("`")) {
106
+ reason = "TEMPLATE_STRING"
107
+ } else if (
108
+ argText.length > 2 &&
109
+ !argText.includes('"') &&
110
+ !argText.includes("'")
111
+ ) {
112
+ reason = "VARIABLE_ARGUMENT"
113
+ }
114
+
115
+ invalidCalls.push({
116
+ reason,
117
+ line: range.start.line,
118
+ text: call.text(),
119
+ })
120
+ continue
112
121
  }
113
122
 
114
- return [validCalls, invalidCalls];
123
+ validCalls.push({
124
+ startOffset: range.start.index,
125
+ endOffset: range.end.index,
126
+ currentValue: value,
127
+ })
128
+ }
129
+
130
+ return [validCalls, invalidCalls]
115
131
  }
116
132
 
117
133
  /**
118
134
  * 更新文件中所有 fileInit 调用的路径参数
119
135
  */
120
136
  async function updateStaticMeta(
121
- filePath: string,
122
- content: string,
123
- root: SgNode,
124
- dryRun: boolean,
125
- signal: AbortSignal,
137
+ filePath: string,
138
+ content: string,
139
+ root: SgNode,
140
+ dryRun: boolean,
141
+ signal: AbortSignal
126
142
  ): Promise<{ isErr: boolean; error?: UpdateError; value?: boolean }> {
127
- // 计算正确路径
128
- const relativePath = getRelativePath(filePath);
129
-
130
- // 检测导入 fileInit
131
- if (!hasImport(root, 'fileInit')) {
132
- return { isErr: false, value: false };
133
- }
134
-
135
- // 查找 fileInit 调用
136
- const [validCalls, invalidCalls] = findFileInitCalls(root);
137
-
138
- // 记录无效调用
139
- if (invalidCalls.length > 0) {
140
- fileId('invalidFileInitCalls').warn(
141
- `⚠️ ${filePath} 包含无效的 fileInit 调用:`,
142
- );
143
- for (const invalid of invalidCalls) {
144
- fileId('invalidFileInitDetail').warn(
145
- ` L${invalid.line} [${invalid.reason}]: ${invalid.text}`,
146
- );
147
- }
148
- }
149
-
150
- // 如果没有有效调用则跳过
151
- if (validCalls.length === 0) {
152
- return { isErr: false, value: false };
143
+ // 计算正确路径
144
+ const relativePath = getRelativePath(filePath)
145
+
146
+ // 检测导入 fileInit
147
+ if (!hasImport(root, "fileInit")) {
148
+ return { isErr: false, value: false }
149
+ }
150
+
151
+ // 查找 fileInit 调用
152
+ const [validCalls, invalidCalls] = findFileInitCalls(root)
153
+
154
+ // 记录无效调用
155
+ if (invalidCalls.length > 0) {
156
+ fileId("invalidFileInitCalls").warn(
157
+ `⚠️ ${filePath} 包含无效的 fileInit 调用:`
158
+ )
159
+ for (const invalid of invalidCalls) {
160
+ fileId("invalidFileInitDetail").warn(
161
+ ` L${invalid.line} [${invalid.reason}]: ${invalid.text}`
162
+ )
153
163
  }
154
-
155
- // 替换内容
156
- const updatedContent = replaceRanges(
157
- content,
158
- validCalls.map((call) => ({
159
- startIndex: call.startOffset,
160
- endIndex: call.endOffset,
161
- replacement: `fileInit('${relativePath}')`,
162
- })),
163
- );
164
-
165
- // 写入文件
166
- if (!dryRun) {
167
- const writeResult = await writeFileAsync(filePath, updatedContent, signal);
168
- if (writeResult.isErr()) {
169
- return {
170
- isErr: true,
171
- error: {
172
- type: 'FILE_ERROR',
173
- message: `无法写入文件: ${writeResult.error.message}`,
174
- filePath,
175
- },
176
- };
177
- }
164
+ }
165
+
166
+ // 如果没有有效调用则跳过
167
+ if (validCalls.length === 0) {
168
+ return { isErr: false, value: false }
169
+ }
170
+
171
+ // 替换内容
172
+ const updatedContent = replaceRanges(
173
+ content,
174
+ validCalls.map((call) => ({
175
+ startIndex: call.startOffset,
176
+ endIndex: call.endOffset,
177
+ replacement: `fileInit('${relativePath}')`,
178
+ }))
179
+ )
180
+
181
+ // 写入文件
182
+ if (!dryRun) {
183
+ const writeResult = await writeFileAsync(filePath, updatedContent, signal)
184
+ if (writeResult.isErr()) {
185
+ return {
186
+ isErr: true,
187
+ error: {
188
+ type: "FILE_ERROR",
189
+ message: `无法写入文件: ${writeResult.error.message}`,
190
+ filePath,
191
+ },
192
+ }
178
193
  }
194
+ }
179
195
 
180
- return { isErr: false, value: true };
196
+ return { isErr: false, value: true }
181
197
  }
182
198
 
183
199
  // ==================== 更新文件执行器 ====================
@@ -186,39 +202,47 @@ async function updateStaticMeta(
186
202
  * 为单个文件创建更新任务 executor
187
203
  */
188
204
  function createUpdateExecutor(
189
- file: string,
190
- dryRun: boolean,
191
- ): (ctx: EffContext) => Promise<Result<{ updated: boolean; file: string } | null, UpdateError>> {
192
- return async ({ signal: innerSignal }: EffContext) => {
193
- if (innerSignal.aborted) {
194
- return err({ type: 'FILE_ERROR', message: '任务已取消', filePath: file });
195
- }
196
-
197
- const readResult = await readFileAsync(file, innerSignal);
198
- if (readResult.isErr()) {
199
- return err({
200
- type: 'FILE_ERROR',
201
- message: `无法读取文件: ${readResult.error.message}`,
202
- filePath: file,
203
- });
204
- }
205
-
206
- const parseResult = await parseFileAsync(file, innerSignal);
207
- if (parseResult.isErr()) {
208
- return err({
209
- type: 'FILE_ERROR',
210
- message: `AST 解析失败: ${parseResult.error.message}`,
211
- filePath: file,
212
- });
213
- }
214
-
215
- const updateResult = await updateStaticMeta(file, readResult.value, parseResult.value.root, dryRun, innerSignal);
216
- if (updateResult.isErr) {
217
- return err(updateResult.error!);
218
- }
219
-
220
- return ok(updateResult.value ? { updated: true, file } : null);
221
- };
205
+ file: string,
206
+ dryRun: boolean
207
+ ): (
208
+ ctx: EffContext
209
+ ) => Promise<Result<{ updated: boolean; file: string } | null, UpdateError>> {
210
+ return async ({ signal: innerSignal }: EffContext) => {
211
+ if (innerSignal.aborted) {
212
+ return err({ type: "FILE_ERROR", message: "任务已取消", filePath: file })
213
+ }
214
+
215
+ const readResult = await readFileAsync(file, innerSignal)
216
+ if (readResult.isErr()) {
217
+ return err({
218
+ type: "FILE_ERROR",
219
+ message: `无法读取文件: ${readResult.error.message}`,
220
+ filePath: file,
221
+ })
222
+ }
223
+
224
+ const parseResult = await parseFileAsync(file, innerSignal)
225
+ if (parseResult.isErr()) {
226
+ return err({
227
+ type: "FILE_ERROR",
228
+ message: `AST 解析失败: ${parseResult.error.message}`,
229
+ filePath: file,
230
+ })
231
+ }
232
+
233
+ const updateResult = await updateStaticMeta(
234
+ file,
235
+ readResult.value,
236
+ parseResult.value.root,
237
+ dryRun,
238
+ innerSignal
239
+ )
240
+ if (updateResult.isErr) {
241
+ return err(updateResult.error!)
242
+ }
243
+
244
+ return ok(updateResult.value ? { updated: true, file } : null)
245
+ }
222
246
  }
223
247
 
224
248
  // ==================== 结果处理与汇总 ====================
@@ -227,108 +251,124 @@ function createUpdateExecutor(
227
251
  * 统计、分类并输出更新结果
228
252
  */
229
253
  function collectAndPrintResults(
230
- results: Result<{ updated: boolean; file: string } | null, UpdateError>[],
231
- files: string[],
232
- dryRun: boolean,
254
+ results: Result<{ updated: boolean; file: string } | null, UpdateError>[],
255
+ files: string[],
256
+ dryRun: boolean
233
257
  ): { updatedFiles: string[]; errors: UpdateError[] } {
234
- const updatedFiles: string[] = [];
235
- const skippedFiles: string[] = [];
236
- const errors: UpdateError[] = [];
237
-
238
- for (let i = 0; i < results.length; i++) {
239
- const result = results[i];
240
- const file = files[i];
241
-
242
- if (result.isErr()) {
243
- errors.push(result.error);
244
- } else if (result.value?.updated) {
245
- updatedFiles.push(getRelativePath(result.value.file));
246
- fileId('updateProgress').info(` ✓ ${getRelativePath(result.value.file)}`);
247
- } else {
248
- skippedFiles.push(getRelativePath(file));
249
- }
258
+ const updatedFiles: string[] = []
259
+ const skippedFiles: string[] = []
260
+ const errors: UpdateError[] = []
261
+
262
+ for (let i = 0; i < results.length; i++) {
263
+ const result = results[i]
264
+ const file = files[i]
265
+
266
+ if (result.isErr()) {
267
+ errors.push(result.error)
268
+ } else if (result.value?.updated) {
269
+ updatedFiles.push(getRelativePath(result.value.file))
270
+ fileId("updateProgress").info(` ✓ ${getRelativePath(result.value.file)}`)
271
+ } else {
272
+ skippedFiles.push(getRelativePath(file))
250
273
  }
274
+ }
275
+
276
+ // 打印汇总
277
+ fileId("summaryHeaderLine1").info(`\n${"=".repeat(60)}`)
278
+ fileId("summaryHeaderLine2").info("更新结果汇总")
279
+ fileId("summaryHeaderLine3").info(`${"=".repeat(60)}`)
280
+
281
+ if (updatedFiles.length > 0) {
282
+ fileId("updateSuccess").info(
283
+ `\n✅ ${dryRun ? "将更新" : "已更新"} ${updatedFiles.length} 个文件`
284
+ )
285
+ }
286
+
287
+ if (skippedFiles.length > 0) {
288
+ fileId("updateSkipped").info(
289
+ `⊘ 已跳过 ${skippedFiles.length} 个文件 (无 fileInit 调用)`
290
+ )
291
+ }
292
+
293
+ if (errors.length > 0) {
294
+ fileId("updateFailed").error(`\n❌ 更新失败 ${errors.length} 个文件:`)
295
+ for (const error of errors) {
296
+ const filePath = error.type === "FILE_ERROR" ? error.filePath : "(验证)"
297
+ const message = error.message
298
+ fileId("updateErrorPath").error(` ${filePath}`)
299
+ fileId("updateErrorMessage").error(` └─ ${message}`)
300
+ }
301
+ }
251
302
 
252
- // 打印汇总
253
- fileId('summaryHeaderLine1').info(`\n${'='.repeat(60)}`);
254
- fileId('summaryHeaderLine2').info('更新结果汇总');
255
- fileId('summaryHeaderLine3').info(`${'='.repeat(60)}`);
303
+ fileId("summaryFooter").info(`${"=".repeat(60)}\n`)
256
304
 
257
- if (updatedFiles.length > 0) {
258
- fileId('updateSuccess').info(
259
- `\n✅ ${dryRun ? '将更新' : '已更新'} ${updatedFiles.length} 个文件`,
260
- );
261
- }
305
+ return { updatedFiles, errors }
306
+ }
262
307
 
263
- if (skippedFiles.length > 0) {
264
- fileId('updateSkipped').info(
265
- `⊘ 已跳过 ${skippedFiles.length} 个文件 (无 fileInit 调用)`,
266
- );
267
- }
308
+ // ==================== 主函数 ====================
268
309
 
269
- if (errors.length > 0) {
270
- fileId('updateFailed').error(`\n❌ 更新失败 ${errors.length} 个文件:`);
271
- for (const error of errors) {
272
- const filePath = error.type === 'FILE_ERROR' ? error.filePath : '(验证)';
273
- const message = error.message;
274
- fileId('updateErrorPath').error(` ${filePath}`);
275
- fileId('updateErrorMessage').error(` └─ ${message}`);
276
- }
310
+ const main = () => {
311
+ return Eff.root(async ({ signal }) => {
312
+ if (signal.aborted) return err(new Error("操作已取消"))
313
+
314
+ const args = process.argv.slice(2)
315
+ const dryRun = args.includes("--dry-run") || args.includes("-n")
316
+
317
+ // 验证 staticMeta 功能
318
+ // const validationResult = validate();
319
+ // if (validationResult.isErr?.()) {
320
+ // fileId('validateFailed').error(
321
+ // '验证失败:',
322
+ // (validationResult as any).error.message,
323
+ // );
324
+ // // 下面的这个 as any 会让类型推断中断,从而无法保证,return 后面的所有语句的推导正确性,再一次证明了 as any 要少用
325
+ // return err((validationResult as any).error);
326
+ // }
327
+
328
+ // 扫描文件
329
+ const files = await scanTypeScriptFilesAsync(["**/staticMeta/idupdate.ts"])
330
+ fileId("updateStart").info(
331
+ `开始更新 fileInit... (共 ${files.length} 个文件)\n`
332
+ )
333
+
334
+ if (files.length === 0) {
335
+ return ok(undefined)
277
336
  }
278
337
 
279
- fileId('summaryFooter').info(`${'='.repeat(60)}\n`);
338
+ // 创建并行更新任务
339
+ type UpdateResult = { updated: boolean; file: string } | null
340
+ const executors = files.map((file) =>
341
+ createUpdateExecutor(file, dryRun)
342
+ ) as unknown as NonEmptyArray<
343
+ (ctx: EffContext) => Promise<Result<UpdateResult, UpdateError>>
344
+ >
280
345
 
281
- return { updatedFiles, errors };
282
- }
346
+ // 执行并行更新
347
+ const allResults = await Eff.allSettled(executors, signal)
348
+ const results = allResults.isOk() ? allResults.value : []
283
349
 
284
- // ==================== 主函数 ====================
350
+ // 统计结果并输出汇总
351
+ const { errors } = collectAndPrintResults(results, files, dryRun)
285
352
 
286
- const main = () => {
287
- return Eff.root(async ({ signal }) => {
288
- if (signal.aborted) return err(new Error('操作已取消'));
289
-
290
- const args = process.argv.slice(2);
291
- const dryRun = args.includes('--dry-run') || args.includes('-n');
292
-
293
- // 验证 staticMeta 功能
294
- const validationResult = validate();
295
- if (validationResult.isErr?.()) {
296
- fileId('validateFailed').error(
297
- '验证失败:',
298
- (validationResult as any).error.message,
299
- );
300
- return err((validationResult as any).error);
301
- }
302
-
303
- // 扫描文件
304
- const files = await scanTypeScriptFilesAsync(['**/staticMeta/idupdate.ts']);
305
- fileId('updateStart').info(`开始更新 fileInit... (共 ${files.length} 个文件)\n`);
306
-
307
- if (files.length === 0) {
308
- return ok(undefined);
309
- }
310
-
311
- // 创建并行更新任务
312
- type UpdateResult = { updated: boolean; file: string } | null;
313
- const executors = files.map((file) =>
314
- createUpdateExecutor(file, dryRun),
315
- ) as unknown as NonEmptyArray<(ctx: EffContext) => Promise<Result<UpdateResult, UpdateError>>>;
316
-
317
- // 执行并行更新
318
- const allResults = await Eff.allSettled(executors, signal);
319
- const results = allResults.isOk() ? allResults.value : [];
320
-
321
- // 统计结果并输出汇总
322
- const { errors } = collectAndPrintResults(results, files, dryRun);
323
-
324
- return errors.length > 0 ? err(errors[0]) : ok(undefined);
325
- });
326
- };
353
+ if (errors.length > 0) {
354
+ const error = errors[0]
355
+ const message =
356
+ error.type === "FILE_ERROR"
357
+ ? `${error.message} (${error.filePath})`
358
+ : error.message
359
+ return err(new Error(message))
360
+ }
361
+
362
+ return ok(undefined)
363
+ })
364
+ }
327
365
 
328
366
  main().match(
329
- () => { /* success */ },
330
- (err) => {
331
- fileId('scriptFailed').error(`脚本执行失败: ${err.message ?? String(err)}`);
332
- process.exit(1);
333
- },
334
- );
367
+ () => {
368
+ /* success */
369
+ },
370
+ (err) => {
371
+ fileId("scriptFailed").error(`脚本执行失败: ${err.message ?? String(err)}`)
372
+ process.exit(1)
373
+ }
374
+ )