autosnippet 1.1.18 → 1.1.20

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/asnip.js CHANGED
@@ -226,4 +226,31 @@ commander
226
226
  });
227
227
  });
228
228
 
229
+ commander
230
+ .command('root')
231
+ .description('mark current directory as project root by creating AutoSnippetRoot.boxspec.json')
232
+ .action(() => {
233
+ const fs = require('fs');
234
+ const path = require('path');
235
+ const rootMarkerPath = path.join(CMD_PATH, 'AutoSnippetRoot.boxspec.json');
236
+
237
+ try {
238
+ // 检查文件是否已存在
239
+ fs.accessSync(rootMarkerPath, fs.constants.F_OK);
240
+ console.log(`根目录标记文件已存在: ${rootMarkerPath}`);
241
+ } catch (err) {
242
+ // 文件不存在,创建它
243
+ try {
244
+ // 创建一个空的标记文件
245
+ fs.writeFileSync(rootMarkerPath, JSON.stringify({
246
+ root: true,
247
+ description: 'This file marks the project root directory for AutoSnippet'
248
+ }, null, 2), 'utf8');
249
+ console.log(`已创建根目录标记文件: ${rootMarkerPath}`);
250
+ } catch (writeErr) {
251
+ console.error(`创建根目录标记文件失败: ${writeErr.message}`);
252
+ }
253
+ }
254
+ });
255
+
229
256
  commander.parse(process.argv);
package/bin/create.js CHANGED
@@ -41,6 +41,51 @@ function determineModuleName(filePath, packageInfo) {
41
41
  return packageInfo.name;
42
42
  }
43
43
 
44
+ /**
45
+ * 从文件路径确定 target 的根目录(包含 Code 或 Sources 的目录)
46
+ * @param {string} filePath - 文件路径
47
+ * @returns {Promise<string|null>} target 根目录路径,如果找不到返回 null
48
+ */
49
+ async function findTargetRootDir(filePath) {
50
+ const fs = require('fs');
51
+ let currentPath = path.dirname(path.resolve(filePath));
52
+ const maxLevels = 10;
53
+ let levelsChecked = 0;
54
+
55
+ // ✅ 向上查找包含 Code 或 Sources 目录的目录(target 根目录)
56
+ // 例如:BDNetworkAPI/Code/xxx.m -> BDNetworkAPI/
57
+ while (currentPath && levelsChecked < maxLevels) {
58
+ try {
59
+ const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
60
+
61
+ // 检查当前目录是否包含 Code 或 Sources 目录
62
+ for (const entry of entries) {
63
+ if (entry.isDirectory() && (entry.name === 'Code' || entry.name === 'Sources')) {
64
+ // 找到包含 Code 或 Sources 的目录,这就是 target 根目录
65
+ console.log(`[findTargetRootDir] 找到 target 根目录: ${currentPath} (包含 ${entry.name})`);
66
+ return currentPath;
67
+ }
68
+ }
69
+
70
+ // 继续向上查找
71
+ const parentPath = path.dirname(currentPath);
72
+ if (parentPath === currentPath) {
73
+ break;
74
+ }
75
+ currentPath = parentPath;
76
+ levelsChecked++;
77
+ } catch (err) {
78
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
79
+ break;
80
+ }
81
+ throw err;
82
+ }
83
+ }
84
+
85
+ console.log(`[findTargetRootDir] 未找到 target 根目录(Code 或 Sources)`);
86
+ return null;
87
+ }
88
+
44
89
  function updateCodeSnippets(specFile, word, key, value) {
45
90
  if (key && key !== 'title' && key !== 'link' && key !== 'summary') {
46
91
  console.log('此项属性不存在或不可修改。');
@@ -224,7 +269,7 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
224
269
  }
225
270
  });
226
271
 
