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 +27 -0
- package/bin/create.js +139 -27
- package/bin/findPath.js +154 -28
- package/bin/init.js +86 -15
- package/package.json +1 -1
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
|
-
// ✅
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
-
// ✅
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
// 在
|
|
271
|
-
const headerPath = await findPath.findSubHeaderPath(
|
|
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 存储相对于
|
|
278
|
-
//
|
|
279
|
-
// 则 headName = "
|
|
280
|
-
const headerRelativePath = path.relative(
|
|
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(
|
|
350
|
+
const readmePath = path.join(targetRootDir, README_NAME);
|
|
290
351
|
await fs.promises.access(readmePath);
|
|
291
|
-
const
|
|
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
|
|
299
|
-
const moduleSpecFile = path.join(
|
|
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
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
+
console.log(err);
|
|
232
253
|
}
|
|
233
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
*
|
|
445
|
-
* @param {string} filePath -
|
|
446
|
-
* @returns {Promise<string|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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
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 =
|
|
25
|
-
const specFilePath =
|
|
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 ===
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
// ✅ 查找
|
|
76
|
-
let
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|