autosnippet 1.1.21 → 1.1.23
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/create.js +3 -8
- package/bin/findPath.js +63 -305
- package/bin/init.js +1 -25
- package/bin/injection.js +121 -31
- package/bin/install.js +11 -1
- package/bin/watch.js +2 -1
- package/package.json +4 -1
package/bin/create.js
CHANGED
|
@@ -61,8 +61,6 @@ async function findTargetRootDir(filePath) {
|
|
|
61
61
|
// 检查当前目录是否包含 Code 或 Sources 目录
|
|
62
62
|
for (const entry of entries) {
|
|
63
63
|
if (entry.isDirectory() && (entry.name === 'Code' || entry.name === 'Sources')) {
|
|
64
|
-
// 找到包含 Code 或 Sources 的目录,这就是 target 根目录
|
|
65
|
-
console.log(`[findTargetRootDir] 找到 target 根目录: ${currentPath} (包含 ${entry.name})`);
|
|
66
64
|
return currentPath;
|
|
67
65
|
}
|
|
68
66
|
}
|
|
@@ -82,7 +80,6 @@ async function findTargetRootDir(filePath) {
|
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
console.log(`[findTargetRootDir] 未找到 target 根目录(Code 或 Sources)`);
|
|
86
83
|
return null;
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -324,7 +321,6 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
|
|
|
324
321
|
// 如果找不到 target 根目录,使用 Package.swift 所在目录作为后备
|
|
325
322
|
targetRootDir = packageInfo.path;
|
|
326
323
|
}
|
|
327
|
-
console.log(`[createCodeSnippets] target 根目录: ${targetRootDir}`);
|
|
328
324
|
const moduleRoot = targetRootDir;
|
|
329
325
|
|
|
330
326
|
// ✅ 查找头文件(适配 SPM 的 include/ModuleName/ 结构)
|
|
@@ -332,7 +328,7 @@ function readStream(specFile, filePathArr, snippet, isHaveHeader) {
|
|
|
332
328
|
const headerPath = await findPath.findSubHeaderPath(targetRootDir, headerNameWithoutExt, moduleName);
|
|
333
329
|
|
|
334
330
|
snippet['{content}'] = codeList;
|
|
335
|
-
snippet['{specName}'] = moduleName;
|
|
331
|
+
snippet['{specName}'] = moduleName;
|
|
336
332
|
|
|
337
333
|
if (headerPath) {
|
|
338
334
|
// ✅ headName 存储相对于 target 根目录的相对路径
|
|
@@ -447,7 +443,7 @@ async function saveFromFile(specFile, snippet) {
|
|
|
447
443
|
if (err.code === 'ENOENT') {
|
|
448
444
|
rootPlaceholder = { list: [] };
|
|
449
445
|
} else {
|
|
450
|
-
|
|
446
|
+
// 忽略错误
|
|
451
447
|
}
|
|
452
448
|
}
|
|
453
449
|
|
|
@@ -475,11 +471,10 @@ async function saveFromFile(specFile, snippet) {
|
|
|
475
471
|
// 写入根配置文件
|
|
476
472
|
const rootContent = JSON.stringify(rootPlaceholder, null, 4);
|
|
477
473
|
fs.writeFileSync(rootSpecFile, rootContent, 'utf8');
|
|
478
|
-
console.log(`[saveFromFile] 已同步 snippet 到根目录配置文件: ${rootSpecFile}`);
|
|
479
474
|
}
|
|
480
475
|
}
|
|
481
476
|
} catch (err) {
|
|
482
|
-
|
|
477
|
+
// 忽略错误
|
|
483
478
|
}
|
|
484
479
|
|
|
485
480
|
// ✅ 只写入刚创建的单个代码片段
|
package/bin/findPath.js
CHANGED
|
@@ -12,9 +12,6 @@ const README_NAME = 'readme.md';
|
|
|
12
12
|
const directoryCache = new Map();
|
|
13
13
|
const CACHE_TTL = 60000; // 缓存 60 秒
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
* 获取目录内容(带缓存)
|
|
17
|
-
*/
|
|
18
15
|
async function getDirectoryEntries(dirPath) {
|
|
19
16
|
const cacheKey = path.resolve(dirPath);
|
|
20
17
|
|
|
@@ -23,15 +20,11 @@ async function getDirectoryEntries(dirPath) {
|
|
|
23
20
|
try {
|
|
24
21
|
const stats = await fs.promises.stat(dirPath);
|
|
25
22
|
if (stats.isFile()) {
|
|
26
|
-
// 如果是文件,清除可能的错误缓存,然后返回 null
|
|
27
|
-
console.log(`[getDirectoryEntries] 路径是文件,返回 null: ${dirPath}`);
|
|
28
23
|
directoryCache.delete(cacheKey);
|
|
29
24
|
return null;
|
|
30
25
|
}
|
|
31
26
|
} catch (err) {
|
|
32
|
-
//
|
|
33
|
-
// 如果是 ENOTDIR,会在下面的 readdir 中被捕获
|
|
34
|
-
console.log(`[getDirectoryEntries] stat 失败: ${dirPath}, 错误: ${err.code || err.message}`);
|
|
27
|
+
// 继续
|
|
35
28
|
}
|
|
36
29
|
|
|
37
30
|
const cached = directoryCache.get(cacheKey);
|
|
@@ -41,12 +34,10 @@ async function getDirectoryEntries(dirPath) {
|
|
|
41
34
|
}
|
|
42
35
|
|
|
43
36
|
try {
|
|
44
|
-
console.log(`[getDirectoryEntries] 尝试读取目录: ${dirPath}`);
|
|
45
37
|
const entries = await fs.promises.readdir(dirPath, {
|
|
46
|
-
withFileTypes: true
|
|
38
|
+
withFileTypes: true
|
|
47
39
|
});
|
|
48
40
|
|
|
49
|
-
// 清理过期缓存(保持缓存大小合理)
|
|
50
41
|
if (directoryCache.size > 1000) {
|
|
51
42
|
const now = Date.now();
|
|
52
43
|
for (const [key, value] of directoryCache.entries()) {
|
|
@@ -63,19 +54,13 @@ async function getDirectoryEntries(dirPath) {
|
|
|
63
54
|
|
|
64
55
|
return entries;
|
|
65
56
|
} catch (err) {
|
|
66
|
-
// ✅ 处理各种文件系统错误
|
|
67
|
-
console.log(`[getDirectoryEntries] readdir 失败: ${dirPath}, 错误: ${err.code || err.message}`);
|
|
68
57
|
if (err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'ENOTDIR') {
|
|
69
|
-
return null;
|
|
58
|
+
return null;
|
|
70
59
|
}
|
|
71
60
|
throw err;
|
|
72
61
|
}
|
|
73
62
|
}
|
|
74
63
|
|
|
75
|
-
/**
|
|
76
|
-
* 检测是否为工程根目录
|
|
77
|
-
* 通过查找常见的工程根目录标记来判断
|
|
78
|
-
*/
|
|
79
64
|
function isProjectRoot(dirPath, entries) {
|
|
80
65
|
if (!entries) {
|
|
81
66
|
return false;
|
|
@@ -83,24 +68,17 @@ function isProjectRoot(dirPath, entries) {
|
|
|
83
68
|
|
|
84
69
|
// 工程根目录标记(按优先级)
|
|
85
70
|
const rootMarkers = [
|
|
86
|
-
'.git',
|
|
87
|
-
PACKAGE_SWIFT,
|
|
88
|
-
'.xcodeproj',
|
|
89
|
-
'.xcworkspace',
|
|
90
|
-
'Podfile',
|
|
91
|
-
'.swiftpm',
|
|
71
|
+
'.git',
|
|
72
|
+
PACKAGE_SWIFT,
|
|
73
|
+
'.xcodeproj',
|
|
74
|
+
'.xcworkspace',
|
|
75
|
+
'Podfile',
|
|
76
|
+
'.swiftpm',
|
|
92
77
|
];
|
|
93
78
|
|
|
94
79
|
for (const entry of entries) {
|
|
95
80
|
const name = entry.name;
|
|
96
|
-
|
|
97
|
-
// 检查文件标记
|
|
98
|
-
if (entry.isFile() && rootMarkers.includes(name)) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 检查目录标记(.git 可能是目录)
|
|
103
|
-
if (entry.isDirectory() && rootMarkers.includes(name)) {
|
|
81
|
+
if (rootMarkers.includes(name) && (entry.isFile() || entry.isDirectory())) {
|
|
104
82
|
return true;
|
|
105
83
|
}
|
|
106
84
|
}
|
|
@@ -108,55 +86,29 @@ function isProjectRoot(dirPath, entries) {
|
|
|
108
86
|
return false;
|
|
109
87
|
}
|
|
110
88
|
|
|
111
|
-
|
|
112
|
-
async function findASSpecPathAsync(filePath) {
|
|
113
|
-
let configPath = null;
|
|
114
|
-
let configDir = null;
|
|
89
|
+
async function searchUpwardForFile(filePath, fileName) {
|
|
115
90
|
let currentPath = path.resolve(filePath);
|
|
116
|
-
const maxLevels = 20;
|
|
91
|
+
const maxLevels = 20;
|
|
117
92
|
let levelsChecked = 0;
|
|
118
93
|
|
|
119
94
|
while (currentPath && levelsChecked < maxLevels) {
|
|
120
95
|
try {
|
|
121
96
|
const entries = await getDirectoryEntries(currentPath);
|
|
122
|
-
|
|
123
|
-
// 查找 AutoSnippet.boxspec.json
|
|
124
97
|
if (entries) {
|
|
125
98
|
for (const entry of entries) {
|
|
126
|
-
if (entry.isFile() && entry.name ===
|
|
127
|
-
|
|
128
|
-
// 第一次找到配置文件
|
|
129
|
-
configPath = path.join(currentPath, entry.name);
|
|
130
|
-
configDir = currentPath;
|
|
131
|
-
}
|
|
99
|
+
if (entry.isFile() && entry.name === fileName) {
|
|
100
|
+
return path.join(currentPath, entry.name);
|
|
132
101
|
}
|
|
133
102
|
}
|
|
134
103
|
}
|
|
135
104
|
|
|
136
|
-
// 如果已经找到配置文件,检查是否到达工程根目录
|
|
137
|
-
if (configPath) {
|
|
138
|
-
// 如果在配置文件所在目录或以上发现了工程根目录标记,停止查找
|
|
139
|
-
if (isProjectRoot(currentPath, entries)) {
|
|
140
|
-
return configPath;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 如果已经向上查找了,但没有找到工程根目录标记,继续查找
|
|
144
|
-
if (path.resolve(currentPath) !== configDir) {
|
|
145
|
-
// 当前目录不是配置文件所在目录,继续向上查找
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// 继续向上查找
|
|
150
105
|
const parentPath = path.dirname(currentPath);
|
|
151
106
|
if (parentPath === currentPath) {
|
|
152
|
-
// 已到达根目录
|
|
153
107
|
break;
|
|
154
108
|
}
|
|
155
109
|
currentPath = parentPath;
|
|
156
110
|
levelsChecked++;
|
|
157
|
-
|
|
158
111
|
} catch (err) {
|
|
159
|
-
// 错误处理:权限错误继续查找
|
|
160
112
|
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
|
161
113
|
const parentPath = path.dirname(currentPath);
|
|
162
114
|
if (parentPath === currentPath) {
|
|
@@ -166,18 +118,22 @@ async function findASSpecPathAsync(filePath) {
|
|
|
166
118
|
levelsChecked++;
|
|
167
119
|
continue;
|
|
168
120
|
}
|
|
169
|
-
// 其他错误,抛出
|
|
170
121
|
throw err;
|
|
171
122
|
}
|
|
172
123
|
}
|
|
173
124
|
|
|
174
|
-
|
|
175
|
-
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function findASSpecPathAsync(filePath) {
|
|
129
|
+
const holderPath = await searchUpwardForFile(filePath, HOLDER_NAME);
|
|
130
|
+
if (holderPath) {
|
|
131
|
+
return holderPath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return await searchUpwardForFile(filePath, ROOT_MARKER_NAME);
|
|
176
135
|
}
|
|
177
136
|
|
|
178
|
-
/**
|
|
179
|
-
* 同步版本:检测是否为工程根目录
|
|
180
|
-
*/
|
|
181
137
|
function isProjectRootSync(dirPath, files) {
|
|
182
138
|
if (!files || files.length === 0) {
|
|
183
139
|
return false;
|
|
@@ -185,17 +141,15 @@ function isProjectRootSync(dirPath, files) {
|
|
|
185
141
|
|
|
186
142
|
// 工程根目录标记(按优先级)
|
|
187
143
|
const rootMarkers = [
|
|
188
|
-
'.git',
|
|
189
|
-
PACKAGE_SWIFT,
|
|
190
|
-
'.xcodeproj',
|
|
191
|
-
'.xcworkspace',
|
|
192
|
-
'Podfile',
|
|
144
|
+
'.git',
|
|
145
|
+
PACKAGE_SWIFT,
|
|
146
|
+
'.xcodeproj',
|
|
147
|
+
'.xcworkspace',
|
|
148
|
+
'Podfile',
|
|
193
149
|
];
|
|
194
150
|
|
|
195
|
-
// 检查文件名
|
|
196
151
|
for (const filename of files) {
|
|
197
152
|
if (rootMarkers.includes(filename)) {
|
|
198
|
-
// 检查是否为文件或目录
|
|
199
153
|
try {
|
|
200
154
|
const filePath = path.join(dirPath, filename);
|
|
201
155
|
const stats = fs.lstatSync(filePath);
|
|
@@ -203,7 +157,7 @@ function isProjectRootSync(dirPath, files) {
|
|
|
203
157
|
return true;
|
|
204
158
|
}
|
|
205
159
|
} catch (err) {
|
|
206
|
-
//
|
|
160
|
+
// 继续检查
|
|
207
161
|
}
|
|
208
162
|
}
|
|
209
163
|
}
|
|
@@ -211,45 +165,35 @@ function isProjectRootSync(dirPath, files) {
|
|
|
211
165
|
return false;
|
|
212
166
|
}
|
|
213
167
|
|
|
214
|
-
// 向上查找AutoSnippet配置文件(保留回调版本,兼容现有代码)
|
|
215
168
|
function findASSpecPath(filePath, callback, configPath, configDir) {
|
|
216
|
-
// 初始化参数
|
|
217
169
|
if (configPath === undefined) configPath = null;
|
|
218
170
|
if (configDir === undefined) configDir = null;
|
|
219
171
|
|
|
220
|
-
const maxLevels = 20;
|
|
172
|
+
const maxLevels = 20;
|
|
221
173
|
let levelsChecked = 0;
|
|
222
174
|
|
|
223
175
|
function search(currentPath, foundConfigPath, foundConfigDir, level) {
|
|
224
176
|
if (level >= maxLevels) {
|
|
225
177
|
if (foundConfigPath) {
|
|
226
178
|
callback(foundConfigPath);
|
|
227
|
-
} else {
|
|
228
|
-
console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
|
|
229
179
|
}
|
|
230
180
|
return;
|
|
231
181
|
}
|
|
232
182
|
|
|
233
183
|
fs.readdir(currentPath, function (err, files) {
|
|
234
184
|
if (err) {
|
|
235
|
-
// 错误处理:权限错误继续查找
|
|
236
185
|
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
|
237
186
|
const parentPath = path.join(currentPath, '/..');
|
|
238
187
|
const parentResolvedPath = path.resolve(parentPath);
|
|
239
188
|
|
|
240
|
-
// 如果已经到达根目录,停止查找
|
|
241
189
|
if (parentResolvedPath === path.resolve(currentPath)) {
|
|
242
190
|
if (foundConfigPath) {
|
|
243
191
|
callback(foundConfigPath);
|
|
244
|
-
} else {
|
|
245
|
-
console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
|
|
246
192
|
}
|
|
247
193
|
return;
|
|
248
194
|
}
|
|
249
195
|
|
|
250
196
|
search(parentPath, foundConfigPath, foundConfigDir, level + 1);
|
|
251
|
-
} else {
|
|
252
|
-
console.log(err);
|
|
253
197
|
}
|
|
254
198
|
return;
|
|
255
199
|
}
|
|
@@ -258,41 +202,32 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
|
|
|
258
202
|
let currentConfigPath = foundConfigPath;
|
|
259
203
|
let currentConfigDir = foundConfigDir;
|
|
260
204
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (filename === HOLDER_NAME) {
|
|
205
|
+
files.forEach(function (filename) {
|
|
206
|
+
if (filename === HOLDER_NAME) {
|
|
264
207
|
isFound = true;
|
|
265
208
|
if (!currentConfigPath) {
|
|
266
|
-
// 第一次找到配置文件,记录路径(不立即调用回调)
|
|
267
209
|
currentConfigPath = path.join(currentPath, filename);
|
|
268
210
|
currentConfigDir = path.resolve(currentPath);
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
272
214
|
|
|
273
|
-
// 如果已经找到配置文件,检查是否到达工程根目录
|
|
274
215
|
if (currentConfigPath) {
|
|
275
|
-
// 如果在配置文件所在目录或以上发现了工程根目录标记,停止查找并调用回调
|
|
276
216
|
if (isProjectRootSync(currentPath, files)) {
|
|
277
217
|
callback(currentConfigPath);
|
|
278
|
-
|
|
279
|
-
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
280
220
|
}
|
|
281
221
|
|
|
282
|
-
// 继续向上查找
|
|
283
222
|
const parentPath = path.join(currentPath, '/..');
|
|
284
223
|
const parentResolvedPath = path.resolve(parentPath);
|
|
285
224
|
|
|
286
|
-
// 如果已经到达根目录,停止查找
|
|
287
225
|
if (parentResolvedPath === path.resolve(currentPath)) {
|
|
288
|
-
// 如果找到了配置文件,即使没找到工程根目录标记,也调用回调
|
|
289
226
|
if (currentConfigPath) {
|
|
290
227
|
callback(currentConfigPath);
|
|
291
|
-
} else {
|
|
292
|
-
console.log('未找到 AutoSnippet.boxspec.json 文件,请检查路径。');
|
|
293
228
|
}
|
|
294
|
-
|
|
295
|
-
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
296
231
|
|
|
297
232
|
search(parentPath, currentConfigPath, currentConfigDir, level + 1);
|
|
298
233
|
});
|
|
@@ -301,7 +236,6 @@ function findASSpecPath(filePath, callback, configPath, configDir) {
|
|
|
301
236
|
search(filePath, configPath, configDir, 0);
|
|
302
237
|
}
|
|
303
238
|
|
|
304
|
-
// 向上查找 Package.swift 文件(SPM 模块规范文件)
|
|
305
239
|
async function findPackageSwiftPath(filePath) {
|
|
306
240
|
let currentPath = path.resolve(filePath);
|
|
307
241
|
|
|
@@ -317,14 +251,12 @@ async function findPackageSwiftPath(filePath) {
|
|
|
317
251
|
continue;
|
|
318
252
|
}
|
|
319
253
|
|
|
320
|
-
// 查找 Package.swift 文件
|
|
321
254
|
for (const entry of entries) {
|
|
322
255
|
if (entry.isFile() && entry.name === PACKAGE_SWIFT) {
|
|
323
256
|
return path.join(currentPath, entry.name);
|
|
324
257
|
}
|
|
325
258
|
}
|
|
326
259
|
|
|
327
|
-
// 继续向上查找
|
|
328
260
|
const parentPath = path.dirname(currentPath);
|
|
329
261
|
if (parentPath === currentPath) {
|
|
330
262
|
break;
|
|
@@ -347,14 +279,9 @@ async function findPackageSwiftPath(filePath) {
|
|
|
347
279
|
return null;
|
|
348
280
|
}
|
|
349
281
|
|
|
350
|
-
/**
|
|
351
|
-
* 解析 Package.swift 文件,提取模块信息
|
|
352
|
-
*/
|
|
353
282
|
async function parsePackageSwift(packagePath) {
|
|
354
283
|
try {
|
|
355
284
|
const content = await fs.promises.readFile(packagePath, 'utf8');
|
|
356
|
-
|
|
357
|
-
// 简单解析 Package.swift(实际可以使用 Swift AST 解析器)
|
|
358
285
|
const packageNameMatch = content.match(/name:\s*"([^"]+)"/);
|
|
359
286
|
const targetsMatch = content.match(/\.target\s*\([^)]+name:\s*"([^"]+)"/g);
|
|
360
287
|
|
|
@@ -376,14 +303,24 @@ async function parsePackageSwift(packagePath) {
|
|
|
376
303
|
path: path.dirname(packagePath)
|
|
377
304
|
};
|
|
378
305
|
} catch (err) {
|
|
379
|
-
console.error('Error parsing Package.swift:', err);
|
|
380
306
|
return null;
|
|
381
307
|
}
|
|
382
308
|
}
|
|
383
309
|
|
|
384
|
-
// 向下查找模块头文件(优化:适配 SPM 结构,优先查找 include/ModuleName/ 目录)
|
|
385
310
|
async function findSubHeaderPath(filePath, headerName, moduleName) {
|
|
386
|
-
|
|
311
|
+
const codePath = path.join(filePath, 'Code');
|
|
312
|
+
try {
|
|
313
|
+
const stats = await fs.promises.stat(codePath);
|
|
314
|
+
if (stats.isDirectory()) {
|
|
315
|
+
const result = await findSubHeaderPath(codePath, headerName, null);
|
|
316
|
+
if (result) {
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// 继续查找
|
|
322
|
+
}
|
|
323
|
+
|
|
387
324
|
if (moduleName) {
|
|
388
325
|
const includePath = path.join(filePath, 'include', moduleName);
|
|
389
326
|
try {
|
|
@@ -394,15 +331,14 @@ async function findSubHeaderPath(filePath, headerName, moduleName) {
|
|
|
394
331
|
await fs.promises.access(headerPath);
|
|
395
332
|
return headerPath;
|
|
396
333
|
} catch {
|
|
397
|
-
//
|
|
334
|
+
// 继续查找
|
|
398
335
|
}
|
|
399
336
|
}
|
|
400
337
|
} catch {
|
|
401
|
-
//
|
|
338
|
+
// 继续查找
|
|
402
339
|
}
|
|
403
340
|
}
|
|
404
341
|
|
|
405
|
-
// ✅ 降级:在整个模块目录中查找(深度限制)
|
|
406
342
|
try {
|
|
407
343
|
const entries = await getDirectoryEntries(filePath);
|
|
408
344
|
if (!entries) {
|
|
@@ -413,7 +349,6 @@ async function findSubHeaderPath(filePath, headerName, moduleName) {
|
|
|
413
349
|
if (entry.isFile() && entry.name === `${headerName}.h`) {
|
|
414
350
|
return path.join(filePath, entry.name);
|
|
415
351
|
} else if (entry.isDirectory()) {
|
|
416
|
-
// 跳过 node_modules、.git 等目录
|
|
417
352
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
418
353
|
continue;
|
|
419
354
|
}
|
|
@@ -425,56 +360,36 @@ async function findSubHeaderPath(filePath, headerName, moduleName) {
|
|
|
425
360
|
}
|
|
426
361
|
}
|
|
427
362
|
} catch (err) {
|
|
428
|
-
|
|
363
|
+
// 忽略错误
|
|
429
364
|
}
|
|
430
365
|
|
|
431
366
|
return null;
|
|
432
367
|
}
|
|
433
368
|
|
|
434
|
-
// 向下查找AutoSnippet配置文件(优化:异步 I/O)
|
|
435
369
|
async function findSubASSpecPath(filePath) {
|
|
436
|
-
console.log(`[findSubASSpecPath] 开始查找,传入路径: ${filePath}`);
|
|
437
370
|
let resultArray = [];
|
|
438
371
|
|
|
439
372
|
try {
|
|
440
|
-
// ✅ 确保 filePath 是目录路径,而不是文件路径
|
|
441
|
-
// 如果传入的是文件路径,获取其所在目录
|
|
442
373
|
let dirPath = filePath;
|
|
443
374
|
|
|
444
|
-
// 首先检查路径是否存在,并确定是文件还是目录
|
|
445
375
|
try {
|
|
446
376
|
const stats = await fs.promises.stat(filePath);
|
|
447
377
|
if (stats.isFile()) {
|
|
448
|
-
// 如果是文件,使用其所在目录
|
|
449
378
|
dirPath = path.dirname(filePath);
|
|
450
|
-
console.log(`[findSubASSpecPath] 检测到文件路径,转换为目录: ${filePath} -> ${dirPath}`);
|
|
451
379
|
} else if (!stats.isDirectory()) {
|
|
452
|
-
// 既不是文件也不是目录,直接返回空数组
|
|
453
|
-
console.log(`[findSubASSpecPath] 路径既不是文件也不是目录,返回空数组: ${filePath}`);
|
|
454
380
|
return resultArray;
|
|
455
381
|
}
|
|
456
382
|
} catch (err) {
|
|
457
|
-
console.log(`[findSubASSpecPath] stat 失败: ${filePath}, 错误: ${err.code || err.message}`);
|
|
458
|
-
// 如果 stat 失败(路径不存在、权限错误等),尝试使用 dirname
|
|
459
383
|
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
|
460
|
-
// 如果路径看起来像文件路径(包含文件名),尝试使用 dirname
|
|
461
384
|
if (path.basename(filePath) === HOLDER_NAME || path.extname(filePath) !== '') {
|
|
462
385
|
dirPath = path.dirname(filePath);
|
|
463
|
-
console.log(`[findSubASSpecPath] 根据路径特征转换为目录: ${filePath} -> ${dirPath}`);
|
|
464
386
|
} else {
|
|
465
|
-
// 可能是目录路径,但不存在,直接返回空数组
|
|
466
|
-
console.log(`[findSubASSpecPath] 路径不存在,返回空数组: ${filePath}`);
|
|
467
387
|
return resultArray;
|
|
468
388
|
}
|
|
469
389
|
} else {
|
|
470
|
-
// 其他错误,直接返回空数组
|
|
471
|
-
console.log(`[findSubASSpecPath] stat 错误,返回空数组: ${filePath}, 错误: ${err.code || err.message}`);
|
|
472
390
|
return resultArray;
|
|
473
391
|
}
|
|
474
392
|
}
|
|
475
|
-
|
|
476
|
-
// ✅ 再次确认 dirPath 是目录(防止 ENOTDIR 错误)
|
|
477
|
-
console.log(`[findSubASSpecPath] 调用 getDirectoryEntries: ${dirPath}`);
|
|
478
393
|
const entries = await getDirectoryEntries(dirPath);
|
|
479
394
|
if (!entries) {
|
|
480
395
|
return resultArray;
|
|
@@ -484,7 +399,6 @@ async function findSubASSpecPath(filePath) {
|
|
|
484
399
|
if (entry.isFile() && entry.name === HOLDER_NAME) {
|
|
485
400
|
resultArray.push(path.join(dirPath, entry.name));
|
|
486
401
|
} else if (entry.isDirectory()) {
|
|
487
|
-
// 跳过 node_modules、.git 等目录
|
|
488
402
|
if (entry.name.startsWith('.') || entry.name === 'node_modules') {
|
|
489
403
|
continue;
|
|
490
404
|
}
|
|
@@ -494,187 +408,34 @@ async function findSubASSpecPath(filePath) {
|
|
|
494
408
|
}
|
|
495
409
|
}
|
|
496
410
|
} catch (err) {
|
|
497
|
-
// ✅ 忽略 ENOTDIR 错误(传入的是文件路径)和其他文件系统错误
|
|
498
411
|
if (err.code !== 'ENOTDIR') {
|
|
499
|
-
|
|
412
|
+
// 忽略错误
|
|
500
413
|
}
|
|
501
414
|
}
|
|
502
415
|
|
|
503
416
|
return resultArray;
|
|
504
417
|
}
|
|
505
418
|
|
|
506
|
-
/**
|
|
507
|
-
* 向上查找项目根目录(查找 AutoSnippetRoot.boxspec.json 文件)
|
|
508
|
-
* @param {string} filePath - 起始文件路径或目录路径
|
|
509
|
-
* @returns {Promise<string|null>} 项目根目录路径(AutoSnippetRoot.boxspec.json 所在目录),如果找不到返回 null
|
|
510
|
-
*/
|
|
511
419
|
async function findProjectRoot(filePath) {
|
|
512
|
-
|
|
513
|
-
// ✅ 简化逻辑:向上查找 AutoSnippetRoot.boxspec.json 文件
|
|
514
|
-
let currentPath = path.resolve(filePath);
|
|
420
|
+
let startPath = path.resolve(filePath);
|
|
515
421
|
|
|
516
|
-
// 检查路径是文件还是目录
|
|
517
422
|
try {
|
|
518
|
-
const stats = await fs.promises.stat(
|
|
423
|
+
const stats = await fs.promises.stat(startPath);
|
|
519
424
|
if (stats.isFile()) {
|
|
520
|
-
|
|
521
|
-
currentPath = path.dirname(currentPath);
|
|
522
|
-
console.log(`[findProjectRoot] 检测到文件路径,转换为目录: ${filePath} -> ${currentPath}`);
|
|
425
|
+
startPath = path.dirname(startPath);
|
|
523
426
|
}
|
|
524
427
|
} catch (err) {
|
|
525
|
-
console.log(`[findProjectRoot] stat 失败: ${currentPath}, 错误: ${err.code || err.message}`);
|
|
526
|
-
// 如果 stat 失败,假设是目录路径,或者使用 dirname 作为后备
|
|
527
428
|
if (err.code === 'ENOENT' || err.code === 'EACCES') {
|
|
528
|
-
// 如果路径看起来像文件路径,使用 dirname
|
|
529
429
|
if (path.basename(filePath).includes('.') || path.extname(filePath) !== '') {
|
|
530
|
-
|
|
430
|
+
startPath = path.dirname(startPath);
|
|
531
431
|
}
|
|
532
432
|
}
|
|
533
|
-
// 其他错误继续处理,可能是目录路径
|
|
534
433
|
}
|
|
535
434
|
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
while (currentPath && levelsChecked < maxLevels) {
|
|
540
|
-
try {
|
|
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
|
-
// 文件不存在,继续向上查找
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const parentPath = path.dirname(currentPath);
|
|
554
|
-
if (parentPath === currentPath) {
|
|
555
|
-
break;
|
|
556
|
-
}
|
|
557
|
-
currentPath = parentPath;
|
|
558
|
-
levelsChecked++;
|
|
559
|
-
} catch (err) {
|
|
560
|
-
// ✅ 处理各种文件系统错误
|
|
561
|
-
if (err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'ENOTDIR') {
|
|
562
|
-
const parentPath = path.dirname(currentPath);
|
|
563
|
-
if (parentPath === currentPath) {
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
currentPath = parentPath;
|
|
567
|
-
levelsChecked++;
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
throw err;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
console.log(`[findProjectRoot] 未找到根目录标记文件 ${ROOT_MARKER_NAME}`);
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* 从项目根目录找到模块,并返回模块名和头文件名
|
|
580
|
-
* @param {string} projectRoot - 项目根目录
|
|
581
|
-
* @param {string} specName - 模块名(target 名称)
|
|
582
|
-
* @param {string} headName - 头文件相对路径(相对于模块根目录)
|
|
583
|
-
* @returns {Promise<{moduleName: string, headerFileName: string}|null>}
|
|
584
|
-
*/
|
|
585
|
-
async function findModuleHeaderFromRoot(projectRoot, specName, headName) {
|
|
586
|
-
if (!projectRoot || !specName || !headName) {
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// 从项目根目录向下查找包含 specName 的目录(可能是模块目录)
|
|
591
|
-
// 例如:BDVideoPlayer、UI/BDVideoPlayer 等
|
|
592
|
-
const headerFileName = path.basename(headName);
|
|
593
|
-
|
|
594
|
-
// 尝试在项目根目录下查找模块目录
|
|
595
|
-
// 1. 直接查找 specName 目录
|
|
596
|
-
const directModulePath = path.join(projectRoot, specName);
|
|
597
|
-
try {
|
|
598
|
-
const entries = await getDirectoryEntries(directModulePath);
|
|
599
|
-
if (entries) {
|
|
600
|
-
// 检查是否有 include/ModuleName/ 目录
|
|
601
|
-
const includePath = path.join(directModulePath, 'include', specName, headerFileName);
|
|
602
|
-
try {
|
|
603
|
-
await fs.promises.access(includePath);
|
|
604
|
-
return {
|
|
605
|
-
moduleName: specName,
|
|
606
|
-
headerFileName: headerFileName
|
|
607
|
-
};
|
|
608
|
-
} catch (err) {
|
|
609
|
-
// 继续查找
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
} catch (err) {
|
|
613
|
-
// 继续查找
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// 2. 递归查找包含 specName 的目录
|
|
617
|
-
const foundModule = await findModuleDirectory(projectRoot, specName, headerFileName);
|
|
618
|
-
if (foundModule) {
|
|
619
|
-
return foundModule;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// 如果找不到,返回默认值(使用 specName 和 headerFileName)
|
|
623
|
-
return {
|
|
624
|
-
moduleName: specName,
|
|
625
|
-
headerFileName: headerFileName
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/**
|
|
630
|
-
* 递归查找模块目录
|
|
631
|
-
*/
|
|
632
|
-
async function findModuleDirectory(dirPath, moduleName, headerFileName) {
|
|
633
|
-
try {
|
|
634
|
-
const entries = await getDirectoryEntries(dirPath);
|
|
635
|
-
if (!entries) {
|
|
636
|
-
return null;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
for (const entry of entries) {
|
|
640
|
-
if (entry.isDirectory()) {
|
|
641
|
-
const dirName = entry.name;
|
|
642
|
-
// 跳过隐藏目录和常见目录
|
|
643
|
-
if (dirName.startsWith('.') || dirName === 'node_modules') {
|
|
644
|
-
continue;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const fullPath = path.join(dirPath, dirName);
|
|
648
|
-
|
|
649
|
-
// 检查是否是模块目录(包含 include/ModuleName/ 结构)
|
|
650
|
-
const includePath = path.join(fullPath, 'include', moduleName, headerFileName);
|
|
651
|
-
try {
|
|
652
|
-
await fs.promises.access(includePath);
|
|
653
|
-
return {
|
|
654
|
-
moduleName: moduleName,
|
|
655
|
-
headerFileName: headerFileName
|
|
656
|
-
};
|
|
657
|
-
} catch (err) {
|
|
658
|
-
// 继续递归查找
|
|
659
|
-
const found = await findModuleDirectory(fullPath, moduleName, headerFileName);
|
|
660
|
-
if (found) {
|
|
661
|
-
return found;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
} catch (err) {
|
|
667
|
-
// 忽略错误
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return null;
|
|
435
|
+
const rootMarkerPath = await searchUpwardForFile(startPath, ROOT_MARKER_NAME);
|
|
436
|
+
return rootMarkerPath ? path.dirname(rootMarkerPath) : null;
|
|
671
437
|
}
|
|
672
438
|
|
|
673
|
-
/**
|
|
674
|
-
* 获取根目录的 AutoSnippetRoot.boxspec.json 文件路径
|
|
675
|
-
* @param {string} filePath - 起始文件路径或目录路径
|
|
676
|
-
* @returns {Promise<string|null>} 根目录的 AutoSnippetRoot.boxspec.json 文件路径,如果找不到返回 null
|
|
677
|
-
*/
|
|
678
439
|
async function getRootSpecFilePath(filePath) {
|
|
679
440
|
const projectRoot = await findProjectRoot(filePath);
|
|
680
441
|
if (!projectRoot) {
|
|
@@ -682,12 +443,10 @@ async function getRootSpecFilePath(filePath) {
|
|
|
682
443
|
}
|
|
683
444
|
const rootSpecFile = path.join(projectRoot, ROOT_MARKER_NAME);
|
|
684
445
|
|
|
685
|
-
// 检查文件是否存在,如果不存在则创建
|
|
686
446
|
try {
|
|
687
447
|
await fs.promises.access(rootSpecFile);
|
|
688
448
|
} catch (err) {
|
|
689
449
|
if (err.code === 'ENOENT') {
|
|
690
|
-
// 文件不存在,创建它
|
|
691
450
|
const specObj = {
|
|
692
451
|
list: []
|
|
693
452
|
};
|
|
@@ -706,6 +465,5 @@ exports.parsePackageSwift = parsePackageSwift;
|
|
|
706
465
|
exports.findSubHeaderPath = findSubHeaderPath;
|
|
707
466
|
exports.findSubASSpecPath = findSubASSpecPath;
|
|
708
467
|
exports.findProjectRoot = findProjectRoot;
|
|
709
|
-
exports.findModuleHeaderFromRoot = findModuleHeaderFromRoot;
|
|
710
468
|
exports.getRootSpecFilePath = getRootSpecFilePath;
|
|
711
469
|
exports.ROOT_MARKER_NAME = ROOT_MARKER_NAME;
|
package/bin/init.js
CHANGED
|
@@ -16,27 +16,18 @@ async function mergeSubSpecs(mainSpecFile) {
|
|
|
16
16
|
list: []
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
// ✅ 找到项目根目录的 AutoSnippetRoot.boxspec.json 文件
|
|
20
|
-
console.log(`[mergeSubSpecs] 主配置文件路径: ${mainSpecFile}`);
|
|
21
19
|
const rootSpecFile = await findPath.getRootSpecFilePath(mainSpecFile);
|
|
22
20
|
if (!rootSpecFile) {
|
|
23
|
-
console.error(`[mergeSubSpecs] 未找到项目根目录,无法聚合配置`);
|
|
24
21
|
return;
|
|
25
22
|
}
|
|
26
|
-
console.log(`[mergeSubSpecs] 根目录配置文件: ${rootSpecFile}`);
|
|
27
23
|
|
|
28
24
|
const projectRoot = path.dirname(rootSpecFile);
|
|
29
25
|
const searchRoot = projectRoot;
|
|
30
|
-
console.log(`[mergeSubSpecs] 项目根目录: ${projectRoot}`);
|
|
31
|
-
console.log(`[mergeSubSpecs] 搜索根目录: ${searchRoot}`);
|
|
32
26
|
|
|
33
27
|
const specSlashIndex = rootSpecFile.lastIndexOf('/');
|
|
34
28
|
const specFilePath = rootSpecFile.substring(0, specSlashIndex + 1);
|
|
35
29
|
|
|
36
|
-
// ✅ 从项目根目录向下查找子模块的 AutoSnippet.boxspec.json
|
|
37
|
-
console.log(`[mergeSubSpecs] 开始查找子模块配置,搜索根目录: ${searchRoot}`);
|
|
38
30
|
const array = await findPath.findSubASSpecPath(searchRoot);
|
|
39
|
-
console.log(`[mergeSubSpecs] 找到 ${array.length} 个子模块配置文件`);
|
|
40
31
|
|
|
41
32
|
for (let i = 0; i < array.length; i++) {
|
|
42
33
|
const filename = array[i];
|
|
@@ -62,8 +53,6 @@ async function mergeSubSpecs(mainSpecFile) {
|
|
|
62
53
|
}
|
|
63
54
|
}
|
|
64
55
|
idsArray.push(item['{identifier}']);
|
|
65
|
-
// ✅ SPM 模块:headName 已经是相对于模块根目录的相对路径
|
|
66
|
-
// headName 保持相对于各自模块根目录的路径(不需要转换)
|
|
67
56
|
return true;
|
|
68
57
|
});
|
|
69
58
|
specObj.list = specObj.list.concat(arr);
|
|
@@ -76,12 +65,10 @@ async function mergeSubSpecs(mainSpecFile) {
|
|
|
76
65
|
try {
|
|
77
66
|
const content = JSON.stringify(specObj, null, 4);
|
|
78
67
|
if (content) {
|
|
79
|
-
// ✅ 写入根目录的 AutoSnippetRoot.boxspec.json
|
|
80
68
|
fs.writeFileSync(rootSpecFile, content, 'utf8');
|
|
81
|
-
console.log(`[mergeSubSpecs] 已聚合 ${specObj.list.length} 个 snippet 到根目录配置文件`);
|
|
82
69
|
}
|
|
83
70
|
} catch (err) {
|
|
84
|
-
|
|
71
|
+
// 忽略错误
|
|
85
72
|
}
|
|
86
73
|
}
|
|
87
74
|
|
|
@@ -104,7 +91,6 @@ async function findTargetRootDirFromPath(dirPath) {
|
|
|
104
91
|
for (const entry of entries) {
|
|
105
92
|
if (entry.isDirectory() && (entry.name === 'Code' || entry.name === 'Sources')) {
|
|
106
93
|
// 找到包含 Code 或 Sources 的目录,这就是 target 根目录
|
|
107
|
-
console.log(`[findTargetRootDirFromPath] 找到 target 根目录: ${currentPath} (包含 ${entry.name})`);
|
|
108
94
|
return currentPath;
|
|
109
95
|
}
|
|
110
96
|
}
|
|
@@ -124,7 +110,6 @@ async function findTargetRootDirFromPath(dirPath) {
|
|
|
124
110
|
}
|
|
125
111
|
}
|
|
126
112
|
|
|
127
|
-
console.log(`[findTargetRootDirFromPath] 未找到 target 根目录(Code 或 Sources)`);
|
|
128
113
|
return null;
|
|
129
114
|
}
|
|
130
115
|
|
|
@@ -137,31 +122,22 @@ async function initSpec() {
|
|
|
137
122
|
let packagePath = await findPath.findPackageSwiftPath(CMD_PATH);
|
|
138
123
|
if (packagePath) {
|
|
139
124
|
targetRootDir = path.dirname(packagePath);
|
|
140
|
-
console.log(`[initSpec] 使用 Package.swift 所在目录作为 target 根目录: ${targetRootDir}`);
|
|
141
125
|
} else {
|
|
142
|
-
// 如果都找不到,使用当前目录
|
|
143
126
|
targetRootDir = CMD_PATH;
|
|
144
|
-
console.log(`[initSpec] 使用当前目录作为 target 根目录: ${targetRootDir}`);
|
|
145
127
|
}
|
|
146
128
|
}
|
|
147
129
|
|
|
148
130
|
const filePath = path.join(targetRootDir, 'AutoSnippet.boxspec.json');
|
|
149
|
-
console.log(`[initSpec] 将在 target 根目录创建配置文件: ${filePath}`);
|
|
150
131
|
|
|
151
|
-
// 如果配置文件不存在,创建一个空的
|
|
152
132
|
try {
|
|
153
133
|
await fs.promises.access(filePath);
|
|
154
|
-
console.log(`[initSpec] 配置文件已存在: ${filePath}`);
|
|
155
134
|
} catch (error) {
|
|
156
135
|
const specObj = {
|
|
157
136
|
list: []
|
|
158
137
|
};
|
|
159
138
|
const content = JSON.stringify(specObj, null, 4);
|
|
160
139
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
161
|
-
console.log(`[initSpec] 已创建配置文件: ${filePath}`);
|
|
162
140
|
}
|
|
163
|
-
|
|
164
|
-
// ✅ 聚合子模块配置到根目录的 AutoSnippetRoot.boxspec.json
|
|
165
141
|
await mergeSubSpecs(filePath);
|
|
166
142
|
}
|
|
167
143
|
|
package/bin/injection.js
CHANGED
|
@@ -91,16 +91,40 @@ async function handleHeaderLine(specFile, updateFile, headerLine, importArray, i
|
|
|
91
91
|
// 获取当前文件所在的模块名(通过路径判断)
|
|
92
92
|
const currentModuleName = determineCurrentModuleName(updateFile, currentPackageInfo);
|
|
93
93
|
|
|
94
|
-
// ✅
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// ✅ 优先从标记中获取相对路径,如果没有则从缓存获取
|
|
95
|
+
// 标记格式:// ahead <Module/Header.h> relative/path/to/Header.h
|
|
96
|
+
let headRelativePath = header.headRelativePathFromMark;
|
|
97
|
+
if (!headRelativePath) {
|
|
98
|
+
// 标记中没有相对路径,尝试从缓存获取
|
|
99
|
+
const headCache = await cache.getHeadCache(specFile);
|
|
100
|
+
if (headCache && headCache[header.headerName]) {
|
|
101
|
+
headRelativePath = headCache[header.headerName];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const headerInfo = await determineHeaderInfo(specFile, header, headRelativePath, currentPackageInfo, currentModuleName);
|
|
97
106
|
|
|
98
|
-
// ✅ 更新 header
|
|
99
|
-
header.moduleName =
|
|
100
|
-
header.
|
|
107
|
+
// ✅ 更新 header 中的模块名、相对路径和 specName
|
|
108
|
+
header.moduleName = headerInfo.moduleName;
|
|
109
|
+
header.headRelativePath = headerInfo.headRelativePath; // 相对于模块根目录的相对路径
|
|
110
|
+
header.specName = '<' + headerInfo.moduleName + '/' + header.headerName + '>';
|
|
101
111
|
|
|
102
112
|
// ✅ 判断是否为同一模块
|
|
103
|
-
const isSameModule = currentModuleName ===
|
|
113
|
+
const isSameModule = currentModuleName === headerInfo.moduleName;
|
|
114
|
+
|
|
115
|
+
// ✅ 如果是同一模块,计算相对于当前文件的相对路径
|
|
116
|
+
if (isSameModule && headerInfo.headRelativePath) {
|
|
117
|
+
// 当前文件相对于模块根目录的路径
|
|
118
|
+
const currentFileRelativeToModuleRoot = path.relative(currentPackageInfo.path, updateFile);
|
|
119
|
+
// 头文件相对于模块根目录的路径
|
|
120
|
+
const headRelativeToModuleRoot = headerInfo.headRelativePath;
|
|
121
|
+
|
|
122
|
+
// 计算从当前文件到头文件的相对路径
|
|
123
|
+
const currentFileDir = path.dirname(currentFileRelativeToModuleRoot);
|
|
124
|
+
const relativePathToHeader = path.relative(currentFileDir, headRelativeToModuleRoot).replace(/\\/g, '/');
|
|
125
|
+
|
|
126
|
+
header.relativePathToCurrentFile = relativePathToHeader;
|
|
127
|
+
}
|
|
104
128
|
|
|
105
129
|
// 如果是同一模块,使用相对路径;否则使用 <> 格式
|
|
106
130
|
handleModuleHeader(specFile, updateFile, header, importArray, !isSameModule);
|
|
@@ -125,57 +149,112 @@ function determineCurrentModuleName(filePath, packageInfo) {
|
|
|
125
149
|
}
|
|
126
150
|
|
|
127
151
|
/**
|
|
128
|
-
*
|
|
152
|
+
* 确定头文件信息(模块名、相对路径等)
|
|
129
153
|
* 从头文件路径查找 Package.swift,确定最近的 target 模块名
|
|
154
|
+
* 同时计算相对于当前文件的相对路径
|
|
155
|
+
* @param {string} specFile - 配置文件路径
|
|
156
|
+
* @param {object} header - 头文件信息对象
|
|
157
|
+
* @param {string} headRelativePath - 头文件相对路径(相对于模块根目录),可能来自标记或缓存
|
|
158
|
+
* @param {object} currentPackageInfo - 当前文件的 Package.swift 信息
|
|
159
|
+
* @param {string} currentModuleName - 当前模块名
|
|
130
160
|
*/
|
|
131
|
-
async function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
161
|
+
async function determineHeaderInfo(specFile, header, headRelativePath, currentPackageInfo, currentModuleName) {
|
|
162
|
+
// 默认值
|
|
163
|
+
let moduleName = header.moduleName;
|
|
164
|
+
let relativePathToCurrentFile = null;
|
|
136
165
|
|
|
137
|
-
// 从头缓存中获取头文件的相对路径
|
|
138
|
-
const headRelativePath = headCache[header.headerName];
|
|
139
166
|
if (!headRelativePath) {
|
|
140
|
-
//
|
|
141
|
-
return
|
|
167
|
+
// 没有相对路径,使用从 headerLine 解析的模块名(可能不准确)
|
|
168
|
+
return {
|
|
169
|
+
moduleName: moduleName,
|
|
170
|
+
headRelativePath: null,
|
|
171
|
+
relativePathToCurrentFile: null
|
|
172
|
+
};
|
|
142
173
|
}
|
|
143
174
|
|
|
144
|
-
// ✅ specFile
|
|
145
|
-
|
|
146
|
-
const
|
|
175
|
+
// ✅ specFile 可能是根目录的 AutoSnippetRoot.boxspec.json,需要找到头文件所在的模块配置
|
|
176
|
+
// 尝试从根目录的配置文件查找头文件所在的模块
|
|
177
|
+
const rootSpecDir = path.dirname(specFile);
|
|
178
|
+
let headPath = null;
|
|
179
|
+
|
|
180
|
+
// 尝试多种可能的路径
|
|
181
|
+
// 1. 直接使用根目录的相对路径
|
|
182
|
+
headPath = path.join(rootSpecDir, headRelativePath);
|
|
183
|
+
|
|
184
|
+
// 2. 如果文件不存在,尝试从子模块中查找
|
|
185
|
+
if (!fs.existsSync(headPath)) {
|
|
186
|
+
// 从头文件路径向上查找 Package.swift
|
|
187
|
+
const headerPackagePath = await findPath.findPackageSwiftPath(headPath);
|
|
188
|
+
if (headerPackagePath) {
|
|
189
|
+
const headerPackageInfo = await findPath.parsePackageSwift(headerPackagePath);
|
|
190
|
+
if (headerPackageInfo) {
|
|
191
|
+
// 找到 Package.swift,使用它的路径作为模块根目录
|
|
192
|
+
const headerModuleRootDir = path.dirname(headerPackagePath);
|
|
193
|
+
headPath = path.join(headerModuleRootDir, headRelativePath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
147
197
|
|
|
148
198
|
// 从头文件路径向上查找 Package.swift
|
|
149
199
|
const headerPackagePath = await findPath.findPackageSwiftPath(headPath);
|
|
150
200
|
if (!headerPackagePath) {
|
|
151
201
|
// 找不到 Package.swift,使用从 headerLine 解析的模块名
|
|
152
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
moduleName: moduleName,
|
|
204
|
+
headRelativePath: headRelativePath,
|
|
205
|
+
relativePathToCurrentFile: null
|
|
206
|
+
};
|
|
153
207
|
}
|
|
154
208
|
|
|
155
209
|
// 解析 Package.swift 获取模块信息
|
|
156
210
|
const headerPackageInfo = await findPath.parsePackageSwift(headerPackagePath);
|
|
157
211
|
if (!headerPackageInfo) {
|
|
158
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
moduleName: moduleName,
|
|
214
|
+
headRelativePath: headRelativePath,
|
|
215
|
+
relativePathToCurrentFile: null
|
|
216
|
+
};
|
|
159
217
|
}
|
|
160
218
|
|
|
161
219
|
// ✅ 根据头文件路径确定最近的 target 模块名
|
|
162
|
-
|
|
220
|
+
moduleName = determineCurrentModuleName(headPath, headerPackageInfo);
|
|
221
|
+
|
|
222
|
+
// ✅ 如果是同一模块,计算相对于当前文件的相对路径
|
|
223
|
+
// 注意:这里需要知道当前文件的路径,所以需要从外部传入
|
|
224
|
+
// 但当前函数中还没有 updateFile,所以这部分逻辑在 handleHeaderLine 中完成
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
moduleName: moduleName,
|
|
228
|
+
headRelativePath: headRelativePath,
|
|
229
|
+
relativePathToCurrentFile: null // 将在 handleHeaderLine 中计算
|
|
230
|
+
};
|
|
163
231
|
}
|
|
164
232
|
|
|
165
233
|
// isOuter区分模块内部引用""格式和模块外部引用<>格式
|
|
166
234
|
// ✅ SPM 模块:
|
|
167
|
-
// - isOuter = false:同一模块内部,使用相对路径 #import "Header.h"
|
|
235
|
+
// - isOuter = false:同一模块内部,使用相对路径 #import "relative/path/Header.h" 或 #import "Header.h"
|
|
168
236
|
// - isOuter = true:不同模块之间,使用 #import <ModuleName/Header.h>
|
|
169
237
|
function handleModuleHeader(specFile, updateFile, header, importArray, isOuter) {
|
|
170
|
-
// ✅ 根据 isOuter
|
|
171
|
-
|
|
238
|
+
// ✅ 根据 isOuter 选择不同的引入格式,用于检查是否已存在
|
|
239
|
+
let headNameToCheck;
|
|
240
|
+
if (isOuter) {
|
|
241
|
+
// 外部模块,使用 <> 格式
|
|
242
|
+
headNameToCheck = header.name; // <ModuleName/Header.h>
|
|
243
|
+
} else {
|
|
244
|
+
// 同一模块内部,使用相对路径
|
|
245
|
+
if (header.relativePathToCurrentFile) {
|
|
246
|
+
headNameToCheck = '"' + header.relativePathToCurrentFile + '"'; // "relative/path/Header.h"
|
|
247
|
+
} else {
|
|
248
|
+
headNameToCheck = header.headerStrName; // "Header.h"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
172
251
|
const moduleName = isOuter ? header.specName : header.moduleStrName;
|
|
173
252
|
|
|
174
253
|
// 检查是否已经引入头文件
|
|
175
254
|
for (let i = 0; i < importArray.length; i++) {
|
|
176
255
|
const importHeader = importArray[i].split(importMark)[1].trim();
|
|
177
256
|
|
|
178
|
-
if (importHeader ===
|
|
257
|
+
if (importHeader === headNameToCheck) {
|
|
179
258
|
// 已经引入头文件
|
|
180
259
|
handelAddHeaderStatus(specFile, updateFile, header, true, false, isOuter);
|
|
181
260
|
return;
|
|
@@ -267,10 +346,21 @@ function removeMarkFromFile(updateFile, header, string) {
|
|
|
267
346
|
function addHeaderToFile(updateFile, header, isOuter) {
|
|
268
347
|
// ✅ 根据 isOuter 选择不同的引入格式
|
|
269
348
|
// isOuter = true: 使用 <> 格式(#import <ModuleName/Header.h>)
|
|
270
|
-
// isOuter = false: 使用相对路径(#import "Header.h")
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
349
|
+
// isOuter = false: 使用相对路径(#import "relative/path/Header.h" 或 #import "Header.h")
|
|
350
|
+
let importLine;
|
|
351
|
+
if (isOuter) {
|
|
352
|
+
// 外部模块,使用 <> 格式
|
|
353
|
+
importLine = importMark + ' ' + header.name; // <ModuleName/Header.h>
|
|
354
|
+
} else {
|
|
355
|
+
// 同一模块内部,使用相对路径
|
|
356
|
+
if (header.relativePathToCurrentFile) {
|
|
357
|
+
// 使用计算出的相对路径(如 "SubDir/Header.h" 或 "../Header.h")
|
|
358
|
+
importLine = importMark + ' "' + header.relativePathToCurrentFile + '"';
|
|
359
|
+
} else {
|
|
360
|
+
// 如果没有相对路径,使用文件名(如 "Header.h")
|
|
361
|
+
importLine = importMark + ' ' + header.headerStrName; // "Header.h"
|
|
362
|
+
}
|
|
363
|
+
}
|
|
274
364
|
|
|
275
365
|
readStream(updateFile, importLine, importMark);
|
|
276
366
|
checkDependency(updateFile, header.moduleName, '自动注入头文件完成。').catch(err => {
|
package/bin/install.js
CHANGED
|
@@ -30,6 +30,11 @@ function writeSingleSnippet(snippet, template) {
|
|
|
30
30
|
// 例如:<BDVideoPlayer/BDVideoCacheManager.h> 而不是 <BDVideoPlayer/UI/BDVideoPlayer/Code/BDVideoCacheManager.h>
|
|
31
31
|
const headerFileName = path.basename(extPlace['{headName}']);
|
|
32
32
|
let header = '<' + extPlace['{specName}'] + '/' + headerFileName + '>';
|
|
33
|
+
|
|
34
|
+
// ✅ 在标记中包含相对路径信息,格式:// ahead <Module/Header.h> relative/path/to/Header.h
|
|
35
|
+
// 这样即使没有缓存也能解析出完整信息
|
|
36
|
+
const headRelativePath = extPlace['{headName}']; // 相对于模块根目录的相对路径
|
|
37
|
+
header = header + ' ' + headRelativePath;
|
|
33
38
|
header = escapeString(header);
|
|
34
39
|
|
|
35
40
|
// swift只需要考虑工作空间是否引入
|
|
@@ -42,7 +47,7 @@ function writeSingleSnippet(snippet, template) {
|
|
|
42
47
|
extPlace['{completion}'] = extPlace['{completion}'] + 'Z';
|
|
43
48
|
extPlace['{summary}'] = extPlace['{summary}'] + header;
|
|
44
49
|
|
|
45
|
-
// 添加替换header
|
|
50
|
+
// 添加替换header标识位(格式:// ahead <Module/Header.h> relative/path/to/Header.h)
|
|
46
51
|
let array = ['// ahead ' + header];
|
|
47
52
|
extPlace['{content}'].forEach(element => {
|
|
48
53
|
array.push(element);
|
|
@@ -159,6 +164,11 @@ function addCodeSnippets(specFile, singleSnippet) {
|
|
|
159
164
|
// ✅ 从项目根目录找到模块,只使用模块名和头文件名
|
|
160
165
|
const headerFileName = path.basename(extPlace['{headName}']);
|
|
161
166
|
let header = '<' + extPlace['{specName}'] + '/' + headerFileName + '>';
|
|
167
|
+
|
|
168
|
+
// ✅ 在标记中包含相对路径信息,格式:// ahead <Module/Header.h> relative/path/to/Header.h
|
|
169
|
+
// 这样即使没有缓存也能解析出完整信息
|
|
170
|
+
const headRelativePath = extPlace['{headName}']; // 相对于模块根目录的相对路径
|
|
171
|
+
header = header + ' ' + headRelativePath;
|
|
162
172
|
header = escapeString(header);
|
|
163
173
|
|
|
164
174
|
// swift只需要考虑工作空间是否引入
|
package/bin/watch.js
CHANGED
|
@@ -14,7 +14,8 @@ const alinkMark = 'alink';
|
|
|
14
14
|
const wellMark = '#';
|
|
15
15
|
const atMark = '@';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// ✅ 更新正则以匹配包含相对路径的新格式:// ahead <Module/Header.h> relative/path/to/Header.h
|
|
18
|
+
const headerReg = /^\/\/ ahead <\w+\/\w+.h>(\s+.+)?$/;
|
|
18
19
|
const headerSwiftReg = /^\/\/ ahead \w+$/;
|
|
19
20
|
const importReg = /^\#import\s*<\w+\/\w+.h>$/;
|
|
20
21
|
const importSwiftReg = /^import\s*\w+$/;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autosnippet",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.23",
|
|
4
4
|
"description": "A iOS module management tool.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,5 +31,8 @@
|
|
|
31
31
|
"devDependencies": {},
|
|
32
32
|
"bugs": {
|
|
33
33
|
"url": "https://github.com/GxFn/AutoSnippet/issues"
|
|
34
|
+
},
|
|
35
|
+
"directories": {
|
|
36
|
+
"doc": "docs"
|
|
34
37
|
}
|
|
35
38
|
}
|