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.
Files changed (55) hide show
  1. package/LICENSE +204 -0
  2. package/README.md +346 -0
  3. package/bin/hcordova +27 -0
  4. package/package.json +39 -0
  5. package/src/base-cli.js +1181 -0
  6. package/src/utils/DependencyChecker.js +382 -0
  7. package/src/utils/PlatformProject.js +452 -0
  8. package/src/utils/PluginConfigParser.js +408 -0
  9. package/src/utils/PluginDownloader.js +181 -0
  10. package/src/utils/PluginHandler.js +1097 -0
  11. package/src/utils/VariableValidator.js +369 -0
  12. package/src/utils/args-processor.js +79 -0
  13. package/templates/project/AppScope/app.json5 +10 -0
  14. package/templates/project/AppScope/resources/base/element/string.json +8 -0
  15. package/templates/project/AppScope/resources/base/media/background.png +0 -0
  16. package/templates/project/AppScope/resources/base/media/foreground.png +0 -0
  17. package/templates/project/AppScope/resources/base/media/layered_image.json +7 -0
  18. package/templates/project/build-profile.json5 +46 -0
  19. package/templates/project/code-linter.json5 +32 -0
  20. package/templates/project/entry/build-profile.json5 +28 -0
  21. package/templates/project/entry/hvigorfile.ts +6 -0
  22. package/templates/project/entry/obfuscation-rules.txt +23 -0
  23. package/templates/project/entry/oh-package.json5 +12 -0
  24. package/templates/project/entry/src/main/ets/entryability/EntryAbility.ets +62 -0
  25. package/templates/project/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +32 -0
  26. package/templates/project/entry/src/main/ets/pages/Index.ets +40 -0
  27. package/templates/project/entry/src/main/module.json5 +52 -0
  28. package/templates/project/entry/src/main/resources/base/element/color.json +8 -0
  29. package/templates/project/entry/src/main/resources/base/element/float.json +8 -0
  30. package/templates/project/entry/src/main/resources/base/element/string.json +16 -0
  31. package/templates/project/entry/src/main/resources/base/media/background.png +0 -0
  32. package/templates/project/entry/src/main/resources/base/media/foreground.png +0 -0
  33. package/templates/project/entry/src/main/resources/base/media/layered_image.json +7 -0
  34. package/templates/project/entry/src/main/resources/base/media/startIcon.png +0 -0
  35. package/templates/project/entry/src/main/resources/base/profile/backup_config.json +3 -0
  36. package/templates/project/entry/src/main/resources/base/profile/main_pages.json +5 -0
  37. package/templates/project/entry/src/main/resources/dark/element/color.json +8 -0
  38. package/templates/project/entry/src/main/resources/rawfile/config.xml +26 -0
  39. package/templates/project/entry/src/main/resources/rawfile/www/cordova.js +1925 -0
  40. package/templates/project/entry/src/main/resources/rawfile/www/css/index.css +158 -0
  41. package/templates/project/entry/src/main/resources/rawfile/www/img/cordova.png +0 -0
  42. package/templates/project/entry/src/main/resources/rawfile/www/img/logo.png +0 -0
  43. package/templates/project/entry/src/main/resources/rawfile/www/index.html +110 -0
  44. package/templates/project/entry/src/main/resources/rawfile/www/js/index.js +68 -0
  45. package/templates/project/entry/src/mock/mock-config.json5 +2 -0
  46. package/templates/project/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
  47. package/templates/project/entry/src/ohosTest/ets/test/List.test.ets +5 -0
  48. package/templates/project/entry/src/ohosTest/module.json5 +13 -0
  49. package/templates/project/entry/src/test/List.test.ets +5 -0
  50. package/templates/project/entry/src/test/LocalUnit.test.ets +33 -0
  51. package/templates/project/hvigor/hvigor-config.json5 +22 -0
  52. package/templates/project/hvigorfile.ts +6 -0
  53. package/templates/project/local.properties +9 -0
  54. package/templates/project/oh-package-lock.json5 +27 -0
  55. package/templates/project/oh-package.json5 +10 -0
