hcordova 1.0.0
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/LICENSE +204 -0
- package/README.md +346 -0
- package/bin/hcordova +27 -0
- package/package.json +39 -0
- package/src/base-cli.js +1181 -0
- package/src/utils/DependencyChecker.js +382 -0
- package/src/utils/PlatformProject.js +452 -0
- package/src/utils/PluginConfigParser.js +408 -0
- package/src/utils/PluginDownloader.js +181 -0
- package/src/utils/PluginHandler.js +1097 -0
- package/src/utils/VariableValidator.js +369 -0
- package/src/utils/args-processor.js +79 -0
- package/templates/project/AppScope/app.json5 +10 -0
- package/templates/project/AppScope/resources/base/element/string.json +8 -0
- package/templates/project/AppScope/resources/base/media/background.png +0 -0
- package/templates/project/AppScope/resources/base/media/foreground.png +0 -0
- package/templates/project/AppScope/resources/base/media/layered_image.json +7 -0
- package/templates/project/build-profile.json5 +46 -0
- package/templates/project/code-linter.json5 +32 -0
- package/templates/project/entry/build-profile.json5 +28 -0
- package/templates/project/entry/hvigorfile.ts +6 -0
- package/templates/project/entry/obfuscation-rules.txt +23 -0
- package/templates/project/entry/oh-package.json5 +12 -0
- package/templates/project/entry/src/main/ets/entryability/EntryAbility.ets +62 -0
- package/templates/project/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +32 -0
- package/templates/project/entry/src/main/ets/pages/Index.ets +40 -0
- package/templates/project/entry/src/main/module.json5 +52 -0
- package/templates/project/entry/src/main/resources/base/element/color.json +8 -0
- package/templates/project/entry/src/main/resources/base/element/float.json +8 -0
- package/templates/project/entry/src/main/resources/base/element/string.json +16 -0
- package/templates/project/entry/src/main/resources/base/media/background.png +0 -0
- package/templates/project/entry/src/main/resources/base/media/foreground.png +0 -0
- package/templates/project/entry/src/main/resources/base/media/layered_image.json +7 -0
- package/templates/project/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/templates/project/entry/src/main/resources/base/profile/backup_config.json +3 -0
- package/templates/project/entry/src/main/resources/base/profile/main_pages.json +5 -0
- package/templates/project/entry/src/main/resources/dark/element/color.json +8 -0
- package/templates/project/entry/src/main/resources/rawfile/config.xml +26 -0
- package/templates/project/entry/src/main/resources/rawfile/www/cordova.js +1925 -0
- package/templates/project/entry/src/main/resources/rawfile/www/css/index.css +158 -0
- package/templates/project/entry/src/main/resources/rawfile/www/img/cordova.png +0 -0
- package/templates/project/entry/src/main/resources/rawfile/www/img/logo.png +0 -0
- package/templates/project/entry/src/main/resources/rawfile/www/index.html +110 -0
- package/templates/project/entry/src/main/resources/rawfile/www/js/index.js +68 -0
- package/templates/project/entry/src/mock/mock-config.json5 +2 -0
- package/templates/project/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
- package/templates/project/entry/src/ohosTest/ets/test/List.test.ets +5 -0
- package/templates/project/entry/src/ohosTest/module.json5 +13 -0
- package/templates/project/entry/src/test/List.test.ets +5 -0
- package/templates/project/entry/src/test/LocalUnit.test.ets +33 -0
- package/templates/project/hvigor/hvigor-config.json5 +22 -0
- package/templates/project/hvigorfile.ts +6 -0
- package/templates/project/local.properties +9 -0
- package/templates/project/oh-package-lock.json5 +27 -0
- package/templates/project/oh-package.json5 +10 -0
|
@@ -0,0 +1,1097 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Huawei Device, Inc. Ltd. and <马弓手>.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
const handlers = {
|
|
22
|
+
'source-file': {
|
|
23
|
+
install: function (obj, plugin, project, options) {
|
|
24
|
+
if (!obj.src) throw new Error(generateAttributeError('src', 'source-file', plugin.id));
|
|
25
|
+
if (!obj.targetDir) throw new Error(generateAttributeError('target-dir', 'source-file', plugin.id));
|
|
26
|
+
|
|
27
|
+
const dest = getInstallDestination(obj, project);
|
|
28
|
+
copyFile(plugin.dir, obj.src, project.root, dest, !!(options && options.link));
|
|
29
|
+
// 处理 runtimeOnly 文件
|
|
30
|
+
if (obj.runtimeOnly === 'true' || obj.runtimeOnly === true) {
|
|
31
|
+
addToRuntimeOnlySources(plugin.dir, obj.src, project, obj);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
uninstall: function (obj, plugin, project, options) {
|
|
35
|
+
if (!obj.src) throw new Error(generateAttributeError('src', 'source-file', plugin.id));
|
|
36
|
+
if (!obj.targetDir) throw new Error(generateAttributeError('target-dir', 'source-file', plugin.id));
|
|
37
|
+
|
|
38
|
+
const dest = getInstallDestination(obj, project);
|
|
39
|
+
const destFile = path.resolve(project.root, dest);
|
|
40
|
+
removeFileF(destFile);
|
|
41
|
+
// 处理 runtimeOnly 文件卸载
|
|
42
|
+
if (obj.runtimeOnly === 'true' || obj.runtimeOnly === true) {
|
|
43
|
+
removeFromRuntimeOnlySources(plugin.dir, obj.src, project, obj);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
'cmake-config': {
|
|
48
|
+
install: function (obj, plugin, project, options) {
|
|
49
|
+
if (!obj.target) throw new Error(generateAttributeError('target', 'cmake-config', plugin.id));
|
|
50
|
+
if (!obj.params || !Array.isArray(obj.params)) {
|
|
51
|
+
throw new Error(generateAttributeError('params', 'cmake-config', plugin.id));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
project.events.emit('log', `Installing CMakeLists configuration to: ${obj.target}`);
|
|
55
|
+
|
|
56
|
+
const cmakePath = path.resolve(project.root, obj.modulesName, obj.target);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(cmakePath)) {
|
|
59
|
+
project.events.emit('warn', `CMakeLists.txt not found at: ${cmakePath}`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
let cmakeContent = fs.readFileSync(cmakePath, 'utf8');
|
|
65
|
+
const newContent = processCmakeListsContent(cmakeContent, obj, project);
|
|
66
|
+
|
|
67
|
+
if (cmakeContent !== newContent) {
|
|
68
|
+
fs.writeFileSync(cmakePath, newContent, 'utf8');
|
|
69
|
+
project.events.emit('log', `Updated CMakeLists.txt: ${obj.target}`);
|
|
70
|
+
|
|
71
|
+
// 记录具体修改
|
|
72
|
+
obj.params.forEach(param => {
|
|
73
|
+
project.events.emit('log', ` - ${param.target}(${param.value})`);
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
project.events.emit('log', `No changes needed for CMakeLists.txt: ${obj.target}`);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error(`Failed to update CMakeLists.txt ${obj.target}: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
uninstall: function (obj, plugin, project, options) {
|
|
83
|
+
if (!obj.target) throw new Error(generateAttributeError('target', 'cmake-config', plugin.id));
|
|
84
|
+
if (!obj.params || !Array.isArray(obj.params)) {
|
|
85
|
+
throw new Error(generateAttributeError('params', 'cmake-config', plugin.id));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const cmakePath = path.resolve(project.root, obj.modulesName, obj.target);
|
|
89
|
+
if (!fs.existsSync(cmakePath)) {
|
|
90
|
+
project.events.emit('warn', `CMakeLists.txt not found at: ${cmakePath}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
let cmakeContent = fs.readFileSync(cmakePath, 'utf8');
|
|
96
|
+
const originalContent = revertCmakeListsContent(cmakeContent, obj, project);
|
|
97
|
+
|
|
98
|
+
if (cmakeContent !== originalContent) {
|
|
99
|
+
fs.writeFileSync(cmakePath, originalContent, 'utf8');
|
|
100
|
+
project.events.emit('log', `Reverted CMakeLists.txt: ${obj.target}`);
|
|
101
|
+
} else {
|
|
102
|
+
project.events.emit('log', `No changes needed for CMakeLists.txt uninstall: ${obj.target}`);
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
project.events.emit('warn', `Failed to revert CMakeLists.txt ${obj.target}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
'config-file': {
|
|
110
|
+
install: function (obj, plugin, project, options) {
|
|
111
|
+
if (!obj.target) throw new Error(generateAttributeError('target', 'config-file', plugin.id));
|
|
112
|
+
if (!obj.content) throw new Error(generateAttributeError('content', 'config-file', plugin.id));
|
|
113
|
+
|
|
114
|
+
project.events.emit('log', `Installing config-file to: ${obj.target}`);
|
|
115
|
+
|
|
116
|
+
const targetPath = path.resolve(project.moduleRoot, obj.target);
|
|
117
|
+
|
|
118
|
+
if (!fs.existsSync(targetPath)) {
|
|
119
|
+
project.events.emit('warn', `Config file not found: ${targetPath}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
let content = fs.readFileSync(targetPath, 'utf8');
|
|
125
|
+
|
|
126
|
+
// 执行变量替换
|
|
127
|
+
const processedContent = replaceVariables(obj.content, options.variables || {});
|
|
128
|
+
const newContent = processConfigFileContent(content, {
|
|
129
|
+
...obj,
|
|
130
|
+
content: processedContent
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (content !== newContent) {
|
|
134
|
+
fs.writeFileSync(targetPath, newContent, 'utf8');
|
|
135
|
+
project.events.emit('log', `Updated config file: ${obj.target}`);
|
|
136
|
+
|
|
137
|
+
// 记录被替换的变量
|
|
138
|
+
const replacedVars = findReplacedVariables(obj.content, options.variables || {});
|
|
139
|
+
if (replacedVars.length > 0) {
|
|
140
|
+
project.events.emit('log', `Replaced variables: ${replacedVars.join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
project.events.emit('log', `No changes needed for config file: ${obj.target}`);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`Failed to update config file ${obj.target}: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
uninstall: function (obj, plugin, project, options) {
|
|
150
|
+
if (!obj.target) throw new Error(generateAttributeError('target', 'config-file', plugin.id));
|
|
151
|
+
if (!obj.content) throw new Error(generateAttributeError('content', 'config-file', plugin.id));
|
|
152
|
+
|
|
153
|
+
project.events.emit('log', `Unstalling config-file to: ${obj.target}`);
|
|
154
|
+
|
|
155
|
+
const targetPath = path.resolve(project.moduleRoot, obj.target);
|
|
156
|
+
|
|
157
|
+
if (!fs.existsSync(targetPath)) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
let content = fs.readFileSync(targetPath, 'utf8');
|
|
163
|
+
|
|
164
|
+
// 执行变量替换(与安装时相同的逻辑)
|
|
165
|
+
const processedContent = replaceVariables(obj.content, options.variables || {});
|
|
166
|
+
const originalContent = revertConfigFileContent(content, {
|
|
167
|
+
...obj,
|
|
168
|
+
content: processedContent
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (content !== originalContent) {
|
|
172
|
+
fs.writeFileSync(targetPath, originalContent, 'utf8');
|
|
173
|
+
project.events.emit('log', `Reverted config file: ${obj.target}`);
|
|
174
|
+
|
|
175
|
+
// 记录被替换的变量(用于卸载时的匹配)
|
|
176
|
+
const replacedVars = findReplacedVariables(obj.content, options.variables || {});
|
|
177
|
+
if (replacedVars.length > 0) {
|
|
178
|
+
project.events.emit('log', `Used variables for uninstall: ${replacedVars.join(', ')}`);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
project.events.emit('log', `No changes needed for config file uninstall: ${obj.target}`);
|
|
182
|
+
}
|
|
183
|
+
} catch (error) {
|
|
184
|
+
project.events.emit('warn', `Failed to revert config file ${obj.target}: ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
'js-module': {
|
|
189
|
+
install: function (obj, plugin, project, options) {
|
|
190
|
+
if (!obj.src) throw new Error(generateAttributeError('src', 'js-module', plugin.id));
|
|
191
|
+
|
|
192
|
+
const moduleSource = path.resolve(plugin.dir, obj.src);
|
|
193
|
+
|
|
194
|
+
if (!fs.existsSync(moduleSource)) {
|
|
195
|
+
throw new Error(`JS module source not found: ${moduleSource}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const moduleName = obj.name || path.basename(obj.src, '.js');
|
|
199
|
+
const fullModuleName = `${plugin.id}.${moduleName}`;
|
|
200
|
+
|
|
201
|
+
// 目标路径
|
|
202
|
+
const destPath = path.resolve(project.moduleRoot, 'src', 'main', 'resources', 'rawfile', 'www', 'plugins', plugin.id, obj.src);
|
|
203
|
+
|
|
204
|
+
// 检查是否已存在(避免覆盖)
|
|
205
|
+
if (fs.existsSync(destPath)) {
|
|
206
|
+
project.events.emit('warn', `JS module already exists: ${destPath}`);
|
|
207
|
+
// 继续执行,因为可能需要更新 cordova_plugins.js
|
|
208
|
+
} else {
|
|
209
|
+
// 创建目录并写入文件
|
|
210
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
211
|
+
|
|
212
|
+
// 读取并包装 JS 内容
|
|
213
|
+
let jsContent = fs.readFileSync(moduleSource, 'utf8');
|
|
214
|
+
jsContent = jsContent.replace(/^\ufeff/, '');
|
|
215
|
+
|
|
216
|
+
const wrappedContent = `cordova.define("${fullModuleName}", function(require, exports, module) {\n${jsContent}});`;
|
|
217
|
+
|
|
218
|
+
fs.writeFileSync(destPath, wrappedContent, 'utf8');
|
|
219
|
+
project.events.emit('log', `Created JS module file: ${destPath}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 安全地更新 cordova_plugins.js
|
|
223
|
+
safeUpdateCordovaPlugins(project, plugin, obj, fullModuleName);
|
|
224
|
+
|
|
225
|
+
project.events.emit('log', `Installed JS module: ${fullModuleName}`);
|
|
226
|
+
},
|
|
227
|
+
uninstall: function (obj, plugin, project, options) {
|
|
228
|
+
const moduleName = obj.name || path.basename(obj.src, '.js');
|
|
229
|
+
const fullModuleName = `${plugin.id}.${moduleName}`;
|
|
230
|
+
|
|
231
|
+
// 删除 JS 文件
|
|
232
|
+
const filePath = path.resolve(project.moduleRoot, 'src', 'main', 'resources', 'rawfile', 'www', 'plugins', plugin.id);
|
|
233
|
+
if (fs.existsSync(filePath)) {
|
|
234
|
+
fs.rmSync(filePath, {recursive: true});
|
|
235
|
+
project.events.emit('log', `Removed JS file: ${filePath}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 从 cordova_plugins.js 中移除
|
|
239
|
+
removeFromCordovaPlugins(project, plugin.id, fullModuleName);
|
|
240
|
+
|
|
241
|
+
project.events.emit('log', `Uninstalled JS module: ${fullModuleName}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 替换配置内容中的变量
|
|
248
|
+
*/
|
|
249
|
+
function replaceVariables(content, variables) {
|
|
250
|
+
return content.replace(/\$([A-Z_][A-Z0-9_]*)/g, (match, variableName) => {
|
|
251
|
+
if (variables.hasOwnProperty(variableName)) {
|
|
252
|
+
return variables[variableName];
|
|
253
|
+
} else {
|
|
254
|
+
// 如果变量未定义,保持原样并发出警告
|
|
255
|
+
console.warn(`Warning: Variable $${variableName} is not defined`);
|
|
256
|
+
return match;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 查找被替换的变量
|
|
263
|
+
*/
|
|
264
|
+
function findReplacedVariables(content, variables) {
|
|
265
|
+
const replacedVars = [];
|
|
266
|
+
const variablePattern = /\$([A-Z_][A-Z0-9_]*)/g;
|
|
267
|
+
let match;
|
|
268
|
+
|
|
269
|
+
while ((match = variablePattern.exec(content)) !== null) {
|
|
270
|
+
const variableName = match[1];
|
|
271
|
+
if (variables.hasOwnProperty(variableName)) {
|
|
272
|
+
replacedVars.push(variableName);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return [...new Set(replacedVars)]; // 去重
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 工具函数
|
|
280
|
+
async function copyFile(plugin_dir, src, project_dir, dest, link) {
|
|
281
|
+
src = path.resolve(plugin_dir, src);
|
|
282
|
+
if (!fs.existsSync(src)) throw new Error('"' + src + '" not found!');
|
|
283
|
+
const real_path = fs.realpathSync(src);
|
|
284
|
+
const real_plugin_path = fs.realpathSync(plugin_dir);
|
|
285
|
+
if (!isPathInside(real_path, real_plugin_path)) {
|
|
286
|
+
throw new Error('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
dest = path.resolve(project_dir, dest);
|
|
290
|
+
if (!isPathInside(dest, project_dir)) {
|
|
291
|
+
throw new Error('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const srcStats = fs.statSync(src);
|
|
295
|
+
if (srcStats.isFile()) {
|
|
296
|
+
dest = path.join(dest, path.basename(src));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
300
|
+
|
|
301
|
+
if (link) {
|
|
302
|
+
symlinkFileOrDirTree(src, dest);
|
|
303
|
+
} else {
|
|
304
|
+
fs.copyFileSync(src, dest);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function symlinkFileOrDirTree(src, dest) {
|
|
309
|
+
if (fs.existsSync(dest)) {
|
|
310
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
311
|
+
}
|
|
312
|
+
if (fs.statSync(src).isDirectory()) {
|
|
313
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
314
|
+
fs.readdirSync(src).forEach(function (entry) {
|
|
315
|
+
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
|
|
316
|
+
});
|
|
317
|
+
} else {
|
|
318
|
+
fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function removeFileF(file) {
|
|
323
|
+
fs.rmSync(file, { recursive: true, force: true });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function generateAttributeError(attribute, element, id) {
|
|
327
|
+
return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 添加文件到 runtimeOnly sources
|
|
332
|
+
*/
|
|
333
|
+
function addToRuntimeOnlySources(pluginDir, src, project, obj) {
|
|
334
|
+
const buildProfilePath = path.join(project.root, 'cordova', 'build-profile.json5');
|
|
335
|
+
|
|
336
|
+
if (!fs.existsSync(buildProfilePath)) {
|
|
337
|
+
project.events.emit('warn', `build-profile.json5 not found at: ${buildProfilePath}`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const json5 = require('json5');
|
|
343
|
+
const buildProfileContent = fs.readFileSync(buildProfilePath, 'utf8');
|
|
344
|
+
const buildProfile = json5.parse(buildProfileContent);
|
|
345
|
+
|
|
346
|
+
// 获取完整的源文件路径(相对于项目根目录)
|
|
347
|
+
const dest = "./"+path.join(obj.targetDir, path.basename(src));
|
|
348
|
+
const relativeSrcPath = dest.replace(/\\/g, '/'); // 统一使用正斜杠
|
|
349
|
+
|
|
350
|
+
// 确保 buildOption.arkOptions.runtimeOnly.sources 配置存在
|
|
351
|
+
if (!buildProfile.buildOption) {
|
|
352
|
+
buildProfile.buildOption = {};
|
|
353
|
+
}
|
|
354
|
+
if (!buildProfile.buildOption.arkOptions) {
|
|
355
|
+
buildProfile.buildOption.arkOptions = {};
|
|
356
|
+
}
|
|
357
|
+
if (!buildProfile.buildOption.arkOptions.runtimeOnly) {
|
|
358
|
+
buildProfile.buildOption.arkOptions.runtimeOnly = {};
|
|
359
|
+
}
|
|
360
|
+
if (!Array.isArray(buildProfile.buildOption.arkOptions.runtimeOnly.sources)) {
|
|
361
|
+
buildProfile.buildOption.arkOptions.runtimeOnly.sources = [];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const sources = buildProfile.buildOption.arkOptions.runtimeOnly.sources;
|
|
365
|
+
|
|
366
|
+
// 检查是否已存在
|
|
367
|
+
const existingIndex = sources.findIndex(source =>
|
|
368
|
+
source === relativeSrcPath
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
if (existingIndex === -1) {
|
|
372
|
+
// 添加相对路径
|
|
373
|
+
sources.push(relativeSrcPath);
|
|
374
|
+
|
|
375
|
+
// 保存更新后的配置
|
|
376
|
+
const updatedContent = json5.stringify(buildProfile, {
|
|
377
|
+
space: 2,
|
|
378
|
+
quote: '"'
|
|
379
|
+
});
|
|
380
|
+
fs.writeFileSync(buildProfilePath, updatedContent, 'utf8');
|
|
381
|
+
|
|
382
|
+
project.events.emit('log', `✓ Added runtimeOnly source: ${relativeSrcPath}`);
|
|
383
|
+
} else {
|
|
384
|
+
project.events.emit('log', `RuntimeOnly source already exists: ${relativeSrcPath}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
} catch (error) {
|
|
388
|
+
project.events.emit('error', `Failed to add runtimeOnly source: ${error.message}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* 从 runtimeOnly sources 移除文件
|
|
394
|
+
*/
|
|
395
|
+
function removeFromRuntimeOnlySources(pluginDir, src, project, obj) {
|
|
396
|
+
const buildProfilePath = path.join(project.root, 'cordova', 'build-profile.json5');
|
|
397
|
+
|
|
398
|
+
if (!fs.existsSync(buildProfilePath)) {
|
|
399
|
+
project.events.emit('log', `build-profile.json5 not found, skipping runtimeOnly cleanup`);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const json5 = require('json5');
|
|
405
|
+
const buildProfileContent = fs.readFileSync(buildProfilePath, 'utf8');
|
|
406
|
+
const buildProfile = json5.parse(buildProfileContent);
|
|
407
|
+
|
|
408
|
+
// 获取完整的源文件路径(相对于项目根目录)
|
|
409
|
+
const dest = "./"+path.join(obj.targetDir, path.basename(src));
|
|
410
|
+
const relativeSrcPath = dest.replace(/\\/g, '/'); // 统一使用正斜杠
|
|
411
|
+
|
|
412
|
+
project.events.emit('log', `Removing runtimeOnly source: ${relativeSrcPath}`);
|
|
413
|
+
|
|
414
|
+
// 检查是否存在 runtimeOnly 配置
|
|
415
|
+
if (!buildProfile.buildOption ||
|
|
416
|
+
!buildProfile.buildOption.arkOptions ||
|
|
417
|
+
!buildProfile.buildOption.arkOptions.runtimeOnly ||
|
|
418
|
+
!Array.isArray(buildProfile.buildOption.arkOptions.runtimeOnly.sources)) {
|
|
419
|
+
project.events.emit('log', `No runtimeOnly sources found to remove`);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const sources = buildProfile.buildOption.arkOptions.runtimeOnly.sources;
|
|
424
|
+
|
|
425
|
+
// 查找并移除
|
|
426
|
+
const originalLength = sources.length;
|
|
427
|
+
const filteredSources = sources.filter(source => {
|
|
428
|
+
const sourcePath = source.replace(/\\/g, '/');
|
|
429
|
+
return sourcePath !== relativeSrcPath;
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
if (filteredSources.length < originalLength) {
|
|
433
|
+
buildProfile.buildOption.arkOptions.runtimeOnly.sources = filteredSources;
|
|
434
|
+
|
|
435
|
+
// 如果 sources 数组为空,清理空的数组
|
|
436
|
+
if (filteredSources.length === 0) {
|
|
437
|
+
buildProfile.buildOption.arkOptions.runtimeOnly.sources = [];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 保存更新后的配置
|
|
441
|
+
const updatedContent = json5.stringify(buildProfile, {
|
|
442
|
+
space: 2,
|
|
443
|
+
quote: '"'
|
|
444
|
+
});
|
|
445
|
+
fs.writeFileSync(buildProfilePath, updatedContent, 'utf8');
|
|
446
|
+
|
|
447
|
+
project.events.emit('log', `✓ Removed runtimeOnly source: ${relativeSrcPath}`);
|
|
448
|
+
} else {
|
|
449
|
+
project.events.emit('log', `RuntimeOnly source not found: ${relativeSrcPath}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
} catch (error) {
|
|
453
|
+
project.events.emit('warn', `Failed to remove runtimeOnly source: ${error.message}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getInstallDestination(obj, project) {
|
|
458
|
+
// 鸿蒙特定的安装路径逻辑
|
|
459
|
+
return path.join(obj.modulesName, obj.targetDir);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 处理配置文件内容 - 插入插件配置
|
|
464
|
+
*/
|
|
465
|
+
function processConfigFileContent(originalContent, configFileObj) {
|
|
466
|
+
// 检查是否已经存在相同内容
|
|
467
|
+
if (originalContent.includes(configFileObj.content)) {
|
|
468
|
+
return originalContent; // 已存在,不插入
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (configFileObj.parent && configFileObj.parent !== '/*') {
|
|
472
|
+
// 在指定父元素内插入
|
|
473
|
+
const parentPattern = new RegExp(`(<${configFileObj.parent}[^>]*>)([\\s\\S]*?)(<\/${configFileObj.parent}>)`, 'i');
|
|
474
|
+
if (parentPattern.test(originalContent)) {
|
|
475
|
+
return originalContent.replace(parentPattern, `$1$2\n ${configFileObj.content}$3`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 默认在 </widget> 前插入
|
|
480
|
+
return originalContent.replace('</widget>', configFileObj.content + '\n</widget>');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 恢复配置文件内容 - 移除插件配置
|
|
485
|
+
*/
|
|
486
|
+
function revertConfigFileContent(currentContent, configFileObj) {
|
|
487
|
+
let content = currentContent;
|
|
488
|
+
|
|
489
|
+
// 移除插入的配置内容
|
|
490
|
+
// 注意:这里使用原始内容(未替换变量的)进行匹配
|
|
491
|
+
const configContent = configFileObj.content.trim();
|
|
492
|
+
const escapedContent = configContent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
493
|
+
const configPattern = new RegExp(`\\s*${escapedContent}\\s*`, 'g');
|
|
494
|
+
|
|
495
|
+
content = content.replace(configPattern, '');
|
|
496
|
+
|
|
497
|
+
// 清理多余的空行
|
|
498
|
+
content = content.replace(/\n\s*\n/g, '\n\n');
|
|
499
|
+
|
|
500
|
+
return content.trim() + '\n';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 处理 CMakeLists.txt 内容 - 添加配置
|
|
505
|
+
*/
|
|
506
|
+
function processCmakeListsContent(originalContent, cmakeConfig, project) {
|
|
507
|
+
let content = originalContent;
|
|
508
|
+
let changesMade = false;
|
|
509
|
+
|
|
510
|
+
for (const param of cmakeConfig.params) {
|
|
511
|
+
const { target, value } = param;
|
|
512
|
+
|
|
513
|
+
switch (target.toLowerCase()) {
|
|
514
|
+
case 'add_library':
|
|
515
|
+
const libraryResult = addLibraryToCmake(content, value, project);
|
|
516
|
+
if (libraryResult.changed) {
|
|
517
|
+
content = libraryResult.content;
|
|
518
|
+
changesMade = true;
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
|
|
522
|
+
case 'target_link_libraries':
|
|
523
|
+
const linkResult = addTargetLinkLibraries(content, value, project);
|
|
524
|
+
if (linkResult.changed) {
|
|
525
|
+
content = linkResult.content;
|
|
526
|
+
changesMade = true;
|
|
527
|
+
}
|
|
528
|
+
break;
|
|
529
|
+
|
|
530
|
+
case 'include_directories':
|
|
531
|
+
const includeResult = addIncludeDirectories(content, value, project);
|
|
532
|
+
if (includeResult.changed) {
|
|
533
|
+
content = includeResult.content;
|
|
534
|
+
changesMade = true;
|
|
535
|
+
}
|
|
536
|
+
break;
|
|
537
|
+
|
|
538
|
+
case 'add_compile_options':
|
|
539
|
+
const compileOptsResult = addCompileOptions(content, value, project);
|
|
540
|
+
if (compileOptsResult.changed) {
|
|
541
|
+
content = compileOptsResult.content;
|
|
542
|
+
changesMade = true;
|
|
543
|
+
}
|
|
544
|
+
break;
|
|
545
|
+
|
|
546
|
+
case 'set':
|
|
547
|
+
const setResult = addSetCommand(content, value, project);
|
|
548
|
+
if (setResult.changed) {
|
|
549
|
+
content = setResult.content;
|
|
550
|
+
changesMade = true;
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
|
|
554
|
+
default:
|
|
555
|
+
project.events.emit('warn', `Unknown CMake command: ${target}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return changesMade ? content : originalContent;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 恢复 CMakeLists.txt 内容 - 移除配置
|
|
564
|
+
*/
|
|
565
|
+
function revertCmakeListsContent(currentContent, cmakeConfig, project) {
|
|
566
|
+
let content = currentContent;
|
|
567
|
+
let changesMade = false;
|
|
568
|
+
|
|
569
|
+
for (const param of cmakeConfig.params) {
|
|
570
|
+
const { target, value } = param;
|
|
571
|
+
|
|
572
|
+
switch (target.toLowerCase()) {
|
|
573
|
+
case 'add_library':
|
|
574
|
+
const libraryResult = removeLibraryFromCmake(content, value, project);
|
|
575
|
+
if (libraryResult.changed) {
|
|
576
|
+
content = libraryResult.content;
|
|
577
|
+
changesMade = true;
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
|
|
581
|
+
case 'target_link_libraries':
|
|
582
|
+
const linkResult = removeTargetLinkLibraries(content, value, project);
|
|
583
|
+
if (linkResult.changed) {
|
|
584
|
+
content = linkResult.content;
|
|
585
|
+
changesMade = true;
|
|
586
|
+
}
|
|
587
|
+
break;
|
|
588
|
+
|
|
589
|
+
case 'include_directories':
|
|
590
|
+
const includeResult = removeIncludeDirectories(content, value, project);
|
|
591
|
+
if (includeResult.changed) {
|
|
592
|
+
content = includeResult.content;
|
|
593
|
+
changesMade = true;
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
|
|
597
|
+
case 'add_compile_options':
|
|
598
|
+
const compileOptsResult = removeCompileOptions(content, value, project);
|
|
599
|
+
if (compileOptsResult.changed) {
|
|
600
|
+
content = compileOptsResult.content;
|
|
601
|
+
changesMade = true;
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
|
|
605
|
+
case 'set':
|
|
606
|
+
const setResult = removeSetCommand(content, value, project);
|
|
607
|
+
if (setResult.changed) {
|
|
608
|
+
content = setResult.content;
|
|
609
|
+
changesMade = true;
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
|
|
613
|
+
default:
|
|
614
|
+
project.events.emit('warn', `Unknown CMake command for removal: ${target}`);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return changesMade ? content : currentContent;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* 添加库到 CMakeLists - 在现有 add_library 命令中添加源文件
|
|
623
|
+
*/
|
|
624
|
+
function addLibraryToCmake(content, value, project) {
|
|
625
|
+
// 解析要添加的源文件
|
|
626
|
+
const parts = value.split(/\s+/).filter(part => part.trim());
|
|
627
|
+
if (parts.length === 0) {
|
|
628
|
+
project.events.emit('warn', `Invalid add_library value format: ${value}`);
|
|
629
|
+
return { changed: false, content };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const sourceFiles = parts; // value 就是源文件列表
|
|
633
|
+
|
|
634
|
+
// 查找现有的 add_library 命令 - 精确匹配整个命令
|
|
635
|
+
const libraryPattern = /add_library\s*\([^)]*\)/g;
|
|
636
|
+
const existingMatches = content.match(libraryPattern);
|
|
637
|
+
|
|
638
|
+
if (!existingMatches || existingMatches.length === 0) {
|
|
639
|
+
project.events.emit('warn', `No add_library command found in CMakeLists`);
|
|
640
|
+
return { changed: false, content };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let changed = false;
|
|
644
|
+
let newSources = "";
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
for(let i=0; i<sourceFiles.length; i++) {
|
|
648
|
+
let isExist = true;
|
|
649
|
+
for(let j=0; j<existingMatches.length; j++) {
|
|
650
|
+
if(existingMatches[j].indexOf(sourceFiles[i]) > 0) {
|
|
651
|
+
isExist = false;
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if(isExist) {
|
|
656
|
+
newSources += " "+sourceFiles[i] + "\n";
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
let newLibrary = "";
|
|
661
|
+
if(newSources.length > 0) {
|
|
662
|
+
changed = true;
|
|
663
|
+
for (let i=0;i <existingMatches.length; i++) {
|
|
664
|
+
let temp = existingMatches[i].trim();
|
|
665
|
+
if(temp.indexOf(")") >= 0) {
|
|
666
|
+
let lastTmp = existingMatches[i];
|
|
667
|
+
let index = lastTmp.indexOf(")");
|
|
668
|
+
newLibrary += lastTmp.slice(0, index) + newSources + lastTmp.slice(index);
|
|
669
|
+
} else {
|
|
670
|
+
newLibrary += existingMatches[i];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const regex = /add_library\s*\([^)]*\)/g;
|
|
676
|
+
const newContent = content.replace(regex, newLibrary);
|
|
677
|
+
|
|
678
|
+
return { changed, content: newContent };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* 删除到 CMakeLists - 在现有 add_library 命令中删除源文件
|
|
683
|
+
*/
|
|
684
|
+
function removeLibraryFromCmake(content, value, project) {
|
|
685
|
+
// 解析要添加的源文件
|
|
686
|
+
const parts = value.split(/\s+/).filter(part => part.trim());
|
|
687
|
+
if (parts.length === 0) {
|
|
688
|
+
project.events.emit('warn', `Invalid add_library value format: ${value}`);
|
|
689
|
+
return { changed: false, content };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const sourceFiles = parts; // value 就是源文件列表
|
|
693
|
+
|
|
694
|
+
// 查找现有的 add_library 命令 - 精确匹配整个命令
|
|
695
|
+
const libraryPattern = /add_library\s*\([^)]*\)/g;
|
|
696
|
+
let existingMatches = content.match(libraryPattern);
|
|
697
|
+
if (!existingMatches || existingMatches.length === 0) {
|
|
698
|
+
project.events.emit('warn', `No add_library command found in CMakeLists`);
|
|
699
|
+
return { changed: false, content };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
for (let i=0;i <existingMatches.length; i++) {
|
|
703
|
+
for(let j=0; j<sourceFiles.length; j++) {
|
|
704
|
+
const sourcePattern = new RegExp(`\\b${escapeRegExp(sourceFiles[j])}\\b\\s*\\n?`, 'g');
|
|
705
|
+
existingMatches[i] = existingMatches[i].replace(sourcePattern, "");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
let newLibrary = "";
|
|
710
|
+
for (let i=0;i <existingMatches.length; i++) {
|
|
711
|
+
let temp = existingMatches[i].trim();
|
|
712
|
+
if(temp != "") {
|
|
713
|
+
newLibrary += existingMatches[i];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const regex = /add_library\s*\([^)]*\)/g;
|
|
717
|
+
const newContent = content.replace(regex, newLibrary);
|
|
718
|
+
const changed = newContent !== content;
|
|
719
|
+
return { changed, content: newContent };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* 转义正则表达式特殊字符
|
|
724
|
+
*/
|
|
725
|
+
function escapeRegExp(string) {
|
|
726
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* 添加目标链接库 - 在现有 target_link_libraries 命令中添加库
|
|
731
|
+
*/
|
|
732
|
+
function addTargetLinkLibraries(content, value, project) {
|
|
733
|
+
const parts = value.split(/\s+/).filter(part => part.trim());
|
|
734
|
+
if (parts.length < 2) {
|
|
735
|
+
project.events.emit('warn', `Invalid target_link_libraries format: ${value}`);
|
|
736
|
+
return { changed: false, content };
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const targetName = parts[0];
|
|
740
|
+
const libraries = parts.slice(1);
|
|
741
|
+
|
|
742
|
+
// 查找现有的 target_link_libraries 命令 - 精确匹配整个命令
|
|
743
|
+
const targetLinkPattern = new RegExp(`target_link_libraries\\s*\\(\\s*${targetName}[^)]*\\)`, 'g');
|
|
744
|
+
const existingMatch = content.match(targetLinkPattern);
|
|
745
|
+
|
|
746
|
+
if (existingMatch) {
|
|
747
|
+
// 更新现有的命令
|
|
748
|
+
const existingLine = existingMatch[0];
|
|
749
|
+
const existingLibraries = extractLibrariesFromTargetLink(existingLine);
|
|
750
|
+
const newLibraries = libraries.filter(lib => !existingLibraries.includes(lib));
|
|
751
|
+
|
|
752
|
+
if (newLibraries.length > 0) {
|
|
753
|
+
// 在 ) 前面插入新的库
|
|
754
|
+
const insertPosition = content.lastIndexOf(existingLine) + existingLine.length - 1;
|
|
755
|
+
const before = content.substring(0, insertPosition);
|
|
756
|
+
const after = content.substring(insertPosition);
|
|
757
|
+
|
|
758
|
+
let insertContent;
|
|
759
|
+
if (existingLine.endsWith('\n')) {
|
|
760
|
+
insertContent = ` ${newLibraries.join('\n ')}`;
|
|
761
|
+
} else {
|
|
762
|
+
insertContent = `\n ${newLibraries.join('\n ')}\n`;
|
|
763
|
+
}
|
|
764
|
+
const cleanedBefore = before.replace(/\n+$/, ''); // 移除结尾的多余换行
|
|
765
|
+
const cleanedAfter = after.replace(/^\n+/, ''); // 移除开头的多余换行
|
|
766
|
+
|
|
767
|
+
const newContent = cleanedBefore + insertContent + cleanedAfter;
|
|
768
|
+
|
|
769
|
+
project.events.emit('log', `Added libraries to ${targetName}: ${newLibraries.join(', ')}`);
|
|
770
|
+
return { changed: true, content: newContent };
|
|
771
|
+
} else {
|
|
772
|
+
project.events.emit('log', `All libraries already exist in ${targetName}`);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
project.events.emit('warn', `target_link_libraries for ${targetName} not found`);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return { changed: false, content };
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function removeTargetLinkLibraries(content, value, project) {
|
|
782
|
+
const parts = value.split(/\s+/).filter(part => part.trim());
|
|
783
|
+
if (parts.length < 2) {
|
|
784
|
+
project.events.emit('warn', `Invalid target_link_libraries format: ${value}`);
|
|
785
|
+
return { changed: false, content };
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const targetName = parts[0];
|
|
789
|
+
const libraries = parts.slice(1);
|
|
790
|
+
|
|
791
|
+
// 查找现有的 target_link_libraries 命令 - 精确匹配整个命令
|
|
792
|
+
const targetLinkPattern = new RegExp(`target_link_libraries\\s*\\(\\s*${targetName}[^)]*\\)`, 'g');
|
|
793
|
+
const existingMatch = content.match(targetLinkPattern);
|
|
794
|
+
|
|
795
|
+
if (existingMatch) {
|
|
796
|
+
// 更新现有的命令
|
|
797
|
+
const existingLine = existingMatch[0];
|
|
798
|
+
const existingLibraries = extractLibrariesFromTargetLink(existingLine);
|
|
799
|
+
const newLibraries = libraries.filter(lib => existingLibraries.includes(lib));
|
|
800
|
+
|
|
801
|
+
if (newLibraries.length > 0) {
|
|
802
|
+
const newContent = existingLibraries.replace(newLibraries, "");
|
|
803
|
+
project.events.emit('log', `Removed libraries to ${targetName}: ${newLibraries.join(', ')}`);
|
|
804
|
+
return { changed: true, content: newContent };
|
|
805
|
+
} else {
|
|
806
|
+
project.events.emit('log', `All libraries already exist in ${targetName}`);
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
project.events.emit('warn', `target_link_libraries for ${targetName} not found`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return { changed: false, content };
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* 添加包含目录 - 在现有 include_directories 命令中添加目录
|
|
816
|
+
*/
|
|
817
|
+
function addIncludeDirectories(content, value, project) {
|
|
818
|
+
const directories = value.split(/\s+/).filter(dir => dir.trim());
|
|
819
|
+
|
|
820
|
+
// 查找现有的 include_directories 命令
|
|
821
|
+
const includePattern = /include_directories\s*\(([^)]*)\)/g;
|
|
822
|
+
const existingMatch = content.match(includePattern);
|
|
823
|
+
|
|
824
|
+
if (existingMatch) {
|
|
825
|
+
// 更新现有的命令
|
|
826
|
+
const existingLine = existingMatch[0];
|
|
827
|
+
const existingDirs = extractDirectoriesFromInclude(existingLine);
|
|
828
|
+
const newDirs = directories.filter(dir => !existingDirs.includes(dir));
|
|
829
|
+
|
|
830
|
+
if (newDirs.length > 0) {
|
|
831
|
+
// 在 ) 前面插入新的目录
|
|
832
|
+
const insertPosition = content.lastIndexOf(existingLine) + existingLine.length - 1;
|
|
833
|
+
const before = content.substring(0, insertPosition);
|
|
834
|
+
const after = content.substring(insertPosition);
|
|
835
|
+
|
|
836
|
+
let insertContent;
|
|
837
|
+
if (existingLine.endsWith('\n')) {
|
|
838
|
+
insertContent = ` ${newDirs.join('\n ')}`;
|
|
839
|
+
} else {
|
|
840
|
+
insertContent = `\n ${newDirs.join('\n ')}\n`;
|
|
841
|
+
}
|
|
842
|
+
const cleanedBefore = before.replace(/\n+$/, ''); // 移除结尾的多余换行
|
|
843
|
+
const cleanedAfter = after.replace(/^\n+/, ''); // 移除开头的多余换行
|
|
844
|
+
|
|
845
|
+
const newContent = cleanedBefore + insertContent + cleanedAfter;
|
|
846
|
+
|
|
847
|
+
project.events.emit('log', `Added include directories: ${newDirs.join(', ')}`);
|
|
848
|
+
return { changed: true, content: newContent };
|
|
849
|
+
} else {
|
|
850
|
+
project.events.emit('log', `All directories already exist in include_directories`);
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
project.events.emit('warn', `include_directories command not found`);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
return { changed: false, content };
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function removeIncludeDirectories(content, value, project) {
|
|
860
|
+
const directories = value.split(/\s+/).filter(dir => dir.trim());
|
|
861
|
+
// 查找现有的 include_directories 命令
|
|
862
|
+
const includePattern = /include_directories\s*\(([^)]*)\)/g;
|
|
863
|
+
const existingMatch = content.match(includePattern);
|
|
864
|
+
|
|
865
|
+
if (existingMatch) {
|
|
866
|
+
// 更新现有的命令
|
|
867
|
+
const existingLine = existingMatch[0];
|
|
868
|
+
const existingDirs = extractDirectoriesFromInclude(existingLine);
|
|
869
|
+
const newDirs = directories.filter(dir => existingDirs.includes(dir));
|
|
870
|
+
|
|
871
|
+
if (newDirs.length > 0) {
|
|
872
|
+
const newContent = existingDirs.replace(newDirs, "");
|
|
873
|
+
project.events.emit('log', `Removed include directories: ${newDirs.join(', ')}`);
|
|
874
|
+
return { changed: true, content: newContent };
|
|
875
|
+
} else {
|
|
876
|
+
project.events.emit('log', `All directories already exist in include_directories`);
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
project.events.emit('warn', `include_directories command not found`);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return { changed: false, content };
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* 从 add_library 命令中提取源文件
|
|
886
|
+
*/
|
|
887
|
+
function extractSourceFilesFromLibrary(libraryLine) {
|
|
888
|
+
// 匹配 add_library(name type file1 file2 ...)
|
|
889
|
+
const match = libraryLine.match(/add_library\s*\(\s*\w+\s+\w+\s+(.+)\)/);
|
|
890
|
+
if (match) {
|
|
891
|
+
return match[1].split(/\s+/).filter(file => file.trim());
|
|
892
|
+
}
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* 从 target_link_libraries 命令中提取库
|
|
898
|
+
*/
|
|
899
|
+
function extractLibrariesFromTargetLink(line) {
|
|
900
|
+
const match = line.match(/target_link_libraries\s*\(\s*(\w+)\s+(.+)\)/);
|
|
901
|
+
return match ? match[2].split(/\s+/).filter(lib => lib.trim()) : [];
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* 从 include_directories 命令中提取目录
|
|
906
|
+
*/
|
|
907
|
+
function extractDirectoriesFromInclude(line) {
|
|
908
|
+
const match = line.match(/include_directories\s*\(\s*(.+)\s*\)/);
|
|
909
|
+
return match ? match[1].split(/\s+/).filter(dir => dir.trim()) : [];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* 安全地更新 cordova_plugins.js(基于正确的数组格式)
|
|
914
|
+
*/
|
|
915
|
+
function safeUpdateCordovaPlugins(project, plugin, jsModule, moduleName) {
|
|
916
|
+
const cordovaPluginsPath = path.resolve(project.moduleRoot, 'src', 'main', 'resources', 'rawfile', 'www', 'cordova_plugins.js');
|
|
917
|
+
|
|
918
|
+
let modulesArray = [];
|
|
919
|
+
let metadata = {};
|
|
920
|
+
let originalContent = '';
|
|
921
|
+
|
|
922
|
+
// 读取现有配置
|
|
923
|
+
if (fs.existsSync(cordovaPluginsPath)) {
|
|
924
|
+
originalContent = fs.readFileSync(cordovaPluginsPath, 'utf8');
|
|
925
|
+
|
|
926
|
+
try {
|
|
927
|
+
// 解析 modules 数组
|
|
928
|
+
const modulesMatch = originalContent.match(/module\.exports\s*=\s*(\[[\s\S]*?\]);/);
|
|
929
|
+
if (modulesMatch) {
|
|
930
|
+
modulesArray = JSON.parse(modulesMatch[1]);
|
|
931
|
+
project.events.emit('log', `Loaded ${modulesArray.length} existing modules`);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// 解析 metadata 对象
|
|
935
|
+
const metadataMatch = originalContent.match(/module\.exports\.metadata\s*=\s*({[\s\S]*?});/);
|
|
936
|
+
if (metadataMatch) {
|
|
937
|
+
metadata = JSON.parse(metadataMatch[1]);
|
|
938
|
+
project.events.emit('log', `Loaded metadata for ${Object.keys(metadata).length} plugins`);
|
|
939
|
+
}
|
|
940
|
+
} catch (error) {
|
|
941
|
+
project.events.emit('warn', `Failed to parse cordova_plugins.js: ${error.message}`);
|
|
942
|
+
// 如果解析失败,创建新的空结构
|
|
943
|
+
modulesArray = [];
|
|
944
|
+
metadata = {};
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// 创建新的模块定义
|
|
949
|
+
const moduleDef = {
|
|
950
|
+
"id": moduleName,
|
|
951
|
+
"file": `plugins/${plugin.id}/${jsModule.src}`,
|
|
952
|
+
"pluginId": plugin.id,
|
|
953
|
+
"clobbers": jsModule.clobbers ? jsModule.clobbers.map(c => c.target) : []
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
// 如果有 runs 配置,添加它
|
|
957
|
+
if (jsModule.runs) {
|
|
958
|
+
moduleDef.runs = true;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// 检查是否已存在同名模块
|
|
962
|
+
const existingModuleIndex = modulesArray.findIndex(m => m.id === moduleName);
|
|
963
|
+
if (existingModuleIndex !== -1) {
|
|
964
|
+
// 更新现有模块
|
|
965
|
+
modulesArray[existingModuleIndex] = moduleDef;
|
|
966
|
+
project.events.emit('log', `Updated existing module: ${moduleName}`);
|
|
967
|
+
} else {
|
|
968
|
+
// 添加新模块
|
|
969
|
+
modulesArray.push(moduleDef);
|
|
970
|
+
project.events.emit('log', `Added new module: ${moduleName}`);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// 更新 metadata
|
|
974
|
+
if (plugin.package && plugin.package.version) {
|
|
975
|
+
metadata[plugin.id] = plugin.package.version;
|
|
976
|
+
} else {
|
|
977
|
+
// 如果没有版本信息,使用默认值
|
|
978
|
+
metadata[plugin.id] = "1.0.0";
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// 生成新的内容
|
|
982
|
+
const newContent = generateCordovaPluginsContent(modulesArray, metadata, originalContent);
|
|
983
|
+
|
|
984
|
+
fs.writeFileSync(cordovaPluginsPath, newContent, 'utf8');
|
|
985
|
+
project.events.emit('log', `Updated cordova_plugins.js for plugin: ${plugin.id}`);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* 生成 cordova_plugins.js 内容
|
|
990
|
+
*/
|
|
991
|
+
function generateCordovaPluginsContent(modulesArray, metadata, originalContent) {
|
|
992
|
+
// 格式化 modules 数组
|
|
993
|
+
const modulesJson = JSON.stringify(modulesArray, null, 2);
|
|
994
|
+
|
|
995
|
+
// 格式化 metadata 对象
|
|
996
|
+
const metadataJson = JSON.stringify(metadata, null, 2);
|
|
997
|
+
|
|
998
|
+
// 构建完整内容
|
|
999
|
+
return `cordova.define('cordova/plugin_list', function(require, exports, module) {
|
|
1000
|
+
module.exports = ${modulesJson};
|
|
1001
|
+
module.exports.metadata = ${metadataJson};
|
|
1002
|
+
});`;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* 从 cordova_plugins.js 中移除插件
|
|
1007
|
+
*/
|
|
1008
|
+
function removeFromCordovaPlugins(project, pluginId, moduleName) {
|
|
1009
|
+
const cordovaPluginsPath = path.resolve(project.moduleRoot, 'src', 'main', 'resources', 'rawfile', 'www', 'cordova_plugins.js');
|
|
1010
|
+
if (!fs.existsSync(cordovaPluginsPath)) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
try {
|
|
1015
|
+
const originalContent = fs.readFileSync(cordovaPluginsPath, 'utf8');
|
|
1016
|
+
|
|
1017
|
+
// 解析现有配置
|
|
1018
|
+
const modulesMatch = originalContent.match(/module\.exports\s*=\s*(\[[\s\S]*?\]);/);
|
|
1019
|
+
const metadataMatch = originalContent.match(/module\.exports\.metadata\s*=\s*({[\s\S]*?});/);
|
|
1020
|
+
|
|
1021
|
+
if (!modulesMatch || !metadataMatch) {
|
|
1022
|
+
project.events.emit('warn', 'Invalid cordova_plugins.js format during uninstall');
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
let modulesArray = JSON.parse(modulesMatch[1]);
|
|
1027
|
+
let metadata = JSON.parse(metadataMatch[1]);
|
|
1028
|
+
|
|
1029
|
+
// 移除指定模块
|
|
1030
|
+
const initialLength = modulesArray.length;
|
|
1031
|
+
modulesArray = modulesArray.filter(module =>
|
|
1032
|
+
module.id != moduleName && module.pluginId != pluginId
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
// 移除 metadata
|
|
1036
|
+
if (metadata[pluginId]) {
|
|
1037
|
+
delete metadata[pluginId];
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
// 如果有变化,更新文件
|
|
1042
|
+
if (modulesArray.length !== initialLength || !metadata[pluginId]) {
|
|
1043
|
+
const newContent = generateCordovaPluginsContent(modulesArray, metadata, originalContent);
|
|
1044
|
+
fs.writeFileSync(cordovaPluginsPath, newContent, 'utf8');
|
|
1045
|
+
project.events.emit('log', `Removed plugin ${pluginId} from cordova_plugins.js`);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
project.events.emit('warn', `Failed to remove plugin from cordova_plugins.js: ${error.message}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 完全跳过 import,使用可靠的自定义实现
|
|
1054
|
+
function isPathInside(childPath, parentPath) {
|
|
1055
|
+
const path = require('path');
|
|
1056
|
+
|
|
1057
|
+
try {
|
|
1058
|
+
if (typeof childPath !== 'string' || typeof parentPath !== 'string') {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// 解析为绝对路径
|
|
1063
|
+
const child = path.resolve(childPath);
|
|
1064
|
+
const parent = path.resolve(parentPath);
|
|
1065
|
+
|
|
1066
|
+
// 相同的路径不算内部
|
|
1067
|
+
if (child === parent) {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// 规范化路径分隔符
|
|
1072
|
+
const normalizedChild = child.replace(/\\/g, '/');
|
|
1073
|
+
const normalizedParent = parent.replace(/\\/g, '/');
|
|
1074
|
+
|
|
1075
|
+
// 检查子路径是否以父路径开头
|
|
1076
|
+
return normalizedChild.startsWith(normalizedParent + '/');
|
|
1077
|
+
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
// 如果路径解析失败,返回 false
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
module.exports = {
|
|
1085
|
+
getInstaller: function (type) {
|
|
1086
|
+
if (handlers[type] && handlers[type].install) {
|
|
1087
|
+
return handlers[type].install;
|
|
1088
|
+
}
|
|
1089
|
+
return null;
|
|
1090
|
+
},
|
|
1091
|
+
getUninstaller: function (type) {
|
|
1092
|
+
if (handlers[type] && handlers[type].uninstall) {
|
|
1093
|
+
return handlers[type].uninstall;
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
};
|