227
- rl.on('close', function () {
272
+ rl.on('close', async function () {
228
273
  if (codeList.length > 1) {
229
274
  codeList.pop();
230
275
 
@@ -243,9 +288,14 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
243
288
  if (!packagePath) {
244
289
  console.log('未找到 Package.swift 文件,请检查路径。');
245
290
  snippet['{content}'] = codeList;
246
- // ✅ SPM 模块:.boxspec 文件位置在模块根目录(与 Package.swift 同级)
247
- const moduleSpecFile = path.join(path.dirname(packagePath), 'AutoSnippet.boxspec.json');
248
- saveFromFile(moduleSpecFile, snippet);
291
+ // ✅ 查找 target 根目录(Code Sources 的父目录)
292
+ let targetRootDir = await findTargetRootDir(filePath);
293
+ if (!targetRootDir) {
294
+ // 如果找不到,使用文件所在目录的父目录作为后备
295
+ targetRootDir = path.dirname(path.dirname(filePath));
296
+ }
297
+ const moduleSpecFile = path.join(targetRootDir, 'AutoSnippet.boxspec.json');
298
+ await saveFromFile(moduleSpecFile, snippet);
249
299
  return;
250
300
  }
251
301
 
@@ -253,8 +303,14 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
253
303
  const packageInfo = await findPath.parsePackageSwift(packagePath);
254
304
  if (!packageInfo) {
255
305
  snippet['{content}'] = codeList;
256
- const moduleSpecFile = path.join(path.dirname(packagePath), 'AutoSnippet.boxspec.json');
257
- saveFromFile(moduleSpecFile, snippet);
306
+ // 查找 target 根目录(Code 或 Sources 的父目录)
307
+ let targetRootDir = await findTargetRootDir(filePath);
308
+ if (!targetRootDir) {
309
+ // 如果找不到,使用 Package.swift 所在目录作为后备
310
+ targetRootDir = path.dirname(packagePath);
311
+ }
312
+ const moduleSpecFile = path.join(targetRootDir, 'AutoSnippet.boxspec.json');
313
+ await saveFromFile(moduleSpecFile, snippet);
258
314
  return;
259
315
  }
260
316
 
@@ -262,50 +318,54 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
262
318
  const moduleName = determineModuleName(filePath, packageInfo);
263
319
  const headerNameWithoutExt = fileName.substring(0, fileName.length - 2); // 移除 .h
264
320
 
265
- // ✅ 模块根目录就是 Package.swift 所在目录(packageInfo.path)
266
- // .boxspec.json 文件将创建在这个目录下(与 Package.swift 同级)
267
- const moduleRoot = packageInfo.path;
321
+ // ✅ 查找 target 根目录(Code 或 Sources 的父目录)
322
+ let targetRootDir = await findTargetRootDir(filePath);
323
+ if (!targetRootDir) {
324
+ // 如果找不到 target 根目录,使用 Package.swift 所在目录作为后备
325
+ targetRootDir = packageInfo.path;
326
+ }
327
+ console.log(`[createCodeSnippets] target 根目录: ${targetRootDir}`);
328
+ const moduleRoot = targetRootDir;
268
329
 
269
330
  // ✅ 查找头文件(适配 SPM 的 include/ModuleName/ 结构)
270
- // 在 Package.swift 所在目录下查找(可能包含 Services/Services/... 这样的结构)
271
- const headerPath = await findPath.findSubHeaderPath(moduleRoot, headerNameWithoutExt, moduleName);
331
+ // 在 target 根目录下查找(可能包含 Code/ 或 Sources/ 这样的结构)
332
+ const headerPath = await findPath.findSubHeaderPath(targetRootDir, headerNameWithoutExt, moduleName);
272
333
 
273
334
  snippet['{content}'] = codeList;
274
335
  snippet['{specName}'] = moduleName; // ✅ specName 是 target 名称(如 BDNetworkAPI),不是包名(如 Business)
275
336
 
276
337
  if (headerPath) {
277
- // ✅ headName 存储相对于 Package.swift 所在目录的相对路径
278
- // 例如:如果 Package.swift 在 Business/,头文件在 Business/Business/BDNetworkAPI/Code/xxx.h
279
- // 则 headName = "Business/BDNetworkAPI/Code/xxx.h"
280
- const headerRelativePath = path.relative(moduleRoot, headerPath);
338
+ // ✅ headName 存储相对于 target 根目录的相对路径
339
+ // 例如:target 根目录在 BDNetworkAPI/,头文件在 BDNetworkAPI/Code/xxx.h
340
+ // 则 headName = "Code/xxx.h"
341
+ const headerRelativePath = path.relative(targetRootDir, headerPath);
281
342
  snippet['{headName}'] = headerRelativePath;
282
343
  } else {
283
344
  // 如果找不到头文件,使用文件名
284
345
  snippet['{headName}'] = fileName;
285
346
  }
286
347
 
287
- // 查找 README.md
348
+ // 查找 README.md(在 target 根目录)
288
349
  try {
289
- const readmePath = path.join(packageInfo.path, README_NAME);
350
+ const readmePath = path.join(targetRootDir, README_NAME);
290
351
  await fs.promises.access(readmePath);
291
- const moduleRoot = packageInfo.path;
292
- const readmeRelativePath = path.relative(moduleRoot, readmePath);
352
+ const readmeRelativePath = path.relative(targetRootDir, readmePath);
293
353
  snippet['{readme}'] = encodeURI(readmeRelativePath);
294
354
  } catch {
295
355
  // README.md 不存在,跳过
296
356
  }
297
357
 
298
- // ✅ SPM 模块:.boxspec 文件位置在模块根目录(与 Package.swift 同级)
299
- const moduleSpecFile = path.join(packageInfo.path, 'AutoSnippet.boxspec.json');
300
- saveFromFile(moduleSpecFile, snippet);
301
- }).catch(function (err) {
358
+ // ✅ SPM 模块:.boxspec 文件位置在 target 根目录(Code 或 Sources 的父目录)
359
+ const moduleSpecFile = path.join(targetRootDir, 'AutoSnippet.boxspec.json');
360
+ await saveFromFile(moduleSpecFile, snippet);
361
+ }).catch(async function (err) {
302
362
  console.error('Error finding Package.swift:', err);
303
363
  snippet['{content}'] = codeList;
304
- saveFromFile(specFile, snippet);
364
+ await saveFromFile(specFile, snippet);
305
365
  });
306
366
  } else {
307
367
  snippet['{content}'] = codeList;
308
- saveFromFile(specFile, snippet);
368
+ await saveFromFile(specFile, snippet);
309
369
  }
310
370
  // 移除ACode标识
311
371
  removeAcodeMark(filePath, positionList);
@@ -315,7 +375,8 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
315
375
  });