@@ -0,0 +1,452 @@
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
+ const os = require('os');
20
+ const {execSync } = require('child_process');
21
+
22
+ class PlatformProject {
23
+ constructor(api) {
24
+ this.api = api;
25
+ this.events = api.events;
26
+ this.sourceRoot = api.sourceRoot; // 从 Api 传递过来
27
+ }
28
+
29
+ // 创建鸿蒙项目
30
+ /*
31
+ *projectPath目标路径
32
+ */
33
+ create(projectPath, packageName, projectName) {
34
+ return new Promise((resolve, reject) => {
35
+ try {
36
+ this.events.emit('log', 'Creating HarmonyOS project...');
37
+
38
+ // 确保目标目录存在
39
+ this.events.emit('verbose', `Ensuring project path: ${projectPath}`);
40
+ this.ensureDirSync(projectPath);
41
+
42
+ // 获取模板目录
43
+ this.events.emit('verbose', 'Getting template directory...');
44
+ const templateDir = this.getTemplateDir();
45
+ this.events.emit('verbose', `Using template directory: ${templateDir}`);
46
+
47
+ // 验证模板
48
+ this.events.emit('verbose', 'Validating template...');
49
+ this.validateTemplate(templateDir);
50
+
51
+ // 复制模板文件
52
+ this.events.emit('verbose', 'Copying template files...');
53
+ this.copyTemplateFiles(templateDir, projectPath);
54
+
55
+ // 复制 Cordova 源代码
56
+ this.events.emit('verbose', 'Copying Cordova source code...');
57
+ try {
58
+ this.copyCordovaSource(projectPath);
59
+ } catch (error) {
60
+ this.events.emit('warn', `Cordova source copy skipped: ${error.message}`);
61
+ }
62
+
63
+ // 处理模板变量
64
+ this.events.emit('verbose', 'Processing template variables...');
65
+ this.processTemplateVariables(projectPath, packageName, projectName);
66
+
67
+ this.events.emit('log', 'HarmonyOS project created successfully');
68
+ resolve();
69
+ } catch (error) {
70
+ this.events.emit('error', `Failed to create HarmonyOS project: ${error.message}`);
71
+ this.events.emit('error', `Stack trace: ${error.stack}`);
72
+ // 确保传递的是 Error 对象,而不是字符串
73
+ reject(new Error(`Platform creation failed: ${error.message}`));
74
+ }
75
+ });
76
+ }
77
+
78
+ // 确保目录存在
79
+ ensureDirSync(dir) {
80
+ try {
81
+ if (!fs.existsSync(dir)) {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ } catch (error) {
85
+ throw new Error(`Failed to create directory ${dir}: ${error.message}`);
86
+ }
87
+ }
88
+
89
+ // 复制目录
90
+ copySync(source, target) {
91
+ try {
92
+ const stat = fs.statSync(source);
93
+
94
+ if (stat.isFile()) {
95
+ const targetDir = path.dirname(target);
96
+ this.ensureDirSync(targetDir);
97
+ fs.copyFileSync(source, target);
98
+ return;
99
+ }
100
+
101
+ if (stat.isDirectory()) {
102
+ this.ensureDirSync(target);
103
+ const items = fs.readdirSync(source);
104
+
105
+ for (const item of items) {
106
+ const sourcePath = path.join(source, item);
107
+ const targetPath = path.join(target, item);
108
+ this.copySync(sourcePath, targetPath);
109
+ }
110
+ }
111
+ } catch (error) {
112
+ throw new Error(`Failed to copy ${source} to ${target}: ${error.message}`);
113
+ }
114
+ }
115
+
116
+ // 下载 cordova-openharmony HAR 工程源码
117
+ async downloadHarSourceCode() {
118
+ const harRepo = 'https://gitcode.com/OpenHarmony-Cordova/cordova-openharmony.git';
119
+ const tempDir = path.join(os.tmpdir(), 'cordova-openharmony-har-' + Date.now());
120
+
121
+ try {
122
+ // 创建临时目录
123
+ if (!fs.existsSync(tempDir)) {
124
+ fs.mkdirSync(tempDir, { recursive: true });
125
+ }
126
+
127
+ // 克隆 HAR 工程仓库
128
+ console.log('Cloning cordova-openharmony HAR repository...');
129
+ execSync(`git clone ${harRepo} ${tempDir}`, { stdio: 'inherit' });
130
+
131
+ // 验证 HAR 工程结构
132
+ const requiredFiles = [
133
+ 'src/main/ets/',
134
+ 'src/main/module.json5',
135
+ 'oh-package.json5'
136
+ ];
137
+
138
+ for (const file of requiredFiles) {
139
+ if (!fs.existsSync(path.join(tempDir, file))) {
140
+ throw new Error(`Invalid HAR project structure: ${file} not found`);
141
+ }
142
+ }
143
+
144
+ console.log('HAR source code downloaded successfully');
145
+ return tempDir;
146
+
147
+ } catch (error) {
148
+ // 清理临时文件
149
+ this.cleanupTempFiles(tempDir);
150
+ throw new Error(`Failed to download HAR source code: ${error.message}`);
151
+ }
152
+ }
153
+
154
+ // 清理临时文件
155
+ cleanupTempFiles(tempDir) {
156
+ if (tempDir && fs.existsSync(tempDir)) {
157
+ try {
158
+ fs.rmSync(tempDir, { recursive: true, force: true });
159
+ } catch (error) {
160
+ // 忽略清理错误
161
+ }
162
+ }
163
+ }
164
+
165
+ // 复制 Cordova 源代码到 cordova 目录
166
+ async copyCordovaSource(projectPath) {
167
+ this.events.emit('log', 'Copying Cordova source code...');
168
+
169
+ try {
170
+ const cordovaOpenHarmonyRoot = await this.downloadHarSourceCode();
171
+
172
+ this.events.emit('verbose', `Cordova root: ${cordovaOpenHarmonyRoot}`);
173
+
174
+ const cordovaTargetDir = path.join(projectPath, 'cordova');
175
+ this.events.emit('verbose', `Cordova target: ${cordovaTargetDir}`);
176
+
177
+ this.ensureDirSync(cordovaTargetDir);
178
+
179
+ // 第一步:复制项目工程文件
180
+ this.events.emit('verbose', 'Copying Cordova project files...');
181
+ this.copyCordovaFiles(cordovaOpenHarmonyRoot, cordovaTargetDir);
182
+ this.events.emit('log', 'Cordova source code copied successfully');
183
+
184
+ } catch (error) {
185
+ this.events.emit('error', `Cordova source copy failed: ${error.message}`);
186
+ this.events.emit('error', `Cordova copy stack: ${error.stack}`);
187
+ throw error; // 重新抛出以便外层捕获
188
+ }
189
+ }
190
+
191
+ // 修改后的方法:复制整个 templates/cordova 目录到工程的 cordova 目录
192
+ ensureEssentialFilesInCordovaDir(sourceRoot, targetCordovaDir) {
193
+ this.events.emit('verbose', 'Copying entire cordova platform directory...');
194
+
195
+ try {
196
+ // 源目录:templates/cordova
197
+ const cordovaSourceDir = path.join(sourceRoot, 'templates', 'cordova');
198
+
199
+ this.events.emit('verbose', `Source cordova dir: ${cordovaSourceDir}`);
200
+ this.events.emit('verbose', `Target cordova dir: ${targetCordovaDir}`);
201
+
202
+ if (!fs.existsSync(cordovaSourceDir)) {
203
+ throw new Error(`Cordova platform directory not found: ${cordovaSourceDir}`);
204
+ }
205
+
206
+ // 复制整个 templates/cordova 目录到目标 cordova 目录
207
+ this.copySync(cordovaSourceDir, targetCordovaDir);
208
+
209
+ // 验证关键文件是否复制成功
210
+ this.validateCordovaFiles(targetCordovaDir);
211
+
212
+ this.events.emit('verbose', 'Entire cordova platform directory copied successfully');
213
+
214
+ } catch (error) {
215
+ this.events.emit('error', `Failed to copy cordova platform directory: ${error.message}`);
216
+ throw error;
217
+ }
218
+ }
219
+
220
+ // 验证 cordova 目录中的关键文件
221
+ validateCordovaFiles(cordovaDir) {
222
+ const requiredFiles = ['Api.js'];
223
+ let allFound = true;
224
+
225
+ for (const file of requiredFiles) {
226
+ const filePath = path.join(cordovaDir, file);
227
+ if (fs.existsSync(filePath)) {
228
+ this.events.emit('verbose', `${file} found in cordova dir`);
229
+ } else {
230
+ this.events.emit('error', `${file} missing in cordova dir`);
231
+ allFound = false;
232
+ }
233
+ }
234
+
235
+ return allFound;
236
+ }
237
+
238
+ // 获取 cordova-openharmony 根目录
239
+ getCordovaOpenHarmonyRoot() {
240
+ return this.sourceRoot;
241
+ }
242
+
243
+ // 复制 Cordova 文件
244
+ copyCordovaFiles(sourceDir, targetDir) {
245
+ const excludeDirs = ['templates', 'bin', 'node_modules', '.git', 'test'];
246
+ const excludeFiles = ['.DS_Store', 'Thumbs.db', '*.log'];
247
+
248
+ try {
249
+ const items = fs.readdirSync(sourceDir);
250
+
251
+ for (const item of items) {
252
+ const sourcePath = path.join(sourceDir, item);
253
+ const targetPath = path.join(targetDir, item);
254
+
255
+ if (this.shouldCopyCordovaItem(item, sourcePath, excludeDirs, excludeFiles)) {
256
+ this.copySync(sourcePath, targetPath);
257
+ }
258
+ }
259
+ } catch (error) {
260
+ throw new Error(`Failed to copy Cordova files: ${error.message}`);
261
+ }
262
+ }
263
+
264
+ // 判断是否应该复制 Cordova 项目中的项目
265
+ shouldCopyCordovaItem(itemName, itemPath, excludeDirs, excludeFiles) {
266
+ // 排除目录
267
+ if (excludeDirs.includes(itemName)) {
268
+ return false;
269
+ }
270
+
271
+ // 排除隐藏文件和系统文件(除了 .gitignore)
272
+ if (itemName.startsWith('.') && itemName !== '.gitignore') {
273
+ return false;
274
+ }
275
+
276
+ // 排除特定文件
277
+ if (excludeFiles.includes(itemName)) {
278
+ return false;
279
+ }
280
+
281
+ // 检查文件状态
282
+ try {
283
+ const stat = fs.statSync(itemPath);
284
+ return stat.isFile() || stat.isDirectory();
285
+ } catch (error) {
286
+ return false;
287
+ }
288
+ }
289
+
290
+ // 获取模板目录
291
+ getTemplateDir() {
292
+ try {
293
+ this.events.emit('verbose', 'Searching for template directory...');
294
+
295
+ const possiblePaths = [
296
+ path.join(this.getCordovaOpenHarmonyRoot(), 'templates', 'project')
297
+ ];
298
+
299
+ this.events.emit('verbose', 'Possible template paths:');
300
+ for (const templatePath of possiblePaths) {
301
+ this.events.emit('verbose', ` - ${templatePath}`);
302
+ }
303
+
304
+ for (const templatePath of possiblePaths) {
305
+ if (fs.existsSync(templatePath)) {
306
+ this.events.emit('verbose', `Found template directory: ${templatePath}`);
307
+ return templatePath;
308
+ }
309
+ }
310
+
311
+ this.events.emit('verbose', `Could not find template directory`);
312
+ const errorMsg = `Could not find template directory. Searched in: ${possiblePaths.join(', ')}`;
313
+ this.events.emit('error', errorMsg);
314
+ throw new Error(errorMsg);
315
+
316
+ } catch (error) {
317
+ this.events.emit('error', `Error in getTemplateDir: ${error.message}`);
318
+ this.events.emit('error', `Stack: ${error.stack}`);
319
+ throw error; // 重新抛出
320
+ }
321
+ }
322
+
323
+ // 验证模板目录
324
+ validateTemplate(templateDir) {
325
+ const requiredFiles = [
326
+ 'build-profile.json5',
327
+ 'oh-package.json5'
328
+ ];
329
+
330
+ const missingFiles = [];
331
+
332
+ for (const file of requiredFiles) {
333
+ const filePath = path.join(templateDir, file);
334
+ if (!fs.existsSync(filePath)) {
335
+ missingFiles.push(file);
336
+ }
337
+ }
338
+
339
+ if (missingFiles.length > 0) {
340
+ throw new Error(`Required template files missing: ${missingFiles.join(', ')}`);
341
+ }
342
+
343
+ this.events.emit('verbose', 'Template validation passed');
344
+ }
345
+
346
+ // 复制模板文件
347
+ copyTemplateFiles(sourceDir, targetDir) {
348
+ this.events.emit('verbose', `Copying template files from ${sourceDir} to ${targetDir}`);
349
+
350
+ try {
351
+ this.copySync(sourceDir, targetDir);
352
+ this.events.emit('verbose', 'Template files copied successfully');
353
+ } catch (error) {
354
+ throw new Error(`Failed to copy template files: ${error.message}`);
355
+ }
356
+ }
357
+
358
+ // 处理模板变量
359
+ processTemplateVariables(projectPath, packageName, projectName) {
360
+ this.events.emit('verbose', 'Processing template variables...');
361
+
362
+ const variables = {
363
+ PACKAGE_NAME: packageName,
364
+ PROJECT_NAME: projectName,
365
+ ID: packageName,
366
+ NAME: projectName
367
+ };
368
+
369
+ try {
370
+ const filesToProcess = this.findTemplateFiles(projectPath);
371
+ for (const filePath of filesToProcess) {
372
+ this.processTemplateFile(filePath, variables);
373
+ }
374
+
375
+ this.events.emit('verbose', `Processed ${filesToProcess.length} template files`);
376
+ } catch (error) {
377
+ this.events.emit('warn', `Template variable processing failed: ${error.message}`);
378
+ }
379
+ }
380
+
381
+ // 查找需要处理的模板文件
382
+ findTemplateFiles(projectPath) {
383
+ const extensions = ['.json5', '.json', '.js', '.ts', '.txt', '.xml'];
384
+ const files = [];
385
+
386
+ const scanDirectory = (dir) => {
387
+ try {
388
+ const items = fs.readdirSync(dir);
389
+
390
+ for (const item of items) {
391
+ const fullPath = path.join(dir, item);
392
+
393
+ try {
394
+ const stat = fs.statSync(fullPath);
395
+
396
+ if (stat.isDirectory()) {
397
+ // 跳过一些不需要扫描的目录
398
+ if (!['node_modules', '.git', 'build'].includes(item)) {
399
+ scanDirectory(fullPath);
400
+ }
401
+ } else if (extensions.some(ext => item.endsWith(ext))) {
402
+ files.push(fullPath);
403
+ }
404
+ } catch (error) {
405
+ // 跳过无法访问的文件
406
+ continue;
407
+ }
408
+ }
409
+ } catch (error) {
410
+ // 跳过无法读取的目录
411
+ this.events.emit('verbose', `Cannot read directory: ${dir}`);
412
+ }
413
+ };
414
+
415
+ scanDirectory(projectPath);
416
+ return files;
417
+ }
418
+
419
+ // 处理单个模板文件
420
+ processTemplateFile(filePath, variables) {
421
+ try {
422
+ let content = fs.readFileSync(filePath, 'utf8');
423
+ let modified = false;
424
+ for (const [key, value] of Object.entries(variables)) {
425
+ const placeholder = `{{${key}}}`;
426
+
427
+ // 使用 split + join 方法替换所有出现
428
+ if (content.includes(placeholder)) {
429
+ const before = content;
430
+ content = content.split(placeholder).join(value);
431
+ const after = content;
432
+
433
+ if (before !== after) {
434
+ modified = true;
435
+ }
436
+ }
437
+ }
438
+
439
+ if (modified) {
440
+ fs.writeFileSync(filePath, content, 'utf8');
441
+ this.events.emit('verbose', `Successfully processed: ${path.relative(process.cwd(), filePath)}`);
442
+ }
443
+ } catch (error) {
444
+ const errorMsg = `Error processing ${filePath}: ${error.message}`;
445
+ this.events.emit('verbose', errorMsg);
446
+ console.error(errorMsg); // 详细错误日志
447
+ throw error; // 重新抛出错误以便上层处理
448
+ }
449
+ }
450
+ }
451
+
452
+ module.exports = PlatformProject;