autosnippet 1.1.13 → 1.1.15

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/bin/findPath.js CHANGED
@@ -4,189 +4,445 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  // 全局常量
6
6
  const HOLDER_NAME = 'AutoSnippet.boxspec.json';
7
- const BOX_SPEC_NAME = '.boxspec';
8
- const POD_SPEC_NAME = '.podspec';
7
+ const PACKAGE_SWIFT = 'Package.swift';
9
8
  const README_NAME = 'readme.md';
10
- const PCH_NAME = '.pch';
11
9
 
12
- // 向上查找AutoSnippet配置文件
13
- function findASSpecPath(filePath, callback) {
14
- fs.readdir(filePath, function (err, files) {
15
- if (err) {
16
- console.log(err);
17
- return;
18
- }
19
-
20
- let isEnd = false;
21
- files.forEach(function (filename) {
10
+ // 目录缓存(优化:减少重复读取)
11
+ const directoryCache = new Map();
12
+ const CACHE_TTL = 60000; // 缓存 60 秒
22
13
 
23
- if (filename === HOLDER_NAME) {
24
- isEnd = true;
25
- callback(path.join(filePath, filename));
14
+ /**
15
+ * 获取目录内容(带缓存)
16
+ */
17
+ async function getDirectoryEntries(dirPath) {
18
+ const cacheKey = path.resolve(dirPath);
19
+ const cached = directoryCache.get(cacheKey);
20
+
21
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
22
+ return cached.entries;
23
+ }
24
+
25
+ try {
26
+ const entries = await fs.promises.readdir(dirPath, {
27
+ withFileTypes: true // 返回 Dirent 对象,减少 stat 调用
28
+ });
29
+
30
+ // 清理过期缓存(保持缓存大小合理)
31
+ if (directoryCache.size > 1000) {
32
+ const now = Date.now();
33
+ for (const [key, value] of directoryCache.entries()) {
34
+ if (now - value.timestamp >= CACHE_TTL) {
35
+ directoryCache.delete(key);
36
+ }
26
37
  }
27
- });
28
-
29
- if (isEnd) {
30
- return;
31
38
  }
32
-
33
- if (!isEnd && filePath !== '/' && filePath !== '/Users') {
34
- findASSpecPath(path.join(filePath, '/..'), callback);
35
- } else {
36
- console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
39
+
40
+ directoryCache.set(cacheKey, {
41
+ entries,
42
+ timestamp: Date.now()
43
+ });
44
+
45
+ return entries;
46
+ } catch (err) {
47
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
48
+ return null; // 路径不存在或权限不足
37
49
  }
38
- });
50
+ throw err;
51
+ }
39
52
  }
40
53
 
41
- // 向上查找.pch文件
42
- function findPCHPath(filePath, callback) {
43
- fs.readdir(filePath, function (err, files) {
44
- if (err) {
45
- console.log(err);
46
- return;
54
+ /**
55
+ * 检测是否为工程根目录
56
+ * 通过查找常见的工程根目录标记来判断
57
+ */
58
+ function isProjectRoot(dirPath, entries) {
59
+ if (!entries) {
60
+ return false;
61
+ }
62
+
63
+ // 工程根目录标记(按优先级)
64
+ const rootMarkers = [
65
+ '.git', // Git 仓库根目录
66
+ PACKAGE_SWIFT, // SPM 项目根目录
67
+ '.xcodeproj', // Xcode 项目根目录
68
+ '.xcworkspace', // Xcode 工作空间根目录
69
+ 'Podfile', // CocoaPods 项目根目录
70
+ '.swiftpm', // Swift Package Manager 元数据目录
71
+ ];
72
+
73
+ for (const entry of entries) {
74
+ const name = entry.name;
75
+
76
+ // 检查文件标记
77
+ if (entry.isFile() && rootMarkers.includes(name)) {
78
+ return true;
47
79
  }
80
+
81
+ // 检查目录标记(.git 可能是目录)
82
+ if (entry.isDirectory() && rootMarkers.includes(name)) {
83
+ return true;
84
+ }
85
+ }
86
+
87
+ return false;
88
+ }
48
89
 
49
- let isEnd = false;
50
- files.forEach(function (filename) {
51
-
52
- if (filename.endsWith(PCH_NAME)) {
53
- isEnd = true;
54
- callback(true, path.join(filePath, filename));
90
+ // 向上查找AutoSnippet配置文件(优化:找到配置文件后,继续向上查找直到找到工程根目录)
91
+ async function findASSpecPathAsync(filePath) {
92
+ let configPath = null;
93
+ let configDir = null;
94
+ let currentPath = path.resolve(filePath);
95
+ const maxLevels = 20; // 安全限制:最多向上查找 20 层
96
+ let levelsChecked = 0;
97
+
98
+ while (currentPath && levelsChecked < maxLevels) {
99
+ try {
100
+ const entries = await getDirectoryEntries(currentPath);
101
+
102
+ // 查找 AutoSnippet.boxspec.json
103
+ if (entries) {
104
+ for (const entry of entries) {
105
+ if (entry.isFile() && entry.name === HOLDER_NAME) {
106
+ if (!configPath) {
107
+ // 第一次找到配置文件
108
+ configPath = path.join(currentPath, entry.name);
109
+ configDir = currentPath;
110
+ }
111
+ }
112
+ }
55
113
  }
56
- });
57
-
58
- if (isEnd) {
59
- return;
114
+
115
+ // 如果已经找到配置文件,检查是否到达工程根目录
116
+ if (configPath) {
117
+ // 如果在配置文件所在目录或以上发现了工程根目录标记,停止查找
118
+ if (isProjectRoot(currentPath, entries)) {
119
+ return configPath;
120
+ }
121
+
122
+ // 如果已经向上查找了,但没有找到工程根目录标记,继续查找
123
+ if (path.resolve(currentPath) !== configDir) {
124
+ // 当前目录不是配置文件所在目录,继续向上查找
125
+ }
126
+ }
127
+
128
+ // 继续向上查找
129
+ const parentPath = path.dirname(currentPath);
130
+ if (parentPath === currentPath) {
131
+ // 已到达根目录
132
+ break;
133
+ }
134
+ currentPath = parentPath;
135
+ levelsChecked++;
136
+
137
+ } catch (err) {
138
+ // 错误处理:权限错误继续查找
139
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
140
+ const parentPath = path.dirname(currentPath);
141
+ if (parentPath === currentPath) {
142
+ break;
143
+ }
144
+ currentPath = parentPath;
145
+ levelsChecked++;
146
+ continue;
147
+ }
148
+ // 其他错误,抛出
149
+ throw err;
60
150
  }
151
+ }
152
+
153
+ // 如果找到配置文件,返回它(即使没有找到明确的工程根目录)
154
+ return configPath || null;
155
+ }
61
156
 
62
- if (!isEnd && filePath !== '/' && filePath !== '/Users') {
63
- findPCHPath(path.join(filePath, '/..'), callback);
64
- } else {
65
- callback(false, null);
157
+ /**
158
+ * 同步版本:检测是否为工程根目录
159
+ */
160
+ function isProjectRootSync(dirPath, files) {
161
+ if (!files || files.length === 0) {
162
+ return false;
163
+ }
164
+
165
+ // 工程根目录标记(按优先级)
166
+ const rootMarkers = [
167
+ '.git', // Git 仓库根目录
168
+ PACKAGE_SWIFT, // SPM 项目根目录
169
+ '.xcodeproj', // Xcode 项目根目录
170
+ '.xcworkspace', // Xcode 工作空间根目录
171
+ 'Podfile', // CocoaPods 项目根目录
172
+ ];
173
+
174
+ // 检查文件名
175
+ for (const filename of files) {
176
+ if (rootMarkers.includes(filename)) {
177
+ // 检查是否为文件或目录
178
+ try {
179
+ const filePath = path.join(dirPath, filename);
180
+ const stats = fs.lstatSync(filePath);
181
+ if (stats.isFile() || stats.isDirectory()) {
182
+ return true;
183
+ }
184
+ } catch (err) {
185
+ // 忽略错误,继续检查
186
+ }
66
187
  }
67
- });
188
+ }
189
+
190
+ return false;
68
191
  }
69
192
 
70
- // 向上查找模块.spec文件
71
- function findBPSpacPath(filePath, callback) {
72
- fs.readdir(filePath, function (err, files) {
73
- if (err) {
74
- console.log(err);
193
+ // 向上查找AutoSnippet配置文件(保留回调版本,兼容现有代码)
194
+ function findASSpecPath(filePath, callback, configPath, configDir) {
195
+ // 初始化参数
196
+ if (configPath === undefined) configPath = null;
197
+ if (configDir === undefined) configDir = null;
198
+
199
+ const maxLevels = 20; // 安全限制:最多向上查找 20 层
200
+ let levelsChecked = 0;
201
+
202
+ function search(currentPath, foundConfigPath, foundConfigDir, level) {
203
+ if (level >= maxLevels) {
204
+ if (foundConfigPath) {
205
+ callback(foundConfigPath);
206
+ } else {
207
+ console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
208
+ }
75
209
  return;
76
210
  }
211
+
212
+ fs.readdir(currentPath, function (err, files) {
213
+ if (err) {
214
+ // 错误处理:权限错误继续查找
215
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
216
+ const parentPath = path.join(currentPath, '/..');
217
+ const parentResolvedPath = path.resolve(parentPath);
218
+
219
+ // 如果已经到达根目录,停止查找
220
+ if (parentResolvedPath === path.resolve(currentPath)) {
221
+ if (foundConfigPath) {
222
+ callback(foundConfigPath);
223
+ } else {
224
+ console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
225
+ }
226
+ return;
227
+ }
228
+
229
+ search(parentPath, foundConfigPath, foundConfigDir, level + 1);
230
+ } else {
231
+ console.log(err);
232
+ }
233
+ return;
234
+ }
77
235
 
78
- let isError = false;
79
- let isEnd = false;
80
- let readme = null;
81
- let specName = null;
236
+ let isFound = false;
237
+ let currentConfigPath = foundConfigPath;
238
+ let currentConfigDir = foundConfigDir;
239
+
240
+ // 查找 AutoSnippet.boxspec.json
241
+ files.forEach(function (filename) {
242
+ if (filename === HOLDER_NAME) {
243
+ isFound = true;
244
+ if (!currentConfigPath) {
245
+ // 第一次找到配置文件,记录路径(不立即调用回调)
246
+ currentConfigPath = path.join(currentPath, filename);
247
+ currentConfigDir = path.resolve(currentPath);
248
+ }
249
+ }
250
+ });
82
251
 
83
- files.forEach(function (filename) {
84
- if (filename === HOLDER_NAME) {
85
- isError = true;
86
- }
87
- if (filename.toLowerCase() === README_NAME) {
88
- readme = filename;
252
+ // 如果已经找到配置文件,检查是否到达工程根目录
253
+ if (currentConfigPath) {
254
+ // 如果在配置文件所在目录或以上发现了工程根目录标记,停止查找并调用回调
255
+ if (isProjectRootSync(currentPath, files)) {
256
+ callback(currentConfigPath);
257
+ return;
258
+ }
89
259
  }
90
- if (filename.endsWith(BOX_SPEC_NAME) || filename.endsWith(POD_SPEC_NAME)) {
91
- const dotIndex = filename.lastIndexOf('.');
92
- const ext = filename.substr(dotIndex);
93
260
 
94
- if (ext === BOX_SPEC_NAME || ext === POD_SPEC_NAME) {
95
- specName = filename;
96
- isEnd = true;
97
- }
261
+ // 继续向上查找
262
+ const parentPath = path.join(currentPath, '/..');
263
+ const parentResolvedPath = path.resolve(parentPath);
264
+
265
+ // 如果已经到达根目录,停止查找
266
+ if (parentResolvedPath === path.resolve(currentPath)) {
267
+ // 如果找到了配置文件,即使没找到工程根目录标记,也调用回调
268
+ if (currentConfigPath) {
269
+ callback(currentConfigPath);
270
+ } else {
271
+ console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
272
+ }
273
+ return;
98
274
  }
99
- });
100
-
101
- if (isEnd && specName) {
102
- callback(filePath, specName, readme);
103
- return;
104
- }
105
275
 
106
- if (isError) {
107
- console.log('先找到了 AutoSnippet.boxspec.json 文件,请检查路径。');
108
- return;
109
- }
276
+ search(parentPath, currentConfigPath, currentConfigDir, level + 1);
277
+ });
278
+ }
279
+
280
+ search(filePath, configPath, configDir, 0);
281
+ }
110
282
 
111
- if (!isEnd && filePath !== '/' && filePath !== '/Users' && filePath !== '/LocalModule') {
112
- findBPSpacPath(path.join(filePath, '/..'), callback);
113
- } else {
114
- console.log('未找到 .boxspec 文件,请检查路径。');
283
+ // 向上查找 Package.swift 文件(SPM 模块规范文件)
284
+ async function findPackageSwiftPath(filePath) {
285
+ let currentPath = path.resolve(filePath);
286
+
287
+ while (currentPath) {
288
+ try {
289
+ const entries = await getDirectoryEntries(currentPath);
290
+ if (!entries) {
291
+ const parentPath = path.dirname(currentPath);
292
+ if (parentPath === currentPath) {
293
+ break;
294
+ }
295
+ currentPath = parentPath;
296
+ continue;
297
+ }
298
+
299
+ // 查找 Package.swift 文件
300
+ for (const entry of entries) {
301
+ if (entry.isFile() && entry.name === PACKAGE_SWIFT) {
302
+ return path.join(currentPath, entry.name);
303
+ }
304
+ }
305
+
306
+ // 继续向上查找
307
+ const parentPath = path.dirname(currentPath);
308
+ if (parentPath === currentPath) {
309
+ break;
310
+ }
311
+ currentPath = parentPath;
312
+
313
+ } catch (err) {
314
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
315
+ const parentPath = path.dirname(currentPath);
316
+ if (parentPath === currentPath) {
317
+ break;
318
+ }
319
+ currentPath = parentPath;
320
+ continue;
321
+ }
322
+ throw err;
115
323
  }
116
- });
324
+ }
325
+
326
+ return null;
117
327
  }
118
328
 
119
- // 向下查找模块默认头文件
120
- async function findSubHeaderPath(filePath, headerName) {
121
- let resultPath = null;
122
-
329
+ /**
330
+ * 解析 Package.swift 文件,提取模块信息
331
+ */
332
+ async function parsePackageSwift(packagePath) {
123
333
  try {
124
- const files = fs.readdirSync(filePath);
125
-
126
- for (let i = 0; i < files.length; i++) {
127
- const filename = files[i];
128
- const filedir = path.join(filePath, filename);
334
+ const content = await fs.promises.readFile(packagePath, 'utf8');
335
+
336
+ // 简单解析 Package.swift(实际可以使用 Swift AST 解析器)
337
+ const packageNameMatch = content.match(/name:\s*"([^"]+)"/);
338
+ const targetsMatch = content.match(/\.target\s*\([^)]+name:\s*"([^"]+)"/g);
339
+
340
+ const packageName = packageNameMatch ? packageNameMatch[1] : null;
341
+ const targets = [];
342
+
343
+ if (targetsMatch) {
344
+ targetsMatch.forEach(match => {
345
+ const targetMatch = match.match(/name:\s*"([^"]+)"/);
346
+ if (targetMatch) {
347
+ targets.push(targetMatch[1]);
348
+ }
349
+ });
350
+ }
351
+
352
+ return {
353
+ name: packageName,
354
+ targets: targets,
355
+ path: path.dirname(packagePath)
356
+ };
357
+ } catch (err) {
358
+ console.error('Error parsing Package.swift:', err);
359
+ return null;
360
+ }
361
+ }
129
362
 
130
- if (filename === (headerName + '.h')) {
131
- resultPath = filedir;
132
- break;
133
- } else {
363
+ // 向下查找模块头文件(优化:适配 SPM 结构,优先查找 include/ModuleName/ 目录)
364
+ async function findSubHeaderPath(filePath, headerName, moduleName) {
365
+ // ✅ 优先查找 include/ModuleName/ 目录(SPM 结构)
366
+ if (moduleName) {
367
+ const includePath = path.join(filePath, 'include', moduleName);
368
+ try {
369
+ const stats = await fs.promises.stat(includePath);
370
+ if (stats.isDirectory()) {
371
+ const headerPath = path.join(includePath, `${headerName}.h`);
134
372
  try {
135
- // 读取路径是否为文件
136
- const stats = fs.lstatSync(filedir);
137
- const isDirectory = stats.isDirectory();
138
- if (isDirectory) {
139
- const result = await findSubHeaderPath(filedir, headerName);
140
- if (result) {
141
- resultPath = result;
142
- }
143
- }
144
- } catch (err) {
145
- console.error(err);
373
+ await fs.promises.access(headerPath);
374
+ return headerPath;
375
+ } catch {
376
+ // 头文件不存在,继续查找
377
+ }
378
+ }
379
+ } catch {
380
+ // include 目录不存在,继续查找
381
+ }
382
+ }
383
+
384
+ // ✅ 降级:在整个模块目录中查找(深度限制)
385
+ try {
386
+ const entries = await getDirectoryEntries(filePath);
387
+ if (!entries) {
388
+ return null;
389
+ }
390
+
391
+ for (const entry of entries) {
392
+ if (entry.isFile() && entry.name === `${headerName}.h`) {
393
+ return path.join(filePath, entry.name);
394
+ } else if (entry.isDirectory()) {
395
+ // 跳过 node_modules、.git 等目录
396
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
397
+ continue;
398
+ }
399
+
400
+ const result = await findSubHeaderPath(path.join(filePath, entry.name), headerName, null);
401
+ if (result) {
402
+ return result;
146
403
  }
147
404
  }
148
405
  }
149
406
  } catch (err) {
150
- console.log(err);
407
+ console.error(err);
151
408
  }
152
- return resultPath;
409
+
410
+ return null;
153
411
  }
154
412
 
155
- // 向下查找AutoSnippet配置文件
413
+ // 向下查找AutoSnippet配置文件(优化:异步 I/O)
156
414
  async function findSubASSpecPath(filePath) {
157
415
  let resultArray = [];
158
416
 
159
417
  try {
160
- const files = fs.readdirSync(filePath);
161
-
162
- for (let i = 0; i < files.length; i++) {
163
- const filename = files[i];
164
- const filedir = path.join(filePath, filename);
418
+ const entries = await getDirectoryEntries(filePath);
419
+ if (!entries) {
420
+ return resultArray;
421
+ }
165
422
 
166
- if (filename === HOLDER_NAME) {
167
- resultArray.push(filedir);
168
- } else {
169
- try {
170
- // 读取路径是否为文件
171
- const stats = fs.lstatSync(filedir);
172
- const isDirectory = stats.isDirectory();
173
- if (isDirectory) {
174
- const array = await findSubASSpecPath(filedir);
175
- resultArray = resultArray.concat(array);
176
- }
177
- } catch (err) {
178
- console.error(err);
423
+ for (const entry of entries) {
424
+ if (entry.isFile() && entry.name === HOLDER_NAME) {
425
+ resultArray.push(path.join(filePath, entry.name));
426
+ } else if (entry.isDirectory()) {
427
+ // 跳过 node_modules、.git 等目录
428
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') {
429
+ continue;
179
430
  }
431
+
432
+ const array = await findSubASSpecPath(path.join(filePath, entry.name));
433
+ resultArray = resultArray.concat(array);
180
434
  }
181
435
  }
182
436
  } catch (err) {
183
437
  console.log(err);
184
438
  }
439
+
185
440
  return resultArray;
186
441
  }
187
442
 
188
443
  exports.findASSpecPath = findASSpecPath;
189
- exports.findBPSpacPath = findBPSpacPath;
190
- exports.findPCHPath = findPCHPath;
444
+ exports.findASSpecPathAsync = findASSpecPathAsync;
445
+ exports.findPackageSwiftPath = findPackageSwiftPath;
446
+ exports.parsePackageSwift = parsePackageSwift;
191
447
  exports.findSubHeaderPath = findSubHeaderPath;
192
- exports.findSubASSpecPath = findSubASSpecPath;
448
+ exports.findSubASSpecPath = findSubASSpecPath;
package/bin/init.js CHANGED
@@ -47,10 +47,14 @@ async function initSpec() {
47
47
  return false;
48
48
  }
49
49
  }
50
- idsArray.push(item['{identifier}']);
51
- // 头文件相对路径需要补齐
50
+ idsArray.push(item['{identifier}']);
51
+ // ✅ 头文件相对路径需要补齐(将子配置的相对路径转换为相对于主配置的路径)
52
52
  if (item['{specHeadPath}']) {
53
- item['{specHeadPath}'] = thePath + item['{specHeadPath}'];
53
+ // 解码原始路径,拼接子配置路径,再编码
54
+ const decodedPath = decodeURI(item['{specHeadPath}']);
55
+ const combinedPath = thePath ? path.join(thePath, decodedPath) : decodedPath;
56
+ // 规范化路径(处理 .. 和 .)
57
+ item['{specHeadPath}'] = encodeURI(path.normalize(combinedPath));
54
58
  }
55
59
  return true;
56
60
  });