@oniroproject/core 0.6.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/dist/index.cjs +1856 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +593 -0
- package/dist/index.d.ts +593 -0
- package/dist/index.js +1748 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1856 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ALL_SDKS: () => ALL_SDKS,
|
|
34
|
+
APL_VALUES: () => APL_VALUES,
|
|
35
|
+
APP_FEATURE_VALUES: () => APP_FEATURE_VALUES,
|
|
36
|
+
CancelledError: () => CancelledError,
|
|
37
|
+
ChecksumMismatchError: () => ChecksumMismatchError,
|
|
38
|
+
CmdToolsNotInstalledError: () => CmdToolsNotInstalledError,
|
|
39
|
+
OHOS_URL_BASE: () => OHOS_URL_BASE,
|
|
40
|
+
OniroError: () => OniroError,
|
|
41
|
+
SdkNotInstalledError: () => SdkNotInstalledError,
|
|
42
|
+
UnsupportedPlatformError: () => UnsupportedPlatformError,
|
|
43
|
+
VERSION: () => VERSION,
|
|
44
|
+
attemptHdcConnection: () => attemptHdcConnection,
|
|
45
|
+
consoleLogger: () => consoleLogger,
|
|
46
|
+
createMaterial: () => createMaterial,
|
|
47
|
+
createScaffold: () => createScaffold,
|
|
48
|
+
decryptPwd: () => decryptPwd,
|
|
49
|
+
defaultContext: () => defaultContext,
|
|
50
|
+
defaultPaths: () => defaultPaths,
|
|
51
|
+
detectProjectSdkVersion: () => detectProjectSdkVersion,
|
|
52
|
+
downloadAndInstallSdk: () => downloadAndInstallSdk,
|
|
53
|
+
downloadFile: () => downloadFile,
|
|
54
|
+
encryptPwd: () => encryptPwd,
|
|
55
|
+
extractTarball: () => extractTarball,
|
|
56
|
+
extractZipWithProgress: () => extractZipWithProgress,
|
|
57
|
+
findAppProcessId: () => findAppProcessId,
|
|
58
|
+
findCmdToolsSourceDir: () => findCmdToolsSourceDir,
|
|
59
|
+
generateSigningConfigs: () => generateSigningConfigs,
|
|
60
|
+
getBundleName: () => getBundleName,
|
|
61
|
+
getCmdToolsBin: () => getCmdToolsBin,
|
|
62
|
+
getCmdToolsDownloadUrl: () => getCmdToolsDownloadUrl,
|
|
63
|
+
getCmdToolsPath: () => getCmdToolsPath,
|
|
64
|
+
getCmdToolsStatus: () => getCmdToolsStatus,
|
|
65
|
+
getEmulatorDir: () => getEmulatorDir,
|
|
66
|
+
getHdcPath: () => getHdcPath,
|
|
67
|
+
getHvigorwPath: () => getHvigorwPath,
|
|
68
|
+
getInstalledSdks: () => getInstalledSdks,
|
|
69
|
+
getKey: () => getKey,
|
|
70
|
+
getMainAbility: () => getMainAbility,
|
|
71
|
+
getOhosBaseSdkHome: () => getOhosBaseSdkHome,
|
|
72
|
+
getOsFolder: () => getOsFolder,
|
|
73
|
+
getSdkFilename: () => getSdkFilename,
|
|
74
|
+
getSdkRootDir: () => getSdkRootDir,
|
|
75
|
+
getSupportedSdksForUi: () => getSupportedSdksForUi,
|
|
76
|
+
installApp: () => installApp,
|
|
77
|
+
installCmdTools: () => installCmdTools,
|
|
78
|
+
installEmulator: () => installEmulator,
|
|
79
|
+
isCmdToolsInstalled: () => isCmdToolsInstalled,
|
|
80
|
+
isEmulatorInstalled: () => isEmulatorInstalled,
|
|
81
|
+
isValidBundleName: () => isValidBundleName,
|
|
82
|
+
isValidProjectName: () => isValidProjectName,
|
|
83
|
+
launchApp: () => launchApp,
|
|
84
|
+
listRunningProcesses: () => listRunningProcesses,
|
|
85
|
+
listTemplates: () => listTemplates,
|
|
86
|
+
nonInteractivePrompter: () => nonInteractivePrompter,
|
|
87
|
+
noopLogger: () => noopLogger,
|
|
88
|
+
noopProgress: () => noopProgress,
|
|
89
|
+
parseHilogLine: () => parseHilogLine,
|
|
90
|
+
readJson5File: () => readJson5File,
|
|
91
|
+
readJsonFile: () => readJsonFile,
|
|
92
|
+
removeCmdTools: () => removeCmdTools,
|
|
93
|
+
removeEmulator: () => removeEmulator,
|
|
94
|
+
removeSdk: () => removeSdk,
|
|
95
|
+
runHvigorw: () => runHvigorw,
|
|
96
|
+
setHilogLevel: () => setHilogLevel,
|
|
97
|
+
startEmulator: () => startEmulator,
|
|
98
|
+
staticConfig: () => staticConfig,
|
|
99
|
+
stopEmulator: () => stopEmulator,
|
|
100
|
+
streamHilog: () => streamHilog,
|
|
101
|
+
validateTemplateLayout: () => validateTemplateLayout,
|
|
102
|
+
verifySha256: () => verifySha256,
|
|
103
|
+
writeJson5File: () => writeJson5File,
|
|
104
|
+
writeJsonFile: () => writeJsonFile
|
|
105
|
+
});
|
|
106
|
+
module.exports = __toCommonJS(index_exports);
|
|
107
|
+
|
|
108
|
+
// src/ports/logger.ts
|
|
109
|
+
var noopLogger = {
|
|
110
|
+
debug() {
|
|
111
|
+
},
|
|
112
|
+
info() {
|
|
113
|
+
},
|
|
114
|
+
warn() {
|
|
115
|
+
},
|
|
116
|
+
error() {
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var consoleLogger = {
|
|
120
|
+
debug: (m) => console.debug(m),
|
|
121
|
+
info: (m) => console.log(m),
|
|
122
|
+
warn: (m) => console.warn(m),
|
|
123
|
+
error: (m) => console.error(m)
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/ports/progress.ts
|
|
127
|
+
var noopProgress = {
|
|
128
|
+
report() {
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/ports/config.ts
|
|
133
|
+
var os = __toESM(require("os"), 1);
|
|
134
|
+
var path = __toESM(require("path"), 1);
|
|
135
|
+
var defaultPaths = {
|
|
136
|
+
sdkRootDir: () => path.join(os.homedir(), "setup-ohos-sdk"),
|
|
137
|
+
cmdToolsPath: () => path.join(os.homedir(), "command-line-tools"),
|
|
138
|
+
emulatorDir: () => path.join(os.homedir(), "oniro-emulator"),
|
|
139
|
+
hapPath: "entry/build/default/outputs/default/entry-default-signed.hap",
|
|
140
|
+
emulatorUrl: "https://github.com/eclipse-oniro4openharmony/device_board_oniro/releases/latest/download/oniro_emulator.zip",
|
|
141
|
+
// The Huawei mirror only hosts a public Linux build of the command-line tools.
|
|
142
|
+
// Windows and macOS require a manual download from the Huawei developer portal
|
|
143
|
+
// (https://developer.huawei.com/...); pass the resulting ZIP to `cmdtools install --from-zip <path>`
|
|
144
|
+
// or set ONIRO_CMD_TOOLS_URL_WINDOWS / ONIRO_CMD_TOOLS_URL_MAC to a self-hosted URL.
|
|
145
|
+
cmdToolsUrlLinux: "https://repo.huaweicloud.com/harmonyos/ohpm/5.1.0/commandline-tools-linux-x64-5.1.0.840.zip"
|
|
146
|
+
};
|
|
147
|
+
function staticConfig(values = {}) {
|
|
148
|
+
return {
|
|
149
|
+
get(key, fallback) {
|
|
150
|
+
const v = values[key];
|
|
151
|
+
if (v === void 0 || v === "") {
|
|
152
|
+
return fallback;
|
|
153
|
+
}
|
|
154
|
+
if (typeof v === "string" && v.includes("${userHome}")) {
|
|
155
|
+
return v.replace(/\$\{userHome\}/g, os.homedir());
|
|
156
|
+
}
|
|
157
|
+
return v;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/ports/prompter.ts
|
|
163
|
+
var nonInteractivePrompter = {
|
|
164
|
+
async confirm(message) {
|
|
165
|
+
throw new Error(`Non-interactive mode: cannot confirm "${message}"`);
|
|
166
|
+
},
|
|
167
|
+
async input(message) {
|
|
168
|
+
throw new Error(`Non-interactive mode: cannot ask "${message}"`);
|
|
169
|
+
},
|
|
170
|
+
async selectDirectory(message) {
|
|
171
|
+
throw new Error(`Non-interactive mode: cannot prompt for directory "${message}"`);
|
|
172
|
+
},
|
|
173
|
+
async selectFile(message) {
|
|
174
|
+
throw new Error(`Non-interactive mode: cannot prompt for file "${message}"`);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/ports/errors.ts
|
|
179
|
+
var OniroError = class extends Error {
|
|
180
|
+
constructor(message, cause) {
|
|
181
|
+
super(message);
|
|
182
|
+
this.cause = cause;
|
|
183
|
+
this.name = "OniroError";
|
|
184
|
+
}
|
|
185
|
+
cause;
|
|
186
|
+
};
|
|
187
|
+
var SdkNotInstalledError = class extends OniroError {
|
|
188
|
+
constructor(api, expectedPath) {
|
|
189
|
+
super(`SDK API ${api} is not installed at ${expectedPath}.`);
|
|
190
|
+
this.api = api;
|
|
191
|
+
this.expectedPath = expectedPath;
|
|
192
|
+
this.name = "SdkNotInstalledError";
|
|
193
|
+
}
|
|
194
|
+
api;
|
|
195
|
+
expectedPath;
|
|
196
|
+
};
|
|
197
|
+
var CmdToolsNotInstalledError = class extends OniroError {
|
|
198
|
+
constructor(expectedPath) {
|
|
199
|
+
super(`OpenHarmony command-line tools are not installed at ${expectedPath}.`);
|
|
200
|
+
this.expectedPath = expectedPath;
|
|
201
|
+
this.name = "CmdToolsNotInstalledError";
|
|
202
|
+
}
|
|
203
|
+
expectedPath;
|
|
204
|
+
};
|
|
205
|
+
var UnsupportedPlatformError = class extends OniroError {
|
|
206
|
+
constructor(platform6) {
|
|
207
|
+
super(`Unsupported platform: ${platform6}.`);
|
|
208
|
+
this.platform = platform6;
|
|
209
|
+
this.name = "UnsupportedPlatformError";
|
|
210
|
+
}
|
|
211
|
+
platform;
|
|
212
|
+
};
|
|
213
|
+
var ChecksumMismatchError = class extends OniroError {
|
|
214
|
+
constructor(expected, actual) {
|
|
215
|
+
super(`SHA256 mismatch: expected ${expected}, got ${actual}.`);
|
|
216
|
+
this.expected = expected;
|
|
217
|
+
this.actual = actual;
|
|
218
|
+
this.name = "ChecksumMismatchError";
|
|
219
|
+
}
|
|
220
|
+
expected;
|
|
221
|
+
actual;
|
|
222
|
+
};
|
|
223
|
+
var CancelledError = class extends OniroError {
|
|
224
|
+
constructor(message = "Operation cancelled.") {
|
|
225
|
+
super(message);
|
|
226
|
+
this.name = "CancelledError";
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// src/ports/context.ts
|
|
231
|
+
function defaultContext(overrides = {}) {
|
|
232
|
+
return {
|
|
233
|
+
logger: overrides.logger ?? noopLogger,
|
|
234
|
+
config: overrides.config ?? staticConfig()
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/sdk/constants.ts
|
|
239
|
+
var ALL_SDKS = [
|
|
240
|
+
{ version: "4.0", api: "10" },
|
|
241
|
+
{ version: "4.1", api: "11" },
|
|
242
|
+
{ version: "5.0.0", api: "12" },
|
|
243
|
+
{ version: "5.0.1", api: "13" },
|
|
244
|
+
{ version: "5.0.2", api: "14" },
|
|
245
|
+
{ version: "5.0.3", api: "15" },
|
|
246
|
+
{ version: "5.1.0", api: "18" },
|
|
247
|
+
{ version: "6.0", api: "20" },
|
|
248
|
+
{ version: "6.1", api: "23" }
|
|
249
|
+
];
|
|
250
|
+
var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
251
|
+
|
|
252
|
+
// src/sdk/platform.ts
|
|
253
|
+
var os2 = __toESM(require("os"), 1);
|
|
254
|
+
function getOsFolder() {
|
|
255
|
+
const platform6 = os2.platform();
|
|
256
|
+
if (platform6 === "linux") return "linux";
|
|
257
|
+
if (platform6 === "darwin") return "darwin";
|
|
258
|
+
if (platform6 === "win32") return "windows";
|
|
259
|
+
throw new UnsupportedPlatformError(platform6);
|
|
260
|
+
}
|
|
261
|
+
function getSdkFilename(version) {
|
|
262
|
+
const platform6 = os2.platform();
|
|
263
|
+
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
264
|
+
if (platform6 === "linux") {
|
|
265
|
+
const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
|
|
266
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
|
|
267
|
+
}
|
|
268
|
+
if (platform6 === "darwin") {
|
|
269
|
+
if (os2.arch() === "arm64") {
|
|
270
|
+
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: 3 };
|
|
271
|
+
}
|
|
272
|
+
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: 3 };
|
|
273
|
+
}
|
|
274
|
+
if (platform6 === "win32") {
|
|
275
|
+
const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
|
|
276
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
|
|
277
|
+
}
|
|
278
|
+
throw new UnsupportedPlatformError(platform6);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/sdk/paths.ts
|
|
282
|
+
var fs = __toESM(require("fs"), 1);
|
|
283
|
+
var os3 = __toESM(require("os"), 1);
|
|
284
|
+
var path2 = __toESM(require("path"), 1);
|
|
285
|
+
function getSdkRootDir(config) {
|
|
286
|
+
return config.get("sdkRootDir", defaultPaths.sdkRootDir());
|
|
287
|
+
}
|
|
288
|
+
function getOhosBaseSdkHome(config) {
|
|
289
|
+
return path2.join(getSdkRootDir(config), getOsFolder());
|
|
290
|
+
}
|
|
291
|
+
function getCmdToolsPath(config) {
|
|
292
|
+
return config.get("cmdToolsPath", defaultPaths.cmdToolsPath());
|
|
293
|
+
}
|
|
294
|
+
function getEmulatorDir(config) {
|
|
295
|
+
return config.get("emulatorDir", defaultPaths.emulatorDir());
|
|
296
|
+
}
|
|
297
|
+
function pickExisting(candidates) {
|
|
298
|
+
for (const c of candidates) {
|
|
299
|
+
if (fs.existsSync(c)) return c;
|
|
300
|
+
}
|
|
301
|
+
return candidates[0];
|
|
302
|
+
}
|
|
303
|
+
function getCmdToolsBin(config) {
|
|
304
|
+
const binDir = path2.join(getCmdToolsPath(config), "bin");
|
|
305
|
+
if (os3.platform() === "win32") {
|
|
306
|
+
return pickExisting([
|
|
307
|
+
path2.join(binDir, "ohpm.exe"),
|
|
308
|
+
path2.join(binDir, "ohpm.cmd"),
|
|
309
|
+
path2.join(binDir, "ohpm.bat"),
|
|
310
|
+
path2.join(binDir, "ohpm")
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
return path2.join(binDir, "ohpm");
|
|
314
|
+
}
|
|
315
|
+
function getHdcPath(config) {
|
|
316
|
+
const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
|
|
317
|
+
if (os3.platform() === "win32") {
|
|
318
|
+
return pickExisting([
|
|
319
|
+
path2.join(base, "hdc.exe"),
|
|
320
|
+
path2.join(base, "hdc.bat"),
|
|
321
|
+
path2.join(base, "hdc.cmd"),
|
|
322
|
+
path2.join(base, "hdc")
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
return path2.join(base, "hdc");
|
|
326
|
+
}
|
|
327
|
+
function getHvigorwPath(config, projectDir) {
|
|
328
|
+
const cmdToolsBin = path2.join(getCmdToolsPath(config), "bin");
|
|
329
|
+
return pickExisting([
|
|
330
|
+
path2.join(projectDir, "hvigorw"),
|
|
331
|
+
path2.join(projectDir, "hvigorw.bat"),
|
|
332
|
+
path2.join(projectDir, "hvigorw.cmd"),
|
|
333
|
+
path2.join(cmdToolsBin, "hvigorw"),
|
|
334
|
+
path2.join(cmdToolsBin, "hvigorw.bat"),
|
|
335
|
+
path2.join(cmdToolsBin, "hvigorw.cmd")
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/sdk/detectProjectSdk.ts
|
|
340
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
341
|
+
var path3 = __toESM(require("path"), 1);
|
|
342
|
+
var import_json5 = __toESM(require("json5"), 1);
|
|
343
|
+
function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
344
|
+
const buildProfilePath = path3.join(projectRoot, "build-profile.json5");
|
|
345
|
+
if (!fs2.existsSync(buildProfilePath)) {
|
|
346
|
+
logger.warn(`build-profile.json5 not found at ${projectRoot}`);
|
|
347
|
+
return void 0;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const content = fs2.readFileSync(buildProfilePath, "utf-8");
|
|
351
|
+
const config = import_json5.default.parse(content);
|
|
352
|
+
const product = config.app?.products?.[0];
|
|
353
|
+
const v = product?.compileSdkVersion;
|
|
354
|
+
return typeof v === "number" ? v : void 0;
|
|
355
|
+
} catch (err) {
|
|
356
|
+
logger.error(`Error reading build-profile.json5 at ${projectRoot}: ${String(err)}`);
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/sdk/download.ts
|
|
362
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
363
|
+
var crypto = __toESM(require("crypto"), 1);
|
|
364
|
+
var import_node_stream = require("stream");
|
|
365
|
+
var import_node_util = require("util");
|
|
366
|
+
var import_follow_redirects = __toESM(require("follow-redirects"), 1);
|
|
367
|
+
var { http, https } = import_follow_redirects.default;
|
|
368
|
+
var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline);
|
|
369
|
+
async function downloadFile(opts) {
|
|
370
|
+
const { url, dest, progress, abortSignal } = opts;
|
|
371
|
+
const proto = url.startsWith("https") ? https : http;
|
|
372
|
+
return new Promise((resolve2, reject) => {
|
|
373
|
+
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
374
|
+
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
375
|
+
let settled = false;
|
|
376
|
+
const done = (err) => {
|
|
377
|
+
if (settled) return;
|
|
378
|
+
settled = true;
|
|
379
|
+
err ? reject(err) : resolve2();
|
|
380
|
+
};
|
|
381
|
+
if (abortSignal?.aborted) {
|
|
382
|
+
done(new CancelledError("Download cancelled before start."));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const file = fs3.createWriteStream(dest);
|
|
386
|
+
const req = proto.get(url, (response) => {
|
|
387
|
+
if (response.statusCode !== 200) {
|
|
388
|
+
try {
|
|
389
|
+
response.destroy();
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
file.close();
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
fs3.unlink(dest, () => {
|
|
397
|
+
});
|
|
398
|
+
done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const total = parseInt(response.headers["content-length"] || "0", 10);
|
|
402
|
+
let downloaded = 0;
|
|
403
|
+
let lastOverall = Math.round(s);
|
|
404
|
+
response.on("data", (chunk) => {
|
|
405
|
+
downloaded += chunk.length;
|
|
406
|
+
if (progress && total) {
|
|
407
|
+
const localPercent = Math.min(100, Math.round(downloaded / total * 100));
|
|
408
|
+
const overall = Math.min(100, Math.round(s + downloaded / total * r));
|
|
409
|
+
const inc = overall - lastOverall;
|
|
410
|
+
if (inc > 0) {
|
|
411
|
+
progress.report({ message: `Downloading: ${localPercent}%`, increment: inc });
|
|
412
|
+
lastOverall = overall;
|
|
413
|
+
} else {
|
|
414
|
+
progress.report({ message: `Downloading: ${localPercent}%`, increment: 0 });
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
response.pipe(file);
|
|
419
|
+
file.on("finish", () => {
|
|
420
|
+
if (progress) {
|
|
421
|
+
const endOverall = Math.min(100, Math.round(s + r));
|
|
422
|
+
const inc = endOverall - lastOverall;
|
|
423
|
+
if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
|
|
424
|
+
}
|
|
425
|
+
file.close((err) => err ? done(err) : done());
|
|
426
|
+
});
|
|
427
|
+
abortSignal?.addEventListener("abort", () => {
|
|
428
|
+
response.destroy();
|
|
429
|
+
file.close();
|
|
430
|
+
fs3.unlink(dest, () => {
|
|
431
|
+
});
|
|
432
|
+
done(new CancelledError("Download cancelled."));
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
req.on("error", (err) => {
|
|
436
|
+
try {
|
|
437
|
+
file.close();
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
fs3.unlink(dest, () => {
|
|
441
|
+
});
|
|
442
|
+
done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
|
|
443
|
+
});
|
|
444
|
+
abortSignal?.addEventListener("abort", () => {
|
|
445
|
+
req.destroy();
|
|
446
|
+
file.close();
|
|
447
|
+
fs3.unlink(dest, () => {
|
|
448
|
+
});
|
|
449
|
+
done(new CancelledError("Download cancelled."));
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
async function verifySha256(filePath, sha256Path) {
|
|
454
|
+
const expected = fs3.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
|
|
455
|
+
const hash = crypto.createHash("sha256");
|
|
456
|
+
await pipelineAsync(fs3.createReadStream(filePath), hash);
|
|
457
|
+
const actual = hash.digest("hex");
|
|
458
|
+
if (actual !== expected) {
|
|
459
|
+
throw new ChecksumMismatchError(expected, actual);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/sdk/extract.ts
|
|
464
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
465
|
+
var os4 = __toESM(require("os"), 1);
|
|
466
|
+
var path4 = __toESM(require("path"), 1);
|
|
467
|
+
var import_node_stream2 = require("stream");
|
|
468
|
+
var import_node_util2 = require("util");
|
|
469
|
+
var tar = __toESM(require("tar"), 1);
|
|
470
|
+
var import_node_stream_zip = __toESM(require("node-stream-zip"), 1);
|
|
471
|
+
var pipelineAsync2 = (0, import_node_util2.promisify)(import_node_stream2.pipeline);
|
|
472
|
+
async function extractZipWithProgress(opts) {
|
|
473
|
+
const { zipPath, dest, progress } = opts;
|
|
474
|
+
const logger = opts.logger ?? noopLogger;
|
|
475
|
+
const zip = new import_node_stream_zip.default.async({ file: zipPath });
|
|
476
|
+
const entries = await zip.entries();
|
|
477
|
+
const files = Object.values(entries);
|
|
478
|
+
const total = files.length || 1;
|
|
479
|
+
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
480
|
+
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
481
|
+
let processed = 0;
|
|
482
|
+
let lastOverall = Math.round(s);
|
|
483
|
+
await fs4.promises.mkdir(dest, { recursive: true });
|
|
484
|
+
const destRoot = path4.resolve(dest);
|
|
485
|
+
const safeResolveTarget = (entryName) => {
|
|
486
|
+
const resolved = path4.resolve(destRoot, entryName);
|
|
487
|
+
const rel = path4.relative(destRoot, resolved);
|
|
488
|
+
if (rel.startsWith("..") || path4.isAbsolute(rel)) {
|
|
489
|
+
throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
|
|
490
|
+
}
|
|
491
|
+
return resolved;
|
|
492
|
+
};
|
|
493
|
+
try {
|
|
494
|
+
for (const file of files) {
|
|
495
|
+
const entryName = file.name;
|
|
496
|
+
const targetPath = safeResolveTarget(entryName);
|
|
497
|
+
const attr = file.attr ? file.attr >>> 16 : 0;
|
|
498
|
+
const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
|
|
499
|
+
if (file.isDirectory) {
|
|
500
|
+
await fs4.promises.mkdir(targetPath, { recursive: true });
|
|
501
|
+
} else if (isSymlink) {
|
|
502
|
+
await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
|
|
503
|
+
const linkTargetBuffer = await zip.entryData(file);
|
|
504
|
+
const linkTarget = linkTargetBuffer.toString("utf8");
|
|
505
|
+
try {
|
|
506
|
+
const isWin = os4.platform() === "win32";
|
|
507
|
+
await fs4.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
|
|
513
|
+
const readStream = await zip.stream(file);
|
|
514
|
+
const writeStream = fs4.createWriteStream(targetPath);
|
|
515
|
+
await pipelineAsync2(readStream, writeStream);
|
|
516
|
+
if (attr > 0) {
|
|
517
|
+
try {
|
|
518
|
+
await fs4.promises.chmod(targetPath, attr & 511);
|
|
519
|
+
} catch {
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
processed++;
|
|
524
|
+
if (progress) {
|
|
525
|
+
const localPercent = Math.min(100, Math.round(processed / total * 100));
|
|
526
|
+
const overall = Math.min(100, Math.round(s + processed / total * r));
|
|
527
|
+
const inc = overall - lastOverall;
|
|
528
|
+
if (inc > 0) {
|
|
529
|
+
progress.report({ message: `Extracting: ${localPercent}%`, increment: inc });
|
|
530
|
+
lastOverall = overall;
|
|
531
|
+
} else {
|
|
532
|
+
progress.report({ message: `Extracting: ${localPercent}%`, increment: 0 });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} finally {
|
|
537
|
+
await zip.close();
|
|
538
|
+
}
|
|
539
|
+
if (progress) {
|
|
540
|
+
const endOverall = Math.min(100, Math.round(s + r));
|
|
541
|
+
const inc = endOverall - lastOverall;
|
|
542
|
+
if (inc > 0) progress.report({ message: "Extracting: 100%", increment: inc });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function extractTarball(tarPath, dest, strip = 0) {
|
|
546
|
+
await tar.x({ file: tarPath, cwd: dest, strip });
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/sdk/list.ts
|
|
550
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
551
|
+
var path5 = __toESM(require("path"), 1);
|
|
552
|
+
function getSupportedSdksForUi(config) {
|
|
553
|
+
const base = path5.join(getSdkRootDir(config), getOsFolder());
|
|
554
|
+
return ALL_SDKS.map((sdk) => ({
|
|
555
|
+
version: sdk.version,
|
|
556
|
+
api: sdk.api,
|
|
557
|
+
installed: fs5.existsSync(path5.join(base, sdk.api))
|
|
558
|
+
})).sort((a, b) => Number(b.api) - Number(a.api));
|
|
559
|
+
}
|
|
560
|
+
function getInstalledSdks(config) {
|
|
561
|
+
const sdkRoot = getSdkRootDir(config);
|
|
562
|
+
const versions = /* @__PURE__ */ new Set();
|
|
563
|
+
if (!fs5.existsSync(sdkRoot)) return [];
|
|
564
|
+
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
565
|
+
const osPath = path5.join(sdkRoot, osFolder);
|
|
566
|
+
if (!fs5.existsSync(osPath) || !fs5.statSync(osPath).isDirectory()) continue;
|
|
567
|
+
for (const api of fs5.readdirSync(osPath)) {
|
|
568
|
+
const apiPath = path5.join(osPath, api);
|
|
569
|
+
if (fs5.statSync(apiPath).isDirectory()) versions.add(api);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/sdk/remove.ts
|
|
576
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
577
|
+
var path6 = __toESM(require("path"), 1);
|
|
578
|
+
function removeSdk(config, api) {
|
|
579
|
+
const sdkRoot = getSdkRootDir(config);
|
|
580
|
+
let removed = false;
|
|
581
|
+
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
582
|
+
const sdkPath = path6.join(sdkRoot, osFolder, api);
|
|
583
|
+
if (fs6.existsSync(sdkPath)) {
|
|
584
|
+
fs6.rmSync(sdkPath, { recursive: true, force: true });
|
|
585
|
+
removed = true;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return removed;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/sdk/install.ts
|
|
592
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
593
|
+
var os5 = __toESM(require("os"), 1);
|
|
594
|
+
var path7 = __toESM(require("path"), 1);
|
|
595
|
+
async function downloadAndInstallSdk(opts) {
|
|
596
|
+
const { config, version, api, progress, abortSignal } = opts;
|
|
597
|
+
const logger = opts.logger ?? noopLogger;
|
|
598
|
+
const { filename, osFolder, strip } = getSdkFilename(version);
|
|
599
|
+
const downloadUrl = `${OHOS_URL_BASE}/${version}-Release/${filename}`;
|
|
600
|
+
const sha256Url = `${downloadUrl}.sha256`;
|
|
601
|
+
const tmpDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
|
|
602
|
+
const tarPath = path7.join(tmpDir, filename);
|
|
603
|
+
const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
|
|
604
|
+
const extractDir = path7.join(tmpDir, "extract");
|
|
605
|
+
fs7.mkdirSync(extractDir);
|
|
606
|
+
const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
|
|
607
|
+
fs7.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
|
|
608
|
+
const checkCancelled = () => {
|
|
609
|
+
if (abortSignal?.aborted) throw new CancelledError();
|
|
610
|
+
};
|
|
611
|
+
try {
|
|
612
|
+
progress?.report({ message: "Downloading SDK archive...", increment: 0 });
|
|
613
|
+
await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
|
|
614
|
+
progress?.report({ message: "Downloading checksum...", increment: 0 });
|
|
615
|
+
await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
|
|
616
|
+
progress?.report({ message: "Verifying checksum...", increment: 0 });
|
|
617
|
+
await verifySha256(tarPath, sha256Path);
|
|
618
|
+
progress?.report({ message: "Verifying checksum...", increment: 5 });
|
|
619
|
+
checkCancelled();
|
|
620
|
+
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 0 });
|
|
621
|
+
await extractTarball(tarPath, extractDir, strip);
|
|
622
|
+
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
623
|
+
checkCancelled();
|
|
624
|
+
const osContentPath = path7.join(extractDir, osFolder);
|
|
625
|
+
if (!fs7.existsSync(osContentPath)) {
|
|
626
|
+
throw new OniroError(
|
|
627
|
+
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
const zipFiles = fs7.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
631
|
+
const componentsStart = 60;
|
|
632
|
+
const componentsBudget = 35;
|
|
633
|
+
const n = zipFiles.length;
|
|
634
|
+
if (n === 0) {
|
|
635
|
+
progress?.report({ message: "No SDK component ZIPs found.", increment: componentsBudget });
|
|
636
|
+
} else {
|
|
637
|
+
progress?.report({ message: "Extracting SDK components...", increment: 0 });
|
|
638
|
+
}
|
|
639
|
+
const base = n > 0 ? Math.floor(componentsBudget / n) : 0;
|
|
640
|
+
let rem = n > 0 ? componentsBudget % n : 0;
|
|
641
|
+
let cursor = componentsStart;
|
|
642
|
+
for (const zipFile of zipFiles) {
|
|
643
|
+
checkCancelled();
|
|
644
|
+
logger.info(`Extracting component ${zipFile}`);
|
|
645
|
+
const zipPath = path7.join(osContentPath, zipFile);
|
|
646
|
+
const thisBudget = base + (rem > 0 ? 1 : 0);
|
|
647
|
+
if (rem > 0) rem--;
|
|
648
|
+
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
649
|
+
cursor += thisBudget;
|
|
650
|
+
fs7.unlinkSync(zipPath);
|
|
651
|
+
}
|
|
652
|
+
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
653
|
+
if (fs7.existsSync(sdkInstallDir)) {
|
|
654
|
+
fs7.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
655
|
+
}
|
|
656
|
+
fs7.renameSync(osContentPath, sdkInstallDir);
|
|
657
|
+
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
658
|
+
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
659
|
+
fs7.rmSync(tmpDir, { recursive: true, force: true });
|
|
660
|
+
progress?.report({ message: "Cleaning up...", increment: 2 });
|
|
661
|
+
} catch (err) {
|
|
662
|
+
logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
663
|
+
fs7.rmSync(tmpDir, { recursive: true, force: true });
|
|
664
|
+
throw err;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// src/cmdTools/install.ts
|
|
669
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
670
|
+
var os6 = __toESM(require("os"), 1);
|
|
671
|
+
var path8 = __toESM(require("path"), 1);
|
|
672
|
+
var import_node_child_process = require("child_process");
|
|
673
|
+
function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
|
|
674
|
+
if (platform6 === "linux") {
|
|
675
|
+
return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
|
|
676
|
+
}
|
|
677
|
+
if (platform6 === "win32") {
|
|
678
|
+
const url = config.get("cmdToolsUrlWindows", "");
|
|
679
|
+
if (!url) {
|
|
680
|
+
throw new OniroError(
|
|
681
|
+
"No Windows command-line tools URL configured. The Huawei mirror does not host a public Windows build; download the ZIP from the Huawei developer portal and run `oniro-app cmdtools install --from-zip <path>`, or set ONIRO_CMD_TOOLS_URL_WINDOWS to a self-hosted URL."
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
return url;
|
|
685
|
+
}
|
|
686
|
+
if (platform6 === "darwin") {
|
|
687
|
+
const url = config.get("cmdToolsUrlMac", "");
|
|
688
|
+
if (!url) {
|
|
689
|
+
throw new OniroError(
|
|
690
|
+
"No macOS command-line tools URL configured. The Huawei mirror does not host a public macOS build; download the ZIP from the Huawei developer portal and run `oniro-app cmdtools install --from-zip <path>`, or set ONIRO_CMD_TOOLS_URL_MAC to a self-hosted URL."
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
return url;
|
|
694
|
+
}
|
|
695
|
+
throw new UnsupportedPlatformError(platform6);
|
|
696
|
+
}
|
|
697
|
+
function findCmdToolsSourceDir(extractPath) {
|
|
698
|
+
const known = [
|
|
699
|
+
path8.join(extractPath, "command-line-tools"),
|
|
700
|
+
path8.join(extractPath, "oh-command-line-tools"),
|
|
701
|
+
path8.join(extractPath, "commandline-tools")
|
|
702
|
+
];
|
|
703
|
+
for (const c of known) {
|
|
704
|
+
if (fs8.existsSync(c)) return c;
|
|
705
|
+
}
|
|
706
|
+
const entries = fs8.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
|
|
707
|
+
for (const dir of entries) {
|
|
708
|
+
if (fs8.existsSync(path8.join(dir, "bin"))) return dir;
|
|
709
|
+
}
|
|
710
|
+
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
711
|
+
}
|
|
712
|
+
async function installCmdTools(opts) {
|
|
713
|
+
const { config, progress, abortSignal } = opts;
|
|
714
|
+
const logger = opts.logger ?? noopLogger;
|
|
715
|
+
const CMD_PATH = getCmdToolsPath(config);
|
|
716
|
+
const tmpDir = fs8.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
|
|
717
|
+
const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
|
|
718
|
+
const extractPath = path8.join(tmpDir, "oh-command-line-tools");
|
|
719
|
+
try {
|
|
720
|
+
if (!opts.localZipPath) {
|
|
721
|
+
const url = getCmdToolsDownloadUrl(config);
|
|
722
|
+
progress?.report({ message: "Downloading command line tools...", increment: 0 });
|
|
723
|
+
await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
|
|
724
|
+
}
|
|
725
|
+
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
726
|
+
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
727
|
+
fs8.mkdirSync(CMD_PATH, { recursive: true });
|
|
728
|
+
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
729
|
+
for (const entry of fs8.readdirSync(srcDir)) {
|
|
730
|
+
const src = path8.join(srcDir, entry);
|
|
731
|
+
const dest = path8.join(CMD_PATH, entry);
|
|
732
|
+
if (fs8.statSync(src).isDirectory()) {
|
|
733
|
+
if (fs8.existsSync(dest)) fs8.rmSync(dest, { recursive: true, force: true });
|
|
734
|
+
fs8.renameSync(src, dest);
|
|
735
|
+
} else {
|
|
736
|
+
fs8.copyFileSync(src, dest);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const binDir = path8.join(CMD_PATH, "bin");
|
|
740
|
+
if (fs8.existsSync(binDir) && os6.platform() !== "win32") {
|
|
741
|
+
for (const file of fs8.readdirSync(binDir)) {
|
|
742
|
+
fs8.chmodSync(path8.join(binDir, file), 493);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
746
|
+
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
747
|
+
} finally {
|
|
748
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function getCmdToolsStatus(config) {
|
|
752
|
+
const cmdPath = getCmdToolsPath(config);
|
|
753
|
+
const binDir = path8.join(cmdPath, "bin");
|
|
754
|
+
const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
755
|
+
const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs8.existsSync(p));
|
|
756
|
+
if (!bin) {
|
|
757
|
+
return { installed: false, status: "Not installed" };
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const versionFile = path8.join(cmdPath, "version.txt");
|
|
761
|
+
if (fs8.existsSync(versionFile)) {
|
|
762
|
+
const content = fs8.readFileSync(versionFile, "utf8");
|
|
763
|
+
for (const line of content.split(/\r?\n/)) {
|
|
764
|
+
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
765
|
+
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} catch {
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
const version = (0, import_node_child_process.execFileSync)(bin, ["-v"], { encoding: "utf8" }).trim();
|
|
772
|
+
return { installed: true, status: `Installed (${version})` };
|
|
773
|
+
} catch {
|
|
774
|
+
return { installed: true, status: "Installed (version unknown)" };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function isCmdToolsInstalled(config) {
|
|
778
|
+
return getCmdToolsStatus(config).installed;
|
|
779
|
+
}
|
|
780
|
+
function removeCmdTools(config) {
|
|
781
|
+
const cmdPath = getCmdToolsPath(config);
|
|
782
|
+
if (fs8.existsSync(cmdPath)) {
|
|
783
|
+
fs8.rmSync(cmdPath, { recursive: true, force: true });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/sign/encryptKey.ts
|
|
788
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
789
|
+
var path9 = __toESM(require("path"), 1);
|
|
790
|
+
var crypto2 = __toESM(require("crypto"), 1);
|
|
791
|
+
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
792
|
+
var DAEMON_SALT_KEY_LENGTH = 16;
|
|
793
|
+
var DAEMON_WORK_KEY_LENGTH = 16;
|
|
794
|
+
var KEY_FILE_DIRECTORY_PERMISSIONS = 493;
|
|
795
|
+
var KEY_FILE_PERMISSIONS = 384;
|
|
796
|
+
var COMPONENT = Buffer.from([
|
|
797
|
+
49,
|
|
798
|
+
243,
|
|
799
|
+
9,
|
|
800
|
+
115,
|
|
801
|
+
214,
|
|
802
|
+
175,
|
|
803
|
+
91,
|
|
804
|
+
184,
|
|
805
|
+
211,
|
|
806
|
+
190,
|
|
807
|
+
177,
|
|
808
|
+
88,
|
|
809
|
+
101,
|
|
810
|
+
131,
|
|
811
|
+
192,
|
|
812
|
+
119
|
|
813
|
+
]);
|
|
814
|
+
function encrypt(key, data) {
|
|
815
|
+
const iv = crypto2.randomBytes(12);
|
|
816
|
+
const cipher = crypto2.createCipheriv("aes-128-gcm", key, iv);
|
|
817
|
+
const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
818
|
+
const authTag = cipher.getAuthTag();
|
|
819
|
+
const totalLength = ciphertext.length + authTag.length;
|
|
820
|
+
const out = Buffer.alloc(4 + iv.length + ciphertext.length + authTag.length);
|
|
821
|
+
out.writeUInt32BE(totalLength, 0);
|
|
822
|
+
iv.copy(out, 4);
|
|
823
|
+
ciphertext.copy(out, 16);
|
|
824
|
+
authTag.copy(out, 16 + ciphertext.length);
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
function decrypt(key, data) {
|
|
828
|
+
const totalLength = data.readUInt32BE(0);
|
|
829
|
+
const iv = data.subarray(4, 16);
|
|
830
|
+
const ciphertextLength = totalLength - 16;
|
|
831
|
+
const ciphertext = data.subarray(16, 16 + ciphertextLength);
|
|
832
|
+
const authTag = data.subarray(16 + ciphertextLength);
|
|
833
|
+
const decipher = crypto2.createDecipheriv("aes-128-gcm", key, iv);
|
|
834
|
+
decipher.setAuthTag(authTag);
|
|
835
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
836
|
+
}
|
|
837
|
+
function xorBuffers(buffers) {
|
|
838
|
+
if (buffers.length === 0) {
|
|
839
|
+
throw new Error("No buffers provided for XOR.");
|
|
840
|
+
}
|
|
841
|
+
const result = Buffer.from(buffers[0]);
|
|
842
|
+
for (let i = 1; i < buffers.length; i++) {
|
|
843
|
+
const buf = buffers[i];
|
|
844
|
+
if (buf.length !== result.length) {
|
|
845
|
+
throw new Error("Buffers have different lengths in XOR.");
|
|
846
|
+
}
|
|
847
|
+
for (let j = 0; j < result.length; j++) {
|
|
848
|
+
result[j] = result[j] ^ buf[j];
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
}
|
|
853
|
+
function getRootKey(fdComponents, salt) {
|
|
854
|
+
const components = fdComponents.concat([COMPONENT]);
|
|
855
|
+
const xored = xorBuffers(components);
|
|
856
|
+
return crypto2.pbkdf2Sync(xored.toString(), salt, 1e4, 16, "sha256");
|
|
857
|
+
}
|
|
858
|
+
function createAndStoreKey(dir, keyLength) {
|
|
859
|
+
const key = crypto2.randomBytes(keyLength);
|
|
860
|
+
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
861
|
+
const filePath = path9.join(dir, hash);
|
|
862
|
+
fs9.writeFileSync(filePath, key);
|
|
863
|
+
fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
864
|
+
return key;
|
|
865
|
+
}
|
|
866
|
+
function createAndStoreEnKey(rootKey, ceDir) {
|
|
867
|
+
const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
|
|
868
|
+
const encrypted = encrypt(rootKey, workKey);
|
|
869
|
+
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
870
|
+
const filePath = path9.join(ceDir, hash);
|
|
871
|
+
fs9.writeFileSync(filePath, encrypted);
|
|
872
|
+
fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
873
|
+
}
|
|
874
|
+
function createMaterial(materialPath) {
|
|
875
|
+
fs9.mkdirSync(materialPath, { recursive: true });
|
|
876
|
+
fs9.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
877
|
+
const fdDir = path9.join(materialPath, "fd");
|
|
878
|
+
const acDir = path9.join(materialPath, "ac");
|
|
879
|
+
const ceDir = path9.join(materialPath, "ce");
|
|
880
|
+
fs9.mkdirSync(fdDir, { recursive: true });
|
|
881
|
+
fs9.mkdirSync(acDir, { recursive: true });
|
|
882
|
+
fs9.mkdirSync(ceDir, { recursive: true });
|
|
883
|
+
const fdSubDirs = ["0", "1", "2"];
|
|
884
|
+
const fdComponents = [];
|
|
885
|
+
for (const sub of fdSubDirs) {
|
|
886
|
+
const subDir = path9.join(fdDir, sub);
|
|
887
|
+
fs9.mkdirSync(subDir, { recursive: true });
|
|
888
|
+
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
889
|
+
fdComponents.push(comp);
|
|
890
|
+
}
|
|
891
|
+
const salt = createAndStoreKey(acDir, DAEMON_SALT_KEY_LENGTH);
|
|
892
|
+
const rootKey = getRootKey(fdComponents, salt);
|
|
893
|
+
createAndStoreEnKey(rootKey, ceDir);
|
|
894
|
+
}
|
|
895
|
+
function readSingleFile(dir) {
|
|
896
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
|
|
897
|
+
throw new Error(`Missing signing material directory: ${dir}`);
|
|
898
|
+
}
|
|
899
|
+
const files = fs9.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
900
|
+
if (files.length !== 1) {
|
|
901
|
+
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
902
|
+
}
|
|
903
|
+
return fs9.readFileSync(path9.join(dir, files[0]));
|
|
904
|
+
}
|
|
905
|
+
function getKey(materialPath) {
|
|
906
|
+
if (!fs9.existsSync(materialPath) || !fs9.statSync(materialPath).isDirectory()) {
|
|
907
|
+
throw new Error("Material directory does not exist.");
|
|
908
|
+
}
|
|
909
|
+
const fdDir = path9.join(materialPath, "fd");
|
|
910
|
+
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path9.join(fdDir, sub)));
|
|
911
|
+
const salt = readSingleFile(path9.join(materialPath, "ac"));
|
|
912
|
+
const rootKey = getRootKey(fdComponents, salt);
|
|
913
|
+
const workMaterial = readSingleFile(path9.join(materialPath, "ce"));
|
|
914
|
+
return decrypt(rootKey, workMaterial);
|
|
915
|
+
}
|
|
916
|
+
function encryptPwd(password, materialPath) {
|
|
917
|
+
const key = getKey(materialPath);
|
|
918
|
+
const pwdBuffer = Buffer.from(password, "utf-8");
|
|
919
|
+
return encrypt(key, pwdBuffer).toString("hex");
|
|
920
|
+
}
|
|
921
|
+
function decryptPwd(encryptedHex, materialPath) {
|
|
922
|
+
const key = getKey(materialPath);
|
|
923
|
+
const encryptedBuffer = Buffer.from(encryptedHex, "hex");
|
|
924
|
+
return decrypt(key, encryptedBuffer).toString("utf-8");
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/sign/generateSigningConfigs.ts
|
|
928
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
929
|
+
var path10 = __toESM(require("path"), 1);
|
|
930
|
+
var import_node_child_process2 = require("child_process");
|
|
931
|
+
var import_json52 = __toESM(require("json5"), 1);
|
|
932
|
+
var APL_VALUES = ["normal", "system_basic", "system_core"];
|
|
933
|
+
var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
|
|
934
|
+
function copyFilesToProject(projectDir, paths, logger) {
|
|
935
|
+
logger.info("[sign] Copying signing material into project...");
|
|
936
|
+
const signaturesDir = path10.join(projectDir, "signatures");
|
|
937
|
+
fs10.mkdirSync(signaturesDir, { recursive: true });
|
|
938
|
+
fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
|
|
939
|
+
fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
940
|
+
fs10.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
941
|
+
}
|
|
942
|
+
function resolveAppFeature(apl, override) {
|
|
943
|
+
if (override) return override;
|
|
944
|
+
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
945
|
+
}
|
|
946
|
+
function modifyProfileTemplate(projectDir, apl, appFeature, logger) {
|
|
947
|
+
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature})...`);
|
|
948
|
+
const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
|
|
949
|
+
const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
950
|
+
const profileCertFilePath = path10.join(projectDir, "signatures/OpenHarmonyProfileRelease.pem");
|
|
951
|
+
if (!fs10.existsSync(appJsonPath)) {
|
|
952
|
+
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
953
|
+
}
|
|
954
|
+
let appJson;
|
|
955
|
+
try {
|
|
956
|
+
appJson = import_json52.default.parse(fs10.readFileSync(appJsonPath, "utf-8"));
|
|
957
|
+
} catch (e) {
|
|
958
|
+
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
959
|
+
}
|
|
960
|
+
let profileTemplate;
|
|
961
|
+
try {
|
|
962
|
+
profileTemplate = JSON.parse(fs10.readFileSync(profileTemplatePath, "utf-8"));
|
|
963
|
+
} catch (e) {
|
|
964
|
+
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
965
|
+
}
|
|
966
|
+
if (!appJson.app?.bundleName) {
|
|
967
|
+
throw new OniroError("app.json5 does not contain app.bundleName.");
|
|
968
|
+
}
|
|
969
|
+
if (!profileTemplate["bundle-info"]) {
|
|
970
|
+
throw new OniroError("UnsgnedReleasedProfileTemplate.json is missing the bundle-info section.");
|
|
971
|
+
}
|
|
972
|
+
profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
|
|
973
|
+
profileTemplate["bundle-info"]["apl"] = apl;
|
|
974
|
+
profileTemplate["bundle-info"]["app-feature"] = appFeature;
|
|
975
|
+
const certContent = fs10.readFileSync(profileCertFilePath, "utf-8");
|
|
976
|
+
const certs = certContent.split("-----END CERTIFICATE-----");
|
|
977
|
+
if (certs.length < 3) {
|
|
978
|
+
throw new OniroError(`${profileCertFilePath} does not contain enough certificates.`);
|
|
979
|
+
}
|
|
980
|
+
const thirdCert = certs[2].trim() + "\n-----END CERTIFICATE-----\n";
|
|
981
|
+
profileTemplate["bundle-info"]["distribution-certificate"] = thirdCert;
|
|
982
|
+
fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
983
|
+
}
|
|
984
|
+
function generateP7bFile(projectDir, paths, logger) {
|
|
985
|
+
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
986
|
+
const signaturesDir = path10.join(projectDir, "signatures");
|
|
987
|
+
const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
988
|
+
const outputProfilePath = path10.join(signaturesDir, "app1-profile.p7b");
|
|
989
|
+
const args = [
|
|
990
|
+
"-jar",
|
|
991
|
+
paths.signTool,
|
|
992
|
+
"sign-profile",
|
|
993
|
+
"-keyAlias",
|
|
994
|
+
"openharmony application profile release",
|
|
995
|
+
"-signAlg",
|
|
996
|
+
"SHA256withECDSA",
|
|
997
|
+
"-mode",
|
|
998
|
+
"localSign",
|
|
999
|
+
"-profileCertFile",
|
|
1000
|
+
paths.profileCert,
|
|
1001
|
+
"-inFile",
|
|
1002
|
+
profileTemplatePath,
|
|
1003
|
+
"-keystoreFile",
|
|
1004
|
+
paths.keystore,
|
|
1005
|
+
"-outFile",
|
|
1006
|
+
outputProfilePath,
|
|
1007
|
+
"-keyPwd",
|
|
1008
|
+
"123456",
|
|
1009
|
+
"-keystorePwd",
|
|
1010
|
+
"123456"
|
|
1011
|
+
];
|
|
1012
|
+
try {
|
|
1013
|
+
(0, import_node_child_process2.execFileSync)("java", args, { stdio: "inherit" });
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
throw new OniroError(
|
|
1016
|
+
`hap-sign-tool failed. Ensure a JDK (with keytool/java) is on PATH. Underlying error: ${e.message}`,
|
|
1017
|
+
e
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function updateBuildProfile(projectDir, logger) {
|
|
1022
|
+
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1023
|
+
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1024
|
+
const buildProfilePath = path10.join(projectDir, "build-profile.json5");
|
|
1025
|
+
const encryptedStorePassword = encryptPwd("123456", materialDir);
|
|
1026
|
+
const encryptedKeyPassword = encryptPwd("123456", materialDir);
|
|
1027
|
+
let buildProfile = { app: {} };
|
|
1028
|
+
if (fs10.existsSync(buildProfilePath)) {
|
|
1029
|
+
try {
|
|
1030
|
+
buildProfile = import_json52.default.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
buildProfile.app = buildProfile.app ?? {};
|
|
1036
|
+
buildProfile.app.signingConfigs = [
|
|
1037
|
+
{
|
|
1038
|
+
name: "default",
|
|
1039
|
+
material: {
|
|
1040
|
+
certpath: "./signatures/OpenHarmonyProfileRelease.pem",
|
|
1041
|
+
storePassword: encryptedStorePassword,
|
|
1042
|
+
keyAlias: "openharmony application profile release",
|
|
1043
|
+
keyPassword: encryptedKeyPassword,
|
|
1044
|
+
profile: "./signatures/app1-profile.p7b",
|
|
1045
|
+
signAlg: "SHA256withECDSA",
|
|
1046
|
+
storeFile: "./signatures/OpenHarmony.p12"
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
];
|
|
1050
|
+
fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1051
|
+
}
|
|
1052
|
+
function prepareMaterialDirectory(projectDir, logger) {
|
|
1053
|
+
logger.info("[sign] Generating fresh signing material...");
|
|
1054
|
+
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1055
|
+
if (fs10.existsSync(materialDir)) {
|
|
1056
|
+
fs10.rmSync(materialDir, { recursive: true, force: true });
|
|
1057
|
+
}
|
|
1058
|
+
createMaterial(materialDir);
|
|
1059
|
+
}
|
|
1060
|
+
function generateSigningConfigs(options) {
|
|
1061
|
+
const { projectDir, sdkHome } = options;
|
|
1062
|
+
const logger = options.logger ?? noopLogger;
|
|
1063
|
+
const apl = options.apl ?? "normal";
|
|
1064
|
+
if (!APL_VALUES.includes(apl)) {
|
|
1065
|
+
throw new OniroError(`Invalid apl '${apl}'. Expected one of: ${APL_VALUES.join(", ")}.`);
|
|
1066
|
+
}
|
|
1067
|
+
const appFeature = resolveAppFeature(apl, options.appFeature);
|
|
1068
|
+
if (!APP_FEATURE_VALUES.includes(appFeature)) {
|
|
1069
|
+
throw new OniroError(
|
|
1070
|
+
`Invalid appFeature '${appFeature}'. Expected one of: ${APP_FEATURE_VALUES.join(", ")}.`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const sdkVersion = detectProjectSdkVersion(projectDir, logger);
|
|
1074
|
+
if (!sdkVersion) {
|
|
1075
|
+
throw new OniroError(
|
|
1076
|
+
"Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
const sdkPath = path10.join(sdkHome, String(sdkVersion));
|
|
1080
|
+
if (!fs10.existsSync(sdkPath)) {
|
|
1081
|
+
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1082
|
+
}
|
|
1083
|
+
const paths = {
|
|
1084
|
+
signTool: path10.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1085
|
+
keystore: path10.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1086
|
+
profileCert: path10.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1087
|
+
unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1088
|
+
};
|
|
1089
|
+
logger.info("[sign] Starting signing configuration generation...");
|
|
1090
|
+
copyFilesToProject(projectDir, paths, logger);
|
|
1091
|
+
modifyProfileTemplate(projectDir, apl, appFeature, logger);
|
|
1092
|
+
generateP7bFile(projectDir, paths, logger);
|
|
1093
|
+
prepareMaterialDirectory(projectDir, logger);
|
|
1094
|
+
updateBuildProfile(projectDir, logger);
|
|
1095
|
+
logger.info("[sign] Signing configuration generated successfully.");
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/emulator/install.ts
|
|
1099
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
1100
|
+
var os7 = __toESM(require("os"), 1);
|
|
1101
|
+
var path11 = __toESM(require("path"), 1);
|
|
1102
|
+
async function installEmulator(opts) {
|
|
1103
|
+
const { config, progress, abortSignal } = opts;
|
|
1104
|
+
const logger = opts.logger ?? noopLogger;
|
|
1105
|
+
const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
|
|
1106
|
+
const emulatorDir = getEmulatorDir(config);
|
|
1107
|
+
const tmpDir = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
|
|
1108
|
+
const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
|
|
1109
|
+
try {
|
|
1110
|
+
fs11.mkdirSync(emulatorDir, { recursive: true });
|
|
1111
|
+
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1112
|
+
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
|
|
1113
|
+
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1114
|
+
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1115
|
+
const runSh = path11.join(emulatorDir, "images", "run.sh");
|
|
1116
|
+
if (fs11.existsSync(runSh)) {
|
|
1117
|
+
fs11.chmodSync(runSh, 493);
|
|
1118
|
+
}
|
|
1119
|
+
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1120
|
+
} finally {
|
|
1121
|
+
fs11.rmSync(tmpDir, { recursive: true, force: true });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
function isEmulatorInstalled(config) {
|
|
1125
|
+
return fs11.existsSync(path11.join(getEmulatorDir(config), "images", "run.sh"));
|
|
1126
|
+
}
|
|
1127
|
+
function removeEmulator(config) {
|
|
1128
|
+
const emulatorDir = getEmulatorDir(config);
|
|
1129
|
+
if (fs11.existsSync(emulatorDir)) {
|
|
1130
|
+
fs11.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/emulator/lifecycle.ts
|
|
1135
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
1136
|
+
var os8 = __toESM(require("os"), 1);
|
|
1137
|
+
var path12 = __toESM(require("path"), 1);
|
|
1138
|
+
var import_node_child_process3 = require("child_process");
|
|
1139
|
+
var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
|
|
1140
|
+
function execPromise(cmd, cwd, logger) {
|
|
1141
|
+
return new Promise((resolve2, reject) => {
|
|
1142
|
+
(0, import_node_child_process3.exec)(cmd, { cwd }, (error, stdout, stderr) => {
|
|
1143
|
+
if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
|
|
1144
|
+
if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
|
|
1145
|
+
if (error) {
|
|
1146
|
+
logger.error(`[emulator] ${error.message}`);
|
|
1147
|
+
reject(error);
|
|
1148
|
+
} else {
|
|
1149
|
+
resolve2();
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1155
|
+
const hdc = getHdcPath(config);
|
|
1156
|
+
try {
|
|
1157
|
+
await execPromise(`"${hdc}" start -r`, void 0, logger);
|
|
1158
|
+
await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
|
|
1159
|
+
return await new Promise((resolve2) => {
|
|
1160
|
+
(0, import_node_child_process3.exec)(`"${hdc}" list targets`, (_error, stdout) => {
|
|
1161
|
+
resolve2(stdout?.includes(address) ?? false);
|
|
1162
|
+
});
|
|
1163
|
+
});
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
logger.warn(`hdc connection attempt failed: ${err.message}`);
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function resolveLauncher(imagesPath, headless, connect) {
|
|
1170
|
+
const runSh = path12.join(imagesPath, "run.sh");
|
|
1171
|
+
const runBat = path12.join(imagesPath, "run.bat");
|
|
1172
|
+
const extra = [];
|
|
1173
|
+
if (headless) extra.push("--headless");
|
|
1174
|
+
if (connect) extra.push("--connect", connect);
|
|
1175
|
+
if (os8.platform() === "win32") {
|
|
1176
|
+
if (fs12.existsSync(runBat)) {
|
|
1177
|
+
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1178
|
+
}
|
|
1179
|
+
if (fs12.existsSync(runSh)) {
|
|
1180
|
+
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1181
|
+
}
|
|
1182
|
+
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1183
|
+
}
|
|
1184
|
+
if (fs12.existsSync(runSh)) {
|
|
1185
|
+
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1186
|
+
}
|
|
1187
|
+
if (fs12.existsSync(runBat)) {
|
|
1188
|
+
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1189
|
+
}
|
|
1190
|
+
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
1191
|
+
}
|
|
1192
|
+
async function startEmulator(opts) {
|
|
1193
|
+
const { config } = opts;
|
|
1194
|
+
const logger = opts.logger ?? noopLogger;
|
|
1195
|
+
if (os8.platform() !== "win32" && fs12.existsSync(PID_FILE)) {
|
|
1196
|
+
try {
|
|
1197
|
+
const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
|
|
1198
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
1199
|
+
try {
|
|
1200
|
+
process.kill(pid, 0);
|
|
1201
|
+
logger.info(`Emulator already running (PID ${pid}); not starting another.`);
|
|
1202
|
+
if (opts.waitForHdcSeconds && opts.waitForHdcSeconds > 0) {
|
|
1203
|
+
await waitForHdc(config, opts.waitForHdcSeconds, opts.hdcPollIntervalMs ?? 5e3, logger, opts.abortSignal);
|
|
1204
|
+
}
|
|
1205
|
+
return;
|
|
1206
|
+
} catch {
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
const imagesPath = path12.join(getEmulatorDir(config), "images");
|
|
1214
|
+
if (!fs12.existsSync(imagesPath)) {
|
|
1215
|
+
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1216
|
+
}
|
|
1217
|
+
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1218
|
+
const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1219
|
+
const logFd = fs12.openSync(logPath, "a");
|
|
1220
|
+
let child;
|
|
1221
|
+
try {
|
|
1222
|
+
child = (0, import_node_child_process3.spawn)(launcher.command, [...launcher.args], {
|
|
1223
|
+
cwd: launcher.cwd,
|
|
1224
|
+
detached: true,
|
|
1225
|
+
stdio: ["ignore", logFd, logFd],
|
|
1226
|
+
windowsHide: true
|
|
1227
|
+
});
|
|
1228
|
+
} finally {
|
|
1229
|
+
fs12.closeSync(logFd);
|
|
1230
|
+
}
|
|
1231
|
+
if (!child.pid) {
|
|
1232
|
+
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1233
|
+
}
|
|
1234
|
+
fs12.writeFileSync(PID_FILE, String(child.pid));
|
|
1235
|
+
child.unref();
|
|
1236
|
+
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1237
|
+
const earlyExit = new Promise((resolve2) => {
|
|
1238
|
+
child.once("exit", (code) => resolve2(code));
|
|
1239
|
+
child.once("error", () => resolve2(-1));
|
|
1240
|
+
});
|
|
1241
|
+
const liveness = new Promise((resolve2) => setTimeout(() => resolve2("alive"), 2e3));
|
|
1242
|
+
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1243
|
+
if (outcome !== "alive") {
|
|
1244
|
+
const tail = readTail(logPath, 4e3);
|
|
1245
|
+
throw new OniroError(
|
|
1246
|
+
`Emulator launcher exited with code ${outcome} before reaching steady state. Tail of ${logPath}:
|
|
1247
|
+
${tail}`
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
if (opts.waitForHdcSeconds && opts.waitForHdcSeconds > 0) {
|
|
1251
|
+
await waitForHdc(config, opts.waitForHdcSeconds, opts.hdcPollIntervalMs ?? 5e3, logger, opts.abortSignal, logPath);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
async function waitForHdc(config, timeoutSeconds, pollIntervalMs, logger, abortSignal, logPathForDiagnostics) {
|
|
1255
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
1256
|
+
let attempt = 0;
|
|
1257
|
+
while (Date.now() < deadline) {
|
|
1258
|
+
if (abortSignal?.aborted) throw new CancelledError("hdc wait cancelled.");
|
|
1259
|
+
attempt++;
|
|
1260
|
+
if (await attemptHdcConnection(config, void 0, noopLogger)) {
|
|
1261
|
+
logger.info(`hdc connected on attempt ${attempt}.`);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
if (attempt % 6 === 0) {
|
|
1265
|
+
logger.info(`hdc not yet ready (attempt ${attempt}, ${Math.max(0, deadline - Date.now()) / 1e3 | 0}s remaining).`);
|
|
1266
|
+
if (logPathForDiagnostics) {
|
|
1267
|
+
const tail2 = readTail(logPathForDiagnostics, 1200);
|
|
1268
|
+
if (tail2.trim()) logger.debug(`emulator log tail:
|
|
1269
|
+
${tail2}`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1273
|
+
}
|
|
1274
|
+
const tail = logPathForDiagnostics ? readTail(logPathForDiagnostics, 4e3) : "";
|
|
1275
|
+
throw new OniroError(`hdc did not connect within ${timeoutSeconds}s.${tail ? `
|
|
1276
|
+
Tail of emulator log:
|
|
1277
|
+
${tail}` : ""}`);
|
|
1278
|
+
}
|
|
1279
|
+
function readTail(filePath, bytes) {
|
|
1280
|
+
try {
|
|
1281
|
+
const stat = fs12.statSync(filePath);
|
|
1282
|
+
const start = Math.max(0, stat.size - bytes);
|
|
1283
|
+
const fd = fs12.openSync(filePath, "r");
|
|
1284
|
+
try {
|
|
1285
|
+
const buf = Buffer.alloc(stat.size - start);
|
|
1286
|
+
fs12.readSync(fd, buf, 0, buf.length, start);
|
|
1287
|
+
return buf.toString("utf8");
|
|
1288
|
+
} finally {
|
|
1289
|
+
fs12.closeSync(fd);
|
|
1290
|
+
}
|
|
1291
|
+
} catch {
|
|
1292
|
+
return "";
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
async function stopEmulator(logger = noopLogger) {
|
|
1296
|
+
if (fs12.existsSync(PID_FILE)) {
|
|
1297
|
+
try {
|
|
1298
|
+
const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
|
|
1299
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
1300
|
+
try {
|
|
1301
|
+
process.kill(pid, "SIGTERM");
|
|
1302
|
+
logger.info(`Sent SIGTERM to emulator PID ${pid}.`);
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
logger.warn(`Could not signal PID ${pid}: ${err.message}`);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
} catch (err) {
|
|
1308
|
+
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
|
|
1312
|
+
try {
|
|
1313
|
+
await execPromise(killCmd, void 0, logger);
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
fs12.rm(PID_FILE, { force: true }, (err) => {
|
|
1317
|
+
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
// src/hdc/project.ts
|
|
1322
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
1323
|
+
var path13 = __toESM(require("path"), 1);
|
|
1324
|
+
var import_json53 = __toESM(require("json5"), 1);
|
|
1325
|
+
function readJson5(filePath) {
|
|
1326
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
1327
|
+
return import_json53.default.parse(content);
|
|
1328
|
+
}
|
|
1329
|
+
function getBundleName(projectDir) {
|
|
1330
|
+
const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
|
|
1331
|
+
if (!fs13.existsSync(appJsonPath)) {
|
|
1332
|
+
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
1333
|
+
}
|
|
1334
|
+
const appJson = readJson5(appJsonPath);
|
|
1335
|
+
if (!appJson.app?.bundleName) {
|
|
1336
|
+
throw new OniroError("bundleName not found in app.json5");
|
|
1337
|
+
}
|
|
1338
|
+
return appJson.app.bundleName;
|
|
1339
|
+
}
|
|
1340
|
+
function getMainAbility(projectDir, moduleName = "entry") {
|
|
1341
|
+
const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
1342
|
+
if (!fs13.existsSync(moduleJsonPath)) {
|
|
1343
|
+
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
1344
|
+
}
|
|
1345
|
+
const moduleJson = readJson5(moduleJsonPath);
|
|
1346
|
+
if (!moduleJson.module?.mainElement) {
|
|
1347
|
+
throw new OniroError("mainElement not found in module.json5");
|
|
1348
|
+
}
|
|
1349
|
+
return moduleJson.module.mainElement;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/hdc/app.ts
|
|
1353
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
1354
|
+
var path14 = __toESM(require("path"), 1);
|
|
1355
|
+
var import_node_child_process4 = require("child_process");
|
|
1356
|
+
function execPromise2(cmd, logger) {
|
|
1357
|
+
return new Promise((resolve2, reject) => {
|
|
1358
|
+
(0, import_node_child_process4.exec)(cmd, (error, stdout, stderr) => {
|
|
1359
|
+
if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
|
|
1360
|
+
if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
|
|
1361
|
+
if (error) {
|
|
1362
|
+
logger.error(`[hdc] ${error.message}`);
|
|
1363
|
+
reject(error);
|
|
1364
|
+
} else {
|
|
1365
|
+
resolve2();
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
async function installApp(opts) {
|
|
1371
|
+
const logger = opts.logger ?? noopLogger;
|
|
1372
|
+
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
1373
|
+
const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
|
|
1374
|
+
if (!fs14.existsSync(hapPath)) {
|
|
1375
|
+
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
1376
|
+
}
|
|
1377
|
+
const hdc = getHdcPath(opts.config);
|
|
1378
|
+
await execPromise2(`"${hdc}" install "${hapPath}"`, logger);
|
|
1379
|
+
}
|
|
1380
|
+
async function launchApp(opts) {
|
|
1381
|
+
const logger = opts.logger ?? noopLogger;
|
|
1382
|
+
const bundleName = getBundleName(opts.projectDir);
|
|
1383
|
+
const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
|
|
1384
|
+
const hdc = getHdcPath(opts.config);
|
|
1385
|
+
await execPromise2(`"${hdc}" shell aa start -a ${mainAbility} -b ${bundleName}`, logger);
|
|
1386
|
+
}
|
|
1387
|
+
function listRunningProcesses(config, options = {}) {
|
|
1388
|
+
const logger = options.logger ?? noopLogger;
|
|
1389
|
+
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
1390
|
+
const hdc = getHdcPath(config);
|
|
1391
|
+
return new Promise((resolve2, reject) => {
|
|
1392
|
+
const proc = (0, import_node_child_process4.spawn)(hdc, ["track-jpid"]);
|
|
1393
|
+
const processes = [];
|
|
1394
|
+
let resolved = false;
|
|
1395
|
+
proc.stdout.on("data", (data) => {
|
|
1396
|
+
for (const line of data.toString().split("\n").filter(Boolean)) {
|
|
1397
|
+
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
1398
|
+
if (!match) continue;
|
|
1399
|
+
const pid = match[1];
|
|
1400
|
+
const name = match[2];
|
|
1401
|
+
processes.push({ pid, name });
|
|
1402
|
+
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
1403
|
+
resolved = true;
|
|
1404
|
+
proc.kill();
|
|
1405
|
+
resolve2(pid);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
proc.stderr.on("data", (data) => {
|
|
1411
|
+
logger.warn(`[hdc track-jpid] ${data.toString()}`);
|
|
1412
|
+
});
|
|
1413
|
+
proc.on("error", (err) => {
|
|
1414
|
+
if (!resolved) {
|
|
1415
|
+
resolved = true;
|
|
1416
|
+
reject(err);
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
const timeout = setTimeout(() => {
|
|
1420
|
+
if (resolved) return;
|
|
1421
|
+
resolved = true;
|
|
1422
|
+
proc.kill();
|
|
1423
|
+
if (options.targetProcessName) {
|
|
1424
|
+
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1425
|
+
} else {
|
|
1426
|
+
resolve2(processes);
|
|
1427
|
+
}
|
|
1428
|
+
}, timeoutMs);
|
|
1429
|
+
proc.on("close", () => {
|
|
1430
|
+
clearTimeout(timeout);
|
|
1431
|
+
if (resolved) return;
|
|
1432
|
+
resolved = true;
|
|
1433
|
+
if (options.targetProcessName) {
|
|
1434
|
+
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
1435
|
+
} else {
|
|
1436
|
+
resolve2(processes);
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
async function findAppProcessId(config, projectDir, logger) {
|
|
1442
|
+
const bundleName = getBundleName(projectDir);
|
|
1443
|
+
const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
|
|
1444
|
+
return result;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// src/hdc/hilog.ts
|
|
1448
|
+
var import_node_child_process5 = require("child_process");
|
|
1449
|
+
function setHilogLevel(opts) {
|
|
1450
|
+
const logger = opts.logger ?? noopLogger;
|
|
1451
|
+
const hdc = getHdcPath(opts.config);
|
|
1452
|
+
return new Promise((resolve2, reject) => {
|
|
1453
|
+
const child = (0, import_node_child_process5.spawn)(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
1454
|
+
let stderr = "";
|
|
1455
|
+
child.stderr.on("data", (chunk) => {
|
|
1456
|
+
stderr += chunk.toString();
|
|
1457
|
+
});
|
|
1458
|
+
child.once("error", (err) => {
|
|
1459
|
+
logger.error(`[hilog] failed to spawn hdc: ${err.message}`);
|
|
1460
|
+
reject(err);
|
|
1461
|
+
});
|
|
1462
|
+
child.once("close", (code) => {
|
|
1463
|
+
if (code === 0) {
|
|
1464
|
+
resolve2();
|
|
1465
|
+
} else {
|
|
1466
|
+
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
1467
|
+
logger.error(`[hilog] ${msg}`);
|
|
1468
|
+
reject(new Error(msg));
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function streamHilog(opts) {
|
|
1474
|
+
const hdc = getHdcPath(opts.config);
|
|
1475
|
+
const args = ["shell", "hilog"];
|
|
1476
|
+
if (opts.processId && opts.processId.trim() !== "") {
|
|
1477
|
+
args.push("-P", opts.processId.trim());
|
|
1478
|
+
}
|
|
1479
|
+
return (0, import_node_child_process5.spawn)(hdc, args);
|
|
1480
|
+
}
|
|
1481
|
+
var HILOG_LINE_RE = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([DIWEF])\s+(\S+):\s+(.*)$/;
|
|
1482
|
+
function parseHilogLine(line) {
|
|
1483
|
+
const match = HILOG_LINE_RE.exec(line);
|
|
1484
|
+
if (!match) return null;
|
|
1485
|
+
return {
|
|
1486
|
+
time: match[1],
|
|
1487
|
+
pid: match[2],
|
|
1488
|
+
tid: match[3],
|
|
1489
|
+
level: match[4],
|
|
1490
|
+
tag: match[5].trim(),
|
|
1491
|
+
message: match[6]
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/build/runHvigorw.ts
|
|
1496
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
1497
|
+
var path15 = __toESM(require("path"), 1);
|
|
1498
|
+
var import_node_child_process6 = require("child_process");
|
|
1499
|
+
function runHvigorw(opts) {
|
|
1500
|
+
const { config, projectDir } = opts;
|
|
1501
|
+
const logger = opts.logger ?? noopLogger;
|
|
1502
|
+
if (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
|
|
1503
|
+
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
1504
|
+
}
|
|
1505
|
+
if (!fs15.existsSync(getCmdToolsPath(config))) {
|
|
1506
|
+
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
1507
|
+
}
|
|
1508
|
+
const hvigorw = getHvigorwPath(config, projectDir);
|
|
1509
|
+
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
1510
|
+
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
1511
|
+
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
1512
|
+
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
1513
|
+
args.push("--stacktrace", "--no-parallel", "--no-daemon");
|
|
1514
|
+
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
1515
|
+
if (process.platform !== "win32") {
|
|
1516
|
+
try {
|
|
1517
|
+
fs15.chmodSync(hvigorw, 493);
|
|
1518
|
+
} catch {
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
const env = {
|
|
1522
|
+
...process.env,
|
|
1523
|
+
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
1524
|
+
};
|
|
1525
|
+
return new Promise((resolve2, reject) => {
|
|
1526
|
+
logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
|
|
1527
|
+
const child = (0, import_node_child_process6.spawn)(hvigorw, args, { cwd: projectDir, env, shell: false });
|
|
1528
|
+
child.stdout.on("data", (chunk) => {
|
|
1529
|
+
const text = chunk.toString();
|
|
1530
|
+
opts.onOutput?.(text, "stdout");
|
|
1531
|
+
logger.info(text.trimEnd());
|
|
1532
|
+
});
|
|
1533
|
+
child.stderr.on("data", (chunk) => {
|
|
1534
|
+
const text = chunk.toString();
|
|
1535
|
+
opts.onOutput?.(text, "stderr");
|
|
1536
|
+
logger.warn(text.trimEnd());
|
|
1537
|
+
});
|
|
1538
|
+
child.on("error", (err) => reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err)));
|
|
1539
|
+
child.on("close", (code) => {
|
|
1540
|
+
const exitCode = code ?? 1;
|
|
1541
|
+
if (exitCode === 0) resolve2({ exitCode });
|
|
1542
|
+
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
1543
|
+
});
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/project/validators.ts
|
|
1548
|
+
function isValidProjectName(name) {
|
|
1549
|
+
if (!name) return false;
|
|
1550
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
1551
|
+
return /^[A-Za-z0-9._-]+$/.test(name);
|
|
1552
|
+
}
|
|
1553
|
+
function isValidBundleName(bundleName) {
|
|
1554
|
+
return /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(bundleName);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/project/templates.ts
|
|
1558
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
1559
|
+
var path16 = __toESM(require("path"), 1);
|
|
1560
|
+
function toHumanTemplateName(folderName) {
|
|
1561
|
+
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
1562
|
+
}
|
|
1563
|
+
function listTemplates(templateRoot) {
|
|
1564
|
+
if (!fs16.existsSync(templateRoot)) return [];
|
|
1565
|
+
const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
|
|
1566
|
+
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
1567
|
+
const dir = path16.join(templateRoot, e.name);
|
|
1568
|
+
const metaPath = path16.join(dir, "template.json");
|
|
1569
|
+
let meta = {};
|
|
1570
|
+
if (fs16.existsSync(metaPath)) {
|
|
1571
|
+
try {
|
|
1572
|
+
meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
id: e.name,
|
|
1578
|
+
label: meta.label ?? toHumanTemplateName(e.name),
|
|
1579
|
+
description: meta.description ?? "",
|
|
1580
|
+
defaultModuleName: (meta.defaultModuleName ?? "entry").trim() || "entry"
|
|
1581
|
+
};
|
|
1582
|
+
}).sort((a, b) => a.label.localeCompare(b.label));
|
|
1583
|
+
}
|
|
1584
|
+
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
1585
|
+
const required = [
|
|
1586
|
+
"build-profile.json5",
|
|
1587
|
+
path16.join("AppScope", "app.json5"),
|
|
1588
|
+
path16.join(defaultModuleName, "src", "main", "module.json5"),
|
|
1589
|
+
path16.join(defaultModuleName, "oh-package.json5"),
|
|
1590
|
+
"hvigorfile.ts"
|
|
1591
|
+
];
|
|
1592
|
+
return required.filter((rel) => !fs16.existsSync(path16.join(templateDir, rel)));
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// src/project/createScaffold.ts
|
|
1596
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
1597
|
+
var path17 = __toESM(require("path"), 1);
|
|
1598
|
+
|
|
1599
|
+
// src/project/jsonHelpers.ts
|
|
1600
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
1601
|
+
var import_json54 = __toESM(require("json5"), 1);
|
|
1602
|
+
function readJson5File(filePath) {
|
|
1603
|
+
return import_json54.default.parse(fs17.readFileSync(filePath, "utf8"));
|
|
1604
|
+
}
|
|
1605
|
+
function writeJson5File(filePath, value) {
|
|
1606
|
+
fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1607
|
+
}
|
|
1608
|
+
function readJsonFile(filePath) {
|
|
1609
|
+
return JSON.parse(fs17.readFileSync(filePath, "utf8"));
|
|
1610
|
+
}
|
|
1611
|
+
function writeJsonFile(filePath, value) {
|
|
1612
|
+
fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/project/createScaffold.ts
|
|
1616
|
+
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
1617
|
+
async function pathExists(p) {
|
|
1618
|
+
try {
|
|
1619
|
+
await fs18.promises.access(p);
|
|
1620
|
+
return true;
|
|
1621
|
+
} catch {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function copyDirRecursive(srcDir, destDir) {
|
|
1626
|
+
await fs18.promises.mkdir(destDir, { recursive: true });
|
|
1627
|
+
const entries = await fs18.promises.readdir(srcDir, { withFileTypes: true });
|
|
1628
|
+
for (const entry of entries) {
|
|
1629
|
+
const src = path17.join(srcDir, entry.name);
|
|
1630
|
+
const dest = path17.join(destDir, entry.name);
|
|
1631
|
+
if (entry.isDirectory()) {
|
|
1632
|
+
await copyDirRecursive(src, dest);
|
|
1633
|
+
} else if (entry.isSymbolicLink()) {
|
|
1634
|
+
continue;
|
|
1635
|
+
} else if (entry.isFile()) {
|
|
1636
|
+
if (entry.name === "template.json") continue;
|
|
1637
|
+
await fs18.promises.copyFile(src, dest);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
async function normalizeJson5ToJson(projectDir, logger) {
|
|
1642
|
+
const stack = [projectDir];
|
|
1643
|
+
while (stack.length > 0) {
|
|
1644
|
+
const currentDir = stack.pop();
|
|
1645
|
+
const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
|
|
1646
|
+
for (const entry of entries) {
|
|
1647
|
+
const fullPath = path17.join(currentDir, entry.name);
|
|
1648
|
+
if (entry.isDirectory()) {
|
|
1649
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1650
|
+
stack.push(fullPath);
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
if (!entry.isFile() || !entry.name.endsWith(".json5")) continue;
|
|
1654
|
+
try {
|
|
1655
|
+
const parsed = readJson5File(fullPath);
|
|
1656
|
+
writeJson5File(fullPath, parsed);
|
|
1657
|
+
} catch (err) {
|
|
1658
|
+
logger.warn(`Failed to normalize ${fullPath}: ${String(err)}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
function toJavaPropertiesPath(p) {
|
|
1664
|
+
return process.platform === "win32" ? p.replace(/\\/g, "\\\\") : p;
|
|
1665
|
+
}
|
|
1666
|
+
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
1667
|
+
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
1668
|
+
`;
|
|
1669
|
+
fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
|
|
1670
|
+
}
|
|
1671
|
+
function renameIfExists(fromPath, toPath) {
|
|
1672
|
+
if (fs18.existsSync(fromPath) && fromPath !== toPath) {
|
|
1673
|
+
fs18.renameSync(fromPath, toPath);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
1677
|
+
const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
|
|
1678
|
+
if (fs18.existsSync(appJsonPath)) {
|
|
1679
|
+
const appJson = readJson5File(appJsonPath);
|
|
1680
|
+
appJson.app = appJson.app ?? {};
|
|
1681
|
+
appJson.app.bundleName = args.bundleName;
|
|
1682
|
+
writeJson5File(appJsonPath, appJson);
|
|
1683
|
+
}
|
|
1684
|
+
const stringsPath = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
1685
|
+
if (fs18.existsSync(stringsPath)) {
|
|
1686
|
+
const strings = readJsonFile(stringsPath);
|
|
1687
|
+
if (Array.isArray(strings.string)) {
|
|
1688
|
+
const appName = strings.string.find((s) => s.name === "app_name");
|
|
1689
|
+
if (appName) appName.value = args.projectName;
|
|
1690
|
+
}
|
|
1691
|
+
writeJsonFile(stringsPath, strings);
|
|
1692
|
+
}
|
|
1693
|
+
const buildProfilePath = path17.join(projectDir, "build-profile.json5");
|
|
1694
|
+
if (fs18.existsSync(buildProfilePath)) {
|
|
1695
|
+
const buildProfile = readJson5File(buildProfilePath);
|
|
1696
|
+
buildProfile.app = buildProfile.app ?? {};
|
|
1697
|
+
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
1698
|
+
buildProfile.app.products[0].compileSdkVersion = args.sdkApi;
|
|
1699
|
+
buildProfile.app.products[0].compatibleSdkVersion = args.sdkApi;
|
|
1700
|
+
}
|
|
1701
|
+
if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
|
|
1702
|
+
buildProfile.modules[0].name = args.moduleName;
|
|
1703
|
+
buildProfile.modules[0].srcPath = `./${args.moduleName}`;
|
|
1704
|
+
}
|
|
1705
|
+
writeJson5File(buildProfilePath, buildProfile);
|
|
1706
|
+
}
|
|
1707
|
+
const moduleJsonPath = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
1708
|
+
if (fs18.existsSync(moduleJsonPath)) {
|
|
1709
|
+
const moduleJson = readJson5File(moduleJsonPath);
|
|
1710
|
+
moduleJson.module = moduleJson.module ?? {};
|
|
1711
|
+
moduleJson.module.name = args.moduleName;
|
|
1712
|
+
writeJson5File(moduleJsonPath, moduleJson);
|
|
1713
|
+
}
|
|
1714
|
+
const moduleOhPackagePath = path17.join(projectDir, args.moduleName, "oh-package.json5");
|
|
1715
|
+
if (fs18.existsSync(moduleOhPackagePath)) {
|
|
1716
|
+
const pkg = readJson5File(moduleOhPackagePath);
|
|
1717
|
+
pkg.name = args.moduleName;
|
|
1718
|
+
writeJson5File(moduleOhPackagePath, pkg);
|
|
1719
|
+
}
|
|
1720
|
+
const vscodeDir = path17.join(projectDir, ".vscode");
|
|
1721
|
+
fs18.mkdirSync(vscodeDir, { recursive: true });
|
|
1722
|
+
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
1723
|
+
writeJsonFile(path17.join(vscodeDir, "settings.json"), {
|
|
1724
|
+
"oniro.hapPath": hapPath,
|
|
1725
|
+
"files.associations": { "*.json5": "jsonc" }
|
|
1726
|
+
});
|
|
1727
|
+
createOrUpdateLocalProperties(projectDir, sdkBaseDir);
|
|
1728
|
+
}
|
|
1729
|
+
async function createScaffold(opts) {
|
|
1730
|
+
const logger = opts.logger ?? noopLogger;
|
|
1731
|
+
if (!isValidProjectName(opts.projectName)) {
|
|
1732
|
+
throw new OniroError(`Invalid project name '${opts.projectName}'. Use letters/numbers/._- and no slashes.`);
|
|
1733
|
+
}
|
|
1734
|
+
if (!isValidBundleName(opts.bundleName)) {
|
|
1735
|
+
throw new OniroError(`Invalid bundle name '${opts.bundleName}'. Example: com.example.myapplication`);
|
|
1736
|
+
}
|
|
1737
|
+
if (!opts.location || !await pathExists(opts.location)) {
|
|
1738
|
+
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
1739
|
+
}
|
|
1740
|
+
const templateDir = path17.join(opts.templateRoot, opts.templateId);
|
|
1741
|
+
if (!await pathExists(templateDir)) {
|
|
1742
|
+
throw new OniroError(`Template not found: ${templateDir}`);
|
|
1743
|
+
}
|
|
1744
|
+
const templates = listTemplates(opts.templateRoot);
|
|
1745
|
+
const selected = templates.find((t) => t.id === opts.templateId);
|
|
1746
|
+
const defaultModuleName = selected?.defaultModuleName ?? "entry";
|
|
1747
|
+
const moduleName = (opts.moduleName ?? defaultModuleName).trim() || defaultModuleName;
|
|
1748
|
+
const missing = validateTemplateLayout(templateDir, defaultModuleName);
|
|
1749
|
+
if (missing.length > 0) {
|
|
1750
|
+
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
1751
|
+
- ${missing.join("\n- ")}`);
|
|
1752
|
+
}
|
|
1753
|
+
const projectDir = path17.join(opts.location, opts.projectName);
|
|
1754
|
+
if (await pathExists(projectDir)) {
|
|
1755
|
+
if (!opts.overwrite) {
|
|
1756
|
+
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
1757
|
+
}
|
|
1758
|
+
await fs18.promises.rm(projectDir, { recursive: true, force: true });
|
|
1759
|
+
}
|
|
1760
|
+
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
1761
|
+
await copyDirRecursive(templateDir, projectDir);
|
|
1762
|
+
if (moduleName !== defaultModuleName) {
|
|
1763
|
+
renameIfExists(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
|
|
1764
|
+
}
|
|
1765
|
+
updateTemplateConfigs(
|
|
1766
|
+
projectDir,
|
|
1767
|
+
{
|
|
1768
|
+
projectName: opts.projectName,
|
|
1769
|
+
bundleName: opts.bundleName,
|
|
1770
|
+
sdkApi: opts.sdkApi,
|
|
1771
|
+
moduleName
|
|
1772
|
+
},
|
|
1773
|
+
getOhosBaseSdkHome(opts.config)
|
|
1774
|
+
);
|
|
1775
|
+
await normalizeJson5ToJson(projectDir, logger);
|
|
1776
|
+
return { projectDir };
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// src/index.ts
|
|
1780
|
+
var VERSION = "0.6.0";
|
|
1781
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1782
|
+
0 && (module.exports = {
|
|
1783
|
+
ALL_SDKS,
|
|
1784
|
+
APL_VALUES,
|
|
1785
|
+
APP_FEATURE_VALUES,
|
|
1786
|
+
CancelledError,
|
|
1787
|
+
ChecksumMismatchError,
|
|
1788
|
+
CmdToolsNotInstalledError,
|
|
1789
|
+
OHOS_URL_BASE,
|
|
1790
|
+
OniroError,
|
|
1791
|
+
SdkNotInstalledError,
|
|
1792
|
+
UnsupportedPlatformError,
|
|
1793
|
+
VERSION,
|
|
1794
|
+
attemptHdcConnection,
|
|
1795
|
+
consoleLogger,
|
|
1796
|
+
createMaterial,
|
|
1797
|
+
createScaffold,
|
|
1798
|
+
decryptPwd,
|
|
1799
|
+
defaultContext,
|
|
1800
|
+
defaultPaths,
|
|
1801
|
+
detectProjectSdkVersion,
|
|
1802
|
+
downloadAndInstallSdk,
|
|
1803
|
+
downloadFile,
|
|
1804
|
+
encryptPwd,
|
|
1805
|
+
extractTarball,
|
|
1806
|
+
extractZipWithProgress,
|
|
1807
|
+
findAppProcessId,
|
|
1808
|
+
findCmdToolsSourceDir,
|
|
1809
|
+
generateSigningConfigs,
|
|
1810
|
+
getBundleName,
|
|
1811
|
+
getCmdToolsBin,
|
|
1812
|
+
getCmdToolsDownloadUrl,
|
|
1813
|
+
getCmdToolsPath,
|
|
1814
|
+
getCmdToolsStatus,
|
|
1815
|
+
getEmulatorDir,
|
|
1816
|
+
getHdcPath,
|
|
1817
|
+
getHvigorwPath,
|
|
1818
|
+
getInstalledSdks,
|
|
1819
|
+
getKey,
|
|
1820
|
+
getMainAbility,
|
|
1821
|
+
getOhosBaseSdkHome,
|
|
1822
|
+
getOsFolder,
|
|
1823
|
+
getSdkFilename,
|
|
1824
|
+
getSdkRootDir,
|
|
1825
|
+
getSupportedSdksForUi,
|
|
1826
|
+
installApp,
|
|
1827
|
+
installCmdTools,
|
|
1828
|
+
installEmulator,
|
|
1829
|
+
isCmdToolsInstalled,
|
|
1830
|
+
isEmulatorInstalled,
|
|
1831
|
+
isValidBundleName,
|
|
1832
|
+
isValidProjectName,
|
|
1833
|
+
launchApp,
|
|
1834
|
+
listRunningProcesses,
|
|
1835
|
+
listTemplates,
|
|
1836
|
+
nonInteractivePrompter,
|
|
1837
|
+
noopLogger,
|
|
1838
|
+
noopProgress,
|
|
1839
|
+
parseHilogLine,
|
|
1840
|
+
readJson5File,
|
|
1841
|
+
readJsonFile,
|
|
1842
|
+
removeCmdTools,
|
|
1843
|
+
removeEmulator,
|
|
1844
|
+
removeSdk,
|
|
1845
|
+
runHvigorw,
|
|
1846
|
+
setHilogLevel,
|
|
1847
|
+
startEmulator,
|
|
1848
|
+
staticConfig,
|
|
1849
|
+
stopEmulator,
|
|
1850
|
+
streamHilog,
|
|
1851
|
+
validateTemplateLayout,
|
|
1852
|
+
verifySha256,
|
|
1853
|
+
writeJson5File,
|
|
1854
|
+
writeJsonFile
|
|
1855
|
+
});
|
|
1856
|
+
//# sourceMappingURL=index.cjs.map
|