@oniroproject/core 0.9.0 → 0.11.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/README.md +33 -0
- package/dist/index.cjs +1761 -399
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +306 -2
- package/dist/index.d.ts +306 -2
- package/dist/index.js +1749 -399
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/dist/index.cjs
CHANGED
|
@@ -33,14 +33,18 @@ __export(index_exports, {
|
|
|
33
33
|
ALL_SDKS: () => ALL_SDKS,
|
|
34
34
|
APL_VALUES: () => APL_VALUES,
|
|
35
35
|
APP_FEATURE_VALUES: () => APP_FEATURE_VALUES,
|
|
36
|
+
AgcError: () => AgcError,
|
|
36
37
|
CancelledError: () => CancelledError,
|
|
37
38
|
ChecksumMismatchError: () => ChecksumMismatchError,
|
|
38
39
|
CmdToolsNotInstalledError: () => CmdToolsNotInstalledError,
|
|
39
40
|
CommandFailedError: () => CommandFailedError,
|
|
41
|
+
HARMONYOS_SDK_VERSIONS: () => HARMONYOS_SDK_VERSIONS,
|
|
42
|
+
HUAWEI_SITES: () => HUAWEI_SITES,
|
|
40
43
|
INSTALL_TMP_DIRNAME: () => INSTALL_TMP_DIRNAME,
|
|
41
44
|
InsufficientSpaceError: () => InsufficientSpaceError,
|
|
42
45
|
OHOS_URL_BASE: () => OHOS_URL_BASE,
|
|
43
46
|
OniroError: () => OniroError,
|
|
47
|
+
SIGNING_ERRORS: () => SIGNING_ERRORS,
|
|
44
48
|
SdkNotInstalledError: () => SdkNotInstalledError,
|
|
45
49
|
TMP_DIR_HINT: () => TMP_DIR_HINT,
|
|
46
50
|
UnsupportedPlatformError: () => UnsupportedPlatformError,
|
|
@@ -56,10 +60,12 @@ __export(index_exports, {
|
|
|
56
60
|
buildRawTouchCommand: () => buildRawTouchCommand,
|
|
57
61
|
captureBurst: () => captureBurst,
|
|
58
62
|
consoleLogger: () => consoleLogger,
|
|
63
|
+
createHarmonyOsSession: () => createHarmonyOsSession,
|
|
59
64
|
createInstallTempDir: () => createInstallTempDir,
|
|
60
65
|
createMaterial: () => createMaterial,
|
|
61
66
|
createScaffold: () => createScaffold,
|
|
62
67
|
createTempWorkDir: () => createTempWorkDir,
|
|
68
|
+
createTokenStore: () => createTokenStore,
|
|
63
69
|
decideInstallMethod: () => decideInstallMethod,
|
|
64
70
|
decideReboot: () => decideReboot,
|
|
65
71
|
decryptPwd: () => decryptPwd,
|
|
@@ -68,6 +74,7 @@ __export(index_exports, {
|
|
|
68
74
|
defaultPaths: () => defaultPaths,
|
|
69
75
|
detectAaStartFailure: () => detectAaStartFailure,
|
|
70
76
|
detectProjectSdkVersion: () => detectProjectSdkVersion,
|
|
77
|
+
detectRuntimeOs: () => detectRuntimeOs,
|
|
71
78
|
detectSigningConfigNames: () => detectSigningConfigNames,
|
|
72
79
|
diffEntryNames: () => diffEntryNames,
|
|
73
80
|
diffHapAssets: () => diffHapAssets,
|
|
@@ -90,6 +97,7 @@ __export(index_exports, {
|
|
|
90
97
|
extractZipWithProgress: () => extractZipWithProgress,
|
|
91
98
|
findAppProcessId: () => findAppProcessId,
|
|
92
99
|
findCmdToolsSourceDir: () => findCmdToolsSourceDir,
|
|
100
|
+
findHarmonyOsSdk: () => findHarmonyOsSdk,
|
|
93
101
|
findRunningProcess: () => findRunningProcess,
|
|
94
102
|
forceStop: () => forceStop,
|
|
95
103
|
formatBytes: () => formatBytes,
|
|
@@ -114,6 +122,8 @@ __export(index_exports, {
|
|
|
114
122
|
getSdkFilename: () => getSdkFilename,
|
|
115
123
|
getSdkRootDir: () => getSdkRootDir,
|
|
116
124
|
getSupportedSdksForUi: () => getSupportedSdksForUi,
|
|
125
|
+
harmonyOsAutoSign: () => harmonyOsAutoSign,
|
|
126
|
+
harmonyOsSdkLabelForApi: () => harmonyOsSdkLabelForApi,
|
|
117
127
|
hdcExec: () => hdcExec,
|
|
118
128
|
installApp: () => installApp,
|
|
119
129
|
installCmdTools: () => installCmdTools,
|
|
@@ -148,12 +158,14 @@ __export(index_exports, {
|
|
|
148
158
|
pruneLayout: () => pruneLayout,
|
|
149
159
|
readJson5File: () => readJson5File,
|
|
150
160
|
readJsonFile: () => readJsonFile,
|
|
161
|
+
readProvisionProfile: () => readProvisionProfile,
|
|
151
162
|
reboot: () => reboot,
|
|
152
163
|
recvFile: () => recvFile,
|
|
153
164
|
removeCmdTools: () => removeCmdTools,
|
|
154
165
|
removeEmulator: () => removeEmulator,
|
|
155
166
|
removeSdk: () => removeSdk,
|
|
156
167
|
removeTempWorkDir: () => removeTempWorkDir,
|
|
168
|
+
requireHarmonyOsSdk: () => requireHarmonyOsSdk,
|
|
157
169
|
resolveTmpRoot: () => resolveTmpRoot,
|
|
158
170
|
runCodelinter: () => runCodelinter,
|
|
159
171
|
runHvigorw: () => runHvigorw,
|
|
@@ -230,6 +242,8 @@ var defaultPaths = {
|
|
|
230
242
|
sdkRootDir: () => path.join(os.homedir(), "setup-ohos-sdk"),
|
|
231
243
|
cmdToolsPath: () => path.join(os.homedir(), "command-line-tools"),
|
|
232
244
|
emulatorDir: () => path.join(os.homedir(), "oniro-emulator"),
|
|
245
|
+
harmonyosAuthDir: () => path.join(os.homedir(), ".oniro", "harmonyos"),
|
|
246
|
+
harmonyosSigningDir: () => path.join(os.homedir(), ".ohos", "config"),
|
|
233
247
|
hapPath: "entry/build/default/outputs/default/entry-default-signed.hap",
|
|
234
248
|
emulatorUrl: "https://github.com/eclipse-oniro4openharmony/device_board_oniro/releases/latest/download/oniro_emulator.zip",
|
|
235
249
|
// The Huawei mirror only hosts a public Linux build of the command-line tools.
|
|
@@ -394,14 +408,94 @@ function getSdkFilename(version) {
|
|
|
394
408
|
}
|
|
395
409
|
|
|
396
410
|
// src/sdk/paths.ts
|
|
411
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
412
|
+
var os4 = __toESM(require("os"), 1);
|
|
413
|
+
var path3 = __toESM(require("path"), 1);
|
|
414
|
+
|
|
415
|
+
// src/harmonyos/sdk.ts
|
|
397
416
|
var fs = __toESM(require("fs"), 1);
|
|
398
417
|
var os3 = __toESM(require("os"), 1);
|
|
399
418
|
var path2 = __toESM(require("path"), 1);
|
|
419
|
+
function readSdkPkg(sdkPath) {
|
|
420
|
+
let data;
|
|
421
|
+
try {
|
|
422
|
+
data = JSON.parse(fs.readFileSync(path2.join(sdkPath, "default", "sdk-pkg.json"), "utf8")).data;
|
|
423
|
+
} catch {
|
|
424
|
+
return {};
|
|
425
|
+
}
|
|
426
|
+
const text = (value) => typeof value === "string" || typeof value === "number" ? String(value) || void 0 : void 0;
|
|
427
|
+
const platformVersion = text(data?.platformVersion);
|
|
428
|
+
const apiVersion = text(data?.apiVersion);
|
|
429
|
+
return {
|
|
430
|
+
displayName: text(data?.displayName),
|
|
431
|
+
version: platformVersion && apiVersion ? `${platformVersion}(${apiVersion})` : void 0
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function resolveSdk(root, requireHarmonyOs) {
|
|
435
|
+
const nested = path2.join(root, "sdk");
|
|
436
|
+
const sdkPath = [nested, root].find((dir) => fs.existsSync(path2.join(dir, "default", "openharmony")));
|
|
437
|
+
if (!sdkPath) return null;
|
|
438
|
+
const pkg = readSdkPkg(sdkPath);
|
|
439
|
+
if (requireHarmonyOs && !pkg.displayName?.startsWith("HarmonyOS")) return null;
|
|
440
|
+
return { sdkPath, installRoot: sdkPath === nested ? root : path2.dirname(root), ...pkg };
|
|
441
|
+
}
|
|
442
|
+
function studioCandidates() {
|
|
443
|
+
if (process.platform === "darwin") {
|
|
444
|
+
return [
|
|
445
|
+
"/Applications/DevEco-Studio.app/Contents",
|
|
446
|
+
path2.join(os3.homedir(), "Applications", "DevEco-Studio.app", "Contents")
|
|
447
|
+
];
|
|
448
|
+
}
|
|
449
|
+
if (process.platform === "win32") {
|
|
450
|
+
return [path2.join(process.env.ProgramFiles ?? "C:\\Program Files", "Huawei", "DevEco Studio")];
|
|
451
|
+
}
|
|
452
|
+
return [];
|
|
453
|
+
}
|
|
454
|
+
function findHarmonyOsSdk(opts) {
|
|
455
|
+
const logger = opts.logger ?? noopLogger;
|
|
456
|
+
const configured = opts.config.get("harmonyosSdkPath", "");
|
|
457
|
+
if (configured) {
|
|
458
|
+
const sdk = resolveSdk(configured, false);
|
|
459
|
+
if (sdk) return sdk;
|
|
460
|
+
logger.warn(`ONIRO_HARMONYOS_SDK_PATH points at ${configured}, which holds no SDK; ignoring it.`);
|
|
461
|
+
}
|
|
462
|
+
const candidates = [
|
|
463
|
+
...studioCandidates(),
|
|
464
|
+
opts.config.get("cmdToolsPath", defaultPaths.cmdToolsPath())
|
|
465
|
+
];
|
|
466
|
+
for (const candidate of candidates) {
|
|
467
|
+
const sdk = resolveSdk(candidate, true);
|
|
468
|
+
if (sdk) {
|
|
469
|
+
logger.debug(`[harmonyos] Using the ${sdk.displayName} SDK at ${sdk.sdkPath}.`);
|
|
470
|
+
return sdk;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
function requireHarmonyOsSdk(opts) {
|
|
476
|
+
const sdk = findHarmonyOsSdk(opts);
|
|
477
|
+
if (sdk) return sdk;
|
|
478
|
+
throw new OniroError(
|
|
479
|
+
"No HarmonyOS SDK found. It ships inside DevEco Studio or the HarmonyOS command-line tools and cannot be downloaded the way the OpenHarmony SDK is. Install one, then set ONIRO_HARMONYOS_SDK_PATH to the install (or its sdk directory)."
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
function findHarmonyOsTool(sdk, name) {
|
|
483
|
+
const names = process.platform === "win32" ? [`${name}.bat`, `${name}.cmd`, `${name}.exe`] : [name];
|
|
484
|
+
for (const dir of [path2.join(sdk.installRoot, "bin"), path2.join(sdk.installRoot, "tools", name, "bin")]) {
|
|
485
|
+
for (const candidate of names) {
|
|
486
|
+
const full = path2.join(dir, candidate);
|
|
487
|
+
if (fs.existsSync(full)) return full;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/sdk/paths.ts
|
|
400
494
|
function getSdkRootDir(config) {
|
|
401
495
|
return config.get("sdkRootDir", defaultPaths.sdkRootDir());
|
|
402
496
|
}
|
|
403
497
|
function getOhosBaseSdkHome(config) {
|
|
404
|
-
return
|
|
498
|
+
return path3.join(getSdkRootDir(config), getOsFolder());
|
|
405
499
|
}
|
|
406
500
|
function getCmdToolsPath(config) {
|
|
407
501
|
return config.get("cmdToolsPath", defaultPaths.cmdToolsPath());
|
|
@@ -409,31 +503,31 @@ function getCmdToolsPath(config) {
|
|
|
409
503
|
function getEmulatorDir(config) {
|
|
410
504
|
return config.get("emulatorDir", defaultPaths.emulatorDir());
|
|
411
505
|
}
|
|
412
|
-
function toLongPath(p, platform7 =
|
|
506
|
+
function toLongPath(p, platform7 = os4.platform()) {
|
|
413
507
|
if (platform7 !== "win32") return p;
|
|
414
508
|
try {
|
|
415
|
-
return
|
|
509
|
+
return fs2.realpathSync.native(p);
|
|
416
510
|
} catch {
|
|
417
511
|
return p;
|
|
418
512
|
}
|
|
419
513
|
}
|
|
420
514
|
function pickExisting(candidates) {
|
|
421
515
|
for (const c of candidates) {
|
|
422
|
-
if (
|
|
516
|
+
if (fs2.existsSync(c)) return c;
|
|
423
517
|
}
|
|
424
518
|
return candidates[0];
|
|
425
519
|
}
|
|
426
520
|
function getCmdToolsBin(config, name = "ohpm") {
|
|
427
|
-
const binDir =
|
|
428
|
-
if (
|
|
521
|
+
const binDir = path3.join(getCmdToolsPath(config), "bin");
|
|
522
|
+
if (os4.platform() === "win32") {
|
|
429
523
|
return pickExisting([
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
524
|
+
path3.join(binDir, `${name}.exe`),
|
|
525
|
+
path3.join(binDir, `${name}.cmd`),
|
|
526
|
+
path3.join(binDir, `${name}.bat`),
|
|
527
|
+
path3.join(binDir, name)
|
|
434
528
|
]);
|
|
435
529
|
}
|
|
436
|
-
return
|
|
530
|
+
return path3.join(binDir, name);
|
|
437
531
|
}
|
|
438
532
|
function getOhpmPath(config) {
|
|
439
533
|
return getCmdToolsBin(config, "ohpm");
|
|
@@ -441,45 +535,52 @@ function getOhpmPath(config) {
|
|
|
441
535
|
function getHdcPath(config) {
|
|
442
536
|
const override = config.get("hdcPath", "");
|
|
443
537
|
if (override) return override;
|
|
444
|
-
const
|
|
445
|
-
if (
|
|
538
|
+
const hdc = hdcInSdk(path3.join(getCmdToolsPath(config), "sdk"));
|
|
539
|
+
if (fs2.existsSync(hdc)) return hdc;
|
|
540
|
+
const harmonySdk = findHarmonyOsSdk({ config });
|
|
541
|
+
const harmonyHdc = harmonySdk ? hdcInSdk(harmonySdk.sdkPath) : void 0;
|
|
542
|
+
return harmonyHdc && fs2.existsSync(harmonyHdc) ? harmonyHdc : hdc;
|
|
543
|
+
}
|
|
544
|
+
function hdcInSdk(sdkDir) {
|
|
545
|
+
const base = path3.join(sdkDir, "default", "openharmony", "toolchains");
|
|
546
|
+
if (os4.platform() === "win32") {
|
|
446
547
|
return pickExisting([
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
548
|
+
path3.join(base, "hdc.exe"),
|
|
549
|
+
path3.join(base, "hdc.bat"),
|
|
550
|
+
path3.join(base, "hdc.cmd"),
|
|
551
|
+
path3.join(base, "hdc")
|
|
451
552
|
]);
|
|
452
553
|
}
|
|
453
|
-
return
|
|
554
|
+
return path3.join(base, "hdc");
|
|
454
555
|
}
|
|
455
|
-
function getHvigorwPath(config, projectDir, platform7 =
|
|
556
|
+
function getHvigorwPath(config, projectDir, platform7 = os4.platform()) {
|
|
456
557
|
const posixFirst = ["hvigorw", "hvigorw.bat", "hvigorw.cmd"];
|
|
457
558
|
const windowsFirst = ["hvigorw.bat", "hvigorw.cmd", "hvigorw"];
|
|
458
559
|
const names = platform7 === "win32" ? windowsFirst : posixFirst;
|
|
459
|
-
const local = pickExisting(names.map((n) =>
|
|
460
|
-
if (
|
|
560
|
+
const local = pickExisting(names.map((n) => path3.join(projectDir, n)));
|
|
561
|
+
if (fs2.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
|
|
461
562
|
return local;
|
|
462
563
|
}
|
|
463
564
|
return getCmdToolsBin(config, "hvigorw");
|
|
464
565
|
}
|
|
465
566
|
function isProjectLocalHvigorwWorking(projectDir) {
|
|
466
|
-
const wrapper =
|
|
467
|
-
const nodeModules =
|
|
468
|
-
return
|
|
567
|
+
const wrapper = path3.join(projectDir, "hvigor", "hvigor-wrapper.js");
|
|
568
|
+
const nodeModules = path3.join(projectDir, "hvigor", "node_modules");
|
|
569
|
+
return fs2.existsSync(wrapper) && fs2.existsSync(nodeModules);
|
|
469
570
|
}
|
|
470
571
|
|
|
471
572
|
// src/sdk/detectProjectSdk.ts
|
|
472
|
-
var
|
|
473
|
-
var
|
|
573
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
574
|
+
var path4 = __toESM(require("path"), 1);
|
|
474
575
|
var import_json5 = __toESM(require("json5"), 1);
|
|
475
576
|
function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
476
|
-
const buildProfilePath =
|
|
477
|
-
if (!
|
|
577
|
+
const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
|
|
578
|
+
if (!fs3.existsSync(buildProfilePath)) {
|
|
478
579
|
logger.warn(`build-profile.json5 not found at ${projectRoot}`);
|
|
479
580
|
return void 0;
|
|
480
581
|
}
|
|
481
582
|
try {
|
|
482
|
-
const content =
|
|
583
|
+
const content = fs3.readFileSync(buildProfilePath, "utf-8");
|
|
483
584
|
const config = import_json5.default.parse(content);
|
|
484
585
|
const product = config.app?.products?.[0];
|
|
485
586
|
const v = product?.compileSdkVersion;
|
|
@@ -491,26 +592,26 @@ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
|
491
592
|
}
|
|
492
593
|
|
|
493
594
|
// src/sdk/tmp.ts
|
|
494
|
-
var
|
|
495
|
-
var
|
|
496
|
-
var
|
|
595
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
596
|
+
var os5 = __toESM(require("os"), 1);
|
|
597
|
+
var path5 = __toESM(require("path"), 1);
|
|
497
598
|
var INSTALL_TMP_DIRNAME = ".oniro-tmp";
|
|
498
599
|
var TMPFS_MAGIC = 16914836;
|
|
499
600
|
var RAMFS_MAGIC = 2240043254;
|
|
500
|
-
var HINT_EXAMPLE =
|
|
601
|
+
var HINT_EXAMPLE = os5.platform() === "win32" ? "D:\\oniro-tmp" : "~/.cache/oniro-tmp";
|
|
501
602
|
var TMP_DIR_HINT = `Point the scratch directory at a filesystem with more room using --tmp-dir <path> (or the ONIRO_TMP_DIR environment variable), e.g. --tmp-dir ${HINT_EXAMPLE}.`;
|
|
502
603
|
function resolveTmpRoot(config, override, installRoot) {
|
|
503
604
|
const explicit = (override ?? "").trim();
|
|
504
|
-
if (explicit) return
|
|
605
|
+
if (explicit) return path5.resolve(explicit);
|
|
505
606
|
const configured = config.get("tmpDir", "").trim();
|
|
506
|
-
if (configured) return
|
|
507
|
-
if (installRoot) return
|
|
508
|
-
return
|
|
607
|
+
if (configured) return path5.resolve(configured);
|
|
608
|
+
if (installRoot) return path5.join(path5.resolve(installRoot), INSTALL_TMP_DIRNAME);
|
|
609
|
+
return os5.tmpdir();
|
|
509
610
|
}
|
|
510
611
|
function createTempWorkDir(root, prefix) {
|
|
511
612
|
try {
|
|
512
|
-
|
|
513
|
-
return
|
|
613
|
+
fs4.mkdirSync(root, { recursive: true });
|
|
614
|
+
return fs4.mkdtempSync(path5.join(root, prefix));
|
|
514
615
|
} catch (err) {
|
|
515
616
|
if (isOutOfSpaceError(err)) throw toSpaceError(err, root, "a temporary directory");
|
|
516
617
|
throw new OniroError(
|
|
@@ -521,10 +622,10 @@ ${TMP_DIR_HINT}`,
|
|
|
521
622
|
}
|
|
522
623
|
}
|
|
523
624
|
function removeTempWorkDir(dir, root) {
|
|
524
|
-
|
|
525
|
-
if (!root ||
|
|
625
|
+
fs4.rmSync(dir, { recursive: true, force: true });
|
|
626
|
+
if (!root || path5.basename(root) !== INSTALL_TMP_DIRNAME) return;
|
|
526
627
|
try {
|
|
527
|
-
|
|
628
|
+
fs4.rmdirSync(root);
|
|
528
629
|
} catch {
|
|
529
630
|
}
|
|
530
631
|
}
|
|
@@ -534,18 +635,18 @@ function createInstallTempDir(opts) {
|
|
|
534
635
|
try {
|
|
535
636
|
return { dir: createTempWorkDir(root, opts.prefix), root };
|
|
536
637
|
} catch (err) {
|
|
537
|
-
const fallback =
|
|
538
|
-
if (requested || isOutOfSpaceError(err) ||
|
|
539
|
-
const reason = err instanceof Error ? err.message.split(
|
|
638
|
+
const fallback = os5.tmpdir();
|
|
639
|
+
if (requested || isOutOfSpaceError(err) || path5.resolve(fallback) === path5.resolve(root)) throw err;
|
|
640
|
+
const reason = err instanceof Error ? err.message.split(/\r?\n/)[0] : String(err);
|
|
540
641
|
(opts.logger ?? noopLogger).warn(`${reason} Falling back to '${fallback}'.`);
|
|
541
642
|
return { dir: createTempWorkDir(fallback, opts.prefix), root: fallback };
|
|
542
643
|
}
|
|
543
644
|
}
|
|
544
645
|
function nearestExisting(dir) {
|
|
545
|
-
let current =
|
|
646
|
+
let current = path5.resolve(dir);
|
|
546
647
|
for (; ; ) {
|
|
547
|
-
if (
|
|
548
|
-
const parent =
|
|
648
|
+
if (fs4.existsSync(current)) return current;
|
|
649
|
+
const parent = path5.dirname(current);
|
|
549
650
|
if (parent === current) return null;
|
|
550
651
|
current = parent;
|
|
551
652
|
}
|
|
@@ -554,7 +655,7 @@ function getFreeSpaceBytes(dir) {
|
|
|
554
655
|
const target = nearestExisting(dir);
|
|
555
656
|
if (!target) return null;
|
|
556
657
|
try {
|
|
557
|
-
const stats =
|
|
658
|
+
const stats = fs4.statfsSync(target);
|
|
558
659
|
return Number(stats.bsize) * Number(stats.bavail);
|
|
559
660
|
} catch {
|
|
560
661
|
return null;
|
|
@@ -564,7 +665,7 @@ function isRamBackedPath(dir) {
|
|
|
564
665
|
const target = nearestExisting(dir);
|
|
565
666
|
if (!target) return false;
|
|
566
667
|
try {
|
|
567
|
-
const type = Number(
|
|
668
|
+
const type = Number(fs4.statfsSync(target).type);
|
|
568
669
|
return type === TMPFS_MAGIC || type === RAMFS_MAGIC;
|
|
569
670
|
} catch {
|
|
570
671
|
return false;
|
|
@@ -628,16 +729,16 @@ function errorPath(err) {
|
|
|
628
729
|
return null;
|
|
629
730
|
}
|
|
630
731
|
function isInside(child, parent) {
|
|
631
|
-
const rel =
|
|
632
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
732
|
+
const rel = path5.relative(path5.resolve(parent), path5.resolve(child));
|
|
733
|
+
return rel === "" || !rel.startsWith("..") && !path5.isAbsolute(rel);
|
|
633
734
|
}
|
|
634
735
|
function toSpaceError(err, tmpRoot, what) {
|
|
635
736
|
if (err instanceof InsufficientSpaceError) return err;
|
|
636
737
|
if (!isOutOfSpaceError(err)) return err;
|
|
637
738
|
const failed = errorPath(err);
|
|
638
|
-
const failedDir = failed ?
|
|
739
|
+
const failedDir = failed ? path5.dirname(path5.resolve(failed)) : path5.resolve(tmpRoot);
|
|
639
740
|
const inTmp = isInside(failedDir, tmpRoot);
|
|
640
|
-
const dir = inTmp ?
|
|
741
|
+
const dir = inTmp ? path5.resolve(tmpRoot) : failedDir;
|
|
641
742
|
return new InsufficientSpaceError(
|
|
642
743
|
spaceMessage(dir, what, { freeBytes: getFreeSpaceBytes(dir), tmpDirHint: inTmp }),
|
|
643
744
|
dir,
|
|
@@ -646,8 +747,8 @@ function toSpaceError(err, tmpRoot, what) {
|
|
|
646
747
|
}
|
|
647
748
|
|
|
648
749
|
// src/sdk/download.ts
|
|
649
|
-
var
|
|
650
|
-
var
|
|
750
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
751
|
+
var path6 = __toESM(require("path"), 1);
|
|
651
752
|
var crypto = __toESM(require("crypto"), 1);
|
|
652
753
|
var import_node_stream = require("stream");
|
|
653
754
|
var import_node_util = require("util");
|
|
@@ -657,26 +758,26 @@ var pipelineAsync = (0, import_node_util.promisify)(import_node_stream.pipeline)
|
|
|
657
758
|
async function downloadFile(opts) {
|
|
658
759
|
const { url, dest, progress, abortSignal } = opts;
|
|
659
760
|
const proto = url.startsWith("https") ? https : http;
|
|
660
|
-
return new Promise((
|
|
761
|
+
return new Promise((resolve6, reject) => {
|
|
661
762
|
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
662
763
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
663
764
|
let settled = false;
|
|
664
765
|
const done = (err) => {
|
|
665
766
|
if (settled) return;
|
|
666
767
|
settled = true;
|
|
667
|
-
err ? reject(err) :
|
|
768
|
+
err ? reject(err) : resolve6();
|
|
668
769
|
};
|
|
669
770
|
if (abortSignal?.aborted) {
|
|
670
771
|
done(new CancelledError("Download cancelled before start."));
|
|
671
772
|
return;
|
|
672
773
|
}
|
|
673
|
-
const destDir =
|
|
774
|
+
const destDir = path6.dirname(path6.resolve(dest));
|
|
674
775
|
const what = opts.what ?? `the download of '${url}'`;
|
|
675
776
|
const writeFailed = (err) => isOutOfSpaceError(err) ? toSpaceError(err, opts.tmpRoot ?? destDir, what) : new OniroError(`Error writing '${dest}': ${err instanceof Error ? err.message : String(err)}`, err);
|
|
676
|
-
const file =
|
|
777
|
+
const file = fs5.createWriteStream(dest);
|
|
677
778
|
let activeResponse;
|
|
678
779
|
const failWith = (err) => {
|
|
679
|
-
file.close(() =>
|
|
780
|
+
file.close(() => fs5.rm(dest, { force: true }, () => done(err)));
|
|
680
781
|
};
|
|
681
782
|
file.on("error", (err) => {
|
|
682
783
|
try {
|
|
@@ -748,9 +849,9 @@ async function downloadFile(opts) {
|
|
|
748
849
|
});
|
|
749
850
|
}
|
|
750
851
|
async function verifySha256(filePath, sha256Path) {
|
|
751
|
-
const expected =
|
|
852
|
+
const expected = fs5.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
|
|
752
853
|
const hash = crypto.createHash("sha256");
|
|
753
|
-
await pipelineAsync(
|
|
854
|
+
await pipelineAsync(fs5.createReadStream(filePath), hash);
|
|
754
855
|
const actual = hash.digest("hex");
|
|
755
856
|
if (actual !== expected) {
|
|
756
857
|
throw new ChecksumMismatchError(expected, actual);
|
|
@@ -758,9 +859,9 @@ async function verifySha256(filePath, sha256Path) {
|
|
|
758
859
|
}
|
|
759
860
|
|
|
760
861
|
// src/sdk/extract.ts
|
|
761
|
-
var
|
|
762
|
-
var
|
|
763
|
-
var
|
|
862
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
863
|
+
var os6 = __toESM(require("os"), 1);
|
|
864
|
+
var path7 = __toESM(require("path"), 1);
|
|
764
865
|
var import_node_stream2 = require("stream");
|
|
765
866
|
var import_node_util2 = require("util");
|
|
766
867
|
var tar = __toESM(require("tar"), 1);
|
|
@@ -777,12 +878,12 @@ async function extractZipWithProgress(opts) {
|
|
|
777
878
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
778
879
|
let processed = 0;
|
|
779
880
|
let lastOverall = Math.round(s);
|
|
780
|
-
await
|
|
781
|
-
const destRoot =
|
|
881
|
+
await fs6.promises.mkdir(dest, { recursive: true });
|
|
882
|
+
const destRoot = path7.resolve(dest);
|
|
782
883
|
const safeResolveTarget = (entryName) => {
|
|
783
|
-
const resolved =
|
|
784
|
-
const rel =
|
|
785
|
-
if (rel.startsWith("..") ||
|
|
884
|
+
const resolved = path7.resolve(destRoot, entryName);
|
|
885
|
+
const rel = path7.relative(destRoot, resolved);
|
|
886
|
+
if (rel.startsWith("..") || path7.isAbsolute(rel)) {
|
|
786
887
|
throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
|
|
787
888
|
}
|
|
788
889
|
return resolved;
|
|
@@ -794,25 +895,25 @@ async function extractZipWithProgress(opts) {
|
|
|
794
895
|
const attr = file.attr ? file.attr >>> 16 : 0;
|
|
795
896
|
const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
|
|
796
897
|
if (file.isDirectory) {
|
|
797
|
-
await
|
|
898
|
+
await fs6.promises.mkdir(targetPath, { recursive: true });
|
|
798
899
|
} else if (isSymlink) {
|
|
799
|
-
await
|
|
900
|
+
await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
|
|
800
901
|
const linkTargetBuffer = await zip.entryData(file);
|
|
801
902
|
const linkTarget = linkTargetBuffer.toString("utf8");
|
|
802
903
|
try {
|
|
803
|
-
const isWin =
|
|
804
|
-
await
|
|
904
|
+
const isWin = os6.platform() === "win32";
|
|
905
|
+
await fs6.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
|
|
805
906
|
} catch (e) {
|
|
806
907
|
logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
|
|
807
908
|
}
|
|
808
909
|
} else {
|
|
809
|
-
await
|
|
910
|
+
await fs6.promises.mkdir(path7.dirname(targetPath), { recursive: true });
|
|
810
911
|
const readStream = await zip.stream(file);
|
|
811
|
-
const writeStream =
|
|
912
|
+
const writeStream = fs6.createWriteStream(targetPath);
|
|
812
913
|
await pipelineAsync2(readStream, writeStream);
|
|
813
914
|
if (attr > 0) {
|
|
814
915
|
try {
|
|
815
|
-
await
|
|
916
|
+
await fs6.promises.chmod(targetPath, attr & 511);
|
|
816
917
|
} catch {
|
|
817
918
|
}
|
|
818
919
|
}
|
|
@@ -844,41 +945,41 @@ async function extractTarball(tarPath, dest, strip = 0) {
|
|
|
844
945
|
}
|
|
845
946
|
|
|
846
947
|
// src/sdk/list.ts
|
|
847
|
-
var
|
|
848
|
-
var
|
|
948
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
949
|
+
var path8 = __toESM(require("path"), 1);
|
|
849
950
|
function getSupportedSdksForUi(config) {
|
|
850
|
-
const base =
|
|
951
|
+
const base = path8.join(getSdkRootDir(config), getOsFolder());
|
|
851
952
|
return ALL_SDKS.map((sdk) => ({
|
|
852
953
|
version: sdk.version,
|
|
853
954
|
api: sdk.api,
|
|
854
|
-
installed:
|
|
955
|
+
installed: fs7.existsSync(path8.join(base, sdk.api))
|
|
855
956
|
})).sort((a, b) => Number(b.api) - Number(a.api));
|
|
856
957
|
}
|
|
857
958
|
function getInstalledSdks(config) {
|
|
858
959
|
const sdkRoot = getSdkRootDir(config);
|
|
859
960
|
const versions = /* @__PURE__ */ new Set();
|
|
860
|
-
if (!
|
|
961
|
+
if (!fs7.existsSync(sdkRoot)) return [];
|
|
861
962
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
862
|
-
const osPath =
|
|
863
|
-
if (!
|
|
864
|
-
for (const api of
|
|
865
|
-
const apiPath =
|
|
866
|
-
if (
|
|
963
|
+
const osPath = path8.join(sdkRoot, osFolder);
|
|
964
|
+
if (!fs7.existsSync(osPath) || !fs7.statSync(osPath).isDirectory()) continue;
|
|
965
|
+
for (const api of fs7.readdirSync(osPath)) {
|
|
966
|
+
const apiPath = path8.join(osPath, api);
|
|
967
|
+
if (fs7.statSync(apiPath).isDirectory()) versions.add(api);
|
|
867
968
|
}
|
|
868
969
|
}
|
|
869
970
|
return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
|
|
870
971
|
}
|
|
871
972
|
|
|
872
973
|
// src/sdk/remove.ts
|
|
873
|
-
var
|
|
874
|
-
var
|
|
974
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
975
|
+
var path9 = __toESM(require("path"), 1);
|
|
875
976
|
function removeSdk(config, api) {
|
|
876
977
|
const sdkRoot = getSdkRootDir(config);
|
|
877
978
|
let removed = false;
|
|
878
979
|
for (const osFolder of ["linux", "darwin", "windows"]) {
|
|
879
|
-
const sdkPath =
|
|
880
|
-
if (
|
|
881
|
-
|
|
980
|
+
const sdkPath = path9.join(sdkRoot, osFolder, api);
|
|
981
|
+
if (fs8.existsSync(sdkPath)) {
|
|
982
|
+
fs8.rmSync(sdkPath, { recursive: true, force: true });
|
|
882
983
|
removed = true;
|
|
883
984
|
}
|
|
884
985
|
}
|
|
@@ -886,18 +987,18 @@ function removeSdk(config, api) {
|
|
|
886
987
|
}
|
|
887
988
|
|
|
888
989
|
// src/sdk/install.ts
|
|
889
|
-
var
|
|
890
|
-
var
|
|
990
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
991
|
+
var path10 = __toESM(require("path"), 1);
|
|
891
992
|
|
|
892
993
|
// src/sdk/move.ts
|
|
893
|
-
var
|
|
994
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
894
995
|
function movePath(src, dest) {
|
|
895
996
|
try {
|
|
896
|
-
|
|
997
|
+
fs9.renameSync(src, dest);
|
|
897
998
|
} catch (err) {
|
|
898
999
|
if (err.code !== "EXDEV") throw err;
|
|
899
|
-
|
|
900
|
-
|
|
1000
|
+
fs9.cpSync(src, dest, { recursive: true });
|
|
1001
|
+
fs9.rmSync(src, { recursive: true, force: true });
|
|
901
1002
|
}
|
|
902
1003
|
}
|
|
903
1004
|
|
|
@@ -918,12 +1019,12 @@ async function downloadAndInstallSdk(opts) {
|
|
|
918
1019
|
prefix: "oniro-sdk-",
|
|
919
1020
|
logger
|
|
920
1021
|
});
|
|
921
|
-
const tarPath =
|
|
922
|
-
const sha256Path =
|
|
923
|
-
const extractDir =
|
|
924
|
-
|
|
925
|
-
const sdkInstallDir =
|
|
926
|
-
|
|
1022
|
+
const tarPath = path10.join(tmpDir, filename);
|
|
1023
|
+
const sha256Path = path10.join(tmpDir, `${filename}.sha256`);
|
|
1024
|
+
const extractDir = path10.join(tmpDir, "extract");
|
|
1025
|
+
fs10.mkdirSync(extractDir);
|
|
1026
|
+
const sdkInstallDir = path10.join(sdkRoot, osFolder, api);
|
|
1027
|
+
fs10.mkdirSync(path10.dirname(sdkInstallDir), { recursive: true });
|
|
927
1028
|
const checkCancelled = () => {
|
|
928
1029
|
if (abortSignal?.aborted) throw new CancelledError();
|
|
929
1030
|
};
|
|
@@ -940,13 +1041,13 @@ async function downloadAndInstallSdk(opts) {
|
|
|
940
1041
|
await extractTarball(tarPath, extractDir, strip);
|
|
941
1042
|
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
942
1043
|
checkCancelled();
|
|
943
|
-
const osContentPath =
|
|
944
|
-
if (!
|
|
1044
|
+
const osContentPath = path10.join(extractDir, osFolder);
|
|
1045
|
+
if (!fs10.existsSync(osContentPath)) {
|
|
945
1046
|
throw new OniroError(
|
|
946
1047
|
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
947
1048
|
);
|
|
948
1049
|
}
|
|
949
|
-
const zipFiles =
|
|
1050
|
+
const zipFiles = fs10.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
950
1051
|
const componentsStart = 60;
|
|
951
1052
|
const componentsBudget = 35;
|
|
952
1053
|
const n = zipFiles.length;
|
|
@@ -961,16 +1062,16 @@ async function downloadAndInstallSdk(opts) {
|
|
|
961
1062
|
for (const zipFile of zipFiles) {
|
|
962
1063
|
checkCancelled();
|
|
963
1064
|
logger.info(`Extracting component ${zipFile}`);
|
|
964
|
-
const zipPath =
|
|
1065
|
+
const zipPath = path10.join(osContentPath, zipFile);
|
|
965
1066
|
const thisBudget = base + (rem > 0 ? 1 : 0);
|
|
966
1067
|
if (rem > 0) rem--;
|
|
967
1068
|
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
968
1069
|
cursor += thisBudget;
|
|
969
|
-
|
|
1070
|
+
fs10.unlinkSync(zipPath);
|
|
970
1071
|
}
|
|
971
1072
|
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
972
|
-
if (
|
|
973
|
-
|
|
1073
|
+
if (fs10.existsSync(sdkInstallDir)) {
|
|
1074
|
+
fs10.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
974
1075
|
}
|
|
975
1076
|
movePath(osContentPath, sdkInstallDir);
|
|
976
1077
|
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
@@ -986,13 +1087,13 @@ async function downloadAndInstallSdk(opts) {
|
|
|
986
1087
|
}
|
|
987
1088
|
|
|
988
1089
|
// src/cmdTools/install.ts
|
|
989
|
-
var
|
|
990
|
-
var
|
|
991
|
-
var
|
|
1090
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
1091
|
+
var os7 = __toESM(require("os"), 1);
|
|
1092
|
+
var path12 = __toESM(require("path"), 1);
|
|
992
1093
|
var import_node_child_process2 = require("child_process");
|
|
993
1094
|
|
|
994
1095
|
// src/hdc/spawnCompat.ts
|
|
995
|
-
var
|
|
1096
|
+
var path11 = __toESM(require("path"), 1);
|
|
996
1097
|
var import_node_child_process = require("child_process");
|
|
997
1098
|
var WINDOWS_BATCH_RE = /\.(bat|cmd)$/i;
|
|
998
1099
|
var CMD_META_RE = /([()[\]%!^"`<>&|;, *?])/g;
|
|
@@ -1016,7 +1117,7 @@ function escapeCmdCommand(command) {
|
|
|
1016
1117
|
function buildCmdWrapper(command, args) {
|
|
1017
1118
|
assertCmdSafe(command, `command ${command}`);
|
|
1018
1119
|
args.forEach((a, i) => assertCmdSafe(a, `argument ${i} (${a})`));
|
|
1019
|
-
const normalized =
|
|
1120
|
+
const normalized = path11.normalize(command);
|
|
1020
1121
|
const double = CMD_SHIM_RE.test(normalized);
|
|
1021
1122
|
const line = [escapeCmdCommand(normalized), ...args.map((a) => escapeCmdArgument(a, double))].join(" ");
|
|
1022
1123
|
return {
|
|
@@ -1064,7 +1165,7 @@ function killProcessTree(child) {
|
|
|
1064
1165
|
}
|
|
1065
1166
|
|
|
1066
1167
|
// src/cmdTools/install.ts
|
|
1067
|
-
function getCmdToolsDownloadUrl(config, platform7 =
|
|
1168
|
+
function getCmdToolsDownloadUrl(config, platform7 = os7.platform()) {
|
|
1068
1169
|
if (platform7 === "linux") {
|
|
1069
1170
|
return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
|
|
1070
1171
|
}
|
|
@@ -1090,16 +1191,16 @@ function getCmdToolsDownloadUrl(config, platform7 = os6.platform()) {
|
|
|
1090
1191
|
}
|
|
1091
1192
|
function findCmdToolsSourceDir(extractPath) {
|
|
1092
1193
|
const known = [
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1194
|
+
path12.join(extractPath, "command-line-tools"),
|
|
1195
|
+
path12.join(extractPath, "oh-command-line-tools"),
|
|
1196
|
+
path12.join(extractPath, "commandline-tools")
|
|
1096
1197
|
];
|
|
1097
1198
|
for (const c of known) {
|
|
1098
|
-
if (
|
|
1199
|
+
if (fs11.existsSync(c)) return c;
|
|
1099
1200
|
}
|
|
1100
|
-
const entries =
|
|
1201
|
+
const entries = fs11.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path12.join(extractPath, e.name));
|
|
1101
1202
|
for (const dir of entries) {
|
|
1102
|
-
if (
|
|
1203
|
+
if (fs11.existsSync(path12.join(dir, "bin"))) return dir;
|
|
1103
1204
|
}
|
|
1104
1205
|
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
1105
1206
|
}
|
|
@@ -1111,12 +1212,12 @@ async function installCmdTools(opts) {
|
|
|
1111
1212
|
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1112
1213
|
config,
|
|
1113
1214
|
override: opts.tmpDir,
|
|
1114
|
-
installRoot:
|
|
1215
|
+
installRoot: path12.dirname(CMD_PATH),
|
|
1115
1216
|
prefix: "oniro-cmdtools-",
|
|
1116
1217
|
logger
|
|
1117
1218
|
});
|
|
1118
|
-
const zipPath = opts.localZipPath ??
|
|
1119
|
-
const extractPath =
|
|
1219
|
+
const zipPath = opts.localZipPath ?? path12.join(tmpDir, "oh-command-line-tools.zip");
|
|
1220
|
+
const extractPath = path12.join(tmpDir, "oh-command-line-tools");
|
|
1120
1221
|
try {
|
|
1121
1222
|
if (!opts.localZipPath) {
|
|
1122
1223
|
const url = getCmdToolsDownloadUrl(config);
|
|
@@ -1125,22 +1226,22 @@ async function installCmdTools(opts) {
|
|
|
1125
1226
|
}
|
|
1126
1227
|
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
1127
1228
|
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
1128
|
-
|
|
1229
|
+
fs11.mkdirSync(CMD_PATH, { recursive: true });
|
|
1129
1230
|
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
1130
|
-
for (const entry of
|
|
1131
|
-
const src =
|
|
1132
|
-
const dest =
|
|
1133
|
-
if (
|
|
1134
|
-
if (
|
|
1231
|
+
for (const entry of fs11.readdirSync(srcDir)) {
|
|
1232
|
+
const src = path12.join(srcDir, entry);
|
|
1233
|
+
const dest = path12.join(CMD_PATH, entry);
|
|
1234
|
+
if (fs11.statSync(src).isDirectory()) {
|
|
1235
|
+
if (fs11.existsSync(dest)) fs11.rmSync(dest, { recursive: true, force: true });
|
|
1135
1236
|
movePath(src, dest);
|
|
1136
1237
|
} else {
|
|
1137
|
-
|
|
1238
|
+
fs11.copyFileSync(src, dest);
|
|
1138
1239
|
}
|
|
1139
1240
|
}
|
|
1140
|
-
const binDir =
|
|
1141
|
-
if (
|
|
1142
|
-
for (const file of
|
|
1143
|
-
|
|
1241
|
+
const binDir = path12.join(CMD_PATH, "bin");
|
|
1242
|
+
if (fs11.existsSync(binDir) && os7.platform() !== "win32") {
|
|
1243
|
+
for (const file of fs11.readdirSync(binDir)) {
|
|
1244
|
+
fs11.chmodSync(path12.join(binDir, file), 493);
|
|
1144
1245
|
}
|
|
1145
1246
|
}
|
|
1146
1247
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
@@ -1153,16 +1254,16 @@ async function installCmdTools(opts) {
|
|
|
1153
1254
|
}
|
|
1154
1255
|
function getCmdToolsStatus(config) {
|
|
1155
1256
|
const cmdPath = getCmdToolsPath(config);
|
|
1156
|
-
const binDir =
|
|
1157
|
-
const candidates =
|
|
1158
|
-
const bin = candidates.map((c) =>
|
|
1257
|
+
const binDir = path12.join(cmdPath, "bin");
|
|
1258
|
+
const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
1259
|
+
const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
|
|
1159
1260
|
if (!bin) {
|
|
1160
1261
|
return { installed: false, status: "Not installed" };
|
|
1161
1262
|
}
|
|
1162
1263
|
try {
|
|
1163
|
-
const versionFile =
|
|
1164
|
-
if (
|
|
1165
|
-
const content =
|
|
1264
|
+
const versionFile = path12.join(cmdPath, "version.txt");
|
|
1265
|
+
if (fs11.existsSync(versionFile)) {
|
|
1266
|
+
const content = fs11.readFileSync(versionFile, "utf8");
|
|
1166
1267
|
for (const line of content.split(/\r?\n/)) {
|
|
1167
1268
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
1168
1269
|
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
@@ -1189,14 +1290,14 @@ function isCmdToolsInstalled(config) {
|
|
|
1189
1290
|
}
|
|
1190
1291
|
function removeCmdTools(config) {
|
|
1191
1292
|
const cmdPath = getCmdToolsPath(config);
|
|
1192
|
-
if (
|
|
1193
|
-
|
|
1293
|
+
if (fs11.existsSync(cmdPath)) {
|
|
1294
|
+
fs11.rmSync(cmdPath, { recursive: true, force: true });
|
|
1194
1295
|
}
|
|
1195
1296
|
}
|
|
1196
1297
|
|
|
1197
1298
|
// src/sign/encryptKey.ts
|
|
1198
|
-
var
|
|
1199
|
-
var
|
|
1299
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
1300
|
+
var path13 = __toESM(require("path"), 1);
|
|
1200
1301
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
1201
1302
|
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
1202
1303
|
var DAEMON_SALT_KEY_LENGTH = 16;
|
|
@@ -1268,33 +1369,33 @@ function getRootKey(fdComponents, salt) {
|
|
|
1268
1369
|
function createAndStoreKey(dir, keyLength) {
|
|
1269
1370
|
const key = crypto2.randomBytes(keyLength);
|
|
1270
1371
|
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
1271
|
-
const filePath =
|
|
1272
|
-
|
|
1273
|
-
|
|
1372
|
+
const filePath = path13.join(dir, hash);
|
|
1373
|
+
fs12.writeFileSync(filePath, key);
|
|
1374
|
+
fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
1274
1375
|
return key;
|
|
1275
1376
|
}
|
|
1276
1377
|
function createAndStoreEnKey(rootKey, ceDir) {
|
|
1277
1378
|
const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
|
|
1278
1379
|
const encrypted = encrypt(rootKey, workKey);
|
|
1279
1380
|
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
1280
|
-
const filePath =
|
|
1281
|
-
|
|
1282
|
-
|
|
1381
|
+
const filePath = path13.join(ceDir, hash);
|
|
1382
|
+
fs12.writeFileSync(filePath, encrypted);
|
|
1383
|
+
fs12.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
1283
1384
|
}
|
|
1284
1385
|
function createMaterial(materialPath) {
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const fdDir =
|
|
1288
|
-
const acDir =
|
|
1289
|
-
const ceDir =
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1386
|
+
fs12.mkdirSync(materialPath, { recursive: true });
|
|
1387
|
+
fs12.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
1388
|
+
const fdDir = path13.join(materialPath, "fd");
|
|
1389
|
+
const acDir = path13.join(materialPath, "ac");
|
|
1390
|
+
const ceDir = path13.join(materialPath, "ce");
|
|
1391
|
+
fs12.mkdirSync(fdDir, { recursive: true });
|
|
1392
|
+
fs12.mkdirSync(acDir, { recursive: true });
|
|
1393
|
+
fs12.mkdirSync(ceDir, { recursive: true });
|
|
1293
1394
|
const fdSubDirs = ["0", "1", "2"];
|
|
1294
1395
|
const fdComponents = [];
|
|
1295
1396
|
for (const sub of fdSubDirs) {
|
|
1296
|
-
const subDir =
|
|
1297
|
-
|
|
1397
|
+
const subDir = path13.join(fdDir, sub);
|
|
1398
|
+
fs12.mkdirSync(subDir, { recursive: true });
|
|
1298
1399
|
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
1299
1400
|
fdComponents.push(comp);
|
|
1300
1401
|
}
|
|
@@ -1303,24 +1404,24 @@ function createMaterial(materialPath) {
|
|
|
1303
1404
|
createAndStoreEnKey(rootKey, ceDir);
|
|
1304
1405
|
}
|
|
1305
1406
|
function readSingleFile(dir) {
|
|
1306
|
-
if (!
|
|
1407
|
+
if (!fs12.existsSync(dir) || !fs12.statSync(dir).isDirectory()) {
|
|
1307
1408
|
throw new Error(`Missing signing material directory: ${dir}`);
|
|
1308
1409
|
}
|
|
1309
|
-
const files =
|
|
1410
|
+
const files = fs12.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
1310
1411
|
if (files.length !== 1) {
|
|
1311
1412
|
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
1312
1413
|
}
|
|
1313
|
-
return
|
|
1414
|
+
return fs12.readFileSync(path13.join(dir, files[0]));
|
|
1314
1415
|
}
|
|
1315
1416
|
function getKey(materialPath) {
|
|
1316
|
-
if (!
|
|
1417
|
+
if (!fs12.existsSync(materialPath) || !fs12.statSync(materialPath).isDirectory()) {
|
|
1317
1418
|
throw new Error("Material directory does not exist.");
|
|
1318
1419
|
}
|
|
1319
|
-
const fdDir =
|
|
1320
|
-
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(
|
|
1321
|
-
const salt = readSingleFile(
|
|
1420
|
+
const fdDir = path13.join(materialPath, "fd");
|
|
1421
|
+
const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path13.join(fdDir, sub)));
|
|
1422
|
+
const salt = readSingleFile(path13.join(materialPath, "ac"));
|
|
1322
1423
|
const rootKey = getRootKey(fdComponents, salt);
|
|
1323
|
-
const workMaterial = readSingleFile(
|
|
1424
|
+
const workMaterial = readSingleFile(path13.join(materialPath, "ce"));
|
|
1324
1425
|
return decrypt(rootKey, workMaterial);
|
|
1325
1426
|
}
|
|
1326
1427
|
function encryptPwd(password, materialPath) {
|
|
@@ -1335,8 +1436,8 @@ function decryptPwd(encryptedHex, materialPath) {
|
|
|
1335
1436
|
}
|
|
1336
1437
|
|
|
1337
1438
|
// src/sign/generateSigningConfigs.ts
|
|
1338
|
-
var
|
|
1339
|
-
var
|
|
1439
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
1440
|
+
var path14 = __toESM(require("path"), 1);
|
|
1340
1441
|
var import_node_child_process3 = require("child_process");
|
|
1341
1442
|
var import_json52 = __toESM(require("json5"), 1);
|
|
1342
1443
|
|
|
@@ -1406,17 +1507,17 @@ var SIGNING_KIND_CONFIG = {
|
|
|
1406
1507
|
};
|
|
1407
1508
|
function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
|
|
1408
1509
|
logger.info("[sign] Copying signing material into project...");
|
|
1409
|
-
const signaturesDir =
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1510
|
+
const signaturesDir = path14.join(projectDir, "signatures");
|
|
1511
|
+
fs13.mkdirSync(signaturesDir, { recursive: true });
|
|
1512
|
+
fs13.copyFileSync(paths.keystore, path14.join(signaturesDir, "OpenHarmony.p12"));
|
|
1513
|
+
fs13.copyFileSync(paths.profileCert, path14.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
1514
|
+
fs13.copyFileSync(paths.unsignedProfileTemplate, path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
1414
1515
|
if (kind === "application-release") {
|
|
1415
|
-
const dest =
|
|
1516
|
+
const dest = path14.join(signaturesDir, "OpenHarmonyApplication.cer");
|
|
1416
1517
|
if (applicationCertPath) {
|
|
1417
|
-
|
|
1518
|
+
fs13.copyFileSync(applicationCertPath, dest);
|
|
1418
1519
|
} else {
|
|
1419
|
-
|
|
1520
|
+
fs13.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
|
|
1420
1521
|
}
|
|
1421
1522
|
}
|
|
1422
1523
|
}
|
|
@@ -1425,7 +1526,7 @@ function resolveAppFeature(apl, override) {
|
|
|
1425
1526
|
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
1426
1527
|
}
|
|
1427
1528
|
function extractLeafCertificate(chainPath) {
|
|
1428
|
-
const certContent =
|
|
1529
|
+
const certContent = fs13.readFileSync(chainPath, "utf-8");
|
|
1429
1530
|
const parts = certContent.split("-----END CERTIFICATE-----");
|
|
1430
1531
|
if (parts.length < 3) {
|
|
1431
1532
|
throw new OniroError(`${chainPath} does not contain enough certificates.`);
|
|
@@ -1435,20 +1536,20 @@ function extractLeafCertificate(chainPath) {
|
|
|
1435
1536
|
function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
|
|
1436
1537
|
const kind = options.kind ?? pickSigningKind(apl);
|
|
1437
1538
|
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
1438
|
-
const appJsonPath =
|
|
1439
|
-
const profileTemplatePath =
|
|
1440
|
-
if (!
|
|
1539
|
+
const appJsonPath = path14.join(projectDir, "AppScope/app.json5");
|
|
1540
|
+
const profileTemplatePath = path14.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
1541
|
+
if (!fs13.existsSync(appJsonPath)) {
|
|
1441
1542
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
1442
1543
|
}
|
|
1443
1544
|
let appJson;
|
|
1444
1545
|
try {
|
|
1445
|
-
appJson = import_json52.default.parse(
|
|
1546
|
+
appJson = import_json52.default.parse(fs13.readFileSync(appJsonPath, "utf-8"));
|
|
1446
1547
|
} catch (e) {
|
|
1447
1548
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
1448
1549
|
}
|
|
1449
1550
|
let profileTemplate;
|
|
1450
1551
|
try {
|
|
1451
|
-
profileTemplate = JSON.parse(
|
|
1552
|
+
profileTemplate = JSON.parse(fs13.readFileSync(profileTemplatePath, "utf-8"));
|
|
1452
1553
|
} catch (e) {
|
|
1453
1554
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
1454
1555
|
}
|
|
@@ -1461,7 +1562,7 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1461
1562
|
profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
|
|
1462
1563
|
profileTemplate["bundle-info"]["apl"] = apl;
|
|
1463
1564
|
profileTemplate["bundle-info"]["app-feature"] = appFeature;
|
|
1464
|
-
const distCertChainPath =
|
|
1565
|
+
const distCertChainPath = path14.join(
|
|
1465
1566
|
projectDir,
|
|
1466
1567
|
"signatures",
|
|
1467
1568
|
SIGNING_KIND_CONFIG[kind].certBasename
|
|
@@ -1471,13 +1572,13 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1471
1572
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
1472
1573
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
1473
1574
|
}
|
|
1474
|
-
|
|
1575
|
+
fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1475
1576
|
}
|
|
1476
1577
|
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
1477
1578
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
1478
|
-
const signaturesDir =
|
|
1479
|
-
const profileTemplatePath =
|
|
1480
|
-
const outputProfilePath =
|
|
1579
|
+
const signaturesDir = path14.join(projectDir, "signatures");
|
|
1580
|
+
const profileTemplatePath = path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
1581
|
+
const outputProfilePath = path14.join(signaturesDir, "app1-profile.p7b");
|
|
1481
1582
|
const args = [
|
|
1482
1583
|
"-jar",
|
|
1483
1584
|
paths.signTool,
|
|
@@ -1511,11 +1612,11 @@ function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
|
1511
1612
|
}
|
|
1512
1613
|
}
|
|
1513
1614
|
function detectSigningConfigNames(projectDir) {
|
|
1514
|
-
const buildProfilePath =
|
|
1515
|
-
if (!
|
|
1615
|
+
const buildProfilePath = path14.join(projectDir, "build-profile.json5");
|
|
1616
|
+
if (!fs13.existsSync(buildProfilePath)) return ["default"];
|
|
1516
1617
|
let parsed;
|
|
1517
1618
|
try {
|
|
1518
|
-
parsed = import_json52.default.parse(
|
|
1619
|
+
parsed = import_json52.default.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
|
|
1519
1620
|
} catch {
|
|
1520
1621
|
return ["default"];
|
|
1521
1622
|
}
|
|
@@ -1532,14 +1633,14 @@ function detectSigningConfigNames(projectDir) {
|
|
|
1532
1633
|
}
|
|
1533
1634
|
function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
1534
1635
|
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1535
|
-
const materialDir =
|
|
1536
|
-
const buildProfilePath =
|
|
1636
|
+
const materialDir = path14.join(projectDir, "signatures", "material");
|
|
1637
|
+
const buildProfilePath = path14.join(projectDir, "build-profile.json5");
|
|
1537
1638
|
const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
|
|
1538
1639
|
const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
|
|
1539
1640
|
let buildProfile = { app: {} };
|
|
1540
|
-
if (
|
|
1641
|
+
if (fs13.existsSync(buildProfilePath)) {
|
|
1541
1642
|
try {
|
|
1542
|
-
buildProfile = import_json52.default.parse(
|
|
1643
|
+
buildProfile = import_json52.default.parse(fs13.readFileSync(buildProfilePath, "utf-8"));
|
|
1543
1644
|
} catch (e) {
|
|
1544
1645
|
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1545
1646
|
}
|
|
@@ -1560,13 +1661,13 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
|
1560
1661
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1561
1662
|
}
|
|
1562
1663
|
}));
|
|
1563
|
-
|
|
1664
|
+
fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1564
1665
|
}
|
|
1565
1666
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
1566
1667
|
logger.info("[sign] Generating fresh signing material...");
|
|
1567
|
-
const materialDir =
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1668
|
+
const materialDir = path14.join(projectDir, "signatures", "material");
|
|
1669
|
+
if (fs13.existsSync(materialDir)) {
|
|
1670
|
+
fs13.rmSync(materialDir, { recursive: true, force: true });
|
|
1570
1671
|
}
|
|
1571
1672
|
createMaterial(materialDir);
|
|
1572
1673
|
}
|
|
@@ -1589,15 +1690,15 @@ function generateSigningConfigs(options) {
|
|
|
1589
1690
|
"Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
|
|
1590
1691
|
);
|
|
1591
1692
|
}
|
|
1592
|
-
const sdkPath =
|
|
1593
|
-
if (!
|
|
1693
|
+
const sdkPath = path14.join(sdkHome, String(sdkVersion));
|
|
1694
|
+
if (!fs13.existsSync(sdkPath)) {
|
|
1594
1695
|
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1595
1696
|
}
|
|
1596
1697
|
const paths = {
|
|
1597
|
-
signTool:
|
|
1598
|
-
keystore:
|
|
1599
|
-
profileCert:
|
|
1600
|
-
unsignedProfileTemplate:
|
|
1698
|
+
signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1699
|
+
keystore: path14.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1700
|
+
profileCert: path14.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1701
|
+
unsignedProfileTemplate: path14.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1601
1702
|
};
|
|
1602
1703
|
const kind = pickSigningKind(apl);
|
|
1603
1704
|
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
@@ -1611,8 +1712,8 @@ function generateSigningConfigs(options) {
|
|
|
1611
1712
|
}
|
|
1612
1713
|
|
|
1613
1714
|
// src/emulator/install.ts
|
|
1614
|
-
var
|
|
1615
|
-
var
|
|
1715
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
1716
|
+
var path15 = __toESM(require("path"), 1);
|
|
1616
1717
|
async function installEmulator(opts) {
|
|
1617
1718
|
const { config, progress, abortSignal } = opts;
|
|
1618
1719
|
const logger = opts.logger ?? noopLogger;
|
|
@@ -1622,20 +1723,20 @@ async function installEmulator(opts) {
|
|
|
1622
1723
|
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1623
1724
|
config,
|
|
1624
1725
|
override: opts.tmpDir,
|
|
1625
|
-
installRoot:
|
|
1726
|
+
installRoot: path15.dirname(emulatorDir),
|
|
1626
1727
|
prefix: "oniro-emulator-",
|
|
1627
1728
|
logger
|
|
1628
1729
|
});
|
|
1629
|
-
const tmpZip =
|
|
1730
|
+
const tmpZip = path15.join(tmpDir, "oniro_emulator.zip");
|
|
1630
1731
|
try {
|
|
1631
|
-
|
|
1732
|
+
fs14.mkdirSync(emulatorDir, { recursive: true });
|
|
1632
1733
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1633
1734
|
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
1634
1735
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1635
1736
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1636
|
-
const runSh =
|
|
1637
|
-
if (
|
|
1638
|
-
|
|
1737
|
+
const runSh = path15.join(emulatorDir, "images", "run.sh");
|
|
1738
|
+
if (fs14.existsSync(runSh)) {
|
|
1739
|
+
fs14.chmodSync(runSh, 493);
|
|
1639
1740
|
}
|
|
1640
1741
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1641
1742
|
} catch (err) {
|
|
@@ -1645,24 +1746,24 @@ async function installEmulator(opts) {
|
|
|
1645
1746
|
}
|
|
1646
1747
|
}
|
|
1647
1748
|
function isEmulatorInstalled(config) {
|
|
1648
|
-
const imagesDir =
|
|
1649
|
-
return
|
|
1749
|
+
const imagesDir = path15.join(getEmulatorDir(config), "images");
|
|
1750
|
+
return fs14.existsSync(path15.join(imagesDir, "run.sh")) || fs14.existsSync(path15.join(imagesDir, "run.bat"));
|
|
1650
1751
|
}
|
|
1651
1752
|
function removeEmulator(config) {
|
|
1652
1753
|
const emulatorDir = getEmulatorDir(config);
|
|
1653
|
-
if (
|
|
1654
|
-
|
|
1754
|
+
if (fs14.existsSync(emulatorDir)) {
|
|
1755
|
+
fs14.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1655
1756
|
}
|
|
1656
1757
|
}
|
|
1657
1758
|
|
|
1658
1759
|
// src/emulator/lifecycle.ts
|
|
1659
|
-
var
|
|
1660
|
-
var
|
|
1661
|
-
var
|
|
1760
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
1761
|
+
var os8 = __toESM(require("os"), 1);
|
|
1762
|
+
var path16 = __toESM(require("path"), 1);
|
|
1662
1763
|
var import_node_child_process4 = require("child_process");
|
|
1663
1764
|
|
|
1664
1765
|
// src/hdc/exec.ts
|
|
1665
|
-
var
|
|
1766
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
1666
1767
|
function errorMessage(err) {
|
|
1667
1768
|
return err instanceof Error ? err.message : String(err);
|
|
1668
1769
|
}
|
|
@@ -1670,12 +1771,12 @@ function runProcess(opts) {
|
|
|
1670
1771
|
const { command, args, cwd, env, onOutput } = opts;
|
|
1671
1772
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1672
1773
|
const { abortSignal } = opts;
|
|
1673
|
-
return new Promise((
|
|
1774
|
+
return new Promise((resolve6, reject) => {
|
|
1674
1775
|
if (abortSignal?.aborted) {
|
|
1675
1776
|
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1676
1777
|
return;
|
|
1677
1778
|
}
|
|
1678
|
-
if (needsCmdWrapper(command) && !
|
|
1779
|
+
if (needsCmdWrapper(command) && !fs15.existsSync(command)) {
|
|
1679
1780
|
reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
|
|
1680
1781
|
return;
|
|
1681
1782
|
}
|
|
@@ -1735,7 +1836,7 @@ ${stderr.trim()}` : "")
|
|
|
1735
1836
|
);
|
|
1736
1837
|
return;
|
|
1737
1838
|
}
|
|
1738
|
-
|
|
1839
|
+
resolve6({ code: code ?? -1, stdout, stderr });
|
|
1739
1840
|
});
|
|
1740
1841
|
});
|
|
1741
1842
|
}
|
|
@@ -1783,7 +1884,7 @@ function ensureRemoteOk(result, command) {
|
|
|
1783
1884
|
}
|
|
1784
1885
|
|
|
1785
1886
|
// src/emulator/lifecycle.ts
|
|
1786
|
-
var PID_FILE =
|
|
1887
|
+
var PID_FILE = path16.join(os8.tmpdir(), "oniro_emulator.pid");
|
|
1787
1888
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1788
1889
|
try {
|
|
1789
1890
|
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
@@ -1796,24 +1897,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
|
|
|
1796
1897
|
}
|
|
1797
1898
|
}
|
|
1798
1899
|
function resolveLauncher(imagesPath, headless, connect) {
|
|
1799
|
-
const runSh =
|
|
1800
|
-
const runBat =
|
|
1900
|
+
const runSh = path16.join(imagesPath, "run.sh");
|
|
1901
|
+
const runBat = path16.join(imagesPath, "run.bat");
|
|
1801
1902
|
const extra = [];
|
|
1802
1903
|
if (headless) extra.push("--headless");
|
|
1803
1904
|
if (connect) extra.push("--connect", connect);
|
|
1804
|
-
if (
|
|
1805
|
-
if (
|
|
1905
|
+
if (os8.platform() === "win32") {
|
|
1906
|
+
if (fs16.existsSync(runBat)) {
|
|
1806
1907
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1807
1908
|
}
|
|
1808
|
-
if (
|
|
1909
|
+
if (fs16.existsSync(runSh)) {
|
|
1809
1910
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1810
1911
|
}
|
|
1811
1912
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1812
1913
|
}
|
|
1813
|
-
if (
|
|
1914
|
+
if (fs16.existsSync(runSh)) {
|
|
1814
1915
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1815
1916
|
}
|
|
1816
|
-
if (
|
|
1917
|
+
if (fs16.existsSync(runBat)) {
|
|
1817
1918
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1818
1919
|
}
|
|
1819
1920
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1821,9 +1922,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1821
1922
|
async function startEmulator(opts) {
|
|
1822
1923
|
const { config } = opts;
|
|
1823
1924
|
const logger = opts.logger ?? noopLogger;
|
|
1824
|
-
if (
|
|
1925
|
+
if (os8.platform() !== "win32" && fs16.existsSync(PID_FILE)) {
|
|
1825
1926
|
try {
|
|
1826
|
-
const pid = Number(
|
|
1927
|
+
const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
|
|
1827
1928
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1828
1929
|
try {
|
|
1829
1930
|
process.kill(pid, 0);
|
|
@@ -1839,13 +1940,13 @@ async function startEmulator(opts) {
|
|
|
1839
1940
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1840
1941
|
}
|
|
1841
1942
|
}
|
|
1842
|
-
const imagesPath =
|
|
1843
|
-
if (!
|
|
1943
|
+
const imagesPath = path16.join(getEmulatorDir(config), "images");
|
|
1944
|
+
if (!fs16.existsSync(imagesPath)) {
|
|
1844
1945
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1845
1946
|
}
|
|
1846
1947
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1847
|
-
const logPath = opts.logFile ?? (
|
|
1848
|
-
const logFd =
|
|
1948
|
+
const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1949
|
+
const logFd = fs16.openSync(logPath, "a");
|
|
1849
1950
|
let child;
|
|
1850
1951
|
try {
|
|
1851
1952
|
child = (0, import_node_child_process4.spawn)(launcher.command, [...launcher.args], {
|
|
@@ -1855,19 +1956,19 @@ async function startEmulator(opts) {
|
|
|
1855
1956
|
windowsHide: true
|
|
1856
1957
|
});
|
|
1857
1958
|
} finally {
|
|
1858
|
-
|
|
1959
|
+
fs16.closeSync(logFd);
|
|
1859
1960
|
}
|
|
1860
1961
|
if (!child.pid) {
|
|
1861
1962
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1862
1963
|
}
|
|
1863
|
-
|
|
1964
|
+
fs16.writeFileSync(PID_FILE, String(child.pid));
|
|
1864
1965
|
child.unref();
|
|
1865
1966
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1866
|
-
const earlyExit = new Promise((
|
|
1867
|
-
child.once("exit", (code) =>
|
|
1868
|
-
child.once("error", () =>
|
|
1967
|
+
const earlyExit = new Promise((resolve6) => {
|
|
1968
|
+
child.once("exit", (code) => resolve6(code));
|
|
1969
|
+
child.once("error", () => resolve6(-1));
|
|
1869
1970
|
});
|
|
1870
|
-
const liveness = new Promise((
|
|
1971
|
+
const liveness = new Promise((resolve6) => setTimeout(() => resolve6("alive"), 2e3));
|
|
1871
1972
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1872
1973
|
if (outcome !== "alive") {
|
|
1873
1974
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1907,24 +2008,24 @@ ${tail}` : ""}`);
|
|
|
1907
2008
|
}
|
|
1908
2009
|
function readTail(filePath, bytes) {
|
|
1909
2010
|
try {
|
|
1910
|
-
const stat =
|
|
2011
|
+
const stat = fs16.statSync(filePath);
|
|
1911
2012
|
const start = Math.max(0, stat.size - bytes);
|
|
1912
|
-
const fd =
|
|
2013
|
+
const fd = fs16.openSync(filePath, "r");
|
|
1913
2014
|
try {
|
|
1914
2015
|
const buf = Buffer.alloc(stat.size - start);
|
|
1915
|
-
|
|
2016
|
+
fs16.readSync(fd, buf, 0, buf.length, start);
|
|
1916
2017
|
return buf.toString("utf8");
|
|
1917
2018
|
} finally {
|
|
1918
|
-
|
|
2019
|
+
fs16.closeSync(fd);
|
|
1919
2020
|
}
|
|
1920
2021
|
} catch {
|
|
1921
2022
|
return "";
|
|
1922
2023
|
}
|
|
1923
2024
|
}
|
|
1924
2025
|
async function stopEmulator(logger = noopLogger) {
|
|
1925
|
-
if (
|
|
2026
|
+
if (fs16.existsSync(PID_FILE)) {
|
|
1926
2027
|
try {
|
|
1927
|
-
const pid = Number(
|
|
2028
|
+
const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
|
|
1928
2029
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1929
2030
|
try {
|
|
1930
2031
|
process.kill(pid, "SIGTERM");
|
|
@@ -1937,13 +2038,13 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1937
2038
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1938
2039
|
}
|
|
1939
2040
|
}
|
|
1940
|
-
const [killCmd, ...killArgs] =
|
|
2041
|
+
const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
1941
2042
|
try {
|
|
1942
2043
|
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
1943
2044
|
} catch (err) {
|
|
1944
2045
|
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
1945
2046
|
}
|
|
1946
|
-
|
|
2047
|
+
fs16.rm(PID_FILE, { force: true }, (err) => {
|
|
1947
2048
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1948
2049
|
});
|
|
1949
2050
|
}
|
|
@@ -2066,7 +2167,7 @@ async function getDisplaySize(config, opts = {}) {
|
|
|
2066
2167
|
|
|
2067
2168
|
// src/hdc/wait.ts
|
|
2068
2169
|
function delay(ms, signal) {
|
|
2069
|
-
return new Promise((
|
|
2170
|
+
return new Promise((resolve6, reject) => {
|
|
2070
2171
|
if (signal?.aborted) {
|
|
2071
2172
|
reject(new CancelledError("Wait cancelled."));
|
|
2072
2173
|
return;
|
|
@@ -2081,7 +2182,7 @@ function delay(ms, signal) {
|
|
|
2081
2182
|
};
|
|
2082
2183
|
const timer = setTimeout(() => {
|
|
2083
2184
|
cleanup();
|
|
2084
|
-
|
|
2185
|
+
resolve6();
|
|
2085
2186
|
}, ms);
|
|
2086
2187
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2087
2188
|
});
|
|
@@ -2110,20 +2211,20 @@ async function waitForCondition(opts) {
|
|
|
2110
2211
|
}
|
|
2111
2212
|
|
|
2112
2213
|
// src/hdc/app.ts
|
|
2113
|
-
var
|
|
2114
|
-
var
|
|
2214
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
2215
|
+
var path18 = __toESM(require("path"), 1);
|
|
2115
2216
|
|
|
2116
2217
|
// src/hdc/project.ts
|
|
2117
|
-
var
|
|
2118
|
-
var
|
|
2218
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
2219
|
+
var path17 = __toESM(require("path"), 1);
|
|
2119
2220
|
var import_json53 = __toESM(require("json5"), 1);
|
|
2120
2221
|
function readJson5(filePath) {
|
|
2121
|
-
const content =
|
|
2222
|
+
const content = fs17.readFileSync(filePath, "utf-8");
|
|
2122
2223
|
return import_json53.default.parse(content);
|
|
2123
2224
|
}
|
|
2124
2225
|
function getBundleName(projectDir) {
|
|
2125
|
-
const appJsonPath =
|
|
2126
|
-
if (!
|
|
2226
|
+
const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
|
|
2227
|
+
if (!fs17.existsSync(appJsonPath)) {
|
|
2127
2228
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
2128
2229
|
}
|
|
2129
2230
|
const appJson = readJson5(appJsonPath);
|
|
@@ -2133,8 +2234,8 @@ function getBundleName(projectDir) {
|
|
|
2133
2234
|
return appJson.app.bundleName;
|
|
2134
2235
|
}
|
|
2135
2236
|
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
2136
|
-
const moduleJsonPath =
|
|
2137
|
-
if (!
|
|
2237
|
+
const moduleJsonPath = path17.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
2238
|
+
if (!fs17.existsSync(moduleJsonPath)) {
|
|
2138
2239
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2139
2240
|
}
|
|
2140
2241
|
const moduleJson = readJson5(moduleJsonPath);
|
|
@@ -2157,11 +2258,16 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
|
2157
2258
|
async function installApp(opts) {
|
|
2158
2259
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2159
2260
|
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
2160
|
-
const hapPath =
|
|
2161
|
-
if (!
|
|
2261
|
+
const hapPath = path18.isAbsolute(relativeHapPath) ? relativeHapPath : path18.join(opts.projectDir, relativeHapPath);
|
|
2262
|
+
if (!fs18.existsSync(hapPath)) {
|
|
2162
2263
|
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
2163
2264
|
}
|
|
2164
|
-
const result = await hdcExec({
|
|
2265
|
+
const result = await hdcExec({
|
|
2266
|
+
config: opts.config,
|
|
2267
|
+
args: ["install", hapPath],
|
|
2268
|
+
deviceSerial: opts.deviceSerial,
|
|
2269
|
+
timeoutMs: 3e5
|
|
2270
|
+
});
|
|
2165
2271
|
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
2166
2272
|
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
2167
2273
|
ensureOk(result, `hdc install ${hapPath}`);
|
|
@@ -2242,7 +2348,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2242
2348
|
const logger = options.logger ?? noopLogger;
|
|
2243
2349
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
2244
2350
|
const hdc = getHdcPath(config);
|
|
2245
|
-
return new Promise((
|
|
2351
|
+
return new Promise((resolve6, reject) => {
|
|
2246
2352
|
let proc;
|
|
2247
2353
|
try {
|
|
2248
2354
|
proc = spawnCompat(hdc, ["track-jpid"]);
|
|
@@ -2253,7 +2359,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2253
2359
|
const processes = [];
|
|
2254
2360
|
let resolved = false;
|
|
2255
2361
|
proc.stdout?.on("data", (data) => {
|
|
2256
|
-
for (const line of data.toString().split(
|
|
2362
|
+
for (const line of data.toString().split(/\r?\n/).filter(Boolean)) {
|
|
2257
2363
|
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
2258
2364
|
if (!match) continue;
|
|
2259
2365
|
const pid = match[1];
|
|
@@ -2262,7 +2368,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2262
2368
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
2263
2369
|
resolved = true;
|
|
2264
2370
|
killProcessTree(proc);
|
|
2265
|
-
|
|
2371
|
+
resolve6(pid);
|
|
2266
2372
|
return;
|
|
2267
2373
|
}
|
|
2268
2374
|
}
|
|
@@ -2283,7 +2389,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2283
2389
|
if (options.targetProcessName) {
|
|
2284
2390
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2285
2391
|
} else {
|
|
2286
|
-
|
|
2392
|
+
resolve6(processes);
|
|
2287
2393
|
}
|
|
2288
2394
|
}, timeoutMs);
|
|
2289
2395
|
proc.on("close", () => {
|
|
@@ -2293,7 +2399,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2293
2399
|
if (options.targetProcessName) {
|
|
2294
2400
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2295
2401
|
} else {
|
|
2296
|
-
|
|
2402
|
+
resolve6(processes);
|
|
2297
2403
|
}
|
|
2298
2404
|
});
|
|
2299
2405
|
});
|
|
@@ -2437,12 +2543,12 @@ async function reboot(opts) {
|
|
|
2437
2543
|
}
|
|
2438
2544
|
|
|
2439
2545
|
// src/hdc/screen.ts
|
|
2440
|
-
var
|
|
2441
|
-
var
|
|
2442
|
-
var
|
|
2546
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
2547
|
+
var os9 = __toESM(require("os"), 1);
|
|
2548
|
+
var path19 = __toESM(require("path"), 1);
|
|
2443
2549
|
var import_node_crypto = require("crypto");
|
|
2444
2550
|
function tmpName(ext) {
|
|
2445
|
-
return
|
|
2551
|
+
return path19.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
|
|
2446
2552
|
}
|
|
2447
2553
|
async function takeScreenshot(opts) {
|
|
2448
2554
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -2452,11 +2558,11 @@ async function takeScreenshot(opts) {
|
|
|
2452
2558
|
const local = tmpName("jpeg");
|
|
2453
2559
|
try {
|
|
2454
2560
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2455
|
-
const pixels = await
|
|
2561
|
+
const pixels = await fs19.promises.readFile(local);
|
|
2456
2562
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2457
2563
|
return { pixels, width: size.width, height: size.height };
|
|
2458
2564
|
} finally {
|
|
2459
|
-
|
|
2565
|
+
fs19.promises.unlink(local).catch(() => {
|
|
2460
2566
|
});
|
|
2461
2567
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2462
2568
|
});
|
|
@@ -2475,9 +2581,9 @@ async function captureBurst(opts) {
|
|
|
2475
2581
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
2476
2582
|
try {
|
|
2477
2583
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2478
|
-
frames.push(await
|
|
2584
|
+
frames.push(await fs19.promises.readFile(local));
|
|
2479
2585
|
} finally {
|
|
2480
|
-
|
|
2586
|
+
fs19.promises.unlink(local).catch(() => {
|
|
2481
2587
|
});
|
|
2482
2588
|
}
|
|
2483
2589
|
}
|
|
@@ -2603,9 +2709,9 @@ async function sendRawTouch(opts) {
|
|
|
2603
2709
|
}
|
|
2604
2710
|
|
|
2605
2711
|
// src/hdc/dump.ts
|
|
2606
|
-
var
|
|
2607
|
-
var
|
|
2608
|
-
var
|
|
2712
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
2713
|
+
var os10 = __toESM(require("os"), 1);
|
|
2714
|
+
var path20 = __toESM(require("path"), 1);
|
|
2609
2715
|
var import_node_crypto2 = require("crypto");
|
|
2610
2716
|
function parseBounds(s) {
|
|
2611
2717
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
@@ -2690,10 +2796,10 @@ async function dumpLayout(opts) {
|
|
|
2690
2796
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2691
2797
|
${dump.stdout}`);
|
|
2692
2798
|
const remote = m[1];
|
|
2693
|
-
const local =
|
|
2799
|
+
const local = path20.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
|
|
2694
2800
|
try {
|
|
2695
2801
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2696
|
-
const parsed = JSON.parse(await
|
|
2802
|
+
const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
|
|
2697
2803
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2698
2804
|
if (opts.prune === false) {
|
|
2699
2805
|
return { display, tree: parsed };
|
|
@@ -2702,7 +2808,7 @@ ${dump.stdout}`);
|
|
|
2702
2808
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2703
2809
|
return { display, tree };
|
|
2704
2810
|
} finally {
|
|
2705
|
-
|
|
2811
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2706
2812
|
});
|
|
2707
2813
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2708
2814
|
});
|
|
@@ -2713,7 +2819,7 @@ ${dump.stdout}`);
|
|
|
2713
2819
|
function setHilogLevel(opts) {
|
|
2714
2820
|
const logger = opts.logger ?? noopLogger;
|
|
2715
2821
|
const hdc = getHdcPath(opts.config);
|
|
2716
|
-
return new Promise((
|
|
2822
|
+
return new Promise((resolve6, reject) => {
|
|
2717
2823
|
let child;
|
|
2718
2824
|
try {
|
|
2719
2825
|
child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
@@ -2733,7 +2839,7 @@ function setHilogLevel(opts) {
|
|
|
2733
2839
|
});
|
|
2734
2840
|
child.once("close", (code) => {
|
|
2735
2841
|
if (code === 0) {
|
|
2736
|
-
|
|
2842
|
+
resolve6();
|
|
2737
2843
|
} else {
|
|
2738
2844
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
2739
2845
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -2788,7 +2894,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
|
|
|
2788
2894
|
return proc?.pid;
|
|
2789
2895
|
}
|
|
2790
2896
|
function waitForLog(opts) {
|
|
2791
|
-
return new Promise((
|
|
2897
|
+
return new Promise((resolve6, reject) => {
|
|
2792
2898
|
const deadline = Date.now() + opts.timeoutMs;
|
|
2793
2899
|
let settled = false;
|
|
2794
2900
|
let child = null;
|
|
@@ -2807,7 +2913,7 @@ function waitForLog(opts) {
|
|
|
2807
2913
|
if (settled) return;
|
|
2808
2914
|
settled = true;
|
|
2809
2915
|
cleanup();
|
|
2810
|
-
|
|
2916
|
+
resolve6(entry);
|
|
2811
2917
|
};
|
|
2812
2918
|
const fail = (err) => {
|
|
2813
2919
|
if (settled) return;
|
|
@@ -2830,10 +2936,9 @@ function waitForLog(opts) {
|
|
|
2830
2936
|
child = stream;
|
|
2831
2937
|
stream.stdout.on("data", (d) => {
|
|
2832
2938
|
buf += d.toString();
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
buf = buf.slice(idx + 1);
|
|
2939
|
+
const lines = buf.split(/\r?\n/);
|
|
2940
|
+
buf = lines.pop() ?? "";
|
|
2941
|
+
for (const line of lines) {
|
|
2837
2942
|
const entry = parseHilogLine(line);
|
|
2838
2943
|
if (entry && opts.pattern.test(entryText(entry))) {
|
|
2839
2944
|
finish(entry);
|
|
@@ -2855,7 +2960,7 @@ function waitForLog(opts) {
|
|
|
2855
2960
|
});
|
|
2856
2961
|
}
|
|
2857
2962
|
function watchLog(opts) {
|
|
2858
|
-
return new Promise((
|
|
2963
|
+
return new Promise((resolve6, reject) => {
|
|
2859
2964
|
const matches = [];
|
|
2860
2965
|
let settled = false;
|
|
2861
2966
|
let child = null;
|
|
@@ -2872,7 +2977,7 @@ function watchLog(opts) {
|
|
|
2872
2977
|
if (settled) return;
|
|
2873
2978
|
settled = true;
|
|
2874
2979
|
cleanup();
|
|
2875
|
-
|
|
2980
|
+
resolve6(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2876
2981
|
};
|
|
2877
2982
|
const onAbort = () => {
|
|
2878
2983
|
if (settled) return;
|
|
@@ -2889,10 +2994,9 @@ function watchLog(opts) {
|
|
|
2889
2994
|
child = stream;
|
|
2890
2995
|
stream.stdout.on("data", (d) => {
|
|
2891
2996
|
buf += d.toString();
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
buf = buf.slice(idx + 1);
|
|
2997
|
+
const lines = buf.split(/\r?\n/);
|
|
2998
|
+
buf = lines.pop() ?? "";
|
|
2999
|
+
for (const line of lines) {
|
|
2896
3000
|
const entry = parseHilogLine(line);
|
|
2897
3001
|
if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
|
|
2898
3002
|
}
|
|
@@ -2925,12 +3029,47 @@ async function dumpLog(opts) {
|
|
|
2925
3029
|
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
2926
3030
|
logger: opts.logger
|
|
2927
3031
|
});
|
|
2928
|
-
return res.stdout.split(
|
|
3032
|
+
return res.stdout.split(/\r?\n/).map((line) => parseHilogLine(line)).filter((e) => e !== null);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
// src/build/runHvigorw.ts
|
|
3036
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
3037
|
+
var path22 = __toESM(require("path"), 1);
|
|
3038
|
+
|
|
3039
|
+
// src/harmonyos/target.ts
|
|
3040
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
3041
|
+
var path21 = __toESM(require("path"), 1);
|
|
3042
|
+
var import_json54 = __toESM(require("json5"), 1);
|
|
3043
|
+
function detectRuntimeOs(projectDir, productName) {
|
|
3044
|
+
let products = [];
|
|
3045
|
+
try {
|
|
3046
|
+
const parsed = import_json54.default.parse(fs21.readFileSync(path21.join(projectDir, "build-profile.json5"), "utf-8"));
|
|
3047
|
+
if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
|
|
3048
|
+
} catch {
|
|
3049
|
+
return "OpenHarmony";
|
|
3050
|
+
}
|
|
3051
|
+
const product = products.find((p) => p.name === productName) ?? products[0];
|
|
3052
|
+
return product?.runtimeOS === "HarmonyOS" ? "HarmonyOS" : "OpenHarmony";
|
|
3053
|
+
}
|
|
3054
|
+
var HARMONYOS_SDK_VERSIONS = /* @__PURE__ */ new Map([
|
|
3055
|
+
[12, "5.0.0"],
|
|
3056
|
+
[13, "5.0.1"],
|
|
3057
|
+
[14, "5.0.2"],
|
|
3058
|
+
[15, "5.0.3"],
|
|
3059
|
+
[16, "5.0.4"],
|
|
3060
|
+
[17, "5.0.5"],
|
|
3061
|
+
[18, "5.1.0"],
|
|
3062
|
+
[19, "5.1.1"],
|
|
3063
|
+
[20, "6.0.0"],
|
|
3064
|
+
[21, "6.0.1"],
|
|
3065
|
+
[24, "6.1.1"]
|
|
3066
|
+
]);
|
|
3067
|
+
function harmonyOsSdkLabelForApi(api) {
|
|
3068
|
+
const version = HARMONYOS_SDK_VERSIONS.get(api);
|
|
3069
|
+
return version ? `${version}(${api})` : void 0;
|
|
2929
3070
|
}
|
|
2930
3071
|
|
|
2931
3072
|
// src/build/runHvigorw.ts
|
|
2932
|
-
var fs20 = __toESM(require("fs"), 1);
|
|
2933
|
-
var path20 = __toESM(require("path"), 1);
|
|
2934
3073
|
function buildHvigorwArgs(opts) {
|
|
2935
3074
|
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2936
3075
|
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
@@ -2945,28 +3084,36 @@ function runHvigorw(opts) {
|
|
|
2945
3084
|
const { config } = opts;
|
|
2946
3085
|
const projectDir = toLongPath(opts.projectDir);
|
|
2947
3086
|
const logger = opts.logger ?? noopLogger;
|
|
2948
|
-
if (!
|
|
3087
|
+
if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
|
|
2949
3088
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
2950
3089
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
3090
|
+
let hvigorw;
|
|
3091
|
+
let sdkEnv;
|
|
3092
|
+
if (detectRuntimeOs(projectDir, opts.product) === "HarmonyOS") {
|
|
3093
|
+
const sdk = requireHarmonyOsSdk({ config, logger });
|
|
3094
|
+
sdkEnv = { DEVECO_SDK_HOME: sdk.sdkPath };
|
|
3095
|
+
const resolved = getHvigorwPath(config, projectDir);
|
|
3096
|
+
hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
|
|
3097
|
+
logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
|
|
3098
|
+
} else {
|
|
3099
|
+
if (!fs22.existsSync(getCmdToolsPath(config))) {
|
|
3100
|
+
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
3101
|
+
}
|
|
3102
|
+
sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
|
|
3103
|
+
hvigorw = getHvigorwPath(config, projectDir);
|
|
2953
3104
|
}
|
|
2954
|
-
|
|
2955
|
-
if (!fs20.existsSync(hvigorw)) {
|
|
3105
|
+
if (!fs22.existsSync(hvigorw)) {
|
|
2956
3106
|
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
2957
3107
|
}
|
|
2958
3108
|
const args = buildHvigorwArgs(opts);
|
|
2959
3109
|
if (process.platform !== "win32") {
|
|
2960
3110
|
try {
|
|
2961
|
-
|
|
3111
|
+
fs22.chmodSync(hvigorw, 493);
|
|
2962
3112
|
} catch {
|
|
2963
3113
|
}
|
|
2964
3114
|
}
|
|
2965
|
-
const env = {
|
|
2966
|
-
|
|
2967
|
-
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
2968
|
-
};
|
|
2969
|
-
return new Promise((resolve4, reject) => {
|
|
3115
|
+
const env = { ...process.env, ...sdkEnv };
|
|
3116
|
+
return new Promise((resolve6, reject) => {
|
|
2970
3117
|
if (opts.abortSignal?.aborted) {
|
|
2971
3118
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
2972
3119
|
return;
|
|
@@ -3010,15 +3157,15 @@ function runHvigorw(opts) {
|
|
|
3010
3157
|
settled = true;
|
|
3011
3158
|
cleanup();
|
|
3012
3159
|
const exitCode = code ?? 1;
|
|
3013
|
-
if (exitCode === 0)
|
|
3160
|
+
if (exitCode === 0) resolve6({ exitCode });
|
|
3014
3161
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
3015
3162
|
});
|
|
3016
3163
|
});
|
|
3017
3164
|
}
|
|
3018
3165
|
|
|
3019
3166
|
// src/build/discoverHaps.ts
|
|
3020
|
-
var
|
|
3021
|
-
var
|
|
3167
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
3168
|
+
var path23 = __toESM(require("path"), 1);
|
|
3022
3169
|
function moduleOfHap(hapFileName) {
|
|
3023
3170
|
const dash = hapFileName.indexOf("-");
|
|
3024
3171
|
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
@@ -3029,9 +3176,9 @@ async function discoverHaps(opts) {
|
|
|
3029
3176
|
const result = {};
|
|
3030
3177
|
const walk = async (dir, depth) => {
|
|
3031
3178
|
if (depth > maxDepth) return;
|
|
3032
|
-
const entries = await
|
|
3179
|
+
const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3033
3180
|
for (const e of entries) {
|
|
3034
|
-
const full =
|
|
3181
|
+
const full = path23.join(dir, e.name);
|
|
3035
3182
|
if (e.isDirectory()) {
|
|
3036
3183
|
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
3037
3184
|
await walk(full, depth + 1);
|
|
@@ -3048,11 +3195,11 @@ async function discoverHaps(opts) {
|
|
|
3048
3195
|
}
|
|
3049
3196
|
|
|
3050
3197
|
// src/build/ohpm.ts
|
|
3051
|
-
var
|
|
3052
|
-
var
|
|
3198
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
3199
|
+
var path24 = __toESM(require("path"), 1);
|
|
3053
3200
|
function runOhpm(opts) {
|
|
3054
3201
|
return runProcess({
|
|
3055
|
-
command: getOhpmPath(opts.config),
|
|
3202
|
+
command: opts.ohpmPath ?? getOhpmPath(opts.config),
|
|
3056
3203
|
args: [...opts.args],
|
|
3057
3204
|
cwd: opts.projectDir,
|
|
3058
3205
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
@@ -3062,12 +3209,13 @@ function runOhpm(opts) {
|
|
|
3062
3209
|
});
|
|
3063
3210
|
}
|
|
3064
3211
|
async function ensureOhModules(opts) {
|
|
3065
|
-
if (
|
|
3212
|
+
if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
|
|
3066
3213
|
return { installed: false };
|
|
3067
3214
|
}
|
|
3068
3215
|
const res = await runOhpm({
|
|
3069
3216
|
config: opts.config,
|
|
3070
3217
|
projectDir: opts.projectDir,
|
|
3218
|
+
ohpmPath: opts.ohpmPath,
|
|
3071
3219
|
args: ["install", "--all"],
|
|
3072
3220
|
abortSignal: opts.abortSignal,
|
|
3073
3221
|
onOutput: opts.onOutput,
|
|
@@ -3115,10 +3263,13 @@ async function buildHap(opts) {
|
|
|
3115
3263
|
const warnings = [];
|
|
3116
3264
|
const start = Date.now();
|
|
3117
3265
|
const projectDir = toLongPath(opts.projectDir);
|
|
3266
|
+
const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
|
|
3118
3267
|
if (opts.autoInstallDeps !== false) {
|
|
3268
|
+
const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
|
|
3119
3269
|
const r = await ensureOhModules({
|
|
3120
3270
|
config: opts.config,
|
|
3121
3271
|
projectDir,
|
|
3272
|
+
ohpmPath: (harmonySdk && findHarmonyOsTool(harmonySdk, "ohpm")) ?? void 0,
|
|
3122
3273
|
abortSignal: opts.abortSignal,
|
|
3123
3274
|
onOutput: opts.onOutput,
|
|
3124
3275
|
logger: opts.logger
|
|
@@ -3141,7 +3292,7 @@ async function buildHap(opts) {
|
|
|
3141
3292
|
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
3142
3293
|
if (!anySigned) {
|
|
3143
3294
|
warnings.push(
|
|
3144
|
-
"No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
|
|
3295
|
+
isHarmonyOs ? "No signed HAPs found \u2014 run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
|
|
3145
3296
|
);
|
|
3146
3297
|
}
|
|
3147
3298
|
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
@@ -3158,21 +3309,21 @@ function isValidBundleName(bundleName) {
|
|
|
3158
3309
|
}
|
|
3159
3310
|
|
|
3160
3311
|
// src/project/templates.ts
|
|
3161
|
-
var
|
|
3162
|
-
var
|
|
3312
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
3313
|
+
var path25 = __toESM(require("path"), 1);
|
|
3163
3314
|
function toHumanTemplateName(folderName) {
|
|
3164
3315
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
3165
3316
|
}
|
|
3166
3317
|
function listTemplates(templateRoot) {
|
|
3167
|
-
if (!
|
|
3168
|
-
const entries =
|
|
3318
|
+
if (!fs25.existsSync(templateRoot)) return [];
|
|
3319
|
+
const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
|
|
3169
3320
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
3170
|
-
const dir =
|
|
3171
|
-
const metaPath =
|
|
3321
|
+
const dir = path25.join(templateRoot, e.name);
|
|
3322
|
+
const metaPath = path25.join(dir, "template.json");
|
|
3172
3323
|
let meta = {};
|
|
3173
|
-
if (
|
|
3324
|
+
if (fs25.existsSync(metaPath)) {
|
|
3174
3325
|
try {
|
|
3175
|
-
meta = JSON.parse(
|
|
3326
|
+
meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
|
|
3176
3327
|
} catch {
|
|
3177
3328
|
}
|
|
3178
3329
|
}
|
|
@@ -3187,57 +3338,57 @@ function listTemplates(templateRoot) {
|
|
|
3187
3338
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
3188
3339
|
const required = [
|
|
3189
3340
|
"build-profile.json5",
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3341
|
+
path25.join("AppScope", "app.json5"),
|
|
3342
|
+
path25.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3343
|
+
path25.join(defaultModuleName, "oh-package.json5"),
|
|
3193
3344
|
"hvigorfile.ts"
|
|
3194
3345
|
];
|
|
3195
|
-
return required.filter((rel) => !
|
|
3346
|
+
return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
|
|
3196
3347
|
}
|
|
3197
3348
|
|
|
3198
3349
|
// src/project/createScaffold.ts
|
|
3199
|
-
var
|
|
3200
|
-
var
|
|
3350
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
3351
|
+
var path26 = __toESM(require("path"), 1);
|
|
3201
3352
|
|
|
3202
3353
|
// src/project/jsonHelpers.ts
|
|
3203
|
-
var
|
|
3204
|
-
var
|
|
3354
|
+
var fs26 = __toESM(require("fs"), 1);
|
|
3355
|
+
var import_json55 = __toESM(require("json5"), 1);
|
|
3205
3356
|
function readJson5File(filePath) {
|
|
3206
|
-
return
|
|
3357
|
+
return import_json55.default.parse(fs26.readFileSync(filePath, "utf8"));
|
|
3207
3358
|
}
|
|
3208
3359
|
function writeJson5File(filePath, value) {
|
|
3209
|
-
|
|
3360
|
+
fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3210
3361
|
}
|
|
3211
3362
|
function readJsonFile(filePath) {
|
|
3212
|
-
return JSON.parse(
|
|
3363
|
+
return JSON.parse(fs26.readFileSync(filePath, "utf8"));
|
|
3213
3364
|
}
|
|
3214
3365
|
function writeJsonFile(filePath, value) {
|
|
3215
|
-
|
|
3366
|
+
fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3216
3367
|
}
|
|
3217
3368
|
|
|
3218
3369
|
// src/project/createScaffold.ts
|
|
3219
3370
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
3220
3371
|
async function pathExists(p) {
|
|
3221
3372
|
try {
|
|
3222
|
-
await
|
|
3373
|
+
await fs27.promises.access(p);
|
|
3223
3374
|
return true;
|
|
3224
3375
|
} catch {
|
|
3225
3376
|
return false;
|
|
3226
3377
|
}
|
|
3227
3378
|
}
|
|
3228
3379
|
async function copyDirRecursive(srcDir, destDir) {
|
|
3229
|
-
await
|
|
3230
|
-
const entries = await
|
|
3380
|
+
await fs27.promises.mkdir(destDir, { recursive: true });
|
|
3381
|
+
const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
|
|
3231
3382
|
for (const entry of entries) {
|
|
3232
|
-
const src =
|
|
3233
|
-
const dest =
|
|
3383
|
+
const src = path26.join(srcDir, entry.name);
|
|
3384
|
+
const dest = path26.join(destDir, entry.name);
|
|
3234
3385
|
if (entry.isDirectory()) {
|
|
3235
3386
|
await copyDirRecursive(src, dest);
|
|
3236
3387
|
} else if (entry.isSymbolicLink()) {
|
|
3237
3388
|
continue;
|
|
3238
3389
|
} else if (entry.isFile()) {
|
|
3239
3390
|
if (entry.name === "template.json") continue;
|
|
3240
|
-
await
|
|
3391
|
+
await fs27.promises.copyFile(src, dest);
|
|
3241
3392
|
}
|
|
3242
3393
|
}
|
|
3243
3394
|
}
|
|
@@ -3245,9 +3396,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
3245
3396
|
const stack = [projectDir];
|
|
3246
3397
|
while (stack.length > 0) {
|
|
3247
3398
|
const currentDir = stack.pop();
|
|
3248
|
-
const entries = await
|
|
3399
|
+
const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
|
|
3249
3400
|
for (const entry of entries) {
|
|
3250
|
-
const fullPath =
|
|
3401
|
+
const fullPath = path26.join(currentDir, entry.name);
|
|
3251
3402
|
if (entry.isDirectory()) {
|
|
3252
3403
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3253
3404
|
stack.push(fullPath);
|
|
@@ -3269,23 +3420,23 @@ function toJavaPropertiesPath(p) {
|
|
|
3269
3420
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
3270
3421
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
3271
3422
|
`;
|
|
3272
|
-
|
|
3423
|
+
fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
|
|
3273
3424
|
}
|
|
3274
3425
|
function renameIfExists(fromPath, toPath) {
|
|
3275
|
-
if (
|
|
3276
|
-
|
|
3426
|
+
if (fs27.existsSync(fromPath) && fromPath !== toPath) {
|
|
3427
|
+
fs27.renameSync(fromPath, toPath);
|
|
3277
3428
|
}
|
|
3278
3429
|
}
|
|
3279
3430
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
3280
|
-
const appJsonPath =
|
|
3281
|
-
if (
|
|
3431
|
+
const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
|
|
3432
|
+
if (fs27.existsSync(appJsonPath)) {
|
|
3282
3433
|
const appJson = readJson5File(appJsonPath);
|
|
3283
3434
|
appJson.app = appJson.app ?? {};
|
|
3284
3435
|
appJson.app.bundleName = args.bundleName;
|
|
3285
3436
|
writeJson5File(appJsonPath, appJson);
|
|
3286
3437
|
}
|
|
3287
|
-
const stringsPath =
|
|
3288
|
-
if (
|
|
3438
|
+
const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
3439
|
+
if (fs27.existsSync(stringsPath)) {
|
|
3289
3440
|
const strings = readJsonFile(stringsPath);
|
|
3290
3441
|
if (Array.isArray(strings.string)) {
|
|
3291
3442
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -3293,13 +3444,13 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3293
3444
|
}
|
|
3294
3445
|
writeJsonFile(stringsPath, strings);
|
|
3295
3446
|
}
|
|
3296
|
-
const buildProfilePath =
|
|
3297
|
-
if (
|
|
3447
|
+
const buildProfilePath = path26.join(projectDir, "build-profile.json5");
|
|
3448
|
+
if (fs27.existsSync(buildProfilePath)) {
|
|
3298
3449
|
const buildProfile = readJson5File(buildProfilePath);
|
|
3299
3450
|
buildProfile.app = buildProfile.app ?? {};
|
|
3300
3451
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
3301
|
-
buildProfile.app.products[0].compileSdkVersion = args.
|
|
3302
|
-
buildProfile.app.products[0].compatibleSdkVersion = args.
|
|
3452
|
+
buildProfile.app.products[0].compileSdkVersion = args.sdkVersion;
|
|
3453
|
+
buildProfile.app.products[0].compatibleSdkVersion = args.sdkVersion;
|
|
3303
3454
|
}
|
|
3304
3455
|
if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
|
|
3305
3456
|
buildProfile.modules[0].name = args.moduleName;
|
|
@@ -3307,23 +3458,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3307
3458
|
}
|
|
3308
3459
|
writeJson5File(buildProfilePath, buildProfile);
|
|
3309
3460
|
}
|
|
3310
|
-
const moduleJsonPath =
|
|
3311
|
-
if (
|
|
3461
|
+
const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
3462
|
+
if (fs27.existsSync(moduleJsonPath)) {
|
|
3312
3463
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
3313
3464
|
moduleJson.module = moduleJson.module ?? {};
|
|
3314
3465
|
moduleJson.module.name = args.moduleName;
|
|
3315
3466
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
3316
3467
|
}
|
|
3317
|
-
const moduleOhPackagePath =
|
|
3318
|
-
if (
|
|
3468
|
+
const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
|
|
3469
|
+
if (fs27.existsSync(moduleOhPackagePath)) {
|
|
3319
3470
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
3320
3471
|
pkg.name = args.moduleName;
|
|
3321
3472
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
3322
3473
|
}
|
|
3323
|
-
const vscodeDir =
|
|
3324
|
-
|
|
3474
|
+
const vscodeDir = path26.join(projectDir, ".vscode");
|
|
3475
|
+
fs27.mkdirSync(vscodeDir, { recursive: true });
|
|
3325
3476
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
3326
|
-
writeJsonFile(
|
|
3477
|
+
writeJsonFile(path26.join(vscodeDir, "settings.json"), {
|
|
3327
3478
|
"oniro.hapPath": hapPath,
|
|
3328
3479
|
"files.associations": { "*.json5": "jsonc" }
|
|
3329
3480
|
});
|
|
@@ -3340,7 +3491,7 @@ async function createScaffold(opts) {
|
|
|
3340
3491
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
3341
3492
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
3342
3493
|
}
|
|
3343
|
-
const templateDir =
|
|
3494
|
+
const templateDir = path26.join(opts.templateRoot, opts.templateId);
|
|
3344
3495
|
if (!await pathExists(templateDir)) {
|
|
3345
3496
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
3346
3497
|
}
|
|
@@ -3353,38 +3504,60 @@ async function createScaffold(opts) {
|
|
|
3353
3504
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
3354
3505
|
- ${missing.join("\n- ")}`);
|
|
3355
3506
|
}
|
|
3356
|
-
|
|
3507
|
+
let sdkVersion = opts.sdkApi;
|
|
3508
|
+
let sdkBaseDir = getOhosBaseSdkHome(opts.config);
|
|
3509
|
+
if (detectRuntimeOs(templateDir) === "HarmonyOS") {
|
|
3510
|
+
const harmonySdk = findHarmonyOsSdk({ config: opts.config, logger });
|
|
3511
|
+
const label = harmonySdk?.version?.endsWith(`(${opts.sdkApi})`) ? harmonySdk.version : harmonyOsSdkLabelForApi(opts.sdkApi);
|
|
3512
|
+
if (!label) {
|
|
3513
|
+
throw new OniroError(
|
|
3514
|
+
`No HarmonyOS release is known for API ${opts.sdkApi}. Known: ${[...HARMONYOS_SDK_VERSIONS.keys()].join(", ")}` + (harmonySdk?.version ? `, and the installed SDK's ${harmonySdk.version}.` : ".")
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
sdkVersion = label;
|
|
3518
|
+
if (!harmonySdk) {
|
|
3519
|
+
logger.warn("[create] No HarmonyOS SDK found; set ONIRO_HARMONYOS_SDK_PATH before building.");
|
|
3520
|
+
} else {
|
|
3521
|
+
sdkBaseDir = harmonySdk.sdkPath;
|
|
3522
|
+
if (harmonySdk.version && harmonySdk.version !== label) {
|
|
3523
|
+
logger.warn(
|
|
3524
|
+
`[create] The HarmonyOS SDK at ${harmonySdk.sdkPath} is ${harmonySdk.version}, so a project declaring ${label} will not build against it. Install ${label}, or create with the installed API level.`
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
const projectDir = path26.join(opts.location, opts.projectName);
|
|
3357
3530
|
if (await pathExists(projectDir)) {
|
|
3358
3531
|
if (!opts.overwrite) {
|
|
3359
3532
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
3360
3533
|
}
|
|
3361
|
-
await
|
|
3534
|
+
await fs27.promises.rm(projectDir, { recursive: true, force: true });
|
|
3362
3535
|
}
|
|
3363
3536
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
3364
3537
|
await copyDirRecursive(templateDir, projectDir);
|
|
3365
3538
|
if (moduleName !== defaultModuleName) {
|
|
3366
|
-
renameIfExists(
|
|
3539
|
+
renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
|
|
3367
3540
|
}
|
|
3368
3541
|
updateTemplateConfigs(
|
|
3369
3542
|
projectDir,
|
|
3370
3543
|
{
|
|
3371
3544
|
projectName: opts.projectName,
|
|
3372
3545
|
bundleName: opts.bundleName,
|
|
3373
|
-
|
|
3546
|
+
sdkVersion,
|
|
3374
3547
|
moduleName
|
|
3375
3548
|
},
|
|
3376
|
-
|
|
3549
|
+
sdkBaseDir
|
|
3377
3550
|
);
|
|
3378
3551
|
await normalizeJson5ToJson(projectDir, logger);
|
|
3379
3552
|
return { projectDir };
|
|
3380
3553
|
}
|
|
3381
3554
|
|
|
3382
3555
|
// src/project/listModules.ts
|
|
3383
|
-
var
|
|
3384
|
-
var
|
|
3556
|
+
var fs28 = __toESM(require("fs"), 1);
|
|
3557
|
+
var path27 = __toESM(require("path"), 1);
|
|
3385
3558
|
function listModules(opts) {
|
|
3386
|
-
const buildProfile =
|
|
3387
|
-
if (!
|
|
3559
|
+
const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
|
|
3560
|
+
if (!fs28.existsSync(buildProfile)) {
|
|
3388
3561
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
3389
3562
|
}
|
|
3390
3563
|
const parsed = readJson5File(buildProfile);
|
|
@@ -3396,12 +3569,12 @@ function listModules(opts) {
|
|
|
3396
3569
|
}
|
|
3397
3570
|
|
|
3398
3571
|
// src/project/abilities.ts
|
|
3399
|
-
var
|
|
3400
|
-
var
|
|
3572
|
+
var fs29 = __toESM(require("fs"), 1);
|
|
3573
|
+
var path28 = __toESM(require("path"), 1);
|
|
3401
3574
|
function listAbilities(opts) {
|
|
3402
3575
|
const moduleName = opts.moduleName ?? "entry";
|
|
3403
|
-
const moduleJsonPath =
|
|
3404
|
-
if (!
|
|
3576
|
+
const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
3577
|
+
if (!fs29.existsSync(moduleJsonPath)) {
|
|
3405
3578
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
3406
3579
|
}
|
|
3407
3580
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -3409,12 +3582,12 @@ function listAbilities(opts) {
|
|
|
3409
3582
|
}
|
|
3410
3583
|
|
|
3411
3584
|
// src/project/prepareSigning.ts
|
|
3412
|
-
var
|
|
3413
|
-
var
|
|
3585
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
3586
|
+
var path29 = __toESM(require("path"), 1);
|
|
3414
3587
|
function hasSigningArtifacts(dir) {
|
|
3415
|
-
if (!
|
|
3416
|
-
const buildProfile =
|
|
3417
|
-
if (!
|
|
3588
|
+
if (!fs30.existsSync(path29.join(dir, "signatures"))) return false;
|
|
3589
|
+
const buildProfile = path29.join(dir, "build-profile.json5");
|
|
3590
|
+
if (!fs30.existsSync(buildProfile)) return false;
|
|
3418
3591
|
try {
|
|
3419
3592
|
const parsed = readJson5File(buildProfile);
|
|
3420
3593
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -3504,8 +3677,8 @@ async function diffHapAssets(opts) {
|
|
|
3504
3677
|
}
|
|
3505
3678
|
|
|
3506
3679
|
// src/install/apply.ts
|
|
3507
|
-
var
|
|
3508
|
-
var
|
|
3680
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
3681
|
+
var path30 = __toESM(require("path"), 1);
|
|
3509
3682
|
function decideInstallMethod(s) {
|
|
3510
3683
|
if (!s.signInfoInconsistent) return "replace";
|
|
3511
3684
|
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
@@ -3526,7 +3699,7 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
3526
3699
|
async function resolveHapPath(opts) {
|
|
3527
3700
|
let hapPath = opts.hapPath;
|
|
3528
3701
|
if (hapPath) {
|
|
3529
|
-
if (!
|
|
3702
|
+
if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
|
|
3530
3703
|
} else {
|
|
3531
3704
|
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
3532
3705
|
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
@@ -3545,7 +3718,7 @@ async function resolveHapPath(opts) {
|
|
|
3545
3718
|
hapPath = withSigned[0][1].signed[0];
|
|
3546
3719
|
}
|
|
3547
3720
|
}
|
|
3548
|
-
if (!
|
|
3721
|
+
if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3549
3722
|
return hapPath;
|
|
3550
3723
|
}
|
|
3551
3724
|
async function applyChanges(opts) {
|
|
@@ -3555,7 +3728,7 @@ async function applyChanges(opts) {
|
|
|
3555
3728
|
const allowUninstall = opts.allowUninstall ?? false;
|
|
3556
3729
|
const hapPath = await resolveHapPath(opts);
|
|
3557
3730
|
let assetsChanged = false;
|
|
3558
|
-
if (opts.installedHapPath &&
|
|
3731
|
+
if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
|
|
3559
3732
|
try {
|
|
3560
3733
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3561
3734
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -3596,21 +3769,1202 @@ async function applyChanges(opts) {
|
|
|
3596
3769
|
return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
|
|
3597
3770
|
}
|
|
3598
3771
|
|
|
3772
|
+
// src/harmonyos/auth/endpoints.ts
|
|
3773
|
+
var HUAWEI_APP_ID = "1009";
|
|
3774
|
+
var HUAWEI_API_VERSION = "1.0.0";
|
|
3775
|
+
var LOGIN_TIMEOUT_MS = 6e5;
|
|
3776
|
+
var HUAWEI_SITES = {
|
|
3777
|
+
CN: {
|
|
3778
|
+
code: "CN",
|
|
3779
|
+
siteId: "1",
|
|
3780
|
+
siteParam: "CN",
|
|
3781
|
+
loginUrl: "https://cn.devecostudio.huawei.com",
|
|
3782
|
+
agcBaseUrl: "https://connect-api.cloud.huawei.com",
|
|
3783
|
+
language: "zh_CN"
|
|
3784
|
+
},
|
|
3785
|
+
SG: {
|
|
3786
|
+
code: "SG",
|
|
3787
|
+
siteId: "5",
|
|
3788
|
+
siteParam: "SG",
|
|
3789
|
+
loginUrl: "https://sg.devecostudio.huawei.com",
|
|
3790
|
+
agcBaseUrl: "https://connect-api-dra.cloud.huawei.com",
|
|
3791
|
+
language: "zh_CN"
|
|
3792
|
+
},
|
|
3793
|
+
EU: {
|
|
3794
|
+
code: "EU",
|
|
3795
|
+
siteId: "7",
|
|
3796
|
+
siteParam: "DE",
|
|
3797
|
+
loginUrl: "https://de.devecostudio.huawei.com",
|
|
3798
|
+
agcBaseUrl: "https://connect-api-dre.cloud.huawei.com",
|
|
3799
|
+
language: "de_DE"
|
|
3800
|
+
},
|
|
3801
|
+
RU: {
|
|
3802
|
+
code: "RU",
|
|
3803
|
+
siteId: "8",
|
|
3804
|
+
siteParam: "RU",
|
|
3805
|
+
loginUrl: "https://ru.devecostudio.huawei.com",
|
|
3806
|
+
agcBaseUrl: "https://connect-api-drru.cloud.huawei.com",
|
|
3807
|
+
language: "ru_RU"
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
var AGC_TEAMS_URL = "https://connect-api.cloud.huawei.com/api/ups/user-permission-service/v1/user-team-list";
|
|
3811
|
+
function siteForId(siteId) {
|
|
3812
|
+
return Object.values(HUAWEI_SITES).find((s) => s.siteId === siteId) ?? null;
|
|
3813
|
+
}
|
|
3814
|
+
var AUTH_PATHS = {
|
|
3815
|
+
/** The browser authenticates here, then redirects to the loopback server. */
|
|
3816
|
+
AUTH_APPLY: "console/DevEcoIDE/apply",
|
|
3817
|
+
/** Exchanges the browser's temp token for a JWT. */
|
|
3818
|
+
TEMP_TOKEN_CHECK: "authrouter/auth/api/temptoken/check",
|
|
3819
|
+
/** Validates a JWT and, with `refresh`, mints an AGC access token. */
|
|
3820
|
+
JWT_TOKEN_CHECK: "authrouter/auth/api/jwToken/check",
|
|
3821
|
+
LOGIN_SUCCESS: "console/DevEcoCLI/loginSuccess",
|
|
3822
|
+
LOGIN_FAILED: "console/DevEcoCLI/loginFailed",
|
|
3823
|
+
LOGOUT: "authrouter/auth/api/logout",
|
|
3824
|
+
/**
|
|
3825
|
+
* Whether an account without real-name verification accepted the developer
|
|
3826
|
+
* agreement, which is what lets it sign. From DevEco Studio (`space.agr.url`).
|
|
3827
|
+
*/
|
|
3828
|
+
UNREALNAME_AGREEMENT: "authrouter/unrealname/agreement"
|
|
3829
|
+
};
|
|
3830
|
+
|
|
3831
|
+
// src/harmonyos/auth/tokenStore.ts
|
|
3832
|
+
var fs32 = __toESM(require("fs"), 1);
|
|
3833
|
+
var os11 = __toESM(require("os"), 1);
|
|
3834
|
+
var path31 = __toESM(require("path"), 1);
|
|
3835
|
+
var crypto3 = __toESM(require("crypto"), 1);
|
|
3836
|
+
var ALGORITHM = "aes-256-gcm";
|
|
3837
|
+
var KEY_LENGTH = 32;
|
|
3838
|
+
var IV_LENGTH = 12;
|
|
3839
|
+
var TOKEN_FILE = "token.enc";
|
|
3840
|
+
var KEY_FILE = "kek.bin";
|
|
3841
|
+
var DIR_MODE = 448;
|
|
3842
|
+
var FILE_MODE = 384;
|
|
3843
|
+
function encrypt2(key, plaintext) {
|
|
3844
|
+
const iv = crypto3.randomBytes(IV_LENGTH);
|
|
3845
|
+
const cipher = crypto3.createCipheriv(ALGORITHM, key, iv);
|
|
3846
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
3847
|
+
return {
|
|
3848
|
+
version: 1,
|
|
3849
|
+
algorithm: ALGORITHM,
|
|
3850
|
+
ciphertext: ciphertext.toString("base64"),
|
|
3851
|
+
iv: iv.toString("base64"),
|
|
3852
|
+
authTag: cipher.getAuthTag().toString("base64")
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
function decrypt2(key, blob) {
|
|
3856
|
+
const decipher = crypto3.createDecipheriv(ALGORITHM, key, Buffer.from(blob.iv, "base64"));
|
|
3857
|
+
decipher.setAuthTag(Buffer.from(blob.authTag, "base64"));
|
|
3858
|
+
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "base64")), decipher.final()]);
|
|
3859
|
+
}
|
|
3860
|
+
function isEncryptedBlob(value) {
|
|
3861
|
+
if (!value || typeof value !== "object") return false;
|
|
3862
|
+
const b = value;
|
|
3863
|
+
return b.algorithm === ALGORITHM && typeof b.ciphertext === "string" && typeof b.iv === "string" && typeof b.authTag === "string";
|
|
3864
|
+
}
|
|
3865
|
+
function permissionHint(dir) {
|
|
3866
|
+
return process.platform === "win32" ? `Permission denied writing ${dir}. Grant write permission to that directory.` : `Permission denied writing ${dir}. Try: chown -R "$(whoami)" ${dir}`;
|
|
3867
|
+
}
|
|
3868
|
+
function ensureDir(dir) {
|
|
3869
|
+
try {
|
|
3870
|
+
fs32.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
3871
|
+
} catch (err) {
|
|
3872
|
+
if (err.code === "EACCES") {
|
|
3873
|
+
throw new OniroError(permissionHint(dir), err);
|
|
3874
|
+
}
|
|
3875
|
+
throw err;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
function createTokenStore(opts) {
|
|
3879
|
+
const logger = opts.logger ?? noopLogger;
|
|
3880
|
+
const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
|
|
3881
|
+
const tokenPath = path31.join(authDir, TOKEN_FILE);
|
|
3882
|
+
const keyPath = path31.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
|
|
3883
|
+
function loadOrCreateKey() {
|
|
3884
|
+
if (fs32.existsSync(keyPath)) {
|
|
3885
|
+
const existing = fs32.readFileSync(keyPath);
|
|
3886
|
+
if (existing.length === KEY_LENGTH) return existing;
|
|
3887
|
+
logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
|
|
3888
|
+
}
|
|
3889
|
+
ensureDir(path31.dirname(keyPath));
|
|
3890
|
+
const key = crypto3.randomBytes(KEY_LENGTH);
|
|
3891
|
+
fs32.writeFileSync(keyPath, key, { mode: FILE_MODE });
|
|
3892
|
+
return key;
|
|
3893
|
+
}
|
|
3894
|
+
return {
|
|
3895
|
+
tokenPath,
|
|
3896
|
+
save(session) {
|
|
3897
|
+
if (!session.jwtToken) throw new OniroError("Refusing to store an empty token.");
|
|
3898
|
+
const payload = Buffer.from(JSON.stringify(session), "utf8");
|
|
3899
|
+
const blob = encrypt2(loadOrCreateKey(), payload);
|
|
3900
|
+
ensureDir(authDir);
|
|
3901
|
+
fs32.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
|
|
3902
|
+
},
|
|
3903
|
+
load() {
|
|
3904
|
+
if (!fs32.existsSync(tokenPath)) return null;
|
|
3905
|
+
try {
|
|
3906
|
+
const blob = JSON.parse(fs32.readFileSync(tokenPath, "utf8"));
|
|
3907
|
+
if (!isEncryptedBlob(blob)) return null;
|
|
3908
|
+
const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
|
|
3909
|
+
return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
|
|
3910
|
+
} catch (err) {
|
|
3911
|
+
const code = err.code;
|
|
3912
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
3913
|
+
logger.warn(`[harmonyos] Cannot read ${tokenPath}: ${permissionHint(authDir)}`);
|
|
3914
|
+
return null;
|
|
3915
|
+
}
|
|
3916
|
+
logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
|
|
3917
|
+
try {
|
|
3918
|
+
fs32.rmSync(tokenPath, { force: true });
|
|
3919
|
+
} catch {
|
|
3920
|
+
}
|
|
3921
|
+
return null;
|
|
3922
|
+
}
|
|
3923
|
+
},
|
|
3924
|
+
clear() {
|
|
3925
|
+
try {
|
|
3926
|
+
fs32.rmSync(tokenPath, { force: true });
|
|
3927
|
+
} catch (err) {
|
|
3928
|
+
throw new OniroError(`Failed to remove ${tokenPath}.`, err);
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
};
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
// src/harmonyos/auth/session.ts
|
|
3935
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
3936
|
+
|
|
3937
|
+
// src/harmonyos/http.ts
|
|
3938
|
+
var import_undici = require("undici");
|
|
3939
|
+
var DEFAULT_HEADERS = {
|
|
3940
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
3941
|
+
"accept-language": "zh-CN",
|
|
3942
|
+
accept: "application/json, text/plain, */*"
|
|
3943
|
+
};
|
|
3944
|
+
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
3945
|
+
var SENSITIVE_PARAMS = /* @__PURE__ */ new Set(["temptoken", "jwttoken", "code", "oauth2token", "accesstoken"]);
|
|
3946
|
+
function redactUrl(url) {
|
|
3947
|
+
let parsed;
|
|
3948
|
+
try {
|
|
3949
|
+
parsed = new URL(url);
|
|
3950
|
+
} catch {
|
|
3951
|
+
return url;
|
|
3952
|
+
}
|
|
3953
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
3954
|
+
if (SENSITIVE_PARAMS.has(key.toLowerCase())) parsed.searchParams.set(key, "***");
|
|
3955
|
+
}
|
|
3956
|
+
return parsed.toString();
|
|
3957
|
+
}
|
|
3958
|
+
function previewBody(body, limit = 300) {
|
|
3959
|
+
const trimmed = body.trim();
|
|
3960
|
+
if (!trimmed) return "(empty body)";
|
|
3961
|
+
if (/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(trimmed)) return "(a token-shaped value)";
|
|
3962
|
+
const collapsed = trimmed.replace(/\s+/g, " ");
|
|
3963
|
+
return collapsed.length > limit ? `${collapsed.slice(0, limit)}\u2026` : collapsed;
|
|
3964
|
+
}
|
|
3965
|
+
function describeResponse(response) {
|
|
3966
|
+
const status = `HTTP ${response.statusCode}${response.statusText ? ` ${response.statusText}` : ""}`;
|
|
3967
|
+
return `${status}: ${previewBody(response.data)}`;
|
|
3968
|
+
}
|
|
3969
|
+
function buildUrl(url, query) {
|
|
3970
|
+
if (!query) return url;
|
|
3971
|
+
const parsed = new URL(url);
|
|
3972
|
+
for (const [key, value] of Object.entries(query)) {
|
|
3973
|
+
if (value !== void 0 && value !== null) parsed.searchParams.set(key, String(value));
|
|
3974
|
+
}
|
|
3975
|
+
return parsed.toString();
|
|
3976
|
+
}
|
|
3977
|
+
function createHttpClient(opts = {}) {
|
|
3978
|
+
const logger = opts.logger ?? noopLogger;
|
|
3979
|
+
const dispatcher = new import_undici.EnvHttpProxyAgent();
|
|
3980
|
+
async function send(method, url, options) {
|
|
3981
|
+
const target = buildUrl(url, options.query);
|
|
3982
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
3983
|
+
const headers = { ...DEFAULT_HEADERS, ...options.headers };
|
|
3984
|
+
let body;
|
|
3985
|
+
if (options.body !== void 0) {
|
|
3986
|
+
body = JSON.stringify(options.body);
|
|
3987
|
+
headers["content-type"] = "application/json";
|
|
3988
|
+
}
|
|
3989
|
+
logger.debug(`[harmonyos] ${method} ${redactUrl(target)}`);
|
|
3990
|
+
try {
|
|
3991
|
+
return await (0, import_undici.fetch)(target, {
|
|
3992
|
+
method,
|
|
3993
|
+
headers,
|
|
3994
|
+
body,
|
|
3995
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
3996
|
+
dispatcher
|
|
3997
|
+
});
|
|
3998
|
+
} catch (err) {
|
|
3999
|
+
const reason = err instanceof Error && err.name === "TimeoutError" ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
|
|
4000
|
+
throw new OniroError(
|
|
4001
|
+
`${method} ${redactUrl(target)} failed: ${reason}. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).`,
|
|
4002
|
+
err
|
|
4003
|
+
);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
async function request(method, url, options = {}) {
|
|
4007
|
+
const response = await send(method, url, options);
|
|
4008
|
+
return { data: await response.text(), statusCode: response.status, statusText: response.statusText };
|
|
4009
|
+
}
|
|
4010
|
+
return {
|
|
4011
|
+
get: (url, o) => request("GET", url, o),
|
|
4012
|
+
post: (url, o) => request("POST", url, o),
|
|
4013
|
+
delete: (url, o) => request("DELETE", url, o),
|
|
4014
|
+
async getBinary(url, o = {}) {
|
|
4015
|
+
const response = await send("GET", url, o);
|
|
4016
|
+
if (!response.ok) {
|
|
4017
|
+
throw new OniroError(`Download of ${redactUrl(response.url || url)} failed (HTTP ${response.status}).`);
|
|
4018
|
+
}
|
|
4019
|
+
return Buffer.from(await response.arrayBuffer());
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
// src/harmonyos/auth/callbackServer.ts
|
|
4025
|
+
var http2 = __toESM(require("http"), 1);
|
|
4026
|
+
var crypto4 = __toESM(require("crypto"), 1);
|
|
4027
|
+
var CALLBACK_PATH = "/callback";
|
|
4028
|
+
var MAX_BODY_BYTES = 65536;
|
|
4029
|
+
function codeMatches(code, secret) {
|
|
4030
|
+
const a = Buffer.from(code ?? "", "utf8");
|
|
4031
|
+
const b = Buffer.from(secret, "utf8");
|
|
4032
|
+
return a.length === b.length && crypto4.timingSafeEqual(a, b);
|
|
4033
|
+
}
|
|
4034
|
+
function isQuit(quit) {
|
|
4035
|
+
return quit === "true" || quit === "access_denied" || quit === "quit";
|
|
4036
|
+
}
|
|
4037
|
+
async function startCallbackServer(opts) {
|
|
4038
|
+
let resolveCallback;
|
|
4039
|
+
let rejectCallback;
|
|
4040
|
+
const callback = new Promise((resolve6, reject) => {
|
|
4041
|
+
resolveCallback = resolve6;
|
|
4042
|
+
rejectCallback = reject;
|
|
4043
|
+
});
|
|
4044
|
+
callback.catch(() => {
|
|
4045
|
+
});
|
|
4046
|
+
const server = http2.createServer((req, res) => {
|
|
4047
|
+
const host = (req.headers.host ?? "").toLowerCase();
|
|
4048
|
+
const port2 = server.address()?.port;
|
|
4049
|
+
if (host !== `127.0.0.1:${port2}` && host !== `localhost:${port2}`) {
|
|
4050
|
+
res.writeHead(400);
|
|
4051
|
+
res.end("Bad Host");
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
const url = new URL(req.url ?? "", `http://${host}`);
|
|
4055
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
4056
|
+
res.writeHead(404);
|
|
4057
|
+
res.end("Not Found");
|
|
4058
|
+
return;
|
|
4059
|
+
}
|
|
4060
|
+
const finish = (params) => {
|
|
4061
|
+
if (!codeMatches(params.get("code"), opts.clientSecret)) {
|
|
4062
|
+
res.writeHead(400);
|
|
4063
|
+
res.end("Bad Request");
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
if (isQuit(params.get("quit"))) {
|
|
4067
|
+
res.writeHead(302, { Location: `${opts.baseUrl}/${opts.failedPath}` });
|
|
4068
|
+
res.end();
|
|
4069
|
+
rejectCallback(new CancelledError("Login was cancelled in the browser."));
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
const tempToken = params.get("tempToken");
|
|
4073
|
+
const siteId = params.get("siteId");
|
|
4074
|
+
if (!tempToken || !siteId) {
|
|
4075
|
+
res.writeHead(400);
|
|
4076
|
+
res.end("Bad Request");
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
4079
|
+
res.writeHead(302, { Location: `${opts.baseUrl}/${opts.successPath}` });
|
|
4080
|
+
res.end();
|
|
4081
|
+
resolveCallback({ tempToken, siteId });
|
|
4082
|
+
};
|
|
4083
|
+
if (req.method === "POST") {
|
|
4084
|
+
let body = "";
|
|
4085
|
+
let size = 0;
|
|
4086
|
+
req.on("data", (chunk) => {
|
|
4087
|
+
size += chunk.length;
|
|
4088
|
+
if (size > MAX_BODY_BYTES) {
|
|
4089
|
+
req.destroy(new Error("Request body too large"));
|
|
4090
|
+
return;
|
|
4091
|
+
}
|
|
4092
|
+
body += chunk.toString();
|
|
4093
|
+
});
|
|
4094
|
+
req.on("end", () => finish(body.trim() ? new URLSearchParams(body) : url.searchParams));
|
|
4095
|
+
} else {
|
|
4096
|
+
finish(url.searchParams);
|
|
4097
|
+
}
|
|
4098
|
+
});
|
|
4099
|
+
server.keepAliveTimeout = 1;
|
|
4100
|
+
await new Promise((resolve6, reject) => {
|
|
4101
|
+
server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
|
|
4102
|
+
server.listen(0, "127.0.0.1", resolve6);
|
|
4103
|
+
});
|
|
4104
|
+
const port = server.address().port;
|
|
4105
|
+
return {
|
|
4106
|
+
port,
|
|
4107
|
+
waitForCallback(timeoutMs) {
|
|
4108
|
+
let timer;
|
|
4109
|
+
const timeout = new Promise((_, reject) => {
|
|
4110
|
+
timer = setTimeout(
|
|
4111
|
+
() => reject(new OniroError(`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for the browser login to complete.`)),
|
|
4112
|
+
timeoutMs
|
|
4113
|
+
);
|
|
4114
|
+
});
|
|
4115
|
+
return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
|
|
4116
|
+
},
|
|
4117
|
+
stop() {
|
|
4118
|
+
return new Promise((resolve6) => {
|
|
4119
|
+
server.closeAllConnections?.();
|
|
4120
|
+
const timer = setTimeout(resolve6, 100);
|
|
4121
|
+
server.close(() => {
|
|
4122
|
+
clearTimeout(timer);
|
|
4123
|
+
resolve6();
|
|
4124
|
+
});
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
// src/harmonyos/auth/browser.ts
|
|
4131
|
+
var import_node_child_process5 = require("child_process");
|
|
4132
|
+
function isSafeUrl(url) {
|
|
4133
|
+
try {
|
|
4134
|
+
const parsed = new URL(url);
|
|
4135
|
+
return ["http:", "https:"].includes(parsed.protocol) && parsed.hostname !== "" && !url.includes('"');
|
|
4136
|
+
} catch {
|
|
4137
|
+
return false;
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
function escapeForCmd(s) {
|
|
4141
|
+
return s.replace(/[&|<>()^%!]/g, (c) => `^${c}`);
|
|
4142
|
+
}
|
|
4143
|
+
function openBrowser(url) {
|
|
4144
|
+
if (!isSafeUrl(url)) {
|
|
4145
|
+
return Promise.reject(new OniroError(`Refusing to open unsafe URL: ${JSON.stringify(url)}`));
|
|
4146
|
+
}
|
|
4147
|
+
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", '""', escapeForCmd(url)]] : process.platform === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
|
|
4148
|
+
const child = (0, import_node_child_process5.spawn)(command, args, {
|
|
4149
|
+
stdio: "ignore",
|
|
4150
|
+
shell: false,
|
|
4151
|
+
detached: true,
|
|
4152
|
+
windowsHide: true
|
|
4153
|
+
});
|
|
4154
|
+
child.unref();
|
|
4155
|
+
return new Promise((resolve6, reject) => {
|
|
4156
|
+
child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
|
|
4157
|
+
child.on(
|
|
4158
|
+
"exit",
|
|
4159
|
+
(code) => code === 0 ? resolve6() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
|
|
4160
|
+
);
|
|
4161
|
+
});
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4164
|
+
// src/harmonyos/auth/session.ts
|
|
4165
|
+
var NOT_SIGNED_IN = "Not signed in. Run `oniro-app auth login` first.";
|
|
4166
|
+
function createHarmonyOsSession(opts) {
|
|
4167
|
+
const logger = opts.logger ?? noopLogger;
|
|
4168
|
+
const http3 = opts.http ?? createHttpClient({ logger });
|
|
4169
|
+
const store = opts.tokenStore ?? createTokenStore({ config: opts.config, logger });
|
|
4170
|
+
async function exchangeTempToken(site, tempToken) {
|
|
4171
|
+
const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.TEMP_TOKEN_CHECK}`, {
|
|
4172
|
+
query: {
|
|
4173
|
+
// The page sometimes appends extra `&`-joined parameters to the token.
|
|
4174
|
+
tempToken: tempToken.split("&")[0],
|
|
4175
|
+
site: site.siteParam,
|
|
4176
|
+
version: HUAWEI_API_VERSION,
|
|
4177
|
+
appid: HUAWEI_APP_ID
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
const jwtToken = response.data.trim();
|
|
4181
|
+
if (response.statusCode === 200 && /^[^.]+\.[^.]+\.[^.]+$/.test(jwtToken)) return jwtToken;
|
|
4182
|
+
throw new OniroError(
|
|
4183
|
+
`${site.loginUrl} would not exchange the login token for a session (${describeResponse(response)}). Login tokens are single-use and short-lived; run \`oniro-app auth login\` again.`
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
async function userFromJwt(site, jwtToken, refresh) {
|
|
4187
|
+
const response = await http3.get(`${site.loginUrl}/${AUTH_PATHS.JWT_TOKEN_CHECK}`, {
|
|
4188
|
+
headers: { refresh: String(refresh), jwtToken }
|
|
4189
|
+
});
|
|
4190
|
+
let info;
|
|
4191
|
+
try {
|
|
4192
|
+
if (response.statusCode !== 200) throw new Error();
|
|
4193
|
+
info = JSON.parse(response.data);
|
|
4194
|
+
} catch {
|
|
4195
|
+
throw new OniroError(`Could not validate the session with ${site.loginUrl} (${describeResponse(response)}).`);
|
|
4196
|
+
}
|
|
4197
|
+
const user = info.userInfo;
|
|
4198
|
+
if (!info.status || !user?.accessToken) return null;
|
|
4199
|
+
return {
|
|
4200
|
+
userId: user.userId ?? "",
|
|
4201
|
+
userName: user.name ?? "",
|
|
4202
|
+
accessToken: user.accessToken,
|
|
4203
|
+
countryCode: user.nationalCode ?? "",
|
|
4204
|
+
siteCode: site.code,
|
|
4205
|
+
isRealName: String(user.realName) === "true"
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
async function getUserInfo(o = {}) {
|
|
4209
|
+
const stored = store.load();
|
|
4210
|
+
const site = stored && siteForId(stored.siteId);
|
|
4211
|
+
const user = site ? await userFromJwt(site, stored.jwtToken, o.refresh ?? false) : null;
|
|
4212
|
+
if (stored && !user) {
|
|
4213
|
+
logger.debug("[harmonyos] The stored session is no longer valid; clearing it.");
|
|
4214
|
+
store.clear();
|
|
4215
|
+
}
|
|
4216
|
+
return user;
|
|
4217
|
+
}
|
|
4218
|
+
async function requireUser() {
|
|
4219
|
+
const user = await getUserInfo({ refresh: true });
|
|
4220
|
+
if (!user) throw new OniroError(NOT_SIGNED_IN);
|
|
4221
|
+
return user;
|
|
4222
|
+
}
|
|
4223
|
+
async function getDeveloperAgreement(user) {
|
|
4224
|
+
try {
|
|
4225
|
+
const response = await http3.get(`${HUAWEI_SITES[user.siteCode].loginUrl}/${AUTH_PATHS.UNREALNAME_AGREEMENT}`, {
|
|
4226
|
+
headers: { accessToken: user.accessToken }
|
|
4227
|
+
});
|
|
4228
|
+
const payload = JSON.parse(response.data);
|
|
4229
|
+
if (response.statusCode !== 200 || String(payload.success) !== "true" || !payload.body) return null;
|
|
4230
|
+
return { signed: payload.body.signedFlag === true, latest: payload.body.signLastestFlag === true };
|
|
4231
|
+
} catch {
|
|
4232
|
+
return null;
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
return {
|
|
4236
|
+
tokenPath: store.tokenPath,
|
|
4237
|
+
getUserInfo,
|
|
4238
|
+
getDeveloperAgreement,
|
|
4239
|
+
async login(o = {}) {
|
|
4240
|
+
const start = HUAWEI_SITES.CN.loginUrl;
|
|
4241
|
+
const clientSecret = crypto5.randomUUID().replace(/-/g, "");
|
|
4242
|
+
const server = await startCallbackServer({
|
|
4243
|
+
clientSecret,
|
|
4244
|
+
baseUrl: start,
|
|
4245
|
+
successPath: AUTH_PATHS.LOGIN_SUCCESS,
|
|
4246
|
+
failedPath: AUTH_PATHS.LOGIN_FAILED
|
|
4247
|
+
});
|
|
4248
|
+
try {
|
|
4249
|
+
const loginUrl = `${start}/${AUTH_PATHS.AUTH_APPLY}?port=${server.port}&appid=${HUAWEI_APP_ID}&code=${clientSecret}`;
|
|
4250
|
+
opts.onLoginUrl?.(loginUrl);
|
|
4251
|
+
openBrowser(loginUrl).catch((err) => {
|
|
4252
|
+
logger.warn(`[harmonyos] Could not open a browser automatically: ${err.message}`);
|
|
4253
|
+
});
|
|
4254
|
+
const callback = await server.waitForCallback(o.timeoutMs ?? LOGIN_TIMEOUT_MS);
|
|
4255
|
+
const site = siteForId(callback.siteId);
|
|
4256
|
+
if (!site) {
|
|
4257
|
+
throw new OniroError(`The account belongs to a Huawei region oniro-app does not know (siteId ${callback.siteId}).`);
|
|
4258
|
+
}
|
|
4259
|
+
const jwtToken = await exchangeTempToken(site, callback.tempToken);
|
|
4260
|
+
const user = await userFromJwt(site, jwtToken, false);
|
|
4261
|
+
if (!user) throw new OniroError("Login did not yield a usable session. Try again.");
|
|
4262
|
+
store.save({ jwtToken, siteId: site.siteId });
|
|
4263
|
+
logger.info(`[harmonyos] Signed in as ${user.userName || user.userId} (${site.code}).`);
|
|
4264
|
+
return user;
|
|
4265
|
+
} finally {
|
|
4266
|
+
await server.stop();
|
|
4267
|
+
}
|
|
4268
|
+
},
|
|
4269
|
+
async logout() {
|
|
4270
|
+
const stored = store.load();
|
|
4271
|
+
if (!stored) return false;
|
|
4272
|
+
const site = siteForId(stored.siteId);
|
|
4273
|
+
try {
|
|
4274
|
+
if (site) {
|
|
4275
|
+
await http3.post(`${site.loginUrl}/${AUTH_PATHS.LOGOUT}`, {
|
|
4276
|
+
query: { jwtToken: stored.jwtToken },
|
|
4277
|
+
timeoutMs: 5e3
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
} catch {
|
|
4281
|
+
} finally {
|
|
4282
|
+
store.clear();
|
|
4283
|
+
}
|
|
4284
|
+
return true;
|
|
4285
|
+
},
|
|
4286
|
+
async listTeams() {
|
|
4287
|
+
const user = await requireUser();
|
|
4288
|
+
const response = await http3.get(AGC_TEAMS_URL, {
|
|
4289
|
+
headers: {
|
|
4290
|
+
oauth2Token: user.accessToken,
|
|
4291
|
+
uid: user.userId,
|
|
4292
|
+
source: "cli",
|
|
4293
|
+
lang: HUAWEI_SITES[user.siteCode].language
|
|
4294
|
+
},
|
|
4295
|
+
timeoutMs: 15e3
|
|
4296
|
+
});
|
|
4297
|
+
if (response.statusCode === 401) {
|
|
4298
|
+
throw new OniroError("The session has expired. Run `oniro-app auth login` again.");
|
|
4299
|
+
}
|
|
4300
|
+
if (response.statusCode !== 200) {
|
|
4301
|
+
throw new OniroError(`Could not list AGC teams (${describeResponse(response)}).`);
|
|
4302
|
+
}
|
|
4303
|
+
return parseTeamList(response.data);
|
|
4304
|
+
},
|
|
4305
|
+
async resolveAgcAuth(o = {}) {
|
|
4306
|
+
const user = await requireUser();
|
|
4307
|
+
if (!user.isRealName) {
|
|
4308
|
+
const agreement = await getDeveloperAgreement(user);
|
|
4309
|
+
if (agreement && !(agreement.signed && agreement.latest)) {
|
|
4310
|
+
throw new OniroError(
|
|
4311
|
+
"This Huawei account has not accepted the current HUAWEI Developer Basic Service Agreement, which accounts without real-name verification need before AppGallery Connect issues signing material. Accept it in AppGallery Connect (https://developer.huawei.com/consumer/en/service/josp/agc/index.html) or by running automatic signing once in DevEco Studio, then try again."
|
|
4312
|
+
);
|
|
4313
|
+
}
|
|
4314
|
+
}
|
|
4315
|
+
return {
|
|
4316
|
+
uid: user.userId,
|
|
4317
|
+
// Personal accounts sign under their own user id; `--team-id` picks another team.
|
|
4318
|
+
teamId: o.teamId ?? user.userId,
|
|
4319
|
+
accessToken: user.accessToken,
|
|
4320
|
+
baseUrl: HUAWEI_SITES[user.siteCode].agcBaseUrl
|
|
4321
|
+
};
|
|
4322
|
+
}
|
|
4323
|
+
};
|
|
4324
|
+
}
|
|
4325
|
+
function parseTeamList(body) {
|
|
4326
|
+
let payload;
|
|
4327
|
+
try {
|
|
4328
|
+
payload = JSON.parse(body);
|
|
4329
|
+
} catch {
|
|
4330
|
+
return [];
|
|
4331
|
+
}
|
|
4332
|
+
if (payload.ret && payload.ret.code !== 0) {
|
|
4333
|
+
throw new OniroError(
|
|
4334
|
+
`AppGallery Connect refused the team list (code ${payload.ret.code}${payload.ret.msg ? `: ${payload.ret.msg}` : ""}).`
|
|
4335
|
+
);
|
|
4336
|
+
}
|
|
4337
|
+
if (!Array.isArray(payload.teams)) return [];
|
|
4338
|
+
return payload.teams.map((t) => ({ id: String(t?.id ?? ""), name: String(t?.name ?? "") })).filter((t) => t.id);
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
// src/harmonyos/signing/errors.ts
|
|
4342
|
+
var AgcError = class extends OniroError {
|
|
4343
|
+
constructor(message, statusCode, retCode) {
|
|
4344
|
+
super(message);
|
|
4345
|
+
this.statusCode = statusCode;
|
|
4346
|
+
this.retCode = retCode;
|
|
4347
|
+
this.name = "AgcError";
|
|
4348
|
+
}
|
|
4349
|
+
statusCode;
|
|
4350
|
+
retCode;
|
|
4351
|
+
};
|
|
4352
|
+
var SIGNING_ERRORS = {
|
|
4353
|
+
FORBIDDEN: "This account has no AppGallery Connect permission for the selected team. Ask the team administrator for access, or pass --team-id for a team you do have access to (`oniro-app auth team list`).",
|
|
4354
|
+
UNAUTHORIZED: "AppGallery Connect rejected the access token. Run `oniro-app auth login` again.",
|
|
4355
|
+
NETWORK: "Could not reach AppGallery Connect. Check the network connection and any proxy configuration (HTTPS_PROXY / HTTP_PROXY).",
|
|
4356
|
+
USER_NOT_HARMONY: "This Huawei account is not enrolled for HarmonyOS development. Request HarmonyOS access for it in AppGallery Connect.",
|
|
4357
|
+
CERT_REQUEST: "AppGallery Connect did not issue the debug certificate.",
|
|
4358
|
+
CERT_LIMIT: "This team has reached its debug-certificate limit. Delete an unused certificate in AppGallery Connect and retry.",
|
|
4359
|
+
DEVICE_ADD: "AppGallery Connect did not register the device.",
|
|
4360
|
+
DEVICE_LIMIT: "This team has reached its device limit. Remove an unused device in AppGallery Connect and retry.",
|
|
4361
|
+
DEVICE_NAME_REPEAT: "A device with that name is already registered. Retry.",
|
|
4362
|
+
DEVICE_NONE: "No device is connected and none is registered to this team, but a debug profile must name at least one. Connect a device and retry.",
|
|
4363
|
+
PROFILE_ADD: "AppGallery Connect did not issue the provisioning profile.",
|
|
4364
|
+
PROFILE_NAME_REPEAT: "A provisioning profile with that name already exists in AppGallery Connect. Retry.",
|
|
4365
|
+
PROVISION_LIMIT: "This team has reached its debug-profile limit. Delete unused profiles in AppGallery Connect and retry.",
|
|
4366
|
+
CERT_INVALID: "AppGallery Connect returned an unreadable certificate. Retry with --force.",
|
|
4367
|
+
CERT_EXPIRED: "The signing certificate is outside its validity window. Check the system clock, then retry with --force.",
|
|
4368
|
+
CERT_PROFILE_MISMATCH: "The provisioning profile was not issued for the downloaded certificate. Retry with --force.",
|
|
4369
|
+
KEYSTORE_CERT_MISMATCH: "The local keystore does not hold the key the downloaded certificate was issued for. Retry with --force.",
|
|
4370
|
+
BUNDLE_NAME_INVALID: "Invalid bundle name. It must be 7\u2013128 characters, start with a letter, and have at least three dot-separated segments (for example com.example.app)."
|
|
4371
|
+
};
|
|
4372
|
+
var RET_CODE_ERRORS = {
|
|
4373
|
+
"205389872": SIGNING_ERRORS.CERT_LIMIT,
|
|
4374
|
+
"205389904": SIGNING_ERRORS.USER_NOT_HARMONY,
|
|
4375
|
+
"205389859": SIGNING_ERRORS.DEVICE_LIMIT,
|
|
4376
|
+
"205389857": SIGNING_ERRORS.DEVICE_NAME_REPEAT,
|
|
4377
|
+
"205389938": SIGNING_ERRORS.PROVISION_LIMIT,
|
|
4378
|
+
"205389830": SIGNING_ERRORS.PROFILE_NAME_REPEAT
|
|
4379
|
+
};
|
|
4380
|
+
var PROXY_BLOCKED = "Openproxy_Blocked_URL_list";
|
|
4381
|
+
function readRet(body) {
|
|
4382
|
+
try {
|
|
4383
|
+
let ret = JSON.parse(body).ret;
|
|
4384
|
+
if (typeof ret === "string") ret = JSON.parse(ret);
|
|
4385
|
+
return ret && typeof ret === "object" ? ret : null;
|
|
4386
|
+
} catch {
|
|
4387
|
+
return null;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
var FALLBACK = {
|
|
4391
|
+
cert: SIGNING_ERRORS.CERT_REQUEST,
|
|
4392
|
+
device: SIGNING_ERRORS.DEVICE_ADD,
|
|
4393
|
+
profile: SIGNING_ERRORS.PROFILE_ADD
|
|
4394
|
+
};
|
|
4395
|
+
function mapAgcError(operation, statusCode, reasonPhrase, body) {
|
|
4396
|
+
if (statusCode === 403) {
|
|
4397
|
+
return new AgcError(reasonPhrase === PROXY_BLOCKED ? SIGNING_ERRORS.NETWORK : SIGNING_ERRORS.FORBIDDEN, 403);
|
|
4398
|
+
}
|
|
4399
|
+
if (statusCode === 401) return new AgcError(SIGNING_ERRORS.UNAUTHORIZED, 401);
|
|
4400
|
+
const ret = readRet(body);
|
|
4401
|
+
const retCode = ret?.code === void 0 ? void 0 : String(ret.code);
|
|
4402
|
+
const msg = typeof ret?.msg === "string" && ret.msg.trim() ? ret.msg : void 0;
|
|
4403
|
+
return new AgcError(retCode && RET_CODE_ERRORS[retCode] || msg || FALLBACK[operation], statusCode, retCode);
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
// src/harmonyos/signing/profile.ts
|
|
4407
|
+
var fs33 = __toESM(require("fs"), 1);
|
|
4408
|
+
var import_node_crypto3 = require("crypto");
|
|
4409
|
+
var import_node_forge = __toESM(require("node-forge"), 1);
|
|
4410
|
+
function extractProfileJson(buffer) {
|
|
4411
|
+
const text = buffer.toString("latin1");
|
|
4412
|
+
for (let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)) {
|
|
4413
|
+
let depth = 0;
|
|
4414
|
+
let inString = false;
|
|
4415
|
+
let escaped = false;
|
|
4416
|
+
for (let i = start; i < text.length; i++) {
|
|
4417
|
+
const ch = text[i];
|
|
4418
|
+
if (escaped) escaped = false;
|
|
4419
|
+
else if (inString) {
|
|
4420
|
+
if (ch === "\\") escaped = true;
|
|
4421
|
+
else if (ch === '"') inString = false;
|
|
4422
|
+
} else if (ch === '"') inString = true;
|
|
4423
|
+
else if (ch === "{") depth++;
|
|
4424
|
+
else if (ch === "}" && --depth === 0) {
|
|
4425
|
+
const json = Buffer.from(text.slice(start, i + 1), "latin1").toString("utf8");
|
|
4426
|
+
try {
|
|
4427
|
+
if ("bundle-info" in JSON.parse(json)) return json;
|
|
4428
|
+
} catch {
|
|
4429
|
+
}
|
|
4430
|
+
break;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
return null;
|
|
4435
|
+
}
|
|
4436
|
+
function readProvisionProfile(profilePath) {
|
|
4437
|
+
let raw;
|
|
4438
|
+
try {
|
|
4439
|
+
const json = extractProfileJson(fs33.readFileSync(profilePath));
|
|
4440
|
+
if (!json) return null;
|
|
4441
|
+
raw = JSON.parse(json);
|
|
4442
|
+
} catch {
|
|
4443
|
+
return null;
|
|
4444
|
+
}
|
|
4445
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
4446
|
+
const strings = (v) => Array.isArray(v) ? v.filter((x2) => typeof x2 === "string") : [];
|
|
4447
|
+
const notAfter = raw.validity?.["not-after"];
|
|
4448
|
+
const bundle = raw["bundle-info"] ?? {};
|
|
4449
|
+
return {
|
|
4450
|
+
bundleName: str(bundle["bundle-name"]),
|
|
4451
|
+
developerId: str(bundle["developer-id"]),
|
|
4452
|
+
// Epoch seconds.
|
|
4453
|
+
notAfter: typeof notAfter === "number" ? new Date(notAfter * 1e3) : void 0,
|
|
4454
|
+
deviceIds: strings(raw["debug-info"]?.["device-ids"]).map((d) => d.toUpperCase()),
|
|
4455
|
+
aclPermissions: strings(raw.acls?.["allowed-acls"]),
|
|
4456
|
+
developmentCertificate: str(bundle["development-certificate"])
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
4459
|
+
function parseCertificate(pem) {
|
|
4460
|
+
try {
|
|
4461
|
+
return new import_node_crypto3.X509Certificate(pem);
|
|
4462
|
+
} catch {
|
|
4463
|
+
throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
4467
|
+
try {
|
|
4468
|
+
const der = import_node_forge.default.util.createBuffer(fs33.readFileSync(p12Path).toString("binary"));
|
|
4469
|
+
const p12 = import_node_forge.default.pkcs12.pkcs12FromAsn1(import_node_forge.default.asn1.fromDer(der), keyPwd);
|
|
4470
|
+
const certBag = import_node_forge.default.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
|
|
4471
|
+
const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
|
|
4472
|
+
return (p12.getBags({ bagType: certBag })[certBag] ?? []).some((bag) => {
|
|
4473
|
+
if (bag.attributes?.friendlyName?.[0]?.toLowerCase() !== keyAlias.toLowerCase()) return false;
|
|
4474
|
+
const asn1 = bag.cert ? import_node_forge.default.pki.certificateToAsn1(bag.cert) : bag.asn1;
|
|
4475
|
+
if (!asn1) return false;
|
|
4476
|
+
const bagCert = new import_node_crypto3.X509Certificate(Buffer.from(import_node_forge.default.asn1.toDer(asn1).getBytes(), "binary"));
|
|
4477
|
+
return bagCert.publicKey.export({ type: "spki", format: "der" }).equals(wanted);
|
|
4478
|
+
});
|
|
4479
|
+
} catch {
|
|
4480
|
+
return false;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function verifySigningMaterial(opts) {
|
|
4484
|
+
const pems = fs33.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
4485
|
+
const chain = (pems ?? []).map(parseCertificate);
|
|
4486
|
+
const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
|
|
4487
|
+
if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
|
|
4488
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
4489
|
+
if (chain.some((c) => now < new Date(c.validFrom) || now > new Date(c.validTo))) {
|
|
4490
|
+
throw new AgcError(SIGNING_ERRORS.CERT_EXPIRED);
|
|
4491
|
+
}
|
|
4492
|
+
const issuedFor = readProvisionProfile(opts.profilePath)?.developmentCertificate;
|
|
4493
|
+
if (!issuedFor || parseCertificate(issuedFor).fingerprint256 !== leaf.fingerprint256) {
|
|
4494
|
+
throw new AgcError(SIGNING_ERRORS.CERT_PROFILE_MISMATCH);
|
|
4495
|
+
}
|
|
4496
|
+
if (!keystoreHoldsKey(opts.p12Path, opts.keyAlias, opts.keyPwd, leaf)) {
|
|
4497
|
+
throw new AgcError(SIGNING_ERRORS.KEYSTORE_CERT_MISMATCH);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
// src/harmonyos/signing/autoSign.ts
|
|
4502
|
+
var crypto9 = __toESM(require("crypto"), 1);
|
|
4503
|
+
var fs38 = __toESM(require("fs"), 1);
|
|
4504
|
+
var path37 = __toESM(require("path"), 1);
|
|
4505
|
+
var import_json57 = __toESM(require("json5"), 1);
|
|
4506
|
+
|
|
4507
|
+
// src/harmonyos/signing/agc.ts
|
|
4508
|
+
var crypto6 = __toESM(require("crypto"), 1);
|
|
4509
|
+
var fs34 = __toESM(require("fs"), 1);
|
|
4510
|
+
var path32 = __toESM(require("path"), 1);
|
|
4511
|
+
var ENDPOINTS = {
|
|
4512
|
+
CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
|
|
4513
|
+
CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
|
|
4514
|
+
CERT_DELETE: "/api/cps/harmony-cert-manage/v1/cert/delete",
|
|
4515
|
+
/** Exchanges an object id for a short-lived signed download URL. */
|
|
4516
|
+
OBJECT_URL: "/api/amis/app-manage/v1/objects/url/reapply",
|
|
4517
|
+
DEVICE_LIST: "/api/cps/device-manage/v1/device/list",
|
|
4518
|
+
DEVICE_ADD: "/api/cps/device-manage/v1/device/add",
|
|
4519
|
+
/** Debug ("test") provisioning profiles — the only kind oniro-app issues. */
|
|
4520
|
+
PROVISION_ADD: "/api/cps/provision-manage/v1/ide/test/provision/add",
|
|
4521
|
+
PROVISION_DELETE: "/api/cps/provision-manage/v1/provision/delete"
|
|
4522
|
+
};
|
|
4523
|
+
var DEVICE_TYPE_CODES = { liteWearable: 1, wearable: 2, tv: 3, phone: 4 };
|
|
4524
|
+
function createAgcClient(http3, auth) {
|
|
4525
|
+
async function call(operation, method, endpoint, opts = {}) {
|
|
4526
|
+
const response = await http3[method](`${auth.baseUrl}${endpoint}`, {
|
|
4527
|
+
...opts,
|
|
4528
|
+
headers: { uid: auth.uid, teamId: auth.teamId, oauth2Token: auth.accessToken }
|
|
4529
|
+
});
|
|
4530
|
+
if (response.statusCode !== 200 || readRet(response.data)?.code !== 0) {
|
|
4531
|
+
throw mapAgcError(operation, response.statusCode, response.statusText, response.data);
|
|
4532
|
+
}
|
|
4533
|
+
return JSON.parse(response.data);
|
|
4534
|
+
}
|
|
4535
|
+
return {
|
|
4536
|
+
async listCertificates() {
|
|
4537
|
+
return (await call("cert", "post", ENDPOINTS.CERT_LIST)).certList ?? [];
|
|
4538
|
+
},
|
|
4539
|
+
async addCertificate(certName, csr) {
|
|
4540
|
+
await call("cert", "post", ENDPOINTS.CERT_ADD, { body: { csr, certName, certType: "1" } });
|
|
4541
|
+
},
|
|
4542
|
+
async deleteCertificate(certId) {
|
|
4543
|
+
await call("cert", "delete", ENDPOINTS.CERT_DELETE, { body: { certIds: [certId] } });
|
|
4544
|
+
},
|
|
4545
|
+
/** Every device registered to the team, across pages. */
|
|
4546
|
+
async listDevices() {
|
|
4547
|
+
const pageSize = 100;
|
|
4548
|
+
const devices = [];
|
|
4549
|
+
for (let start = 1; ; start++) {
|
|
4550
|
+
const page = await call("device", "get", ENDPOINTS.DEVICE_LIST, {
|
|
4551
|
+
query: { encodeFlag: 0, start, pageSize }
|
|
4552
|
+
});
|
|
4553
|
+
devices.push(...page.list ?? []);
|
|
4554
|
+
if (!page.list?.length || devices.length >= (page.totalCount ?? 0)) return devices;
|
|
4555
|
+
}
|
|
4556
|
+
},
|
|
4557
|
+
async addDevice(udid, deviceKind) {
|
|
4558
|
+
const deviceName = `oniro_app_device_${crypto6.randomBytes(4).toString("hex")}`;
|
|
4559
|
+
await call("device", "post", ENDPOINTS.DEVICE_ADD, {
|
|
4560
|
+
body: { deviceName, udid, deviceType: String(DEVICE_TYPE_CODES[deviceKind] ?? DEVICE_TYPE_CODES.phone) }
|
|
4561
|
+
});
|
|
4562
|
+
},
|
|
4563
|
+
/** Issue a debug profile; returns its id and object handle. */
|
|
4564
|
+
async addDebugProfile(opts) {
|
|
4565
|
+
return call("profile", "post", ENDPOINTS.PROVISION_ADD, {
|
|
4566
|
+
body: {
|
|
4567
|
+
provisionName: opts.provisionName,
|
|
4568
|
+
packageName: opts.bundleName,
|
|
4569
|
+
certList: [opts.certId],
|
|
4570
|
+
deviceList: opts.deviceIds,
|
|
4571
|
+
...opts.aclPermissions.length ? { aclPermissionList: opts.aclPermissions } : {}
|
|
4572
|
+
}
|
|
4573
|
+
});
|
|
4574
|
+
},
|
|
4575
|
+
async deleteProfile(id) {
|
|
4576
|
+
await call("profile", "delete", ENDPOINTS.PROVISION_DELETE, { query: { id } });
|
|
4577
|
+
},
|
|
4578
|
+
/**
|
|
4579
|
+
* Download a certificate or profile object to `dest`, checking the digest AGC
|
|
4580
|
+
* supplies with the URL. Nothing is written unless it matches.
|
|
4581
|
+
*/
|
|
4582
|
+
async download(operation, objectId, dest) {
|
|
4583
|
+
const { urlsInfo } = await call(
|
|
4584
|
+
operation,
|
|
4585
|
+
"post",
|
|
4586
|
+
ENDPOINTS.OBJECT_URL,
|
|
4587
|
+
{ body: { sourceUrls: objectId } }
|
|
4588
|
+
);
|
|
4589
|
+
const target = urlsInfo?.[0];
|
|
4590
|
+
if (!target) throw mapAgcError(operation, 200, "", "");
|
|
4591
|
+
const buffer = await http3.getBinary(target.newUrl, { timeoutMs: 6e4 });
|
|
4592
|
+
const actual = crypto6.createHash("sha256").update(buffer).digest("hex");
|
|
4593
|
+
if (target.sha256 && actual !== target.sha256.toLowerCase()) {
|
|
4594
|
+
throw new ChecksumMismatchError(target.sha256, actual);
|
|
4595
|
+
}
|
|
4596
|
+
fs34.mkdirSync(path32.dirname(dest), { recursive: true, mode: 448 });
|
|
4597
|
+
fs34.writeFileSync(dest, buffer, { mode: 384 });
|
|
4598
|
+
}
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
|
|
4602
|
+
// src/harmonyos/signing/devices.ts
|
|
4603
|
+
function parseUdid(stdout) {
|
|
4604
|
+
return stdout.match(/\b[A-Fa-f0-9]{64}\b/)?.[0].toUpperCase() ?? "";
|
|
4605
|
+
}
|
|
4606
|
+
function parseDeviceKind(stdout) {
|
|
4607
|
+
return ["liteWearable", "wearable", "tv"].find((kind) => stdout.includes(kind)) ?? "phone";
|
|
4608
|
+
}
|
|
4609
|
+
async function collectLocalDevices(config, logger) {
|
|
4610
|
+
const devices = [];
|
|
4611
|
+
for (const target of await listDevices(config, { logger, timeoutMs: 15e3 })) {
|
|
4612
|
+
if (target.status !== "Connected") continue;
|
|
4613
|
+
const shell2 = async (...args) => (await hdcExec({ config, deviceSerial: target.serial, args: ["shell", ...args], timeoutMs: 15e3, logger })).stdout;
|
|
4614
|
+
try {
|
|
4615
|
+
const udid = parseUdid(await shell2("bm", "get", "-u"));
|
|
4616
|
+
if (!udid) {
|
|
4617
|
+
logger.warn(`[harmonyos] ${target.serial} did not report a UDID; skipping it.`);
|
|
4618
|
+
continue;
|
|
4619
|
+
}
|
|
4620
|
+
devices.push({ serial: target.serial, udid, kind: parseDeviceKind(await shell2("getprop", "hw_sc.build.os.deviceType")) });
|
|
4621
|
+
} catch (err) {
|
|
4622
|
+
logger.warn(`[harmonyos] Could not read ${target.serial}: ${err.message}`);
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
return devices;
|
|
4626
|
+
}
|
|
4627
|
+
async function registerDevices(agc, connected, logger) {
|
|
4628
|
+
let devices = await agc.listDevices();
|
|
4629
|
+
const registered = new Set(devices.map((d) => d.udid.toUpperCase()));
|
|
4630
|
+
const unregistered = connected.filter((d) => !registered.has(d.udid));
|
|
4631
|
+
for (const device of unregistered) {
|
|
4632
|
+
logger.info(`[harmonyos] Registering ${device.serial} with AppGallery Connect...`);
|
|
4633
|
+
await agc.addDevice(device.udid, device.kind);
|
|
4634
|
+
}
|
|
4635
|
+
if (unregistered.length > 0) devices = await agc.listDevices();
|
|
4636
|
+
const deviceIds = devices.map((d) => d.id);
|
|
4637
|
+
if (deviceIds.length === 0) throw new AgcError(SIGNING_ERRORS.DEVICE_NONE);
|
|
4638
|
+
if (connected.length === 0) {
|
|
4639
|
+
logger.warn(
|
|
4640
|
+
`[harmonyos] No device is connected; the profile will name the ${deviceIds.length} device(s) already registered to this team.`
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4643
|
+
return deviceIds;
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4646
|
+
// src/harmonyos/signing/keystore.ts
|
|
4647
|
+
var crypto7 = __toESM(require("crypto"), 1);
|
|
4648
|
+
var fs36 = __toESM(require("fs"), 1);
|
|
4649
|
+
var path34 = __toESM(require("path"), 1);
|
|
4650
|
+
|
|
4651
|
+
// src/harmonyos/java.ts
|
|
4652
|
+
var fs35 = __toESM(require("fs"), 1);
|
|
4653
|
+
var path33 = __toESM(require("path"), 1);
|
|
4654
|
+
var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
|
|
4655
|
+
function findJava(sdk, env = process.env) {
|
|
4656
|
+
const candidates = [
|
|
4657
|
+
env.JAVA_HOME && path33.join(env.JAVA_HOME, "bin", JAVA_BIN),
|
|
4658
|
+
sdk && path33.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
|
|
4659
|
+
// macOS: the JBR is itself a bundle inside Studio's Contents.
|
|
4660
|
+
sdk && path33.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
|
|
4661
|
+
...(env.PATH ?? "").split(path33.delimiter).map((dir) => dir && path33.join(dir, JAVA_BIN))
|
|
4662
|
+
];
|
|
4663
|
+
const found = candidates.find((c) => !!c && fs35.existsSync(c));
|
|
4664
|
+
if (found) return found;
|
|
4665
|
+
throw new OniroError(
|
|
4666
|
+
"No Java runtime found. HarmonyOS signing runs the SDK's hap-sign-tool.jar, which needs one: install a JDK and set JAVA_HOME, or install DevEco Studio, whose bundled runtime is used automatically."
|
|
4667
|
+
);
|
|
4668
|
+
}
|
|
4669
|
+
|
|
4670
|
+
// src/harmonyos/signing/keystore.ts
|
|
4671
|
+
var KEY_ALIAS = "debugKey";
|
|
4672
|
+
var SIGN_ALG = "SHA256withECDSA";
|
|
4673
|
+
function redactArgs(args) {
|
|
4674
|
+
return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
|
|
4675
|
+
}
|
|
4676
|
+
async function generateKeystoreAndCsr(opts) {
|
|
4677
|
+
const jar = path34.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
|
|
4678
|
+
if (!fs36.existsSync(jar)) {
|
|
4679
|
+
throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
|
|
4680
|
+
}
|
|
4681
|
+
const java = findJava(opts.sdk);
|
|
4682
|
+
const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
|
|
4683
|
+
fs36.mkdirSync(path34.dirname(opts.p12Path), { recursive: true, mode: 448 });
|
|
4684
|
+
const run = async (what, args) => {
|
|
4685
|
+
const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
|
|
4686
|
+
opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
|
|
4687
|
+
const result = await runProcess({ command: java, args: argv, timeoutMs: 12e4, logger: opts.logger });
|
|
4688
|
+
if (result.code !== 0) {
|
|
4689
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
4690
|
+
throw new OniroError(`hap-sign-tool failed to ${what} (exit ${result.code}).${detail ? `
|
|
4691
|
+
${detail}` : ""}`);
|
|
4692
|
+
}
|
|
4693
|
+
};
|
|
4694
|
+
opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
|
|
4695
|
+
await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
|
|
4696
|
+
await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
|
|
4697
|
+
return { keyPwd, csr: fs36.readFileSync(opts.csrPath, "utf8") };
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4700
|
+
// src/harmonyos/signing/paths.ts
|
|
4701
|
+
var crypto8 = __toESM(require("crypto"), 1);
|
|
4702
|
+
var path35 = __toESM(require("path"), 1);
|
|
4703
|
+
function sanitizeNamePart(raw) {
|
|
4704
|
+
const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
|
|
4705
|
+
return safe.slice(0, 64) || "default";
|
|
4706
|
+
}
|
|
4707
|
+
function resolveSigningMaterialPaths(config, productName, projectRoot) {
|
|
4708
|
+
const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
|
|
4709
|
+
const base = path35.join(
|
|
4710
|
+
config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
|
|
4711
|
+
`${sanitizeNamePart(productName)}_${path35.basename(projectRoot)}_${hash}=`
|
|
4712
|
+
);
|
|
4713
|
+
return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
// src/harmonyos/signing/project.ts
|
|
4717
|
+
var fs37 = __toESM(require("fs"), 1);
|
|
4718
|
+
var path36 = __toESM(require("path"), 1);
|
|
4719
|
+
var import_json56 = __toESM(require("json5"), 1);
|
|
4720
|
+
|
|
4721
|
+
// src/harmonyos/signing/aclPermissionList.ts
|
|
4722
|
+
var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
4723
|
+
"ohos.permission.ACCESS_DDK_HID",
|
|
4724
|
+
"ohos.permission.ACCESS_DDK_SCSI_PERIPHERAL",
|
|
4725
|
+
"ohos.permission.ACCESS_DDK_USB",
|
|
4726
|
+
"ohos.permission.ACCESS_DDK_USB_SERIAL",
|
|
4727
|
+
"ohos.permission.ACCESS_DISK_PHY_INFO",
|
|
4728
|
+
"ohos.permission.ACCESS_FIDO2_ONLINEAUTH",
|
|
4729
|
+
"ohos.permission.ACCESS_NET_TRACE_INFO",
|
|
4730
|
+
"ohos.permission.ACCESS_USER_FULL_DISK",
|
|
4731
|
+
"ohos.permission.ACCESS_VIRTUAL_SCREEN",
|
|
4732
|
+
"ohos.permission.CUSTOMIZE_SAVE_BUTTON",
|
|
4733
|
+
"ohos.permission.CUSTOM_SCREEN_RECORDING",
|
|
4734
|
+
"ohos.permission.DLP_GET_HIDE_STATUS",
|
|
4735
|
+
"ohos.permission.FILE_ACCESS_PERSIST",
|
|
4736
|
+
"ohos.permission.GET_ABILITY_INFO",
|
|
4737
|
+
"ohos.permission.GET_ETHERNET_LOCAL_MAC",
|
|
4738
|
+
"ohos.permission.GET_IP_MAC_INFO",
|
|
4739
|
+
"ohos.permission.GET_WIFI_LOCAL_MAC",
|
|
4740
|
+
"ohos.permission.GET_WIFI_PEERS_MAC",
|
|
4741
|
+
"ohos.permission.HOOK_KEY_EVENT",
|
|
4742
|
+
"ohos.permission.INPUT_MONITORING",
|
|
4743
|
+
"ohos.permission.INTERCEPT_INPUT_EVENT",
|
|
4744
|
+
"ohos.permission.KEEP_BACKGROUND_RUNNING_SYSTEM",
|
|
4745
|
+
"ohos.permission.LINKTURBO",
|
|
4746
|
+
"ohos.permission.MANAGE_APN_SETTING",
|
|
4747
|
+
"ohos.permission.MANAGE_PASTEBOARD_APP_SHARE_OPTION",
|
|
4748
|
+
"ohos.permission.MANAGE_SCREEN_TIME_GUARD",
|
|
4749
|
+
"ohos.permission.MANAGE_UDMF_APP_SHARE_OPTION",
|
|
4750
|
+
"ohos.permission.PERSISTENT_BLUETOOTH_PEERS_MAC",
|
|
4751
|
+
"ohos.permission.PERSONAL_MANAGE_RESTRICTIONS",
|
|
4752
|
+
"ohos.permission.PRELOAD_FILE",
|
|
4753
|
+
"ohos.permission.READ_AUDIO",
|
|
4754
|
+
"ohos.permission.READ_CONTACTS",
|
|
4755
|
+
"ohos.permission.READ_IMAGEVIDEO",
|
|
4756
|
+
"ohos.permission.READ_LOCAL_DEVICE_NAME",
|
|
4757
|
+
"ohos.permission.READ_PASTEBOARD",
|
|
4758
|
+
"ohos.permission.READ_WRITE_DESKTOP_DIRECTORY",
|
|
4759
|
+
"ohos.permission.READ_WRITE_DOCUMENTS_DIRECTORY",
|
|
4760
|
+
"ohos.permission.READ_WRITE_DOWNLOAD_DIRECTORY",
|
|
4761
|
+
"ohos.permission.READ_WRITE_USB_DEV",
|
|
4762
|
+
"ohos.permission.READ_WRITE_USER_FILE",
|
|
4763
|
+
"ohos.permission.SET_PAC_URL",
|
|
4764
|
+
"ohos.permission.SET_SYSTEMSHARE_APPLAUNCHTRUSTLIST",
|
|
4765
|
+
"ohos.permission.SHORT_TERM_WRITE_IMAGEVIDEO",
|
|
4766
|
+
"ohos.permission.START_PROVISIONING_MESSAGE",
|
|
4767
|
+
"ohos.permission.SUBSCRIBE_NOTIFICATION",
|
|
4768
|
+
"ohos.permission.SYSTEM_FLOAT_WINDOW",
|
|
4769
|
+
"ohos.permission.USE_FLOAT_BALL",
|
|
4770
|
+
"ohos.permission.USE_FRAUD_APP_PICKER",
|
|
4771
|
+
"ohos.permission.USE_FRAUD_CALL_LOG_PICKER",
|
|
4772
|
+
"ohos.permission.USE_FRAUD_MESSAGES_PICKER",
|
|
4773
|
+
"ohos.permission.WEB_NATIVE_MESSAGING",
|
|
4774
|
+
"ohos.permission.WRITE_AUDIO",
|
|
4775
|
+
"ohos.permission.WRITE_CONTACTS",
|
|
4776
|
+
"ohos.permission.WRITE_IMAGEVIDEO",
|
|
4777
|
+
"ohos.permission.atomicService.MANAGE_STORAGE",
|
|
4778
|
+
"ohos.permission.kernel.ALLOW_EXECUTABLE_FORT_MEMORY",
|
|
4779
|
+
"ohos.permission.kernel.ALLOW_WRITABLE_CODE_MEMORY",
|
|
4780
|
+
"ohos.permission.kernel.DISABLE_CODE_MEMORY_PROTECTION",
|
|
4781
|
+
"ohos.permission.kernel.DISABLE_GOTPLT_RO_PROTECTION"
|
|
4782
|
+
]);
|
|
4783
|
+
|
|
4784
|
+
// src/harmonyos/signing/project.ts
|
|
4785
|
+
function readRequestedPermissions(moduleConfigPath) {
|
|
4786
|
+
if (!fs37.existsSync(moduleConfigPath)) return [];
|
|
4787
|
+
try {
|
|
4788
|
+
const parsed = import_json56.default.parse(fs37.readFileSync(moduleConfigPath, "utf-8"));
|
|
4789
|
+
return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
4790
|
+
} catch {
|
|
4791
|
+
return [];
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
4795
|
+
const requested = /* @__PURE__ */ new Set();
|
|
4796
|
+
for (const module2 of listModules({ projectDir })) {
|
|
4797
|
+
const moduleRoot = path36.resolve(projectDir, module2.srcPath);
|
|
4798
|
+
for (const sourceSet of ["main", "ohosTest"]) {
|
|
4799
|
+
for (const name of readRequestedPermissions(
|
|
4800
|
+
path36.join(moduleRoot, "src", sourceSet, "module.json5")
|
|
4801
|
+
)) {
|
|
4802
|
+
requested.add(name);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
const acls = [...requested].filter((p) => ACL_PERMISSIONS.has(p)).sort();
|
|
4807
|
+
if (acls.length > 0) {
|
|
4808
|
+
logger.info(`[harmonyos] Project requests ${acls.length} ACL permission(s): ${acls.join(", ")}`);
|
|
4809
|
+
}
|
|
4810
|
+
return acls;
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
// src/harmonyos/signing/autoSign.ts
|
|
4814
|
+
function shouldRegenerate(input) {
|
|
4815
|
+
if (input.force) return "--force was passed";
|
|
4816
|
+
const missing = Object.values(input.paths).find((file) => !fs38.existsSync(file));
|
|
4817
|
+
if (missing) return `${missing} is missing`;
|
|
4818
|
+
const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
|
|
4819
|
+
if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
|
|
4820
|
+
return "build-profile.json5 has no signingConfig for this material";
|
|
4821
|
+
}
|
|
4822
|
+
const profile = readProvisionProfile(input.paths.profilePath);
|
|
4823
|
+
if (!profile) return "the provisioning profile could not be read";
|
|
4824
|
+
if (profile.bundleName !== input.bundleName) return `the profile is for bundle '${profile.bundleName}'`;
|
|
4825
|
+
if (profile.developerId && profile.developerId !== input.teamId) {
|
|
4826
|
+
return `the profile was issued to team '${profile.developerId}'`;
|
|
4827
|
+
}
|
|
4828
|
+
if (profile.notAfter && profile.notAfter <= (input.now ?? /* @__PURE__ */ new Date())) return "the profile has expired";
|
|
4829
|
+
const unnamed = input.connectedUdids.filter((udid) => !profile.deviceIds.includes(udid.toUpperCase()));
|
|
4830
|
+
if (unnamed.length > 0) return `${unnamed.length} connected device(s) are not named in the profile`;
|
|
4831
|
+
const acls = input.aclPermissions.filter((p) => !profile.aclPermissions.includes(p));
|
|
4832
|
+
if (acls.length > 0) return `the profile lacks ACL permission(s) ${acls.join(", ")}`;
|
|
4833
|
+
return null;
|
|
4834
|
+
}
|
|
4835
|
+
function writeSigningConfig(opts) {
|
|
4836
|
+
const app = opts.buildProfile.app ??= {};
|
|
4837
|
+
const entry = {
|
|
4838
|
+
name: opts.productName,
|
|
4839
|
+
// `type` is what makes hvigor treat this as HarmonyOS signing material.
|
|
4840
|
+
type: "HarmonyOS",
|
|
4841
|
+
material: {
|
|
4842
|
+
certpath: opts.paths.cerPath,
|
|
4843
|
+
keyAlias: KEY_ALIAS,
|
|
4844
|
+
keyPassword: opts.encryptedPassword,
|
|
4845
|
+
profile: opts.paths.profilePath,
|
|
4846
|
+
signAlg: SIGN_ALG,
|
|
4847
|
+
storeFile: opts.paths.p12Path,
|
|
4848
|
+
storePassword: opts.encryptedPassword
|
|
4849
|
+
}
|
|
4850
|
+
};
|
|
4851
|
+
app.signingConfigs = [...(app.signingConfigs ?? []).filter((c) => c.name !== opts.productName), entry];
|
|
4852
|
+
for (const product of app.products ?? []) {
|
|
4853
|
+
if (product.name === opts.productName) product.signingConfig = opts.productName;
|
|
4854
|
+
}
|
|
4855
|
+
fs38.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
|
|
4856
|
+
}
|
|
4857
|
+
async function harmonyOsAutoSign(opts) {
|
|
4858
|
+
const logger = opts.logger ?? noopLogger;
|
|
4859
|
+
const productName = opts.productName ?? "default";
|
|
4860
|
+
const projectDir = path37.resolve(opts.projectDir);
|
|
4861
|
+
const buildProfilePath = path37.join(projectDir, "build-profile.json5");
|
|
4862
|
+
let buildProfile;
|
|
4863
|
+
try {
|
|
4864
|
+
buildProfile = import_json57.default.parse(fs38.readFileSync(buildProfilePath, "utf-8"));
|
|
4865
|
+
} catch (err) {
|
|
4866
|
+
throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
|
|
4867
|
+
}
|
|
4868
|
+
const product = buildProfile.app?.products?.find((p) => p.name === productName);
|
|
4869
|
+
if (!product) throw new OniroError(`${buildProfilePath} declares no product named '${productName}'.`);
|
|
4870
|
+
const bundleName = product.bundleName || getBundleName(projectDir);
|
|
4871
|
+
if (!isValidBundleName(bundleName) || bundleName.split(".").length < 3 || bundleName.length < 7 || bundleName.length > 128) {
|
|
4872
|
+
throw new AgcError(SIGNING_ERRORS.BUNDLE_NAME_INVALID);
|
|
4873
|
+
}
|
|
4874
|
+
const sdk = requireHarmonyOsSdk({ config: opts.config, logger });
|
|
4875
|
+
const auth = await opts.session.resolveAgcAuth({ teamId: opts.teamId });
|
|
4876
|
+
const aclPermissions = collectAclPermissions(projectDir, logger);
|
|
4877
|
+
const paths = resolveSigningMaterialPaths(opts.config, productName, projectDir);
|
|
4878
|
+
logger.info(`[harmonyos] Signing ${bundleName} (product '${productName}', team ${auth.teamId}).`);
|
|
4879
|
+
const connected = await collectLocalDevices(opts.config, logger);
|
|
4880
|
+
const result = (regenerated) => ({
|
|
4881
|
+
bundleName,
|
|
4882
|
+
teamId: auth.teamId,
|
|
4883
|
+
paths,
|
|
4884
|
+
regenerated,
|
|
4885
|
+
deviceIds: readProvisionProfile(paths.profilePath)?.deviceIds ?? []
|
|
4886
|
+
});
|
|
4887
|
+
const reason = shouldRegenerate({
|
|
4888
|
+
paths,
|
|
4889
|
+
buildProfile,
|
|
4890
|
+
productName,
|
|
4891
|
+
bundleName,
|
|
4892
|
+
teamId: auth.teamId,
|
|
4893
|
+
connectedUdids: connected.map((d) => d.udid),
|
|
4894
|
+
aclPermissions,
|
|
4895
|
+
force: opts.force
|
|
4896
|
+
});
|
|
4897
|
+
if (!reason) {
|
|
4898
|
+
logger.info("[harmonyos] Reusing the existing signing material, which is still valid.");
|
|
4899
|
+
return result(false);
|
|
4900
|
+
}
|
|
4901
|
+
logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
|
|
4902
|
+
const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
|
|
4903
|
+
const deviceIds = await registerDevices(agc, connected, logger);
|
|
4904
|
+
for (const file of Object.values(paths)) fs38.rmSync(file, { force: true });
|
|
4905
|
+
const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
|
|
4906
|
+
const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
|
|
4907
|
+
if (stale) {
|
|
4908
|
+
logger.info(`[harmonyos] Replacing the previous '${certName}' certificate in AppGallery Connect...`);
|
|
4909
|
+
await agc.deleteCertificate(stale.id);
|
|
4910
|
+
}
|
|
4911
|
+
const { keyPwd, csr } = await generateKeystoreAndCsr({ sdk, p12Path: paths.p12Path, csrPath: paths.csrPath, logger });
|
|
4912
|
+
logger.info("[harmonyos] Requesting a debug certificate from AppGallery Connect...");
|
|
4913
|
+
await agc.addCertificate(certName, csr);
|
|
4914
|
+
const cert = (await agc.listCertificates()).find((c) => c.certName === certName);
|
|
4915
|
+
if (!cert) throw new AgcError(SIGNING_ERRORS.CERT_REQUEST);
|
|
4916
|
+
await agc.download("cert", cert.certObjectId, paths.cerPath);
|
|
4917
|
+
logger.info("[harmonyos] Issuing the debug provisioning profile...");
|
|
4918
|
+
const profile = await agc.addDebugProfile({
|
|
4919
|
+
provisionName: `oniro_${crypto9.randomBytes(6).toString("hex")}`,
|
|
4920
|
+
bundleName,
|
|
4921
|
+
certId: cert.id,
|
|
4922
|
+
deviceIds,
|
|
4923
|
+
aclPermissions
|
|
4924
|
+
});
|
|
4925
|
+
try {
|
|
4926
|
+
await agc.download("profile", profile.provisionFileUrl, paths.profilePath);
|
|
4927
|
+
} finally {
|
|
4928
|
+
await agc.deleteProfile(profile.id).catch((err) => {
|
|
4929
|
+
logger.debug(`[harmonyos] Could not delete profile ${profile.id} from AppGallery Connect: ${err.message}`);
|
|
4930
|
+
});
|
|
4931
|
+
}
|
|
4932
|
+
try {
|
|
4933
|
+
verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
|
|
4934
|
+
} catch (err) {
|
|
4935
|
+
fs38.rmSync(paths.profilePath, { force: true });
|
|
4936
|
+
throw err;
|
|
4937
|
+
}
|
|
4938
|
+
const materialDir = path37.join(path37.dirname(paths.p12Path), "material");
|
|
4939
|
+
try {
|
|
4940
|
+
getKey(materialDir);
|
|
4941
|
+
} catch {
|
|
4942
|
+
createMaterial(materialDir);
|
|
4943
|
+
}
|
|
4944
|
+
writeSigningConfig({ buildProfilePath, buildProfile, productName, paths, encryptedPassword: encryptPwd(keyPwd, materialDir) });
|
|
4945
|
+
logger.info(`[harmonyos] Wrote signingConfigs['${productName}'] to ${buildProfilePath}.`);
|
|
4946
|
+
return result(true);
|
|
4947
|
+
}
|
|
4948
|
+
|
|
3599
4949
|
// src/index.ts
|
|
3600
|
-
var VERSION = "0.
|
|
4950
|
+
var VERSION = "0.11.0";
|
|
3601
4951
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3602
4952
|
0 && (module.exports = {
|
|
3603
4953
|
ALL_SDKS,
|
|
3604
4954
|
APL_VALUES,
|
|
3605
4955
|
APP_FEATURE_VALUES,
|
|
4956
|
+
AgcError,
|
|
3606
4957
|
CancelledError,
|
|
3607
4958
|
ChecksumMismatchError,
|
|
3608
4959
|
CmdToolsNotInstalledError,
|
|
3609
4960
|
CommandFailedError,
|
|
4961
|
+
HARMONYOS_SDK_VERSIONS,
|
|
4962
|
+
HUAWEI_SITES,
|
|
3610
4963
|
INSTALL_TMP_DIRNAME,
|
|
3611
4964
|
InsufficientSpaceError,
|
|
3612
4965
|
OHOS_URL_BASE,
|
|
3613
4966
|
OniroError,
|
|
4967
|
+
SIGNING_ERRORS,
|
|
3614
4968
|
SdkNotInstalledError,
|
|
3615
4969
|
TMP_DIR_HINT,
|
|
3616
4970
|
UnsupportedPlatformError,
|
|
@@ -3626,10 +4980,12 @@ var VERSION = "0.9.0";
|
|
|
3626
4980
|
buildRawTouchCommand,
|
|
3627
4981
|
captureBurst,
|
|
3628
4982
|
consoleLogger,
|
|
4983
|
+
createHarmonyOsSession,
|
|
3629
4984
|
createInstallTempDir,
|
|
3630
4985
|
createMaterial,
|
|
3631
4986
|
createScaffold,
|
|
3632
4987
|
createTempWorkDir,
|
|
4988
|
+
createTokenStore,
|
|
3633
4989
|
decideInstallMethod,
|
|
3634
4990
|
decideReboot,
|
|
3635
4991
|
decryptPwd,
|
|
@@ -3638,6 +4994,7 @@ var VERSION = "0.9.0";
|
|
|
3638
4994
|
defaultPaths,
|
|
3639
4995
|
detectAaStartFailure,
|
|
3640
4996
|
detectProjectSdkVersion,
|
|
4997
|
+
detectRuntimeOs,
|
|
3641
4998
|
detectSigningConfigNames,
|
|
3642
4999
|
diffEntryNames,
|
|
3643
5000
|
diffHapAssets,
|
|
@@ -3660,6 +5017,7 @@ var VERSION = "0.9.0";
|
|
|
3660
5017
|
extractZipWithProgress,
|
|
3661
5018
|
findAppProcessId,
|
|
3662
5019
|
findCmdToolsSourceDir,
|
|
5020
|
+
findHarmonyOsSdk,
|
|
3663
5021
|
findRunningProcess,
|
|
3664
5022
|
forceStop,
|
|
3665
5023
|
formatBytes,
|
|
@@ -3684,6 +5042,8 @@ var VERSION = "0.9.0";
|
|
|
3684
5042
|
getSdkFilename,
|
|
3685
5043
|
getSdkRootDir,
|
|
3686
5044
|
getSupportedSdksForUi,
|
|
5045
|
+
harmonyOsAutoSign,
|
|
5046
|
+
harmonyOsSdkLabelForApi,
|
|
3687
5047
|
hdcExec,
|
|
3688
5048
|
installApp,
|
|
3689
5049
|
installCmdTools,
|
|
@@ -3718,12 +5078,14 @@ var VERSION = "0.9.0";
|
|
|
3718
5078
|
pruneLayout,
|
|
3719
5079
|
readJson5File,
|
|
3720
5080
|
readJsonFile,
|
|
5081
|
+
readProvisionProfile,
|
|
3721
5082
|
reboot,
|
|
3722
5083
|
recvFile,
|
|
3723
5084
|
removeCmdTools,
|
|
3724
5085
|
removeEmulator,
|
|
3725
5086
|
removeSdk,
|
|
3726
5087
|
removeTempWorkDir,
|
|
5088
|
+
requireHarmonyOsSdk,
|
|
3727
5089
|
resolveTmpRoot,
|
|
3728
5090
|
runCodelinter,
|
|
3729
5091
|
runHvigorw,
|