316
376
  }
317
377
 
318
- function saveFromFile(specFile, snippet) {
378
+ async function saveFromFile(specFile, snippet) {
379
+ const findPath = require('./findPath.js');
319
380
  let placeholder = null;
320
381
 
321
382
  try {
@@ -370,7 +431,58 @@ function saveFromFile(specFile, snippet) {
370
431
  console.log(err);
371
432
  }
372
433
  cache.updateCache(specFile, content);
373
- // ✅ 只写入刚创建的代码片段,而不是所有代码片段
434
+
435
+ // ✅ 同步到根目录的 AutoSnippetRoot.boxspec.json
436
+ try {
437
+ const rootSpecFile = await findPath.getRootSpecFilePath(specFile);
438
+ if (rootSpecFile) {
439
+ // 读取根配置文件
440
+ let rootPlaceholder = null;
441
+ try {
442
+ const rootData = fs.readFileSync(rootSpecFile, 'utf8');
443
+ if (rootData) {
444
+ rootPlaceholder = JSON.parse(rootData);
445
+ }
446
+ } catch (err) {
447
+ if (err.code === 'ENOENT') {
448
+ rootPlaceholder = { list: [] };
449
+ } else {
450
+ console.error(`[saveFromFile] 读取根配置文件失败: ${err.message}`);
451
+ }
452
+ }
453
+
454
+ if (rootPlaceholder != null) {
455
+ // 检查是否已存在相同的 identifier
456
+ let exists = false;
457
+ if (rootPlaceholder.list) {
458
+ for (let i = 0; i < rootPlaceholder.list.length; i++) {
459
+ if (rootPlaceholder.list[i]['{identifier}'] === snippet['{identifier}']) {
460
+ // 已存在,更新它
461
+ rootPlaceholder.list[i] = snippet;
462
+ exists = true;
463
+ break;
464
+ }
465
+ }
466
+ } else {
467
+ rootPlaceholder.list = [];
468
+ }
469
+
470
+ // 如果不存在,添加到列表
471
+ if (!exists) {
472
+ rootPlaceholder.list.push(snippet);
473
+ }
474
+
475
+ // 写入根配置文件
476
+ const rootContent = JSON.stringify(rootPlaceholder, null, 4);
477
+ fs.writeFileSync(rootSpecFile, rootContent, 'utf8');
478
+ console.log(`[saveFromFile] 已同步 snippet 到根目录配置文件: ${rootSpecFile}`);
479
+ }
480
+ }
481
+ } catch (err) {
482
+ console.error(`[saveFromFile] 同步到根配置文件失败: ${err.message}`);
483
+ }
484
+
485
+ // ✅ 只写入刚创建的单个代码片段
374
486
  install.addCodeSnippets(specFile, snippet);
375
487
  }
376
488
  }
package/bin/findPath.js CHANGED
@@ -4,6 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  // 全局常量
6
6
  const HOLDER_NAME = 'AutoSnippet.boxspec.json';
7
+ const ROOT_MARKER_NAME = 'AutoSnippetRoot.boxspec.json'; // 项目根目录标记文件
7
8
  const PACKAGE_SWIFT = 'Package.swift';
8
9
  const README_NAME = 'readme.md';
9
10
 
@@ -16,6 +17,23 @@ const CACHE_TTL = 60000; // 缓存 60 秒
16
17
  */
17
18
  async function getDirectoryEntries(dirPath) {
18
19
  const cacheKey = path.resolve(dirPath);
20
+
21
+ // ✅ 首先检查路径是否是文件,如果是文件,立即返回 null(避免 ENOTDIR 错误)
22
+ // 注意:这个检查要在缓存检查之前,因为缓存可能包含错误的条目
23
+ try {
24
+ const stats = await fs.promises.stat(dirPath);
25
+ if (stats.isFile()) {
26
+ // 如果是文件,清除可能的错误缓存,然后返回 null
27
+ console.log(`[getDirectoryEntries] 路径是文件,返回 null: ${dirPath}`);
28
+ directoryCache.delete(cacheKey);
29
+ return null;
30
+ }
31
+ } catch (err) {
32
+ // 如果 stat 失败,可能是路径不存在或其他错误,继续处理
33
+ // 如果是 ENOTDIR,会在下面的 readdir 中被捕获
34
+ console.log(`[getDirectoryEntries] stat 失败: ${dirPath}, 错误: ${err.code || err.message}`);
35
+ }
36
+
19
37
  const cached = directoryCache.get(cacheKey);
20
38
 
21
39
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
@@ -23,6 +41,7 @@ async function getDirectoryEntries(dirPath) {
23
41
  }
24
42
 
25
43
  try {
44
+ console.log(`[getDirectoryEntries] 尝试读取目录: ${dirPath}`);
26
45
  const entries = await fs.promises.readdir(dirPath, {
27
46
  withFileTypes: true // 返回 Dirent 对象,减少 stat 调用
28
47
  });
@@ -44,8 +63,10 @@ async function getDirectoryEntries(dirPath) {
44
63
 
45
64
  return entries;
46
65
  } catch (err) {
47
- if (err.code === 'ENOENT' || err.code === 'EACCES') {
48
- return null; // 路径不存在或权限不足
66
+ // 处理各种文件系统错误
67
+ console.log(`[getDirectoryEntries] readdir 失败: ${dirPath}, 错误: ${err.code || err.message}`);
68
+ if (err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'ENOTDIR') {
69
+ return null; // 路径不存在、权限不足或不是目录
49
70
  }
50
71
  throw err;
51
72
  }
@@ -208,7 +229,7 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
208
229
  }
209
230
  return;
210
231
  }
211
-
232
+
212
233
  fs.readdir(currentPath, function (err, files) {
213
234
  if (err) {
214
235
  // 错误处理:权限错误继续查找
@@ -220,7 +241,7 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
220
241
  if (parentResolvedPath === path.resolve(currentPath)) {
221
242
  if (foundConfigPath) {
222
243
  callback(foundConfigPath);
223
- } else {
244
+ } else {
224
245
  console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
225
246
  }
226
247
  return;
@@ -228,34 +249,34 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
228
249
 
229
250
  search(parentPath, foundConfigPath, foundConfigDir, level + 1);
230
251
  } else {
231
- console.log(err);
252
+ console.log(err);
232
253
  }
233
- return;
234
- }
254
+ return;
255
+ }
235
256
 
236
257
  let isFound = false;
237
258
  let currentConfigPath = foundConfigPath;
238
259
  let currentConfigDir = foundConfigDir;
239
260
 
240
261
  // 查找 AutoSnippet.boxspec.json
241
- files.forEach(function (filename) {
242
- if (filename === HOLDER_NAME) {
262
+ files.forEach(function (filename) {
263
+ if (filename === HOLDER_NAME) {
243
264
  isFound = true;
244
265
  if (!currentConfigPath) {
245
266
  // 第一次找到配置文件,记录路径(不立即调用回调)
246
267
  currentConfigPath = path.join(currentPath, filename);
247
268
  currentConfigDir = path.resolve(currentPath);
248
- }
249
- }
250
- });
269
+ }
270
+ }
271
+ });
251
272
 
252
273
  // 如果已经找到配置文件,检查是否到达工程根目录
253
274
  if (currentConfigPath) {
254
275
  // 如果在配置文件所在目录或以上发现了工程根目录标记,停止查找并调用回调
255
276
  if (isProjectRootSync(currentPath, files)) {
256
277
  callback(currentConfigPath);
257
- return;
258
- }
278
+ return;
279
+ }
259
280
  }
260
281
 
261
282
  // 继续向上查找
@@ -270,8 +291,8 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
270
291
  } else {
271
292
  console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
272
293
  }
