@oniroproject/core 0.10.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 +1746 -387
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +305 -2
- package/dist/index.d.ts +305 -2
- package/dist/index.js +1734 -387
- 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) ||
|
|
638
|
+
const fallback = os5.tmpdir();
|
|
639
|
+
if (requested || isOutOfSpaceError(err) || path5.resolve(fallback) === path5.resolve(root)) throw err;
|
|
539
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,8 +2258,8 @@ 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
2265
|
const result = await hdcExec({
|
|
@@ -2247,7 +2348,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2247
2348
|
const logger = options.logger ?? noopLogger;
|
|
2248
2349
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
2249
2350
|
const hdc = getHdcPath(config);
|
|
2250
|
-
return new Promise((
|
|
2351
|
+
return new Promise((resolve6, reject) => {
|
|
2251
2352
|
let proc;
|
|
2252
2353
|
try {
|
|
2253
2354
|
proc = spawnCompat(hdc, ["track-jpid"]);
|
|
@@ -2267,7 +2368,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2267
2368
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
2268
2369
|
resolved = true;
|
|
2269
2370
|
killProcessTree(proc);
|
|
2270
|
-
|
|
2371
|
+
resolve6(pid);
|
|
2271
2372
|
return;
|
|
2272
2373
|
}
|
|
2273
2374
|
}
|
|
@@ -2288,7 +2389,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2288
2389
|
if (options.targetProcessName) {
|
|
2289
2390
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2290
2391
|
} else {
|
|
2291
|
-
|
|
2392
|
+
resolve6(processes);
|
|
2292
2393
|
}
|
|
2293
2394
|
}, timeoutMs);
|
|
2294
2395
|
proc.on("close", () => {
|
|
@@ -2298,7 +2399,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2298
2399
|
if (options.targetProcessName) {
|
|
2299
2400
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2300
2401
|
} else {
|
|
2301
|
-
|
|
2402
|
+
resolve6(processes);
|
|
2302
2403
|
}
|
|
2303
2404
|
});
|
|
2304
2405
|
});
|
|
@@ -2442,12 +2543,12 @@ async function reboot(opts) {
|
|
|
2442
2543
|
}
|
|
2443
2544
|
|
|
2444
2545
|
// src/hdc/screen.ts
|
|
2445
|
-
var
|
|
2446
|
-
var
|
|
2447
|
-
var
|
|
2546
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
2547
|
+
var os9 = __toESM(require("os"), 1);
|
|
2548
|
+
var path19 = __toESM(require("path"), 1);
|
|
2448
2549
|
var import_node_crypto = require("crypto");
|
|
2449
2550
|
function tmpName(ext) {
|
|
2450
|
-
return
|
|
2551
|
+
return path19.join(os9.tmpdir(), `oniro_screen_${(0, import_node_crypto.randomBytes)(6).toString("hex")}.${ext}`);
|
|
2451
2552
|
}
|
|
2452
2553
|
async function takeScreenshot(opts) {
|
|
2453
2554
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -2457,11 +2558,11 @@ async function takeScreenshot(opts) {
|
|
|
2457
2558
|
const local = tmpName("jpeg");
|
|
2458
2559
|
try {
|
|
2459
2560
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2460
|
-
const pixels = await
|
|
2561
|
+
const pixels = await fs19.promises.readFile(local);
|
|
2461
2562
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2462
2563
|
return { pixels, width: size.width, height: size.height };
|
|
2463
2564
|
} finally {
|
|
2464
|
-
|
|
2565
|
+
fs19.promises.unlink(local).catch(() => {
|
|
2465
2566
|
});
|
|
2466
2567
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2467
2568
|
});
|
|
@@ -2480,9 +2581,9 @@ async function captureBurst(opts) {
|
|
|
2480
2581
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
2481
2582
|
try {
|
|
2482
2583
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2483
|
-
frames.push(await
|
|
2584
|
+
frames.push(await fs19.promises.readFile(local));
|
|
2484
2585
|
} finally {
|
|
2485
|
-
|
|
2586
|
+
fs19.promises.unlink(local).catch(() => {
|
|
2486
2587
|
});
|
|
2487
2588
|
}
|
|
2488
2589
|
}
|
|
@@ -2608,9 +2709,9 @@ async function sendRawTouch(opts) {
|
|
|
2608
2709
|
}
|
|
2609
2710
|
|
|
2610
2711
|
// src/hdc/dump.ts
|
|
2611
|
-
var
|
|
2612
|
-
var
|
|
2613
|
-
var
|
|
2712
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
2713
|
+
var os10 = __toESM(require("os"), 1);
|
|
2714
|
+
var path20 = __toESM(require("path"), 1);
|
|
2614
2715
|
var import_node_crypto2 = require("crypto");
|
|
2615
2716
|
function parseBounds(s) {
|
|
2616
2717
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
@@ -2695,10 +2796,10 @@ async function dumpLayout(opts) {
|
|
|
2695
2796
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2696
2797
|
${dump.stdout}`);
|
|
2697
2798
|
const remote = m[1];
|
|
2698
|
-
const local =
|
|
2799
|
+
const local = path20.join(os10.tmpdir(), `oniro_layout_${(0, import_node_crypto2.randomBytes)(6).toString("hex")}.json`);
|
|
2699
2800
|
try {
|
|
2700
2801
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2701
|
-
const parsed = JSON.parse(await
|
|
2802
|
+
const parsed = JSON.parse(await fs20.promises.readFile(local, "utf8"));
|
|
2702
2803
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2703
2804
|
if (opts.prune === false) {
|
|
2704
2805
|
return { display, tree: parsed };
|
|
@@ -2707,7 +2808,7 @@ ${dump.stdout}`);
|
|
|
2707
2808
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2708
2809
|
return { display, tree };
|
|
2709
2810
|
} finally {
|
|
2710
|
-
|
|
2811
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2711
2812
|
});
|
|
2712
2813
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2713
2814
|
});
|
|
@@ -2718,7 +2819,7 @@ ${dump.stdout}`);
|
|
|
2718
2819
|
function setHilogLevel(opts) {
|
|
2719
2820
|
const logger = opts.logger ?? noopLogger;
|
|
2720
2821
|
const hdc = getHdcPath(opts.config);
|
|
2721
|
-
return new Promise((
|
|
2822
|
+
return new Promise((resolve6, reject) => {
|
|
2722
2823
|
let child;
|
|
2723
2824
|
try {
|
|
2724
2825
|
child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
@@ -2738,7 +2839,7 @@ function setHilogLevel(opts) {
|
|
|
2738
2839
|
});
|
|
2739
2840
|
child.once("close", (code) => {
|
|
2740
2841
|
if (code === 0) {
|
|
2741
|
-
|
|
2842
|
+
resolve6();
|
|
2742
2843
|
} else {
|
|
2743
2844
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
2744
2845
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -2793,7 +2894,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
|
|
|
2793
2894
|
return proc?.pid;
|
|
2794
2895
|
}
|
|
2795
2896
|
function waitForLog(opts) {
|
|
2796
|
-
return new Promise((
|
|
2897
|
+
return new Promise((resolve6, reject) => {
|
|
2797
2898
|
const deadline = Date.now() + opts.timeoutMs;
|
|
2798
2899
|
let settled = false;
|
|
2799
2900
|
let child = null;
|
|
@@ -2812,7 +2913,7 @@ function waitForLog(opts) {
|
|
|
2812
2913
|
if (settled) return;
|
|
2813
2914
|
settled = true;
|
|
2814
2915
|
cleanup();
|
|
2815
|
-
|
|
2916
|
+
resolve6(entry);
|
|
2816
2917
|
};
|
|
2817
2918
|
const fail = (err) => {
|
|
2818
2919
|
if (settled) return;
|
|
@@ -2859,7 +2960,7 @@ function waitForLog(opts) {
|
|
|
2859
2960
|
});
|
|
2860
2961
|
}
|
|
2861
2962
|
function watchLog(opts) {
|
|
2862
|
-
return new Promise((
|
|
2963
|
+
return new Promise((resolve6, reject) => {
|
|
2863
2964
|
const matches = [];
|
|
2864
2965
|
let settled = false;
|
|
2865
2966
|
let child = null;
|
|
@@ -2876,7 +2977,7 @@ function watchLog(opts) {
|
|
|
2876
2977
|
if (settled) return;
|
|
2877
2978
|
settled = true;
|
|
2878
2979
|
cleanup();
|
|
2879
|
-
|
|
2980
|
+
resolve6(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2880
2981
|
};
|
|
2881
2982
|
const onAbort = () => {
|
|
2882
2983
|
if (settled) return;
|
|
@@ -2932,8 +3033,43 @@ async function dumpLog(opts) {
|
|
|
2932
3033
|
}
|
|
2933
3034
|
|
|
2934
3035
|
// src/build/runHvigorw.ts
|
|
2935
|
-
var
|
|
2936
|
-
var
|
|
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;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// src/build/runHvigorw.ts
|
|
2937
3073
|
function buildHvigorwArgs(opts) {
|
|
2938
3074
|
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2939
3075
|
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
@@ -2948,28 +3084,36 @@ function runHvigorw(opts) {
|
|
|
2948
3084
|
const { config } = opts;
|
|
2949
3085
|
const projectDir = toLongPath(opts.projectDir);
|
|
2950
3086
|
const logger = opts.logger ?? noopLogger;
|
|
2951
|
-
if (!
|
|
3087
|
+
if (!fs22.existsSync(path22.join(projectDir, "build-profile.json5"))) {
|
|
2952
3088
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
2953
3089
|
}
|
|
2954
|
-
|
|
2955
|
-
|
|
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);
|
|
2956
3104
|
}
|
|
2957
|
-
|
|
2958
|
-
if (!fs20.existsSync(hvigorw)) {
|
|
3105
|
+
if (!fs22.existsSync(hvigorw)) {
|
|
2959
3106
|
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
2960
3107
|
}
|
|
2961
3108
|
const args = buildHvigorwArgs(opts);
|
|
2962
3109
|
if (process.platform !== "win32") {
|
|
2963
3110
|
try {
|
|
2964
|
-
|
|
3111
|
+
fs22.chmodSync(hvigorw, 493);
|
|
2965
3112
|
} catch {
|
|
2966
3113
|
}
|
|
2967
3114
|
}
|
|
2968
|
-
const env = {
|
|
2969
|
-
|
|
2970
|
-
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
2971
|
-
};
|
|
2972
|
-
return new Promise((resolve4, reject) => {
|
|
3115
|
+
const env = { ...process.env, ...sdkEnv };
|
|
3116
|
+
return new Promise((resolve6, reject) => {
|
|
2973
3117
|
if (opts.abortSignal?.aborted) {
|
|
2974
3118
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
2975
3119
|
return;
|
|
@@ -3013,15 +3157,15 @@ function runHvigorw(opts) {
|
|
|
3013
3157
|
settled = true;
|
|
3014
3158
|
cleanup();
|
|
3015
3159
|
const exitCode = code ?? 1;
|
|
3016
|
-
if (exitCode === 0)
|
|
3160
|
+
if (exitCode === 0) resolve6({ exitCode });
|
|
3017
3161
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
3018
3162
|
});
|
|
3019
3163
|
});
|
|
3020
3164
|
}
|
|
3021
3165
|
|
|
3022
3166
|
// src/build/discoverHaps.ts
|
|
3023
|
-
var
|
|
3024
|
-
var
|
|
3167
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
3168
|
+
var path23 = __toESM(require("path"), 1);
|
|
3025
3169
|
function moduleOfHap(hapFileName) {
|
|
3026
3170
|
const dash = hapFileName.indexOf("-");
|
|
3027
3171
|
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
@@ -3032,9 +3176,9 @@ async function discoverHaps(opts) {
|
|
|
3032
3176
|
const result = {};
|
|
3033
3177
|
const walk = async (dir, depth) => {
|
|
3034
3178
|
if (depth > maxDepth) return;
|
|
3035
|
-
const entries = await
|
|
3179
|
+
const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3036
3180
|
for (const e of entries) {
|
|
3037
|
-
const full =
|
|
3181
|
+
const full = path23.join(dir, e.name);
|
|
3038
3182
|
if (e.isDirectory()) {
|
|
3039
3183
|
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
3040
3184
|
await walk(full, depth + 1);
|
|
@@ -3051,11 +3195,11 @@ async function discoverHaps(opts) {
|
|
|
3051
3195
|
}
|
|
3052
3196
|
|
|
3053
3197
|
// src/build/ohpm.ts
|
|
3054
|
-
var
|
|
3055
|
-
var
|
|
3198
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
3199
|
+
var path24 = __toESM(require("path"), 1);
|
|
3056
3200
|
function runOhpm(opts) {
|
|
3057
3201
|
return runProcess({
|
|
3058
|
-
command: getOhpmPath(opts.config),
|
|
3202
|
+
command: opts.ohpmPath ?? getOhpmPath(opts.config),
|
|
3059
3203
|
args: [...opts.args],
|
|
3060
3204
|
cwd: opts.projectDir,
|
|
3061
3205
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
@@ -3065,12 +3209,13 @@ function runOhpm(opts) {
|
|
|
3065
3209
|
});
|
|
3066
3210
|
}
|
|
3067
3211
|
async function ensureOhModules(opts) {
|
|
3068
|
-
if (
|
|
3212
|
+
if (fs24.existsSync(path24.join(opts.projectDir, "oh_modules"))) {
|
|
3069
3213
|
return { installed: false };
|
|
3070
3214
|
}
|
|
3071
3215
|
const res = await runOhpm({
|
|
3072
3216
|
config: opts.config,
|
|
3073
3217
|
projectDir: opts.projectDir,
|
|
3218
|
+
ohpmPath: opts.ohpmPath,
|
|
3074
3219
|
args: ["install", "--all"],
|
|
3075
3220
|
abortSignal: opts.abortSignal,
|
|
3076
3221
|
onOutput: opts.onOutput,
|
|
@@ -3118,10 +3263,13 @@ async function buildHap(opts) {
|
|
|
3118
3263
|
const warnings = [];
|
|
3119
3264
|
const start = Date.now();
|
|
3120
3265
|
const projectDir = toLongPath(opts.projectDir);
|
|
3266
|
+
const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
|
|
3121
3267
|
if (opts.autoInstallDeps !== false) {
|
|
3268
|
+
const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
|
|
3122
3269
|
const r = await ensureOhModules({
|
|
3123
3270
|
config: opts.config,
|
|
3124
3271
|
projectDir,
|
|
3272
|
+
ohpmPath: (harmonySdk && findHarmonyOsTool(harmonySdk, "ohpm")) ?? void 0,
|
|
3125
3273
|
abortSignal: opts.abortSignal,
|
|
3126
3274
|
onOutput: opts.onOutput,
|
|
3127
3275
|
logger: opts.logger
|
|
@@ -3144,7 +3292,7 @@ async function buildHap(opts) {
|
|
|
3144
3292
|
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
3145
3293
|
if (!anySigned) {
|
|
3146
3294
|
warnings.push(
|
|
3147
|
-
"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)."
|
|
3148
3296
|
);
|
|
3149
3297
|
}
|
|
3150
3298
|
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
@@ -3161,21 +3309,21 @@ function isValidBundleName(bundleName) {
|
|
|
3161
3309
|
}
|
|
3162
3310
|
|
|
3163
3311
|
// src/project/templates.ts
|
|
3164
|
-
var
|
|
3165
|
-
var
|
|
3312
|
+
var fs25 = __toESM(require("fs"), 1);
|
|
3313
|
+
var path25 = __toESM(require("path"), 1);
|
|
3166
3314
|
function toHumanTemplateName(folderName) {
|
|
3167
3315
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
3168
3316
|
}
|
|
3169
3317
|
function listTemplates(templateRoot) {
|
|
3170
|
-
if (!
|
|
3171
|
-
const entries =
|
|
3318
|
+
if (!fs25.existsSync(templateRoot)) return [];
|
|
3319
|
+
const entries = fs25.readdirSync(templateRoot, { withFileTypes: true });
|
|
3172
3320
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
3173
|
-
const dir =
|
|
3174
|
-
const metaPath =
|
|
3321
|
+
const dir = path25.join(templateRoot, e.name);
|
|
3322
|
+
const metaPath = path25.join(dir, "template.json");
|
|
3175
3323
|
let meta = {};
|
|
3176
|
-
if (
|
|
3324
|
+
if (fs25.existsSync(metaPath)) {
|
|
3177
3325
|
try {
|
|
3178
|
-
meta = JSON.parse(
|
|
3326
|
+
meta = JSON.parse(fs25.readFileSync(metaPath, "utf8"));
|
|
3179
3327
|
} catch {
|
|
3180
3328
|
}
|
|
3181
3329
|
}
|
|
@@ -3190,57 +3338,57 @@ function listTemplates(templateRoot) {
|
|
|
3190
3338
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
3191
3339
|
const required = [
|
|
3192
3340
|
"build-profile.json5",
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3341
|
+
path25.join("AppScope", "app.json5"),
|
|
3342
|
+
path25.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3343
|
+
path25.join(defaultModuleName, "oh-package.json5"),
|
|
3196
3344
|
"hvigorfile.ts"
|
|
3197
3345
|
];
|
|
3198
|
-
return required.filter((rel) => !
|
|
3346
|
+
return required.filter((rel) => !fs25.existsSync(path25.join(templateDir, rel)));
|
|
3199
3347
|
}
|
|
3200
3348
|
|
|
3201
3349
|
// src/project/createScaffold.ts
|
|
3202
|
-
var
|
|
3203
|
-
var
|
|
3350
|
+
var fs27 = __toESM(require("fs"), 1);
|
|
3351
|
+
var path26 = __toESM(require("path"), 1);
|
|
3204
3352
|
|
|
3205
3353
|
// src/project/jsonHelpers.ts
|
|
3206
|
-
var
|
|
3207
|
-
var
|
|
3354
|
+
var fs26 = __toESM(require("fs"), 1);
|
|
3355
|
+
var import_json55 = __toESM(require("json5"), 1);
|
|
3208
3356
|
function readJson5File(filePath) {
|
|
3209
|
-
return
|
|
3357
|
+
return import_json55.default.parse(fs26.readFileSync(filePath, "utf8"));
|
|
3210
3358
|
}
|
|
3211
3359
|
function writeJson5File(filePath, value) {
|
|
3212
|
-
|
|
3360
|
+
fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3213
3361
|
}
|
|
3214
3362
|
function readJsonFile(filePath) {
|
|
3215
|
-
return JSON.parse(
|
|
3363
|
+
return JSON.parse(fs26.readFileSync(filePath, "utf8"));
|
|
3216
3364
|
}
|
|
3217
3365
|
function writeJsonFile(filePath, value) {
|
|
3218
|
-
|
|
3366
|
+
fs26.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3219
3367
|
}
|
|
3220
3368
|
|
|
3221
3369
|
// src/project/createScaffold.ts
|
|
3222
3370
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
3223
3371
|
async function pathExists(p) {
|
|
3224
3372
|
try {
|
|
3225
|
-
await
|
|
3373
|
+
await fs27.promises.access(p);
|
|
3226
3374
|
return true;
|
|
3227
3375
|
} catch {
|
|
3228
3376
|
return false;
|
|
3229
3377
|
}
|
|
3230
3378
|
}
|
|
3231
3379
|
async function copyDirRecursive(srcDir, destDir) {
|
|
3232
|
-
await
|
|
3233
|
-
const entries = await
|
|
3380
|
+
await fs27.promises.mkdir(destDir, { recursive: true });
|
|
3381
|
+
const entries = await fs27.promises.readdir(srcDir, { withFileTypes: true });
|
|
3234
3382
|
for (const entry of entries) {
|
|
3235
|
-
const src =
|
|
3236
|
-
const dest =
|
|
3383
|
+
const src = path26.join(srcDir, entry.name);
|
|
3384
|
+
const dest = path26.join(destDir, entry.name);
|
|
3237
3385
|
if (entry.isDirectory()) {
|
|
3238
3386
|
await copyDirRecursive(src, dest);
|
|
3239
3387
|
} else if (entry.isSymbolicLink()) {
|
|
3240
3388
|
continue;
|
|
3241
3389
|
} else if (entry.isFile()) {
|
|
3242
3390
|
if (entry.name === "template.json") continue;
|
|
3243
|
-
await
|
|
3391
|
+
await fs27.promises.copyFile(src, dest);
|
|
3244
3392
|
}
|
|
3245
3393
|
}
|
|
3246
3394
|
}
|
|
@@ -3248,9 +3396,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
3248
3396
|
const stack = [projectDir];
|
|
3249
3397
|
while (stack.length > 0) {
|
|
3250
3398
|
const currentDir = stack.pop();
|
|
3251
|
-
const entries = await
|
|
3399
|
+
const entries = await fs27.promises.readdir(currentDir, { withFileTypes: true });
|
|
3252
3400
|
for (const entry of entries) {
|
|
3253
|
-
const fullPath =
|
|
3401
|
+
const fullPath = path26.join(currentDir, entry.name);
|
|
3254
3402
|
if (entry.isDirectory()) {
|
|
3255
3403
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3256
3404
|
stack.push(fullPath);
|
|
@@ -3272,23 +3420,23 @@ function toJavaPropertiesPath(p) {
|
|
|
3272
3420
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
3273
3421
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
3274
3422
|
`;
|
|
3275
|
-
|
|
3423
|
+
fs27.writeFileSync(path26.join(projectDir, "local.properties"), content, "utf8");
|
|
3276
3424
|
}
|
|
3277
3425
|
function renameIfExists(fromPath, toPath) {
|
|
3278
|
-
if (
|
|
3279
|
-
|
|
3426
|
+
if (fs27.existsSync(fromPath) && fromPath !== toPath) {
|
|
3427
|
+
fs27.renameSync(fromPath, toPath);
|
|
3280
3428
|
}
|
|
3281
3429
|
}
|
|
3282
3430
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
3283
|
-
const appJsonPath =
|
|
3284
|
-
if (
|
|
3431
|
+
const appJsonPath = path26.join(projectDir, "AppScope", "app.json5");
|
|
3432
|
+
if (fs27.existsSync(appJsonPath)) {
|
|
3285
3433
|
const appJson = readJson5File(appJsonPath);
|
|
3286
3434
|
appJson.app = appJson.app ?? {};
|
|
3287
3435
|
appJson.app.bundleName = args.bundleName;
|
|
3288
3436
|
writeJson5File(appJsonPath, appJson);
|
|
3289
3437
|
}
|
|
3290
|
-
const stringsPath =
|
|
3291
|
-
if (
|
|
3438
|
+
const stringsPath = path26.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
3439
|
+
if (fs27.existsSync(stringsPath)) {
|
|
3292
3440
|
const strings = readJsonFile(stringsPath);
|
|
3293
3441
|
if (Array.isArray(strings.string)) {
|
|
3294
3442
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -3296,13 +3444,13 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3296
3444
|
}
|
|
3297
3445
|
writeJsonFile(stringsPath, strings);
|
|
3298
3446
|
}
|
|
3299
|
-
const buildProfilePath =
|
|
3300
|
-
if (
|
|
3447
|
+
const buildProfilePath = path26.join(projectDir, "build-profile.json5");
|
|
3448
|
+
if (fs27.existsSync(buildProfilePath)) {
|
|
3301
3449
|
const buildProfile = readJson5File(buildProfilePath);
|
|
3302
3450
|
buildProfile.app = buildProfile.app ?? {};
|
|
3303
3451
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
3304
|
-
buildProfile.app.products[0].compileSdkVersion = args.
|
|
3305
|
-
buildProfile.app.products[0].compatibleSdkVersion = args.
|
|
3452
|
+
buildProfile.app.products[0].compileSdkVersion = args.sdkVersion;
|
|
3453
|
+
buildProfile.app.products[0].compatibleSdkVersion = args.sdkVersion;
|
|
3306
3454
|
}
|
|
3307
3455
|
if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
|
|
3308
3456
|
buildProfile.modules[0].name = args.moduleName;
|
|
@@ -3310,23 +3458,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3310
3458
|
}
|
|
3311
3459
|
writeJson5File(buildProfilePath, buildProfile);
|
|
3312
3460
|
}
|
|
3313
|
-
const moduleJsonPath =
|
|
3314
|
-
if (
|
|
3461
|
+
const moduleJsonPath = path26.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
3462
|
+
if (fs27.existsSync(moduleJsonPath)) {
|
|
3315
3463
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
3316
3464
|
moduleJson.module = moduleJson.module ?? {};
|
|
3317
3465
|
moduleJson.module.name = args.moduleName;
|
|
3318
3466
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
3319
3467
|
}
|
|
3320
|
-
const moduleOhPackagePath =
|
|
3321
|
-
if (
|
|
3468
|
+
const moduleOhPackagePath = path26.join(projectDir, args.moduleName, "oh-package.json5");
|
|
3469
|
+
if (fs27.existsSync(moduleOhPackagePath)) {
|
|
3322
3470
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
3323
3471
|
pkg.name = args.moduleName;
|
|
3324
3472
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
3325
3473
|
}
|
|
3326
|
-
const vscodeDir =
|
|
3327
|
-
|
|
3474
|
+
const vscodeDir = path26.join(projectDir, ".vscode");
|
|
3475
|
+
fs27.mkdirSync(vscodeDir, { recursive: true });
|
|
3328
3476
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
3329
|
-
writeJsonFile(
|
|
3477
|
+
writeJsonFile(path26.join(vscodeDir, "settings.json"), {
|
|
3330
3478
|
"oniro.hapPath": hapPath,
|
|
3331
3479
|
"files.associations": { "*.json5": "jsonc" }
|
|
3332
3480
|
});
|
|
@@ -3343,7 +3491,7 @@ async function createScaffold(opts) {
|
|
|
3343
3491
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
3344
3492
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
3345
3493
|
}
|
|
3346
|
-
const templateDir =
|
|
3494
|
+
const templateDir = path26.join(opts.templateRoot, opts.templateId);
|
|
3347
3495
|
if (!await pathExists(templateDir)) {
|
|
3348
3496
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
3349
3497
|
}
|
|
@@ -3356,38 +3504,60 @@ async function createScaffold(opts) {
|
|
|
3356
3504
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
3357
3505
|
- ${missing.join("\n- ")}`);
|
|
3358
3506
|
}
|
|
3359
|
-
|
|
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);
|
|
3360
3530
|
if (await pathExists(projectDir)) {
|
|
3361
3531
|
if (!opts.overwrite) {
|
|
3362
3532
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
3363
3533
|
}
|
|
3364
|
-
await
|
|
3534
|
+
await fs27.promises.rm(projectDir, { recursive: true, force: true });
|
|
3365
3535
|
}
|
|
3366
3536
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
3367
3537
|
await copyDirRecursive(templateDir, projectDir);
|
|
3368
3538
|
if (moduleName !== defaultModuleName) {
|
|
3369
|
-
renameIfExists(
|
|
3539
|
+
renameIfExists(path26.join(projectDir, defaultModuleName), path26.join(projectDir, moduleName));
|
|
3370
3540
|
}
|
|
3371
3541
|
updateTemplateConfigs(
|
|
3372
3542
|
projectDir,
|
|
3373
3543
|
{
|
|
3374
3544
|
projectName: opts.projectName,
|
|
3375
3545
|
bundleName: opts.bundleName,
|
|
3376
|
-
|
|
3546
|
+
sdkVersion,
|
|
3377
3547
|
moduleName
|
|
3378
3548
|
},
|
|
3379
|
-
|
|
3549
|
+
sdkBaseDir
|
|
3380
3550
|
);
|
|
3381
3551
|
await normalizeJson5ToJson(projectDir, logger);
|
|
3382
3552
|
return { projectDir };
|
|
3383
3553
|
}
|
|
3384
3554
|
|
|
3385
3555
|
// src/project/listModules.ts
|
|
3386
|
-
var
|
|
3387
|
-
var
|
|
3556
|
+
var fs28 = __toESM(require("fs"), 1);
|
|
3557
|
+
var path27 = __toESM(require("path"), 1);
|
|
3388
3558
|
function listModules(opts) {
|
|
3389
|
-
const buildProfile =
|
|
3390
|
-
if (!
|
|
3559
|
+
const buildProfile = path27.join(opts.projectDir, "build-profile.json5");
|
|
3560
|
+
if (!fs28.existsSync(buildProfile)) {
|
|
3391
3561
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
3392
3562
|
}
|
|
3393
3563
|
const parsed = readJson5File(buildProfile);
|
|
@@ -3399,12 +3569,12 @@ function listModules(opts) {
|
|
|
3399
3569
|
}
|
|
3400
3570
|
|
|
3401
3571
|
// src/project/abilities.ts
|
|
3402
|
-
var
|
|
3403
|
-
var
|
|
3572
|
+
var fs29 = __toESM(require("fs"), 1);
|
|
3573
|
+
var path28 = __toESM(require("path"), 1);
|
|
3404
3574
|
function listAbilities(opts) {
|
|
3405
3575
|
const moduleName = opts.moduleName ?? "entry";
|
|
3406
|
-
const moduleJsonPath =
|
|
3407
|
-
if (!
|
|
3576
|
+
const moduleJsonPath = path28.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
3577
|
+
if (!fs29.existsSync(moduleJsonPath)) {
|
|
3408
3578
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
3409
3579
|
}
|
|
3410
3580
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -3412,12 +3582,12 @@ function listAbilities(opts) {
|
|
|
3412
3582
|
}
|
|
3413
3583
|
|
|
3414
3584
|
// src/project/prepareSigning.ts
|
|
3415
|
-
var
|
|
3416
|
-
var
|
|
3585
|
+
var fs30 = __toESM(require("fs"), 1);
|
|
3586
|
+
var path29 = __toESM(require("path"), 1);
|
|
3417
3587
|
function hasSigningArtifacts(dir) {
|
|
3418
|
-
if (!
|
|
3419
|
-
const buildProfile =
|
|
3420
|
-
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;
|
|
3421
3591
|
try {
|
|
3422
3592
|
const parsed = readJson5File(buildProfile);
|
|
3423
3593
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -3507,8 +3677,8 @@ async function diffHapAssets(opts) {
|
|
|
3507
3677
|
}
|
|
3508
3678
|
|
|
3509
3679
|
// src/install/apply.ts
|
|
3510
|
-
var
|
|
3511
|
-
var
|
|
3680
|
+
var fs31 = __toESM(require("fs"), 1);
|
|
3681
|
+
var path30 = __toESM(require("path"), 1);
|
|
3512
3682
|
function decideInstallMethod(s) {
|
|
3513
3683
|
if (!s.signInfoInconsistent) return "replace";
|
|
3514
3684
|
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
@@ -3529,7 +3699,7 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
3529
3699
|
async function resolveHapPath(opts) {
|
|
3530
3700
|
let hapPath = opts.hapPath;
|
|
3531
3701
|
if (hapPath) {
|
|
3532
|
-
if (!
|
|
3702
|
+
if (!path30.isAbsolute(hapPath) && opts.projectDir) hapPath = path30.join(opts.projectDir, hapPath);
|
|
3533
3703
|
} else {
|
|
3534
3704
|
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
3535
3705
|
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
@@ -3548,7 +3718,7 @@ async function resolveHapPath(opts) {
|
|
|
3548
3718
|
hapPath = withSigned[0][1].signed[0];
|
|
3549
3719
|
}
|
|
3550
3720
|
}
|
|
3551
|
-
if (!
|
|
3721
|
+
if (!fs31.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3552
3722
|
return hapPath;
|
|
3553
3723
|
}
|
|
3554
3724
|
async function applyChanges(opts) {
|
|
@@ -3558,7 +3728,7 @@ async function applyChanges(opts) {
|
|
|
3558
3728
|
const allowUninstall = opts.allowUninstall ?? false;
|
|
3559
3729
|
const hapPath = await resolveHapPath(opts);
|
|
3560
3730
|
let assetsChanged = false;
|
|
3561
|
-
if (opts.installedHapPath &&
|
|
3731
|
+
if (opts.installedHapPath && fs31.existsSync(opts.installedHapPath)) {
|
|
3562
3732
|
try {
|
|
3563
3733
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3564
3734
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -3599,21 +3769,1202 @@ async function applyChanges(opts) {
|
|
|
3599
3769
|
return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
|
|
3600
3770
|
}
|
|
3601
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
|
+
|
|
3602
4949
|
// src/index.ts
|
|
3603
|
-
var VERSION = "0.
|
|
4950
|
+
var VERSION = "0.11.0";
|
|
3604
4951
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3605
4952
|
0 && (module.exports = {
|
|
3606
4953
|
ALL_SDKS,
|
|
3607
4954
|
APL_VALUES,
|
|
3608
4955
|
APP_FEATURE_VALUES,
|
|
4956
|
+
AgcError,
|
|
3609
4957
|
CancelledError,
|
|
3610
4958
|
ChecksumMismatchError,
|
|
3611
4959
|
CmdToolsNotInstalledError,
|
|
3612
4960
|
CommandFailedError,
|
|
4961
|
+
HARMONYOS_SDK_VERSIONS,
|
|
4962
|
+
HUAWEI_SITES,
|
|
3613
4963
|
INSTALL_TMP_DIRNAME,
|
|
3614
4964
|
InsufficientSpaceError,
|
|
3615
4965
|
OHOS_URL_BASE,
|
|
3616
4966
|
OniroError,
|
|
4967
|
+
SIGNING_ERRORS,
|
|
3617
4968
|
SdkNotInstalledError,
|
|
3618
4969
|
TMP_DIR_HINT,
|
|
3619
4970
|
UnsupportedPlatformError,
|
|
@@ -3629,10 +4980,12 @@ var VERSION = "0.10.0";
|
|
|
3629
4980
|
buildRawTouchCommand,
|
|
3630
4981
|
captureBurst,
|
|
3631
4982
|
consoleLogger,
|
|
4983
|
+
createHarmonyOsSession,
|
|
3632
4984
|
createInstallTempDir,
|
|
3633
4985
|
createMaterial,
|
|
3634
4986
|
createScaffold,
|
|
3635
4987
|
createTempWorkDir,
|
|
4988
|
+
createTokenStore,
|
|
3636
4989
|
decideInstallMethod,
|
|
3637
4990
|
decideReboot,
|
|
3638
4991
|
decryptPwd,
|
|
@@ -3641,6 +4994,7 @@ var VERSION = "0.10.0";
|
|
|
3641
4994
|
defaultPaths,
|
|
3642
4995
|
detectAaStartFailure,
|
|
3643
4996
|
detectProjectSdkVersion,
|
|
4997
|
+
detectRuntimeOs,
|
|
3644
4998
|
detectSigningConfigNames,
|
|
3645
4999
|
diffEntryNames,
|
|
3646
5000
|
diffHapAssets,
|
|
@@ -3663,6 +5017,7 @@ var VERSION = "0.10.0";
|
|
|
3663
5017
|
extractZipWithProgress,
|
|
3664
5018
|
findAppProcessId,
|
|
3665
5019
|
findCmdToolsSourceDir,
|
|
5020
|
+
findHarmonyOsSdk,
|
|
3666
5021
|
findRunningProcess,
|
|
3667
5022
|
forceStop,
|
|
3668
5023
|
formatBytes,
|
|
@@ -3687,6 +5042,8 @@ var VERSION = "0.10.0";
|
|
|
3687
5042
|
getSdkFilename,
|
|
3688
5043
|
getSdkRootDir,
|
|
3689
5044
|
getSupportedSdksForUi,
|
|
5045
|
+
harmonyOsAutoSign,
|
|
5046
|
+
harmonyOsSdkLabelForApi,
|
|
3690
5047
|
hdcExec,
|
|
3691
5048
|
installApp,
|
|
3692
5049
|
installCmdTools,
|
|
@@ -3721,12 +5078,14 @@ var VERSION = "0.10.0";
|
|
|
3721
5078
|
pruneLayout,
|
|
3722
5079
|
readJson5File,
|
|
3723
5080
|
readJsonFile,
|
|
5081
|
+
readProvisionProfile,
|
|
3724
5082
|
reboot,
|
|
3725
5083
|
recvFile,
|
|
3726
5084
|
removeCmdTools,
|
|
3727
5085
|
removeEmulator,
|
|
3728
5086
|
removeSdk,
|
|
3729
5087
|
removeTempWorkDir,
|
|
5088
|
+
requireHarmonyOsSdk,
|
|
3730
5089
|
resolveTmpRoot,
|
|
3731
5090
|
runCodelinter,
|
|
3732
5091
|
runHvigorw,
|