273
- return;
274
- }
294
+ return;
295
+ }
275
296
 
276
297
  search(parentPath, currentConfigPath, currentConfigDir, level + 1);
277
298
  });
@@ -412,49 +433,121 @@ async function findSubHeaderPath(filePath, headerName, moduleName) {
412
433
 
413
434
  // 向下查找AutoSnippet配置文件(优化:异步 I/O)
414
435
  async function findSubASSpecPath(filePath) {
436
+ console.log(`[findSubASSpecPath] 开始查找,传入路径: ${filePath}`);
415
437
  let resultArray = [];
416
438
 
417
439
  try {
418
- const entries = await getDirectoryEntries(filePath);
440
+ // 确保 filePath 是目录路径,而不是文件路径
441
+ // 如果传入的是文件路径,获取其所在目录
442
+ let dirPath = filePath;
443
+
444
+ // 首先检查路径是否存在,并确定是文件还是目录
445
+ try {
446
+ const stats = await fs.promises.stat(filePath);
447
+ if (stats.isFile()) {
448
+ // 如果是文件,使用其所在目录
449
+ dirPath = path.dirname(filePath);
450
+ console.log(`[findSubASSpecPath] 检测到文件路径,转换为目录: ${filePath} -> ${dirPath}`);
451
+ } else if (!stats.isDirectory()) {
452
+ // 既不是文件也不是目录,直接返回空数组
453
+ console.log(`[findSubASSpecPath] 路径既不是文件也不是目录,返回空数组: ${filePath}`);
454
+ return resultArray;
455
+ }
456
+ } catch (err) {
457
+ console.log(`[findSubASSpecPath] stat 失败: ${filePath}, 错误: ${err.code || err.message}`);
458
+ // 如果 stat 失败(路径不存在、权限错误等),尝试使用 dirname
459
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
460
+ // 如果路径看起来像文件路径(包含文件名),尝试使用 dirname
461
+ if (path.basename(filePath) === HOLDER_NAME || path.extname(filePath) !== '') {
462
+ dirPath = path.dirname(filePath);
463
+ console.log(`[findSubASSpecPath] 根据路径特征转换为目录: ${filePath} -> ${dirPath}`);
464
+ } else {
465
+ // 可能是目录路径,但不存在,直接返回空数组
466
+ console.log(`[findSubASSpecPath] 路径不存在,返回空数组: ${filePath}`);
467
+ return resultArray;
468
+ }
469
+ } else {
470
+ // 其他错误,直接返回空数组
471
+ console.log(`[findSubASSpecPath] stat 错误,返回空数组: ${filePath}, 错误: ${err.code || err.message}`);
472
+ return resultArray;
473
+ }
474
+ }
475
+
476
+ // ✅ 再次确认 dirPath 是目录(防止 ENOTDIR 错误)
477
+ console.log(`[findSubASSpecPath] 调用 getDirectoryEntries: ${dirPath}`);
478
+ const entries = await getDirectoryEntries(dirPath);
419
479
  if (!entries) {
420
480
  return resultArray;
421
481
  }
422
482
 
423
483
  for (const entry of entries) {
424
484
  if (entry.isFile() && entry.name === HOLDER_NAME) {
425
- resultArray.push(path.join(filePath, entry.name));
485
+ resultArray.push(path.join(dirPath, entry.name));
426
486
  } else if (entry.isDirectory()) {
427
487
  // 跳过 node_modules、.git 等目录
428
488
  if (entry.name.startsWith('.') || entry.name === 'node_modules') {
429
489
  continue;
430
490
  }
431
491
 
432
- const array = await findSubASSpecPath(path.join(filePath, entry.name));
492
+ const array = await findSubASSpecPath(path.join(dirPath, entry.name));
433
493
  resultArray = resultArray.concat(array);
434
494
  }
435
495
  }
436
496
  } catch (err) {
437
- console.log(err);
497
+ // ✅ 忽略 ENOTDIR 错误(传入的是文件路径)和其他文件系统错误
498
+ if (err.code !== 'ENOTDIR') {
499
+ console.log(err);
500
+ }
438
501
  }
439
502
 
440
503
  return resultArray;
441
504
  }
442
505
 
443
506
  /**
444
- * 向上查找项目根目录(包含 .git、Package.swift 等的目录)
445
- * @param {string} filePath - 起始文件路径
446
- * @returns {Promise<string|null>} 项目根目录路径,如果找不到返回 null
507
+ * 向上查找项目根目录(查找 AutoSnippetRoot.boxspec.json 文件)
508
+ * @param {string} filePath - 起始文件路径或目录路径
509
+ * @returns {Promise<string|null>} 项目根目录路径(AutoSnippetRoot.boxspec.json 所在目录),如果找不到返回 null
447
510
  */
448
511
  async function findProjectRoot(filePath) {
512
+ console.log(`[findProjectRoot] 开始查找,传入路径: ${filePath}`);
513
+ // ✅ 简化逻辑:向上查找 AutoSnippetRoot.boxspec.json 文件
449
514
  let currentPath = path.resolve(filePath);
515
+
516
+ // 检查路径是文件还是目录
517
+ try {
518
+ const stats = await fs.promises.stat(currentPath);
519
+ if (stats.isFile()) {
520
+ // 如果是文件,使用其所在目录
521
+ currentPath = path.dirname(currentPath);
522
+ console.log(`[findProjectRoot] 检测到文件路径,转换为目录: ${filePath} -> ${currentPath}`);
523
+ }
524
+ } catch (err) {
525
+ console.log(`[findProjectRoot] stat 失败: ${currentPath}, 错误: ${err.code || err.message}`);
526
+ // 如果 stat 失败,假设是目录路径,或者使用 dirname 作为后备
527
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
528
+ // 如果路径看起来像文件路径,使用 dirname
529
+ if (path.basename(filePath).includes('.') || path.extname(filePath) !== '') {
530
+ currentPath = path.dirname(currentPath);
531
+ }
532
+ }
533
+ // 其他错误继续处理,可能是目录路径
534
+ }
535
+
450
536
  const maxLevels = 20;
451
537
  let levelsChecked = 0;
452
538
 
453
539
  while (currentPath && levelsChecked < maxLevels) {
454
540
  try {
455
- const entries = await getDirectoryEntries(currentPath);
456
- if (entries && isProjectRoot(currentPath, entries)) {
457
- return currentPath;
541
+ // 查找 AutoSnippetRoot.boxspec.json 文件
542
+ const rootMarkerPath = path.join(currentPath, ROOT_MARKER_NAME);
543
+ try {
544
+ const stats = await fs.promises.stat(rootMarkerPath);
545
+ if (stats.isFile()) {
546
+ console.log(`[findProjectRoot] 找到根目录标记文件: ${rootMarkerPath}`);
547
+ return currentPath;
548
+ }
549
+ } catch (err) {
550
+ // 文件不存在,继续向上查找
458
551
  }
459
552
 
460
553
  const parentPath = path.dirname(currentPath);
@@ -464,7 +557,8 @@ async function findProjectRoot(filePath) {
464
557
  currentPath = parentPath;
465
558
  levelsChecked++;
466
559
  } catch (err) {
467
- if (err.code === 'ENOENT' || err.code === 'EACCES') {
560
+ // 处理各种文件系统错误
561
+ if (err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'ENOTDIR') {
468
562
  const parentPath = path.dirname(currentPath);
469
563
  if (parentPath === currentPath) {
470
564
  break;
@@ -477,6 +571,7 @@ async function findProjectRoot(filePath) {
477
571
  }
478
572
  }
479
573
 
574
+ console.log(`[findProjectRoot] 未找到根目录标记文件 ${ROOT_MARKER_NAME}`);
480
575
  return null;
481
576
  }
482
577
 
@@ -575,6 +670,35 @@ async function findModuleDirectory(dirPath, moduleName, headerFileName) {
575
670
  return null;
576
671
  }
577
672
 
673
+ /**
674
+ * 获取根目录的 AutoSnippetRoot.boxspec.json 文件路径
675
+ * @param {string} filePath - 起始文件路径或目录路径
676
+ * @returns {Promise<string|null>} 根目录的 AutoSnippetRoot.boxspec.json 文件路径,如果找不到返回 null
677
+ */
678
+ async function getRootSpecFilePath(filePath) {
679
+ const projectRoot = await findProjectRoot(filePath);
680
+ if (!projectRoot) {
681
+ return null;
682
+ }
683
+ const rootSpecFile = path.join(projectRoot, ROOT_MARKER_NAME);
684
+
685
+ // 检查文件是否存在,如果不存在则创建
686
+ try {
687
+ await fs.promises.access(rootSpecFile);
688
+ } catch (err) {
689
+ if (err.code === 'ENOENT') {
690
+ // 文件不存在,创建它
691
+ const specObj = {
692
+ list: []
693
+ };
694
+ const content = JSON.stringify(specObj, null, 4);
695
+ fs.writeFileSync(rootSpecFile, content, 'utf8');
696
+ }
697
+ }
698
+
699
+ return rootSpecFile;
700
+ }
701
+
578
702
  exports.findASSpecPath = findASSpecPath;
579
703
  exports.findASSpecPathAsync = findASSpecPathAsync;
580
704
  exports.findPackageSwiftPath = findPackageSwiftPath;
@@ -582,4 +706,6 @@ exports.parsePackageSwift = parsePackageSwift;
582
706
  exports.findSubHeaderPath = findSubHeaderPath;
583
707
  exports.findSubASSpecPath = findSubASSpecPath;
584
708
  exports.findProjectRoot = findProjectRoot;
585
- exports.findModuleHeaderFromRoot = findModuleHeaderFromRoot;
709
+ exports.findModuleHeaderFromRoot = findModuleHeaderFromRoot;
710
+ exports.getRootSpecFilePath = getRootSpecFilePath;
711
+ exports.ROOT_MARKER_NAME = ROOT_MARKER_NAME;
package/bin/init.js CHANGED
@@ -16,24 +16,36 @@ async function mergeSubSpecs(mainSpecFile) {
16
16
  list: []
17
17
  };
18
18
 
19
- // ✅ 从项目根目录开始查找,而不是从主配置文件所在目录
20
- // 找到项目根目录(包含 .git、Package.swift 等的目录)
21
- const projectRoot = await findPath.findProjectRoot(mainSpecFile);
22
- const searchRoot = projectRoot || path.dirname(mainSpecFile);
19
+ // ✅ 找到项目根目录的 AutoSnippetRoot.boxspec.json 文件
20
+ console.log(`[mergeSubSpecs] 主配置文件路径: ${mainSpecFile}`);
21
+ const rootSpecFile = await findPath.getRootSpecFilePath(mainSpecFile);
22
+ if (!rootSpecFile) {
23
+ console.error(`[mergeSubSpecs] 未找到项目根目录,无法聚合配置`);
24
+ return;
25
+ }
26
+ console.log(`[mergeSubSpecs] 根目录配置文件: ${rootSpecFile}`);
27
+
28
+ const projectRoot = path.dirname(rootSpecFile);
29
+ const searchRoot = projectRoot;
30
+ console.log(`[mergeSubSpecs] 项目根目录: ${projectRoot}`);
31
+ console.log(`[mergeSubSpecs] 搜索根目录: ${searchRoot}`);
23
32
 
24
- const specSlashIndex = mainSpecFile.lastIndexOf('/');
25
- const specFilePath = mainSpecFile.substring(0, specSlashIndex + 1);
33
+ const specSlashIndex = rootSpecFile.lastIndexOf('/');
34
+ const specFilePath = rootSpecFile.substring(0, specSlashIndex + 1);
26
35
 
27
36
  // ✅ 从项目根目录向下查找子模块的 AutoSnippet.boxspec.json
37
+ console.log(`[mergeSubSpecs] 开始查找子模块配置,搜索根目录: ${searchRoot}`);
28
38
  const array = await findPath.findSubASSpecPath(searchRoot);
39
+ console.log(`[mergeSubSpecs] 找到 ${array.length} 个子模块配置文件`);
29
40
 
30
41
  for (let i = 0; i < array.length; i++) {
31
42
  const filename = array[i];
32
43
 
33
44
  const slashIndex = filename.lastIndexOf('/');
34
45
  let thePath = filename.substring(0, slashIndex + 1);
35
- if (filename === mainSpecFile) {
36
- thePath = '';
46
+ if (filename === rootSpecFile) {
47
+ // 跳过根目录的 AutoSnippetRoot.boxspec.json,避免循环引用
48
+ continue;
37
49
  } else {
38
50
  thePath = thePath.replace(specFilePath, '');
39
51
  }
@@ -64,33 +76,92 @@ async function mergeSubSpecs(mainSpecFile) {
64
76
  try {
65
77
  const content = JSON.stringify(specObj, null, 4);
66
78
  if (content) {
67
- fs.writeFileSync(mainSpecFile, content, 'utf8');
79
+ // 写入根目录的 AutoSnippetRoot.boxspec.json
80
+ fs.writeFileSync(rootSpecFile, content, 'utf8');
81
+ console.log(`[mergeSubSpecs] 已聚合 ${specObj.list.length} 个 snippet 到根目录配置文件`);
68
82
  }
69
83
  } catch (err) {
70
84
  console.error(err);
71
85
  }
72
86
  }
73
87
 
88
+ /**
89
+ * 从目录路径确定 target 的根目录(包含 Code 或 Sources 的目录)
90
+ * @param {string} dirPath - 目录路径
91
+ * @returns {Promise<string|null>} target 根目录路径,如果找不到返回 null
92
+ */
93
+ async function findTargetRootDirFromPath(dirPath) {
94
+ let currentPath = path.resolve(dirPath);
95
+ const maxLevels = 10;
96
+ let levelsChecked = 0;
97
+
98
+ // ✅ 向上查找包含 Code 或 Sources 目录的目录(target 根目录)
99
+ while (currentPath && levelsChecked < maxLevels) {
100
+ try {
101
+ const entries = await fs.promises.readdir(currentPath, { withFileTypes: true });
102
+
103
+ // 检查当前目录是否包含 Code 或 Sources 目录
104
+ for (const entry of entries) {
105
+ if (entry.isDirectory() && (entry.name === 'Code' || entry.name === 'Sources')) {
106
+ // 找到包含 Code 或 Sources 的目录,这就是 target 根目录
107
+ console.log(`[findTargetRootDirFromPath] 找到 target 根目录: ${currentPath} (包含 ${entry.name})`);
108
+ return currentPath;
109
+ }
110
+ }
111
+
112
+ // 继续向上查找
113
+ const parentPath = path.dirname(currentPath);
114
+ if (parentPath === currentPath) {
115
+ break;
116
+ }
117
+ currentPath = parentPath;
118
+ levelsChecked++;
119
+ } catch (err) {
120
+ if (err.code === 'ENOENT' || err.code === 'EACCES') {
121
+ break;
122
+ }
123
+ throw err;
124
+ }
125
+ }
126
+
127
+ console.log(`[findTargetRootDirFromPath] 未找到 target 根目录(Code 或 Sources)`);
128
+ return null;
129
+ }
130
+
74
131
  async function initSpec() {
75
- // ✅ 查找 Package.swift,在其同级目录创建 AutoSnippet.boxspec.json
76
- let packagePath = await findPath.findPackageSwiftPath(CMD_PATH);
132
+ // ✅ 查找 target 根目录(包含 Code 或 Sources 的目录),在该目录创建 AutoSnippet.boxspec.json
133
+ let targetRootDir = await findTargetRootDirFromPath(CMD_PATH);
134
+
135
+ // 如果找不到 target 根目录,尝试查找 Package.swift 作为后备
136
+ if (!targetRootDir) {
137
+ let packagePath = await findPath.findPackageSwiftPath(CMD_PATH);
138
+ if (packagePath) {
139
+ targetRootDir = path.dirname(packagePath);
140
+ console.log(`[initSpec] 使用 Package.swift 所在目录作为 target 根目录: ${targetRootDir}`);
141
+ } else {
142
+ // 如果都找不到,使用当前目录
143
+ targetRootDir = CMD_PATH;
144
+ console.log(`[initSpec] 使用当前目录作为 target 根目录: ${targetRootDir}`);
145
+ }
146
+ }
77
147
 
78
- // 如果找不到 Package.swift,使用当前目录
79
- const configDir = packagePath ? path.dirname(packagePath) : CMD_PATH;
80
- const filePath = path.join(configDir, 'AutoSnippet.boxspec.json');
148
+ const filePath = path.join(targetRootDir, 'AutoSnippet.boxspec.json');
149
+ console.log(`[initSpec] 将在 target 根目录创建配置文件: ${filePath}`);
81
150
 
82
151
  // 如果配置文件不存在,创建一个空的
83
152
  try {
84
153
  await fs.promises.access(filePath);
154
+ console.log(`[initSpec] 配置文件已存在: ${filePath}`);
85
155
  } catch (error) {
86
156
  const specObj = {
87
157
  list: []
88
158
  };
89
159
  const content = JSON.stringify(specObj, null, 4);
90
160
  fs.writeFileSync(filePath, content, 'utf8');
161
+ console.log(`[initSpec] 已创建配置文件: ${filePath}`);
91
162
  }
92
163
 
93
- // ✅ 聚合子模块配置到主配置文件
164
+ // ✅ 聚合子模块配置到根目录的 AutoSnippetRoot.boxspec.json
94
165
  await mergeSubSpecs(filePath);
95
166
  }
96
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "A iOS module management tool.",
5
5
  "main": "index.js",
6
6
  "scripts": {