@oniroproject/core 0.6.2 → 0.7.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 +255 -7
- package/dist/index.cjs +1602 -257
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +876 -21
- package/dist/index.d.ts +876 -21
- package/dist/index.js +1546 -258
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,15 @@ var consoleLogger = {
|
|
|
15
15
|
warn: (m) => console.warn(m),
|
|
16
16
|
error: (m) => console.error(m)
|
|
17
17
|
};
|
|
18
|
+
function scopedLogger(logger, scope) {
|
|
19
|
+
const prefix = `[${scope}] `;
|
|
20
|
+
return {
|
|
21
|
+
debug: (m) => logger.debug(prefix + m),
|
|
22
|
+
info: (m) => logger.info(prefix + m),
|
|
23
|
+
warn: (m) => logger.warn(prefix + m),
|
|
24
|
+
error: (m) => logger.error(prefix + m)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
// src/ports/progress.ts
|
|
20
29
|
var noopProgress = {
|
|
@@ -119,6 +128,19 @@ var CancelledError = class extends OniroError {
|
|
|
119
128
|
this.name = "CancelledError";
|
|
120
129
|
}
|
|
121
130
|
};
|
|
131
|
+
var CommandFailedError = class extends OniroError {
|
|
132
|
+
constructor(command, exitCode, stderr) {
|
|
133
|
+
super(`Command failed (exit ${exitCode}): ${command}${stderr ? `
|
|
134
|
+
${stderr}` : ""}`);
|
|
135
|
+
this.command = command;
|
|
136
|
+
this.exitCode = exitCode;
|
|
137
|
+
this.stderr = stderr;
|
|
138
|
+
this.name = "CommandFailedError";
|
|
139
|
+
}
|
|
140
|
+
command;
|
|
141
|
+
exitCode;
|
|
142
|
+
stderr;
|
|
143
|
+
};
|
|
122
144
|
|
|
123
145
|
// src/ports/context.ts
|
|
124
146
|
function defaultContext(overrides = {}) {
|
|
@@ -130,15 +152,16 @@ function defaultContext(overrides = {}) {
|
|
|
130
152
|
|
|
131
153
|
// src/sdk/constants.ts
|
|
132
154
|
var ALL_SDKS = [
|
|
133
|
-
{ version: "4.0", api: "10" },
|
|
134
|
-
{ version: "4.1", api: "11" },
|
|
135
|
-
{ version: "5.0.0", api: "12" },
|
|
136
|
-
{ version: "5.0.1", api: "13" },
|
|
137
|
-
{ version: "5.0.2", api: "14" },
|
|
138
|
-
{ version: "5.0.3", api: "15" },
|
|
139
|
-
{ version: "5.1.0", api: "18" },
|
|
140
|
-
{ version: "
|
|
141
|
-
{ version: "6.
|
|
155
|
+
{ version: "4.0", api: "10", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
156
|
+
{ version: "4.1", api: "11", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
157
|
+
{ version: "5.0.0", api: "12", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
158
|
+
{ version: "5.0.1", api: "13", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
159
|
+
{ version: "5.0.2", api: "14", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
160
|
+
{ version: "5.0.3", api: "15", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
161
|
+
{ version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
162
|
+
{ version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
163
|
+
{ version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
164
|
+
{ version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
|
|
142
165
|
];
|
|
143
166
|
var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
144
167
|
|
|
@@ -154,19 +177,18 @@ function getOsFolder() {
|
|
|
154
177
|
function getSdkFilename(version) {
|
|
155
178
|
const platform6 = os2.platform();
|
|
156
179
|
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
180
|
+
const strips = ALL_SDKS.find((r) => r.version === v)?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
|
|
157
181
|
if (platform6 === "linux") {
|
|
158
|
-
|
|
159
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
|
|
182
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip: strips.linuxWindows };
|
|
160
183
|
}
|
|
161
184
|
if (platform6 === "darwin") {
|
|
162
185
|
if (os2.arch() === "arm64") {
|
|
163
|
-
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip:
|
|
186
|
+
return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
164
187
|
}
|
|
165
|
-
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip:
|
|
188
|
+
return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: strips.darwin };
|
|
166
189
|
}
|
|
167
190
|
if (platform6 === "win32") {
|
|
168
|
-
|
|
169
|
-
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
|
|
191
|
+
return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip: strips.linuxWindows };
|
|
170
192
|
}
|
|
171
193
|
throw new UnsupportedPlatformError(platform6);
|
|
172
194
|
}
|
|
@@ -193,17 +215,20 @@ function pickExisting(candidates) {
|
|
|
193
215
|
}
|
|
194
216
|
return candidates[0];
|
|
195
217
|
}
|
|
196
|
-
function getCmdToolsBin(config) {
|
|
218
|
+
function getCmdToolsBin(config, name = "ohpm") {
|
|
197
219
|
const binDir = path2.join(getCmdToolsPath(config), "bin");
|
|
198
220
|
if (os3.platform() === "win32") {
|
|
199
221
|
return pickExisting([
|
|
200
|
-
path2.join(binDir,
|
|
201
|
-
path2.join(binDir,
|
|
202
|
-
path2.join(binDir,
|
|
203
|
-
path2.join(binDir,
|
|
222
|
+
path2.join(binDir, `${name}.exe`),
|
|
223
|
+
path2.join(binDir, `${name}.cmd`),
|
|
224
|
+
path2.join(binDir, `${name}.bat`),
|
|
225
|
+
path2.join(binDir, name)
|
|
204
226
|
]);
|
|
205
227
|
}
|
|
206
|
-
return path2.join(binDir,
|
|
228
|
+
return path2.join(binDir, name);
|
|
229
|
+
}
|
|
230
|
+
function getOhpmPath(config) {
|
|
231
|
+
return getCmdToolsBin(config, "ohpm");
|
|
207
232
|
}
|
|
208
233
|
function getHdcPath(config) {
|
|
209
234
|
const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
|
|
@@ -218,15 +243,20 @@ function getHdcPath(config) {
|
|
|
218
243
|
return path2.join(base, "hdc");
|
|
219
244
|
}
|
|
220
245
|
function getHvigorwPath(config, projectDir) {
|
|
221
|
-
const
|
|
222
|
-
return pickExisting([
|
|
246
|
+
const local = pickExisting([
|
|
223
247
|
path2.join(projectDir, "hvigorw"),
|
|
224
248
|
path2.join(projectDir, "hvigorw.bat"),
|
|
225
|
-
path2.join(projectDir, "hvigorw.cmd")
|
|
226
|
-
path2.join(cmdToolsBin, "hvigorw"),
|
|
227
|
-
path2.join(cmdToolsBin, "hvigorw.bat"),
|
|
228
|
-
path2.join(cmdToolsBin, "hvigorw.cmd")
|
|
249
|
+
path2.join(projectDir, "hvigorw.cmd")
|
|
229
250
|
]);
|
|
251
|
+
if (fs.existsSync(local) && isProjectLocalHvigorwWorking(projectDir)) {
|
|
252
|
+
return local;
|
|
253
|
+
}
|
|
254
|
+
return getCmdToolsBin(config, "hvigorw");
|
|
255
|
+
}
|
|
256
|
+
function isProjectLocalHvigorwWorking(projectDir) {
|
|
257
|
+
const wrapper = path2.join(projectDir, "hvigor", "hvigor-wrapper.js");
|
|
258
|
+
const nodeModules = path2.join(projectDir, "hvigor", "node_modules");
|
|
259
|
+
return fs.existsSync(wrapper) && fs.existsSync(nodeModules);
|
|
230
260
|
}
|
|
231
261
|
|
|
232
262
|
// src/sdk/detectProjectSdk.ts
|
|
@@ -482,22 +512,37 @@ function removeSdk(config, api) {
|
|
|
482
512
|
}
|
|
483
513
|
|
|
484
514
|
// src/sdk/install.ts
|
|
485
|
-
import * as
|
|
515
|
+
import * as fs8 from "fs";
|
|
486
516
|
import * as os5 from "os";
|
|
487
517
|
import * as path7 from "path";
|
|
518
|
+
|
|
519
|
+
// src/sdk/move.ts
|
|
520
|
+
import * as fs7 from "fs";
|
|
521
|
+
function movePath(src, dest) {
|
|
522
|
+
try {
|
|
523
|
+
fs7.renameSync(src, dest);
|
|
524
|
+
} catch (err) {
|
|
525
|
+
if (err.code !== "EXDEV") throw err;
|
|
526
|
+
fs7.cpSync(src, dest, { recursive: true });
|
|
527
|
+
fs7.rmSync(src, { recursive: true, force: true });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/sdk/install.ts
|
|
488
532
|
async function downloadAndInstallSdk(opts) {
|
|
489
533
|
const { config, version, api, progress, abortSignal } = opts;
|
|
490
534
|
const logger = opts.logger ?? noopLogger;
|
|
491
535
|
const { filename, osFolder, strip } = getSdkFilename(version);
|
|
492
|
-
const
|
|
536
|
+
const urlBase = config.get("sdkUrlBase", OHOS_URL_BASE);
|
|
537
|
+
const downloadUrl = `${urlBase}/${version}-Release/${filename}`;
|
|
493
538
|
const sha256Url = `${downloadUrl}.sha256`;
|
|
494
|
-
const tmpDir =
|
|
539
|
+
const tmpDir = fs8.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
|
|
495
540
|
const tarPath = path7.join(tmpDir, filename);
|
|
496
541
|
const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
|
|
497
542
|
const extractDir = path7.join(tmpDir, "extract");
|
|
498
|
-
|
|
543
|
+
fs8.mkdirSync(extractDir);
|
|
499
544
|
const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
|
|
500
|
-
|
|
545
|
+
fs8.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
|
|
501
546
|
const checkCancelled = () => {
|
|
502
547
|
if (abortSignal?.aborted) throw new CancelledError();
|
|
503
548
|
};
|
|
@@ -515,12 +560,12 @@ async function downloadAndInstallSdk(opts) {
|
|
|
515
560
|
progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
|
|
516
561
|
checkCancelled();
|
|
517
562
|
const osContentPath = path7.join(extractDir, osFolder);
|
|
518
|
-
if (!
|
|
563
|
+
if (!fs8.existsSync(osContentPath)) {
|
|
519
564
|
throw new OniroError(
|
|
520
565
|
`Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
|
|
521
566
|
);
|
|
522
567
|
}
|
|
523
|
-
const zipFiles =
|
|
568
|
+
const zipFiles = fs8.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
|
|
524
569
|
const componentsStart = 60;
|
|
525
570
|
const componentsBudget = 35;
|
|
526
571
|
const n = zipFiles.length;
|
|
@@ -540,26 +585,26 @@ async function downloadAndInstallSdk(opts) {
|
|
|
540
585
|
if (rem > 0) rem--;
|
|
541
586
|
await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
|
|
542
587
|
cursor += thisBudget;
|
|
543
|
-
|
|
588
|
+
fs8.unlinkSync(zipPath);
|
|
544
589
|
}
|
|
545
590
|
progress?.report({ message: "Finalizing installation...", increment: 0 });
|
|
546
|
-
if (
|
|
547
|
-
|
|
591
|
+
if (fs8.existsSync(sdkInstallDir)) {
|
|
592
|
+
fs8.rmSync(sdkInstallDir, { recursive: true, force: true });
|
|
548
593
|
}
|
|
549
|
-
|
|
594
|
+
movePath(osContentPath, sdkInstallDir);
|
|
550
595
|
progress?.report({ message: "Finalizing installation...", increment: 3 });
|
|
551
596
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
552
|
-
|
|
597
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
553
598
|
progress?.report({ message: "Cleaning up...", increment: 2 });
|
|
554
599
|
} catch (err) {
|
|
555
600
|
logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
556
|
-
|
|
601
|
+
fs8.rmSync(tmpDir, { recursive: true, force: true });
|
|
557
602
|
throw err;
|
|
558
603
|
}
|
|
559
604
|
}
|
|
560
605
|
|
|
561
606
|
// src/cmdTools/install.ts
|
|
562
|
-
import * as
|
|
607
|
+
import * as fs9 from "fs";
|
|
563
608
|
import * as os6 from "os";
|
|
564
609
|
import * as path8 from "path";
|
|
565
610
|
import { execFileSync } from "child_process";
|
|
@@ -594,11 +639,11 @@ function findCmdToolsSourceDir(extractPath) {
|
|
|
594
639
|
path8.join(extractPath, "commandline-tools")
|
|
595
640
|
];
|
|
596
641
|
for (const c of known) {
|
|
597
|
-
if (
|
|
642
|
+
if (fs9.existsSync(c)) return c;
|
|
598
643
|
}
|
|
599
|
-
const entries =
|
|
644
|
+
const entries = fs9.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
|
|
600
645
|
for (const dir of entries) {
|
|
601
|
-
if (
|
|
646
|
+
if (fs9.existsSync(path8.join(dir, "bin"))) return dir;
|
|
602
647
|
}
|
|
603
648
|
throw new OniroError("Could not locate command line tools folder in the extracted archive.");
|
|
604
649
|
}
|
|
@@ -606,7 +651,7 @@ async function installCmdTools(opts) {
|
|
|
606
651
|
const { config, progress, abortSignal } = opts;
|
|
607
652
|
const logger = opts.logger ?? noopLogger;
|
|
608
653
|
const CMD_PATH = getCmdToolsPath(config);
|
|
609
|
-
const tmpDir =
|
|
654
|
+
const tmpDir = fs9.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
|
|
610
655
|
const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
|
|
611
656
|
const extractPath = path8.join(tmpDir, "oh-command-line-tools");
|
|
612
657
|
try {
|
|
@@ -617,42 +662,42 @@ async function installCmdTools(opts) {
|
|
|
617
662
|
}
|
|
618
663
|
progress?.report({ message: "Extracting tools...", increment: 0 });
|
|
619
664
|
await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
|
|
620
|
-
|
|
665
|
+
fs9.mkdirSync(CMD_PATH, { recursive: true });
|
|
621
666
|
const srcDir = findCmdToolsSourceDir(extractPath);
|
|
622
|
-
for (const entry of
|
|
667
|
+
for (const entry of fs9.readdirSync(srcDir)) {
|
|
623
668
|
const src = path8.join(srcDir, entry);
|
|
624
669
|
const dest = path8.join(CMD_PATH, entry);
|
|
625
|
-
if (
|
|
626
|
-
if (
|
|
627
|
-
|
|
670
|
+
if (fs9.statSync(src).isDirectory()) {
|
|
671
|
+
if (fs9.existsSync(dest)) fs9.rmSync(dest, { recursive: true, force: true });
|
|
672
|
+
movePath(src, dest);
|
|
628
673
|
} else {
|
|
629
|
-
|
|
674
|
+
fs9.copyFileSync(src, dest);
|
|
630
675
|
}
|
|
631
676
|
}
|
|
632
677
|
const binDir = path8.join(CMD_PATH, "bin");
|
|
633
|
-
if (
|
|
634
|
-
for (const file of
|
|
635
|
-
|
|
678
|
+
if (fs9.existsSync(binDir) && os6.platform() !== "win32") {
|
|
679
|
+
for (const file of fs9.readdirSync(binDir)) {
|
|
680
|
+
fs9.chmodSync(path8.join(binDir, file), 493);
|
|
636
681
|
}
|
|
637
682
|
}
|
|
638
683
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
639
684
|
progress?.report({ message: "Cleaning up...", increment: 0 });
|
|
640
685
|
} finally {
|
|
641
|
-
|
|
686
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
642
687
|
}
|
|
643
688
|
}
|
|
644
689
|
function getCmdToolsStatus(config) {
|
|
645
690
|
const cmdPath = getCmdToolsPath(config);
|
|
646
691
|
const binDir = path8.join(cmdPath, "bin");
|
|
647
692
|
const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
648
|
-
const bin = candidates.map((c) => path8.join(binDir, c)).find((p) =>
|
|
693
|
+
const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs9.existsSync(p));
|
|
649
694
|
if (!bin) {
|
|
650
695
|
return { installed: false, status: "Not installed" };
|
|
651
696
|
}
|
|
652
697
|
try {
|
|
653
698
|
const versionFile = path8.join(cmdPath, "version.txt");
|
|
654
|
-
if (
|
|
655
|
-
const content =
|
|
699
|
+
if (fs9.existsSync(versionFile)) {
|
|
700
|
+
const content = fs9.readFileSync(versionFile, "utf8");
|
|
656
701
|
for (const line of content.split(/\r?\n/)) {
|
|
657
702
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
658
703
|
if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
|
|
@@ -672,13 +717,13 @@ function isCmdToolsInstalled(config) {
|
|
|
672
717
|
}
|
|
673
718
|
function removeCmdTools(config) {
|
|
674
719
|
const cmdPath = getCmdToolsPath(config);
|
|
675
|
-
if (
|
|
676
|
-
|
|
720
|
+
if (fs9.existsSync(cmdPath)) {
|
|
721
|
+
fs9.rmSync(cmdPath, { recursive: true, force: true });
|
|
677
722
|
}
|
|
678
723
|
}
|
|
679
724
|
|
|
680
725
|
// src/sign/encryptKey.ts
|
|
681
|
-
import * as
|
|
726
|
+
import * as fs10 from "fs";
|
|
682
727
|
import * as path9 from "path";
|
|
683
728
|
import * as crypto2 from "crypto";
|
|
684
729
|
var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
|
|
@@ -752,8 +797,8 @@ function createAndStoreKey(dir, keyLength) {
|
|
|
752
797
|
const key = crypto2.randomBytes(keyLength);
|
|
753
798
|
const hash = crypto2.createHash("sha256").update(key).digest("hex");
|
|
754
799
|
const filePath = path9.join(dir, hash);
|
|
755
|
-
|
|
756
|
-
|
|
800
|
+
fs10.writeFileSync(filePath, key);
|
|
801
|
+
fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
757
802
|
return key;
|
|
758
803
|
}
|
|
759
804
|
function createAndStoreEnKey(rootKey, ceDir) {
|
|
@@ -761,23 +806,23 @@ function createAndStoreEnKey(rootKey, ceDir) {
|
|
|
761
806
|
const encrypted = encrypt(rootKey, workKey);
|
|
762
807
|
const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
|
|
763
808
|
const filePath = path9.join(ceDir, hash);
|
|
764
|
-
|
|
765
|
-
|
|
809
|
+
fs10.writeFileSync(filePath, encrypted);
|
|
810
|
+
fs10.chmodSync(filePath, KEY_FILE_PERMISSIONS);
|
|
766
811
|
}
|
|
767
812
|
function createMaterial(materialPath) {
|
|
768
|
-
|
|
769
|
-
|
|
813
|
+
fs10.mkdirSync(materialPath, { recursive: true });
|
|
814
|
+
fs10.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
|
|
770
815
|
const fdDir = path9.join(materialPath, "fd");
|
|
771
816
|
const acDir = path9.join(materialPath, "ac");
|
|
772
817
|
const ceDir = path9.join(materialPath, "ce");
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
818
|
+
fs10.mkdirSync(fdDir, { recursive: true });
|
|
819
|
+
fs10.mkdirSync(acDir, { recursive: true });
|
|
820
|
+
fs10.mkdirSync(ceDir, { recursive: true });
|
|
776
821
|
const fdSubDirs = ["0", "1", "2"];
|
|
777
822
|
const fdComponents = [];
|
|
778
823
|
for (const sub of fdSubDirs) {
|
|
779
824
|
const subDir = path9.join(fdDir, sub);
|
|
780
|
-
|
|
825
|
+
fs10.mkdirSync(subDir, { recursive: true });
|
|
781
826
|
const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
|
|
782
827
|
fdComponents.push(comp);
|
|
783
828
|
}
|
|
@@ -786,17 +831,17 @@ function createMaterial(materialPath) {
|
|
|
786
831
|
createAndStoreEnKey(rootKey, ceDir);
|
|
787
832
|
}
|
|
788
833
|
function readSingleFile(dir) {
|
|
789
|
-
if (!
|
|
834
|
+
if (!fs10.existsSync(dir) || !fs10.statSync(dir).isDirectory()) {
|
|
790
835
|
throw new Error(`Missing signing material directory: ${dir}`);
|
|
791
836
|
}
|
|
792
|
-
const files =
|
|
837
|
+
const files = fs10.readdirSync(dir).filter((f) => f !== ".DS_Store");
|
|
793
838
|
if (files.length !== 1) {
|
|
794
839
|
throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
|
|
795
840
|
}
|
|
796
|
-
return
|
|
841
|
+
return fs10.readFileSync(path9.join(dir, files[0]));
|
|
797
842
|
}
|
|
798
843
|
function getKey(materialPath) {
|
|
799
|
-
if (!
|
|
844
|
+
if (!fs10.existsSync(materialPath) || !fs10.statSync(materialPath).isDirectory()) {
|
|
800
845
|
throw new Error("Material directory does not exist.");
|
|
801
846
|
}
|
|
802
847
|
const fdDir = path9.join(materialPath, "fd");
|
|
@@ -818,7 +863,7 @@ function decryptPwd(encryptedHex, materialPath) {
|
|
|
818
863
|
}
|
|
819
864
|
|
|
820
865
|
// src/sign/generateSigningConfigs.ts
|
|
821
|
-
import * as
|
|
866
|
+
import * as fs11 from "fs";
|
|
822
867
|
import * as path10 from "path";
|
|
823
868
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
824
869
|
import JSON52 from "json5";
|
|
@@ -873,6 +918,7 @@ zAlF08DnbJrOOtOnQq5wHOPlDYB4OtUzOYJk9scotrEnJxJzGsh/
|
|
|
873
918
|
// src/sign/generateSigningConfigs.ts
|
|
874
919
|
var APL_VALUES = ["normal", "system_basic", "system_core"];
|
|
875
920
|
var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
|
|
921
|
+
var DEFAULT_PASSWORDS = { store: "123456", key: "123456" };
|
|
876
922
|
function pickSigningKind(apl) {
|
|
877
923
|
return apl === "normal" ? "profile-release" : "application-release";
|
|
878
924
|
}
|
|
@@ -886,18 +932,20 @@ var SIGNING_KIND_CONFIG = {
|
|
|
886
932
|
certBasename: "OpenHarmonyApplication.cer"
|
|
887
933
|
}
|
|
888
934
|
};
|
|
889
|
-
function copyFilesToProject(projectDir, paths, kind, logger) {
|
|
935
|
+
function copyFilesToProject(projectDir, paths, kind, logger, applicationCertPath) {
|
|
890
936
|
logger.info("[sign] Copying signing material into project...");
|
|
891
937
|
const signaturesDir = path10.join(projectDir, "signatures");
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
938
|
+
fs11.mkdirSync(signaturesDir, { recursive: true });
|
|
939
|
+
fs11.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
|
|
940
|
+
fs11.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
941
|
+
fs11.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
|
|
896
942
|
if (kind === "application-release") {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
943
|
+
const dest = path10.join(signaturesDir, "OpenHarmonyApplication.cer");
|
|
944
|
+
if (applicationCertPath) {
|
|
945
|
+
fs11.copyFileSync(applicationCertPath, dest);
|
|
946
|
+
} else {
|
|
947
|
+
fs11.writeFileSync(dest, OPEN_HARMONY_APPLICATION_CERT);
|
|
948
|
+
}
|
|
901
949
|
}
|
|
902
950
|
}
|
|
903
951
|
function resolveAppFeature(apl, override) {
|
|
@@ -905,7 +953,7 @@ function resolveAppFeature(apl, override) {
|
|
|
905
953
|
return apl === "normal" ? "hos_normal_app" : "hos_system_app";
|
|
906
954
|
}
|
|
907
955
|
function extractLeafCertificate(chainPath) {
|
|
908
|
-
const certContent =
|
|
956
|
+
const certContent = fs11.readFileSync(chainPath, "utf-8");
|
|
909
957
|
const parts = certContent.split("-----END CERTIFICATE-----");
|
|
910
958
|
if (parts.length < 3) {
|
|
911
959
|
throw new OniroError(`${chainPath} does not contain enough certificates.`);
|
|
@@ -917,18 +965,18 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
917
965
|
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
918
966
|
const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
|
|
919
967
|
const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
920
|
-
if (!
|
|
968
|
+
if (!fs11.existsSync(appJsonPath)) {
|
|
921
969
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
922
970
|
}
|
|
923
971
|
let appJson;
|
|
924
972
|
try {
|
|
925
|
-
appJson = JSON52.parse(
|
|
973
|
+
appJson = JSON52.parse(fs11.readFileSync(appJsonPath, "utf-8"));
|
|
926
974
|
} catch (e) {
|
|
927
975
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
928
976
|
}
|
|
929
977
|
let profileTemplate;
|
|
930
978
|
try {
|
|
931
|
-
profileTemplate = JSON.parse(
|
|
979
|
+
profileTemplate = JSON.parse(fs11.readFileSync(profileTemplatePath, "utf-8"));
|
|
932
980
|
} catch (e) {
|
|
933
981
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
934
982
|
}
|
|
@@ -951,9 +999,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
951
999
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
952
1000
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
953
1001
|
}
|
|
954
|
-
|
|
1002
|
+
fs11.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
955
1003
|
}
|
|
956
|
-
function generateP7bFile(projectDir, paths, logger) {
|
|
1004
|
+
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
957
1005
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
958
1006
|
const signaturesDir = path10.join(projectDir, "signatures");
|
|
959
1007
|
const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
@@ -977,9 +1025,9 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
977
1025
|
"-outFile",
|
|
978
1026
|
outputProfilePath,
|
|
979
1027
|
"-keyPwd",
|
|
980
|
-
|
|
1028
|
+
passwords.key,
|
|
981
1029
|
"-keystorePwd",
|
|
982
|
-
|
|
1030
|
+
passwords.store
|
|
983
1031
|
];
|
|
984
1032
|
try {
|
|
985
1033
|
execFileSync2("java", args, { stdio: "inherit" });
|
|
@@ -992,10 +1040,10 @@ function generateP7bFile(projectDir, paths, logger) {
|
|
|
992
1040
|
}
|
|
993
1041
|
function detectSigningConfigNames(projectDir) {
|
|
994
1042
|
const buildProfilePath = path10.join(projectDir, "build-profile.json5");
|
|
995
|
-
if (!
|
|
1043
|
+
if (!fs11.existsSync(buildProfilePath)) return ["default"];
|
|
996
1044
|
let parsed;
|
|
997
1045
|
try {
|
|
998
|
-
parsed = JSON52.parse(
|
|
1046
|
+
parsed = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
|
|
999
1047
|
} catch {
|
|
1000
1048
|
return ["default"];
|
|
1001
1049
|
}
|
|
@@ -1010,16 +1058,16 @@ function detectSigningConfigNames(projectDir) {
|
|
|
1010
1058
|
}
|
|
1011
1059
|
return names.length > 0 ? names : ["default"];
|
|
1012
1060
|
}
|
|
1013
|
-
function updateBuildProfile(projectDir, kind, logger) {
|
|
1061
|
+
function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
1014
1062
|
logger.info("[sign] Writing signing configs into build-profile.json5...");
|
|
1015
1063
|
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1016
1064
|
const buildProfilePath = path10.join(projectDir, "build-profile.json5");
|
|
1017
|
-
const encryptedStorePassword = encryptPwd(
|
|
1018
|
-
const encryptedKeyPassword = encryptPwd(
|
|
1065
|
+
const encryptedStorePassword = encryptPwd(passwords.store, materialDir);
|
|
1066
|
+
const encryptedKeyPassword = encryptPwd(passwords.key, materialDir);
|
|
1019
1067
|
let buildProfile = { app: {} };
|
|
1020
|
-
if (
|
|
1068
|
+
if (fs11.existsSync(buildProfilePath)) {
|
|
1021
1069
|
try {
|
|
1022
|
-
buildProfile = JSON52.parse(
|
|
1070
|
+
buildProfile = JSON52.parse(fs11.readFileSync(buildProfilePath, "utf-8"));
|
|
1023
1071
|
} catch (e) {
|
|
1024
1072
|
throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
|
|
1025
1073
|
}
|
|
@@ -1040,13 +1088,13 @@ function updateBuildProfile(projectDir, kind, logger) {
|
|
|
1040
1088
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1041
1089
|
}
|
|
1042
1090
|
}));
|
|
1043
|
-
|
|
1091
|
+
fs11.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1044
1092
|
}
|
|
1045
1093
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
1046
1094
|
logger.info("[sign] Generating fresh signing material...");
|
|
1047
1095
|
const materialDir = path10.join(projectDir, "signatures", "material");
|
|
1048
|
-
if (
|
|
1049
|
-
|
|
1096
|
+
if (fs11.existsSync(materialDir)) {
|
|
1097
|
+
fs11.rmSync(materialDir, { recursive: true, force: true });
|
|
1050
1098
|
}
|
|
1051
1099
|
createMaterial(materialDir);
|
|
1052
1100
|
}
|
|
@@ -1070,7 +1118,7 @@ function generateSigningConfigs(options) {
|
|
|
1070
1118
|
);
|
|
1071
1119
|
}
|
|
1072
1120
|
const sdkPath = path10.join(sdkHome, String(sdkVersion));
|
|
1073
|
-
if (!
|
|
1121
|
+
if (!fs11.existsSync(sdkPath)) {
|
|
1074
1122
|
throw new OniroError(`SDK path does not exist: ${sdkPath}`);
|
|
1075
1123
|
}
|
|
1076
1124
|
const paths = {
|
|
@@ -1080,17 +1128,18 @@ function generateSigningConfigs(options) {
|
|
|
1080
1128
|
unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1081
1129
|
};
|
|
1082
1130
|
const kind = pickSigningKind(apl);
|
|
1131
|
+
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
1083
1132
|
logger.info("[sign] Starting signing configuration generation...");
|
|
1084
|
-
copyFilesToProject(projectDir, paths, kind, logger);
|
|
1133
|
+
copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
|
|
1085
1134
|
modifyProfileTemplate(projectDir, apl, appFeature, logger, { kind, acls: options.acls });
|
|
1086
|
-
generateP7bFile(projectDir, paths, logger);
|
|
1135
|
+
generateP7bFile(projectDir, paths, logger, passwords);
|
|
1087
1136
|
prepareMaterialDirectory(projectDir, logger);
|
|
1088
|
-
updateBuildProfile(projectDir, kind, logger);
|
|
1137
|
+
updateBuildProfile(projectDir, kind, logger, passwords);
|
|
1089
1138
|
logger.info("[sign] Signing configuration generated successfully.");
|
|
1090
1139
|
}
|
|
1091
1140
|
|
|
1092
1141
|
// src/emulator/install.ts
|
|
1093
|
-
import * as
|
|
1142
|
+
import * as fs12 from "fs";
|
|
1094
1143
|
import * as os7 from "os";
|
|
1095
1144
|
import * as path11 from "path";
|
|
1096
1145
|
async function installEmulator(opts) {
|
|
@@ -1098,63 +1147,142 @@ async function installEmulator(opts) {
|
|
|
1098
1147
|
const logger = opts.logger ?? noopLogger;
|
|
1099
1148
|
const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
|
|
1100
1149
|
const emulatorDir = getEmulatorDir(config);
|
|
1101
|
-
const tmpDir =
|
|
1150
|
+
const tmpDir = fs12.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
|
|
1102
1151
|
const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
|
|
1103
1152
|
try {
|
|
1104
|
-
|
|
1153
|
+
fs12.mkdirSync(emulatorDir, { recursive: true });
|
|
1105
1154
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1106
1155
|
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
|
|
1107
1156
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1108
1157
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1109
1158
|
const runSh = path11.join(emulatorDir, "images", "run.sh");
|
|
1110
|
-
if (
|
|
1111
|
-
|
|
1159
|
+
if (fs12.existsSync(runSh)) {
|
|
1160
|
+
fs12.chmodSync(runSh, 493);
|
|
1112
1161
|
}
|
|
1113
1162
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1114
1163
|
} finally {
|
|
1115
|
-
|
|
1164
|
+
fs12.rmSync(tmpDir, { recursive: true, force: true });
|
|
1116
1165
|
}
|
|
1117
1166
|
}
|
|
1118
1167
|
function isEmulatorInstalled(config) {
|
|
1119
|
-
|
|
1168
|
+
const imagesDir = path11.join(getEmulatorDir(config), "images");
|
|
1169
|
+
return fs12.existsSync(path11.join(imagesDir, "run.sh")) || fs12.existsSync(path11.join(imagesDir, "run.bat"));
|
|
1120
1170
|
}
|
|
1121
1171
|
function removeEmulator(config) {
|
|
1122
1172
|
const emulatorDir = getEmulatorDir(config);
|
|
1123
|
-
if (
|
|
1124
|
-
|
|
1173
|
+
if (fs12.existsSync(emulatorDir)) {
|
|
1174
|
+
fs12.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1125
1175
|
}
|
|
1126
1176
|
}
|
|
1127
1177
|
|
|
1128
1178
|
// src/emulator/lifecycle.ts
|
|
1129
|
-
import * as
|
|
1179
|
+
import * as fs13 from "fs";
|
|
1130
1180
|
import * as os8 from "os";
|
|
1131
1181
|
import * as path12 from "path";
|
|
1132
|
-
import {
|
|
1133
|
-
|
|
1134
|
-
|
|
1182
|
+
import { spawn as spawn2 } from "child_process";
|
|
1183
|
+
|
|
1184
|
+
// src/hdc/exec.ts
|
|
1185
|
+
import { spawn } from "child_process";
|
|
1186
|
+
function runProcess(opts) {
|
|
1187
|
+
const { command, args, cwd, env, onOutput } = opts;
|
|
1188
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1189
|
+
const { abortSignal } = opts;
|
|
1135
1190
|
return new Promise((resolve2, reject) => {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1191
|
+
if (abortSignal?.aborted) {
|
|
1192
|
+
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const child = spawn(command, [...args], { cwd, env, shell: false });
|
|
1196
|
+
let stdout = "";
|
|
1197
|
+
let stderr = "";
|
|
1198
|
+
let settled = false;
|
|
1199
|
+
let timedOut = false;
|
|
1200
|
+
const cleanup = () => {
|
|
1201
|
+
clearTimeout(timer);
|
|
1202
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
1203
|
+
};
|
|
1204
|
+
const onAbort = () => {
|
|
1205
|
+
if (settled) return;
|
|
1206
|
+
settled = true;
|
|
1207
|
+
cleanup();
|
|
1208
|
+
try {
|
|
1209
|
+
child.kill("SIGKILL");
|
|
1210
|
+
} catch {
|
|
1211
|
+
}
|
|
1212
|
+
reject(new CancelledError(`Command aborted: ${command} ${args.join(" ")}`));
|
|
1213
|
+
};
|
|
1214
|
+
const timer = setTimeout(() => {
|
|
1215
|
+
timedOut = true;
|
|
1216
|
+
try {
|
|
1217
|
+
child.kill("SIGKILL");
|
|
1218
|
+
} catch {
|
|
1219
|
+
}
|
|
1220
|
+
}, timeoutMs);
|
|
1221
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1222
|
+
child.stdout.on("data", (d) => {
|
|
1223
|
+
const s = d.toString();
|
|
1224
|
+
stdout += s;
|
|
1225
|
+
onOutput?.(s, "stdout");
|
|
1226
|
+
});
|
|
1227
|
+
child.stderr.on("data", (d) => {
|
|
1228
|
+
const s = d.toString();
|
|
1229
|
+
stderr += s;
|
|
1230
|
+
onOutput?.(s, "stderr");
|
|
1231
|
+
});
|
|
1232
|
+
child.on("error", (err) => {
|
|
1233
|
+
if (settled) return;
|
|
1234
|
+
settled = true;
|
|
1235
|
+
cleanup();
|
|
1236
|
+
reject(new OniroError(`Failed to spawn ${command}: ${err.message}`, err));
|
|
1237
|
+
});
|
|
1238
|
+
child.on("close", (code) => {
|
|
1239
|
+
if (settled) return;
|
|
1240
|
+
settled = true;
|
|
1241
|
+
cleanup();
|
|
1242
|
+
if (timedOut) {
|
|
1243
|
+
reject(
|
|
1244
|
+
new OniroError(
|
|
1245
|
+
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}` + (stderr.trim() ? `
|
|
1246
|
+
${stderr.trim()}` : "")
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
return;
|
|
1144
1250
|
}
|
|
1251
|
+
resolve2({ code: code ?? -1, stdout, stderr });
|
|
1145
1252
|
});
|
|
1146
1253
|
});
|
|
1147
1254
|
}
|
|
1255
|
+
function hdcExec(opts) {
|
|
1256
|
+
const hdc = getHdcPath(opts.config);
|
|
1257
|
+
const argv = opts.deviceSerial ? ["-t", opts.deviceSerial, ...opts.args] : [...opts.args];
|
|
1258
|
+
return runProcess({
|
|
1259
|
+
command: hdc,
|
|
1260
|
+
args: argv,
|
|
1261
|
+
timeoutMs: opts.timeoutMs,
|
|
1262
|
+
abortSignal: opts.abortSignal,
|
|
1263
|
+
onOutput: opts.onOutput,
|
|
1264
|
+
logger: opts.logger
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
function shell(opts) {
|
|
1268
|
+
const { command, ...rest } = opts;
|
|
1269
|
+
return hdcExec({ ...rest, args: ["shell", command] });
|
|
1270
|
+
}
|
|
1271
|
+
function ensureOk(result, command) {
|
|
1272
|
+
if (result.code !== 0) {
|
|
1273
|
+
throw new CommandFailedError(command, result.code, result.stderr.trim());
|
|
1274
|
+
}
|
|
1275
|
+
return result;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// src/emulator/lifecycle.ts
|
|
1279
|
+
var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
|
|
1148
1280
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1149
|
-
const hdc = getHdcPath(config);
|
|
1150
1281
|
try {
|
|
1151
|
-
await
|
|
1152
|
-
await
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
resolve2(stdout?.includes(address) ?? false);
|
|
1156
|
-
});
|
|
1157
|
-
});
|
|
1282
|
+
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
1283
|
+
await hdcExec({ config, args: ["tconn", address], logger });
|
|
1284
|
+
const targets = await hdcExec({ config, args: ["list", "targets"], logger });
|
|
1285
|
+
return targets.stdout.includes(address);
|
|
1158
1286
|
} catch (err) {
|
|
1159
1287
|
logger.warn(`hdc connection attempt failed: ${err.message}`);
|
|
1160
1288
|
return false;
|
|
@@ -1167,18 +1295,18 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1167
1295
|
if (headless) extra.push("--headless");
|
|
1168
1296
|
if (connect) extra.push("--connect", connect);
|
|
1169
1297
|
if (os8.platform() === "win32") {
|
|
1170
|
-
if (
|
|
1298
|
+
if (fs13.existsSync(runBat)) {
|
|
1171
1299
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1172
1300
|
}
|
|
1173
|
-
if (
|
|
1301
|
+
if (fs13.existsSync(runSh)) {
|
|
1174
1302
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1175
1303
|
}
|
|
1176
1304
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1177
1305
|
}
|
|
1178
|
-
if (
|
|
1306
|
+
if (fs13.existsSync(runSh)) {
|
|
1179
1307
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1180
1308
|
}
|
|
1181
|
-
if (
|
|
1309
|
+
if (fs13.existsSync(runBat)) {
|
|
1182
1310
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1183
1311
|
}
|
|
1184
1312
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1186,9 +1314,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1186
1314
|
async function startEmulator(opts) {
|
|
1187
1315
|
const { config } = opts;
|
|
1188
1316
|
const logger = opts.logger ?? noopLogger;
|
|
1189
|
-
if (os8.platform() !== "win32" &&
|
|
1317
|
+
if (os8.platform() !== "win32" && fs13.existsSync(PID_FILE)) {
|
|
1190
1318
|
try {
|
|
1191
|
-
const pid = Number(
|
|
1319
|
+
const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
|
|
1192
1320
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1193
1321
|
try {
|
|
1194
1322
|
process.kill(pid, 0);
|
|
@@ -1205,27 +1333,27 @@ async function startEmulator(opts) {
|
|
|
1205
1333
|
}
|
|
1206
1334
|
}
|
|
1207
1335
|
const imagesPath = path12.join(getEmulatorDir(config), "images");
|
|
1208
|
-
if (!
|
|
1336
|
+
if (!fs13.existsSync(imagesPath)) {
|
|
1209
1337
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1210
1338
|
}
|
|
1211
1339
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1212
1340
|
const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
|
|
1213
|
-
const logFd =
|
|
1341
|
+
const logFd = fs13.openSync(logPath, "a");
|
|
1214
1342
|
let child;
|
|
1215
1343
|
try {
|
|
1216
|
-
child =
|
|
1344
|
+
child = spawn2(launcher.command, [...launcher.args], {
|
|
1217
1345
|
cwd: launcher.cwd,
|
|
1218
1346
|
detached: true,
|
|
1219
1347
|
stdio: ["ignore", logFd, logFd],
|
|
1220
1348
|
windowsHide: true
|
|
1221
1349
|
});
|
|
1222
1350
|
} finally {
|
|
1223
|
-
|
|
1351
|
+
fs13.closeSync(logFd);
|
|
1224
1352
|
}
|
|
1225
1353
|
if (!child.pid) {
|
|
1226
1354
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1227
1355
|
}
|
|
1228
|
-
|
|
1356
|
+
fs13.writeFileSync(PID_FILE, String(child.pid));
|
|
1229
1357
|
child.unref();
|
|
1230
1358
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1231
1359
|
const earlyExit = new Promise((resolve2) => {
|
|
@@ -1272,24 +1400,24 @@ ${tail}` : ""}`);
|
|
|
1272
1400
|
}
|
|
1273
1401
|
function readTail(filePath, bytes) {
|
|
1274
1402
|
try {
|
|
1275
|
-
const stat =
|
|
1403
|
+
const stat = fs13.statSync(filePath);
|
|
1276
1404
|
const start = Math.max(0, stat.size - bytes);
|
|
1277
|
-
const fd =
|
|
1405
|
+
const fd = fs13.openSync(filePath, "r");
|
|
1278
1406
|
try {
|
|
1279
1407
|
const buf = Buffer.alloc(stat.size - start);
|
|
1280
|
-
|
|
1408
|
+
fs13.readSync(fd, buf, 0, buf.length, start);
|
|
1281
1409
|
return buf.toString("utf8");
|
|
1282
1410
|
} finally {
|
|
1283
|
-
|
|
1411
|
+
fs13.closeSync(fd);
|
|
1284
1412
|
}
|
|
1285
1413
|
} catch {
|
|
1286
1414
|
return "";
|
|
1287
1415
|
}
|
|
1288
1416
|
}
|
|
1289
1417
|
async function stopEmulator(logger = noopLogger) {
|
|
1290
|
-
if (
|
|
1418
|
+
if (fs13.existsSync(PID_FILE)) {
|
|
1291
1419
|
try {
|
|
1292
|
-
const pid = Number(
|
|
1420
|
+
const pid = Number(fs13.readFileSync(PID_FILE, "utf8").trim());
|
|
1293
1421
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1294
1422
|
try {
|
|
1295
1423
|
process.kill(pid, "SIGTERM");
|
|
@@ -1302,27 +1430,194 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1302
1430
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1303
1431
|
}
|
|
1304
1432
|
}
|
|
1305
|
-
const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
|
|
1433
|
+
const [killCmd, ...killArgs] = os8.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
1306
1434
|
try {
|
|
1307
|
-
await
|
|
1308
|
-
} catch {
|
|
1435
|
+
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
1309
1438
|
}
|
|
1310
|
-
|
|
1439
|
+
fs13.rm(PID_FILE, { force: true }, (err) => {
|
|
1311
1440
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1312
1441
|
});
|
|
1313
1442
|
}
|
|
1314
1443
|
|
|
1444
|
+
// src/hdc/devices.ts
|
|
1445
|
+
var STATUS_TOKENS = /* @__PURE__ */ new Set([
|
|
1446
|
+
"Connected",
|
|
1447
|
+
"Offline",
|
|
1448
|
+
"Unauthorized",
|
|
1449
|
+
"Authorizing",
|
|
1450
|
+
"Disconnected",
|
|
1451
|
+
"Recovery"
|
|
1452
|
+
]);
|
|
1453
|
+
var CONNECTION_TOKENS = /* @__PURE__ */ new Set(["USB", "TCP", "UART", "BT", "UNKNOWN"]);
|
|
1454
|
+
function parseDeviceList(stdout) {
|
|
1455
|
+
const devices = [];
|
|
1456
|
+
for (const line of stdout.split("\n")) {
|
|
1457
|
+
const trimmed = line.trim();
|
|
1458
|
+
if (!trimmed || trimmed.startsWith("[Empty]")) continue;
|
|
1459
|
+
const cols = trimmed.split(/\s+/);
|
|
1460
|
+
const serial = cols[0];
|
|
1461
|
+
if (!serial) continue;
|
|
1462
|
+
const rest = cols.slice(1);
|
|
1463
|
+
const connection = rest.find((c) => CONNECTION_TOKENS.has(c));
|
|
1464
|
+
const status = rest.find((c) => STATUS_TOKENS.has(c)) ?? "Unknown";
|
|
1465
|
+
const info = { serial, status };
|
|
1466
|
+
if (connection) info.connection = connection;
|
|
1467
|
+
devices.push(info);
|
|
1468
|
+
}
|
|
1469
|
+
return devices;
|
|
1470
|
+
}
|
|
1471
|
+
async function listDevices(config, opts = {}) {
|
|
1472
|
+
const res = await hdcExec({
|
|
1473
|
+
config,
|
|
1474
|
+
args: ["list", "targets", "-v"],
|
|
1475
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1476
|
+
logger: opts.logger
|
|
1477
|
+
});
|
|
1478
|
+
return parseDeviceList(res.stdout);
|
|
1479
|
+
}
|
|
1480
|
+
async function selectDevice(config, serial, opts = {}) {
|
|
1481
|
+
if (serial && serial !== "auto") return serial;
|
|
1482
|
+
const envSerial = process.env.ONIRO_DEVICE_SERIAL || process.env.DEVICE_SERIAL;
|
|
1483
|
+
if (envSerial && envSerial !== "auto") return envSerial;
|
|
1484
|
+
const usable = (await listDevices(config, opts)).filter(
|
|
1485
|
+
(d) => d.status === "Connected" || d.status === "Unknown"
|
|
1486
|
+
);
|
|
1487
|
+
if (usable.length === 1) return usable[0].serial;
|
|
1488
|
+
if (usable.length === 0) {
|
|
1489
|
+
throw new OniroError("No connected device found. Connect a device/emulator or set ONIRO_DEVICE_SERIAL.");
|
|
1490
|
+
}
|
|
1491
|
+
throw new OniroError(
|
|
1492
|
+
`Multiple devices connected (${usable.map((d) => d.serial).join(", ")}). Set ONIRO_DEVICE_SERIAL to choose one.`
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// src/hdc/param.ts
|
|
1497
|
+
async function paramGet(config, key, opts = {}) {
|
|
1498
|
+
const res = await shell({
|
|
1499
|
+
config,
|
|
1500
|
+
command: `param get ${key}`,
|
|
1501
|
+
deviceSerial: opts.deviceSerial,
|
|
1502
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1503
|
+
logger: opts.logger
|
|
1504
|
+
});
|
|
1505
|
+
ensureOk(res, `hdc shell param get ${key}`);
|
|
1506
|
+
return res.stdout.trim();
|
|
1507
|
+
}
|
|
1508
|
+
async function paramSet(config, key, value, opts = {}) {
|
|
1509
|
+
const res = await shell({
|
|
1510
|
+
config,
|
|
1511
|
+
command: `param set ${key} ${value}`,
|
|
1512
|
+
deviceSerial: opts.deviceSerial,
|
|
1513
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1514
|
+
logger: opts.logger
|
|
1515
|
+
});
|
|
1516
|
+
ensureOk(res, `hdc shell param set ${key} ${value}`);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/hdc/files.ts
|
|
1520
|
+
async function sendFile(opts) {
|
|
1521
|
+
const res = await hdcExec({
|
|
1522
|
+
config: opts.config,
|
|
1523
|
+
args: ["file", "send", opts.local, opts.remote],
|
|
1524
|
+
deviceSerial: opts.deviceSerial,
|
|
1525
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1526
|
+
abortSignal: opts.abortSignal,
|
|
1527
|
+
onOutput: opts.onOutput,
|
|
1528
|
+
logger: opts.logger
|
|
1529
|
+
});
|
|
1530
|
+
ensureOk(res, `hdc file send ${opts.local} ${opts.remote}`);
|
|
1531
|
+
}
|
|
1532
|
+
async function recvFile(opts) {
|
|
1533
|
+
const res = await hdcExec({
|
|
1534
|
+
config: opts.config,
|
|
1535
|
+
args: ["file", "recv", opts.remote, opts.local],
|
|
1536
|
+
deviceSerial: opts.deviceSerial,
|
|
1537
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1538
|
+
abortSignal: opts.abortSignal,
|
|
1539
|
+
onOutput: opts.onOutput,
|
|
1540
|
+
logger: opts.logger
|
|
1541
|
+
});
|
|
1542
|
+
ensureOk(res, `hdc file recv ${opts.remote} ${opts.local}`);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/hdc/display.ts
|
|
1546
|
+
async function getDisplaySize(config, opts = {}) {
|
|
1547
|
+
const res = await shell({
|
|
1548
|
+
config,
|
|
1549
|
+
command: "hidumper -s 10 -a screen",
|
|
1550
|
+
deviceSerial: opts.deviceSerial,
|
|
1551
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1552
|
+
logger: opts.logger
|
|
1553
|
+
});
|
|
1554
|
+
if (res.code !== 0) return null;
|
|
1555
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
1556
|
+
if (!m) return null;
|
|
1557
|
+
return { width: Number(m[1]), height: Number(m[2]) };
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// src/hdc/wait.ts
|
|
1561
|
+
function delay(ms, signal) {
|
|
1562
|
+
return new Promise((resolve2, reject) => {
|
|
1563
|
+
if (signal?.aborted) {
|
|
1564
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
const onAbort = () => {
|
|
1568
|
+
cleanup();
|
|
1569
|
+
reject(new CancelledError("Wait cancelled."));
|
|
1570
|
+
};
|
|
1571
|
+
const cleanup = () => {
|
|
1572
|
+
clearTimeout(timer);
|
|
1573
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1574
|
+
};
|
|
1575
|
+
const timer = setTimeout(() => {
|
|
1576
|
+
cleanup();
|
|
1577
|
+
resolve2();
|
|
1578
|
+
}, ms);
|
|
1579
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
async function waitForCondition(opts) {
|
|
1583
|
+
const pollMs = opts.pollMs ?? 2e3;
|
|
1584
|
+
const heartbeatEvery = opts.heartbeatEvery ?? 6;
|
|
1585
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
1586
|
+
let attempt = 0;
|
|
1587
|
+
while (Date.now() < deadline) {
|
|
1588
|
+
if (opts.abortSignal?.aborted) throw new CancelledError("Wait cancelled.");
|
|
1589
|
+
attempt++;
|
|
1590
|
+
let ok = false;
|
|
1591
|
+
try {
|
|
1592
|
+
ok = await opts.probe();
|
|
1593
|
+
} catch {
|
|
1594
|
+
ok = false;
|
|
1595
|
+
}
|
|
1596
|
+
if (ok) return;
|
|
1597
|
+
if (opts.onHeartbeat && attempt % heartbeatEvery === 0) {
|
|
1598
|
+
opts.onHeartbeat(attempt, Math.max(0, deadline - Date.now()));
|
|
1599
|
+
}
|
|
1600
|
+
await delay(pollMs, opts.abortSignal);
|
|
1601
|
+
}
|
|
1602
|
+
throw new OniroError(opts.timeoutMessage ?? `Condition not met within ${opts.timeoutMs}ms.`);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/hdc/app.ts
|
|
1606
|
+
import * as fs15 from "fs";
|
|
1607
|
+
import * as path14 from "path";
|
|
1608
|
+
import { spawn as spawn3 } from "child_process";
|
|
1609
|
+
|
|
1315
1610
|
// src/hdc/project.ts
|
|
1316
|
-
import * as
|
|
1611
|
+
import * as fs14 from "fs";
|
|
1317
1612
|
import * as path13 from "path";
|
|
1318
1613
|
import JSON53 from "json5";
|
|
1319
1614
|
function readJson5(filePath) {
|
|
1320
|
-
const content =
|
|
1615
|
+
const content = fs14.readFileSync(filePath, "utf-8");
|
|
1321
1616
|
return JSON53.parse(content);
|
|
1322
1617
|
}
|
|
1323
1618
|
function getBundleName(projectDir) {
|
|
1324
1619
|
const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
|
|
1325
|
-
if (!
|
|
1620
|
+
if (!fs14.existsSync(appJsonPath)) {
|
|
1326
1621
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
1327
1622
|
}
|
|
1328
1623
|
const appJson = readJson5(appJsonPath);
|
|
@@ -1331,59 +1626,72 @@ function getBundleName(projectDir) {
|
|
|
1331
1626
|
}
|
|
1332
1627
|
return appJson.app.bundleName;
|
|
1333
1628
|
}
|
|
1334
|
-
function getMainAbility(projectDir, moduleName = "entry") {
|
|
1629
|
+
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
1335
1630
|
const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
1336
|
-
if (!
|
|
1631
|
+
if (!fs14.existsSync(moduleJsonPath)) {
|
|
1337
1632
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
1338
1633
|
}
|
|
1339
1634
|
const moduleJson = readJson5(moduleJsonPath);
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1635
|
+
const module = moduleJson.module ?? {};
|
|
1636
|
+
if (abilityName) {
|
|
1637
|
+
const match = module.abilities?.find((a) => a.name === abilityName);
|
|
1638
|
+
if (match?.name) return match.name;
|
|
1639
|
+
throw new OniroError(`Ability "${abilityName}" not found in module.json5 at ${moduleJsonPath}`);
|
|
1640
|
+
}
|
|
1641
|
+
if (module.mainElement) return module.mainElement;
|
|
1642
|
+
const abilities = module.abilities ?? [];
|
|
1643
|
+
const visible = abilities.find((a) => a.visible && a.name);
|
|
1644
|
+
if (visible?.name) return visible.name;
|
|
1645
|
+
const first = abilities.find((a) => a.name);
|
|
1646
|
+
if (first?.name) return first.name;
|
|
1647
|
+
throw new OniroError("mainElement not found in module.json5");
|
|
1344
1648
|
}
|
|
1345
1649
|
|
|
1346
1650
|
// src/hdc/app.ts
|
|
1347
|
-
import * as fs14 from "fs";
|
|
1348
|
-
import * as path14 from "path";
|
|
1349
|
-
import { exec as exec2, spawn as spawn2 } from "child_process";
|
|
1350
|
-
function execPromise2(cmd, logger) {
|
|
1351
|
-
return new Promise((resolve2, reject) => {
|
|
1352
|
-
exec2(cmd, (error, stdout, stderr) => {
|
|
1353
|
-
if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
|
|
1354
|
-
if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
|
|
1355
|
-
if (error) {
|
|
1356
|
-
logger.error(`[hdc] ${error.message}`);
|
|
1357
|
-
reject(error);
|
|
1358
|
-
} else {
|
|
1359
|
-
resolve2();
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
1651
|
async function installApp(opts) {
|
|
1365
|
-
const logger = opts.logger ?? noopLogger;
|
|
1652
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1366
1653
|
const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
|
|
1367
1654
|
const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
|
|
1368
|
-
if (!
|
|
1655
|
+
if (!fs15.existsSync(hapPath)) {
|
|
1369
1656
|
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
1370
1657
|
}
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1658
|
+
const result = await hdcExec({ config: opts.config, args: ["install", hapPath], timeoutMs: 3e5 });
|
|
1659
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
1660
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
1661
|
+
ensureOk(result, `hdc install ${hapPath}`);
|
|
1662
|
+
let bundleName = "";
|
|
1663
|
+
try {
|
|
1664
|
+
bundleName = getBundleName(opts.projectDir);
|
|
1665
|
+
} catch {
|
|
1666
|
+
}
|
|
1667
|
+
return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
|
|
1668
|
+
}
|
|
1669
|
+
function detectAaStartFailure(output) {
|
|
1670
|
+
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
1373
1671
|
}
|
|
1374
1672
|
async function launchApp(opts) {
|
|
1375
|
-
const logger = opts.logger ?? noopLogger;
|
|
1673
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1376
1674
|
const bundleName = getBundleName(opts.projectDir);
|
|
1377
|
-
const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1675
|
+
const mainAbility = getMainAbility(opts.projectDir, opts.moduleName, opts.abilityName);
|
|
1676
|
+
const result = await hdcExec({
|
|
1677
|
+
config: opts.config,
|
|
1678
|
+
args: ["shell", "aa", "start", "-a", mainAbility, "-b", bundleName]
|
|
1679
|
+
});
|
|
1680
|
+
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
1681
|
+
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
1682
|
+
ensureOk(result, `hdc shell aa start -a ${mainAbility} -b ${bundleName}`);
|
|
1683
|
+
const failure = detectAaStartFailure(`${result.stdout}
|
|
1684
|
+
${result.stderr}`);
|
|
1685
|
+
if (failure) {
|
|
1686
|
+
throw new OniroError(`Failed to launch ${bundleName} (${mainAbility}): ${failure}`);
|
|
1687
|
+
}
|
|
1380
1688
|
}
|
|
1381
1689
|
function listRunningProcesses(config, options = {}) {
|
|
1382
1690
|
const logger = options.logger ?? noopLogger;
|
|
1383
1691
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
1384
1692
|
const hdc = getHdcPath(config);
|
|
1385
1693
|
return new Promise((resolve2, reject) => {
|
|
1386
|
-
const proc =
|
|
1694
|
+
const proc = spawn3(hdc, ["track-jpid"]);
|
|
1387
1695
|
const processes = [];
|
|
1388
1696
|
let resolved = false;
|
|
1389
1697
|
proc.stdout.on("data", (data) => {
|
|
@@ -1437,14 +1745,395 @@ async function findAppProcessId(config, projectDir, logger) {
|
|
|
1437
1745
|
const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
|
|
1438
1746
|
return result;
|
|
1439
1747
|
}
|
|
1748
|
+
async function findRunningProcess(opts) {
|
|
1749
|
+
const safe = opts.bundle.replace(/'/g, `'\\''`);
|
|
1750
|
+
const res = await shell({
|
|
1751
|
+
config: opts.config,
|
|
1752
|
+
command: `pidof '${safe}'`,
|
|
1753
|
+
deviceSerial: opts.deviceSerial,
|
|
1754
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
1755
|
+
logger: opts.logger
|
|
1756
|
+
});
|
|
1757
|
+
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
1758
|
+
if (res.code !== 0 || !pid || !/^\d+$/.test(pid)) return null;
|
|
1759
|
+
return { pid, name: opts.bundle };
|
|
1760
|
+
}
|
|
1761
|
+
async function uninstallApp(opts) {
|
|
1762
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1763
|
+
const res = await hdcExec({
|
|
1764
|
+
config: opts.config,
|
|
1765
|
+
args: ["uninstall", opts.bundle],
|
|
1766
|
+
deviceSerial: opts.deviceSerial,
|
|
1767
|
+
timeoutMs: opts.timeoutMs ?? 12e4
|
|
1768
|
+
});
|
|
1769
|
+
if (res.stdout.trim()) logger.info(res.stdout.trim());
|
|
1770
|
+
if (res.stderr.trim()) logger.warn(res.stderr.trim());
|
|
1771
|
+
ensureOk(res, `hdc uninstall ${opts.bundle}`);
|
|
1772
|
+
}
|
|
1773
|
+
async function forceStop(opts) {
|
|
1774
|
+
const res = await shell({
|
|
1775
|
+
config: opts.config,
|
|
1776
|
+
command: `aa force-stop ${opts.bundle}`,
|
|
1777
|
+
deviceSerial: opts.deviceSerial,
|
|
1778
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
1779
|
+
logger: opts.logger
|
|
1780
|
+
});
|
|
1781
|
+
ensureOk(res, `hdc shell aa force-stop ${opts.bundle}`);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/hdc/lifecycle.ts
|
|
1785
|
+
async function isReachable(config, deviceSerial) {
|
|
1786
|
+
try {
|
|
1787
|
+
const res = await shell({ config, command: "echo oniro_ping", deviceSerial, timeoutMs: 5e3 });
|
|
1788
|
+
return res.code === 0 && res.stdout.includes("oniro_ping");
|
|
1789
|
+
} catch {
|
|
1790
|
+
return false;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
var TCP_SERIAL_RE = /^.+:\d+$/;
|
|
1794
|
+
async function reconnectIfTcp(config, deviceSerial, logger) {
|
|
1795
|
+
if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
|
|
1796
|
+
try {
|
|
1797
|
+
await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
async function waitForBundle(opts) {
|
|
1802
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1803
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
1804
|
+
await waitForCondition({
|
|
1805
|
+
probe: async () => {
|
|
1806
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
1807
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
1808
|
+
},
|
|
1809
|
+
timeoutMs,
|
|
1810
|
+
pollMs: 2e3,
|
|
1811
|
+
abortSignal: opts.abortSignal,
|
|
1812
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for ${opts.bundle} (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
1813
|
+
timeoutMessage: `Bundle ${opts.bundle} did not start within ${timeoutMs}ms.`
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
async function waitForBoot(opts) {
|
|
1817
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1818
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
1819
|
+
await waitForCondition({
|
|
1820
|
+
probe: async () => {
|
|
1821
|
+
await reconnectIfTcp(opts.config, opts.deviceSerial, logger);
|
|
1822
|
+
if (!await isReachable(opts.config, opts.deviceSerial)) return false;
|
|
1823
|
+
if (!opts.untilPidOf) return true;
|
|
1824
|
+
return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
1825
|
+
},
|
|
1826
|
+
timeoutMs,
|
|
1827
|
+
pollMs: 2e3,
|
|
1828
|
+
abortSignal: opts.abortSignal,
|
|
1829
|
+
onHeartbeat: (attempt, remaining) => logger.info(`waiting for boot (attempt ${attempt}, ${Math.floor(remaining / 1e3)}s left)`),
|
|
1830
|
+
timeoutMessage: `Device did not boot within ${timeoutMs}ms.`
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
async function reboot(opts) {
|
|
1834
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
1835
|
+
const mode = opts.mode ?? "system";
|
|
1836
|
+
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
1837
|
+
logger.info(`Rebooting device (${mode})...`);
|
|
1838
|
+
try {
|
|
1839
|
+
if (mode === "system") {
|
|
1840
|
+
await paramSet(opts.config, "ohos.startup.powerctrl", "reboot", {
|
|
1841
|
+
deviceSerial: opts.deviceSerial,
|
|
1842
|
+
timeoutMs: 15e3,
|
|
1843
|
+
logger: opts.logger
|
|
1844
|
+
});
|
|
1845
|
+
} else {
|
|
1846
|
+
await shell({ config: opts.config, command: `reboot ${mode}`, deviceSerial: opts.deviceSerial, timeoutMs: 15e3 });
|
|
1847
|
+
}
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
logger.debug(`reboot command did not return cleanly: ${err.message}`);
|
|
1850
|
+
}
|
|
1851
|
+
if (mode !== "system" || !opts.waitForBundle) return;
|
|
1852
|
+
try {
|
|
1853
|
+
await waitForCondition({
|
|
1854
|
+
probe: async () => !await isReachable(opts.config, opts.deviceSerial),
|
|
1855
|
+
timeoutMs: Math.min(timeoutMs, 3e4),
|
|
1856
|
+
pollMs: 1500,
|
|
1857
|
+
abortSignal: opts.abortSignal,
|
|
1858
|
+
timeoutMessage: "device did not become unreachable"
|
|
1859
|
+
});
|
|
1860
|
+
} catch {
|
|
1861
|
+
}
|
|
1862
|
+
await waitForBundle({
|
|
1863
|
+
config: opts.config,
|
|
1864
|
+
bundle: opts.waitForBundle,
|
|
1865
|
+
deviceSerial: opts.deviceSerial,
|
|
1866
|
+
timeoutMs,
|
|
1867
|
+
abortSignal: opts.abortSignal,
|
|
1868
|
+
logger: opts.logger
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// src/hdc/screen.ts
|
|
1873
|
+
import * as fs16 from "fs";
|
|
1874
|
+
import * as os9 from "os";
|
|
1875
|
+
import * as path15 from "path";
|
|
1876
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
1877
|
+
function tmpName(ext) {
|
|
1878
|
+
return path15.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
|
|
1879
|
+
}
|
|
1880
|
+
async function takeScreenshot(opts) {
|
|
1881
|
+
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
1882
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
1883
|
+
const cap = await shell({ config: opts.config, command: `snapshot_display -f ${remote}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
1884
|
+
ensureOk(cap, `snapshot_display -f ${remote}`);
|
|
1885
|
+
const local = tmpName("jpeg");
|
|
1886
|
+
try {
|
|
1887
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
1888
|
+
const pixels = await fs16.promises.readFile(local);
|
|
1889
|
+
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
1890
|
+
return { pixels, width: size.width, height: size.height };
|
|
1891
|
+
} finally {
|
|
1892
|
+
fs16.promises.unlink(local).catch(() => {
|
|
1893
|
+
});
|
|
1894
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
async function captureBurst(opts) {
|
|
1899
|
+
const count = Math.max(1, Math.floor(opts.count));
|
|
1900
|
+
const remoteDir = `/data/local/tmp/oniro_burst_${randomBytes2(6).toString("hex")}`;
|
|
1901
|
+
const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
|
|
1902
|
+
const loop = `mkdir -p ${remoteDir}; i=0; while [ $i -lt ${count} ]; do snapshot_display -f ${remoteDir}/f$i.jpeg; [ ${count} -gt 1 ] && sleep ${sleepSec}; i=$((i+1)); done`;
|
|
1903
|
+
const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
|
|
1904
|
+
ensureOk(cap, "snapshot_display burst");
|
|
1905
|
+
const frames = [];
|
|
1906
|
+
try {
|
|
1907
|
+
for (let i = 0; i < count; i++) {
|
|
1908
|
+
const local = tmpName(`burst_${i}.jpeg`);
|
|
1909
|
+
try {
|
|
1910
|
+
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
1911
|
+
frames.push(await fs16.promises.readFile(local));
|
|
1912
|
+
} finally {
|
|
1913
|
+
fs16.promises.unlink(local).catch(() => {
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
} finally {
|
|
1918
|
+
void shell({ config: opts.config, command: `rm -rf ${remoteDir}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
1919
|
+
});
|
|
1920
|
+
}
|
|
1921
|
+
return frames;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// src/hdc/input.ts
|
|
1925
|
+
function reqNum(value, name, type) {
|
|
1926
|
+
if (value === void 0 || Number.isNaN(value)) {
|
|
1927
|
+
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
1928
|
+
}
|
|
1929
|
+
return value;
|
|
1930
|
+
}
|
|
1931
|
+
function buildInputCommand(opts) {
|
|
1932
|
+
switch (opts.type) {
|
|
1933
|
+
case "click":
|
|
1934
|
+
case "doubleClick":
|
|
1935
|
+
case "longClick":
|
|
1936
|
+
return `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)}`;
|
|
1937
|
+
case "swipe":
|
|
1938
|
+
case "drag":
|
|
1939
|
+
case "fling": {
|
|
1940
|
+
const base = `uitest uiInput ${opts.type} ${reqNum(opts.pxX, "pxX", opts.type)} ${reqNum(opts.pxY, "pxY", opts.type)} ${reqNum(opts.pxX2, "pxX2", opts.type)} ${reqNum(opts.pxY2, "pxY2", opts.type)}`;
|
|
1941
|
+
return opts.speed !== void 0 ? `${base} ${opts.speed}` : base;
|
|
1942
|
+
}
|
|
1943
|
+
case "keyEvent":
|
|
1944
|
+
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
1945
|
+
return `uitest uiInput keyEvent ${opts.key}`;
|
|
1946
|
+
case "inputText": {
|
|
1947
|
+
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
1948
|
+
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
1949
|
+
return `uitest uiInput inputText ${opts.pxX} ${opts.pxY} '${t}'`;
|
|
1950
|
+
}
|
|
1951
|
+
return `uitest uiInput text '${t}'`;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
async function sendInput(opts) {
|
|
1956
|
+
const cmd = buildInputCommand(opts);
|
|
1957
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
1958
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
1959
|
+
}
|
|
1960
|
+
function buildGestureCommands(waypoints) {
|
|
1961
|
+
if (waypoints.length < 2) {
|
|
1962
|
+
throw new OniroError("sendGesture requires at least two waypoints.");
|
|
1963
|
+
}
|
|
1964
|
+
const cmds = [];
|
|
1965
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
1966
|
+
const a = waypoints[i - 1];
|
|
1967
|
+
const b = waypoints[i];
|
|
1968
|
+
const dt = b.t - a.t;
|
|
1969
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
1970
|
+
let cmd = `uitest uiInput drag ${a.x} ${a.y} ${b.x} ${b.y}`;
|
|
1971
|
+
if (dt > 0 && dist > 0) cmd += ` ${Math.round(dist / (dt / 1e3))}`;
|
|
1972
|
+
cmds.push(cmd);
|
|
1973
|
+
}
|
|
1974
|
+
return cmds;
|
|
1975
|
+
}
|
|
1976
|
+
function buildRawTouchCommand(events) {
|
|
1977
|
+
if (events.length === 0) throw new OniroError("sendRawTouch requires at least one event.");
|
|
1978
|
+
const ops = [];
|
|
1979
|
+
let prev = null;
|
|
1980
|
+
for (const ev of events) {
|
|
1981
|
+
if (prev && ev.t > prev.t) ops.push(`-i ${ev.t - prev.t}`);
|
|
1982
|
+
if (ev.type === "down") ops.push(`-d ${ev.x} ${ev.y}`);
|
|
1983
|
+
else if (ev.type === "up") ops.push(`-u ${ev.x} ${ev.y}`);
|
|
1984
|
+
else ops.push(`-m ${prev ? `${prev.x} ${prev.y} ` : ""}${ev.x} ${ev.y}`);
|
|
1985
|
+
prev = ev;
|
|
1986
|
+
}
|
|
1987
|
+
return `uinput -T ${ops.join(" ")}`;
|
|
1988
|
+
}
|
|
1989
|
+
function waypointsToRawEvents(waypoints, holdStartMs = 0, holdEndMs = 0) {
|
|
1990
|
+
if (waypoints.length < 2) throw new OniroError("sendGesture requires at least two waypoints.");
|
|
1991
|
+
const first = waypoints[0];
|
|
1992
|
+
const events = [{ type: "down", x: first.x, y: first.y, t: 0 }];
|
|
1993
|
+
for (let i = 1; i < waypoints.length; i++) {
|
|
1994
|
+
const w = waypoints[i];
|
|
1995
|
+
events.push({ type: "move", x: w.x, y: w.y, t: w.t + holdStartMs });
|
|
1996
|
+
}
|
|
1997
|
+
const last = waypoints[waypoints.length - 1];
|
|
1998
|
+
events.push({ type: "up", x: last.x, y: last.y, t: last.t + holdStartMs + holdEndMs });
|
|
1999
|
+
return events;
|
|
2000
|
+
}
|
|
2001
|
+
async function sendGesture(opts) {
|
|
2002
|
+
const wantsHold = (opts.holdStartMs ?? 0) > 0 || (opts.holdEndMs ?? 0) > 0;
|
|
2003
|
+
if (wantsHold) {
|
|
2004
|
+
await sendRawTouch({
|
|
2005
|
+
config: opts.config,
|
|
2006
|
+
events: waypointsToRawEvents(opts.waypoints, opts.holdStartMs, opts.holdEndMs),
|
|
2007
|
+
deviceSerial: opts.deviceSerial,
|
|
2008
|
+
logger: opts.logger
|
|
2009
|
+
});
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
for (const cmd of buildGestureCommands(opts.waypoints)) {
|
|
2013
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2014
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
async function sendRawTouch(opts) {
|
|
2018
|
+
const cmd = buildRawTouchCommand(opts.events);
|
|
2019
|
+
const res = await shell({ config: opts.config, command: cmd, deviceSerial: opts.deviceSerial, timeoutMs: 3e4, logger: opts.logger });
|
|
2020
|
+
ensureOk(res, `hdc shell ${cmd}`);
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// src/hdc/dump.ts
|
|
2024
|
+
import * as fs17 from "fs";
|
|
2025
|
+
import * as os10 from "os";
|
|
2026
|
+
import * as path16 from "path";
|
|
2027
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2028
|
+
function parseBounds(s) {
|
|
2029
|
+
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2030
|
+
if (!m) return null;
|
|
2031
|
+
return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
|
|
2032
|
+
}
|
|
2033
|
+
var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
|
|
2034
|
+
"Stack",
|
|
2035
|
+
"Column",
|
|
2036
|
+
"Row",
|
|
2037
|
+
"Flex",
|
|
2038
|
+
"__Common__",
|
|
2039
|
+
"Grid",
|
|
2040
|
+
"GridItem",
|
|
2041
|
+
"List",
|
|
2042
|
+
"ListItem",
|
|
2043
|
+
"Scroll",
|
|
2044
|
+
"Swiper",
|
|
2045
|
+
"SwiperContent",
|
|
2046
|
+
"Navigator",
|
|
2047
|
+
"Tabs",
|
|
2048
|
+
"TabContent",
|
|
2049
|
+
"RelativeContainer",
|
|
2050
|
+
"WaterFlow",
|
|
2051
|
+
"FlowItem"
|
|
2052
|
+
]);
|
|
2053
|
+
var FLAG_MAP = [
|
|
2054
|
+
["clickable", "click"],
|
|
2055
|
+
["checkable", "check"],
|
|
2056
|
+
["checked", "checked"],
|
|
2057
|
+
["selected", "sel"],
|
|
2058
|
+
["focused", "focus"],
|
|
2059
|
+
["scrollable", "scroll"],
|
|
2060
|
+
["longClickable", "longClick"]
|
|
2061
|
+
];
|
|
2062
|
+
function pruneLayout(node, display) {
|
|
2063
|
+
const a = node.attributes ?? {};
|
|
2064
|
+
const px = parseBounds(a.bounds);
|
|
2065
|
+
const out = {};
|
|
2066
|
+
if (px && display) {
|
|
2067
|
+
const w = display.width || 1;
|
|
2068
|
+
const h = display.height || 1;
|
|
2069
|
+
out.b = [
|
|
2070
|
+
+(px[0] / w).toFixed(3),
|
|
2071
|
+
+(px[1] / h).toFixed(3),
|
|
2072
|
+
+(px[2] / w).toFixed(3),
|
|
2073
|
+
+(px[3] / h).toFixed(3)
|
|
2074
|
+
];
|
|
2075
|
+
out.c = [+((px[0] + px[2]) / 2 / w).toFixed(3), +((px[1] + px[3]) / 2 / h).toFixed(3)];
|
|
2076
|
+
}
|
|
2077
|
+
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2078
|
+
if (a.text) out.text = a.text;
|
|
2079
|
+
if (a.description) out.desc = a.description;
|
|
2080
|
+
if (a.id) out.id = a.id;
|
|
2081
|
+
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2082
|
+
if (a.bundleName) out.bundle = a.bundleName;
|
|
2083
|
+
for (const [attr, short] of FLAG_MAP) {
|
|
2084
|
+
if (a[attr] === "true") out[short] = true;
|
|
2085
|
+
}
|
|
2086
|
+
const children = [];
|
|
2087
|
+
for (const c of node.children ?? []) {
|
|
2088
|
+
const pc = pruneLayout(c, display);
|
|
2089
|
+
if (pc) {
|
|
2090
|
+
if (Array.isArray(pc)) children.push(...pc);
|
|
2091
|
+
else children.push(pc);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
const hasSignal = Boolean(
|
|
2095
|
+
out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2096
|
+
);
|
|
2097
|
+
if (!hasSignal && children.length === 0) return null;
|
|
2098
|
+
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2099
|
+
if (children.length) out.children = children;
|
|
2100
|
+
return out;
|
|
2101
|
+
}
|
|
2102
|
+
async function dumpLayout(opts) {
|
|
2103
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2104
|
+
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
2105
|
+
const dump = await shell({ config: opts.config, command: `uitest dumpLayout${bundleArg}`, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2106
|
+
ensureOk(dump, "uitest dumpLayout");
|
|
2107
|
+
const m = dump.stdout.match(/saved to:\s*(\S+)/i);
|
|
2108
|
+
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2109
|
+
${dump.stdout}`);
|
|
2110
|
+
const remote = m[1];
|
|
2111
|
+
const local = path16.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
|
|
2112
|
+
try {
|
|
2113
|
+
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2114
|
+
const parsed = JSON.parse(await fs17.promises.readFile(local, "utf8"));
|
|
2115
|
+
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2116
|
+
if (opts.prune === false) {
|
|
2117
|
+
return { display, tree: parsed };
|
|
2118
|
+
}
|
|
2119
|
+
const prunedRaw = pruneLayout(parsed, display);
|
|
2120
|
+
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2121
|
+
return { display, tree };
|
|
2122
|
+
} finally {
|
|
2123
|
+
fs17.promises.unlink(local).catch(() => {
|
|
2124
|
+
});
|
|
2125
|
+
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
1440
2129
|
|
|
1441
2130
|
// src/hdc/hilog.ts
|
|
1442
|
-
import { spawn as
|
|
2131
|
+
import { spawn as spawn4 } from "child_process";
|
|
1443
2132
|
function setHilogLevel(opts) {
|
|
1444
2133
|
const logger = opts.logger ?? noopLogger;
|
|
1445
2134
|
const hdc = getHdcPath(opts.config);
|
|
1446
2135
|
return new Promise((resolve2, reject) => {
|
|
1447
|
-
const child =
|
|
2136
|
+
const child = spawn4(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
1448
2137
|
let stderr = "";
|
|
1449
2138
|
child.stderr.on("data", (chunk) => {
|
|
1450
2139
|
stderr += chunk.toString();
|
|
@@ -1466,11 +2155,16 @@ function setHilogLevel(opts) {
|
|
|
1466
2155
|
}
|
|
1467
2156
|
function streamHilog(opts) {
|
|
1468
2157
|
const hdc = getHdcPath(opts.config);
|
|
1469
|
-
const args = [
|
|
2158
|
+
const args = [];
|
|
2159
|
+
if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
|
|
2160
|
+
args.push("shell", "hilog");
|
|
1470
2161
|
if (opts.processId && opts.processId.trim() !== "") {
|
|
1471
2162
|
args.push("-P", opts.processId.trim());
|
|
1472
2163
|
}
|
|
1473
|
-
|
|
2164
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") {
|
|
2165
|
+
args.push("-D", `${opts.domain}`);
|
|
2166
|
+
}
|
|
2167
|
+
return spawn4(hdc, args);
|
|
1474
2168
|
}
|
|
1475
2169
|
var HILOG_LINE_RE = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([DIWEF])\s+(\S+):\s+(.*)$/;
|
|
1476
2170
|
function parseHilogLine(line) {
|
|
@@ -1485,30 +2179,194 @@ function parseHilogLine(line) {
|
|
|
1485
2179
|
message: match[6]
|
|
1486
2180
|
};
|
|
1487
2181
|
}
|
|
2182
|
+
function entryText(e) {
|
|
2183
|
+
return `${e.tag}: ${e.message}`;
|
|
2184
|
+
}
|
|
2185
|
+
function dedupEntries(entries) {
|
|
2186
|
+
const out = [];
|
|
2187
|
+
let lastKey = "";
|
|
2188
|
+
for (const e of entries) {
|
|
2189
|
+
const key = `${e.level}|${e.tag}|${e.message}`;
|
|
2190
|
+
if (key === lastKey) continue;
|
|
2191
|
+
out.push(e);
|
|
2192
|
+
lastKey = key;
|
|
2193
|
+
}
|
|
2194
|
+
return out;
|
|
2195
|
+
}
|
|
2196
|
+
async function resolveProcessId(config, bundle, deviceSerial) {
|
|
2197
|
+
if (!bundle) return void 0;
|
|
2198
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
2199
|
+
return proc?.pid;
|
|
2200
|
+
}
|
|
2201
|
+
function waitForLog(opts) {
|
|
2202
|
+
return new Promise((resolve2, reject) => {
|
|
2203
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
2204
|
+
let settled = false;
|
|
2205
|
+
let child = null;
|
|
2206
|
+
let reconnectTimer;
|
|
2207
|
+
let buf = "";
|
|
2208
|
+
const cleanup = () => {
|
|
2209
|
+
clearTimeout(timer);
|
|
2210
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2211
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2212
|
+
if (child) {
|
|
2213
|
+
try {
|
|
2214
|
+
child.kill("SIGKILL");
|
|
2215
|
+
} catch {
|
|
2216
|
+
}
|
|
2217
|
+
child = null;
|
|
2218
|
+
}
|
|
2219
|
+
};
|
|
2220
|
+
const finish = (entry) => {
|
|
2221
|
+
if (settled) return;
|
|
2222
|
+
settled = true;
|
|
2223
|
+
cleanup();
|
|
2224
|
+
resolve2(entry);
|
|
2225
|
+
};
|
|
2226
|
+
const fail = (err) => {
|
|
2227
|
+
if (settled) return;
|
|
2228
|
+
settled = true;
|
|
2229
|
+
cleanup();
|
|
2230
|
+
reject(err);
|
|
2231
|
+
};
|
|
2232
|
+
const onAbort = () => fail(new CancelledError("waitForLog cancelled."));
|
|
2233
|
+
const timer = setTimeout(
|
|
2234
|
+
() => fail(new OniroError(`No log line matched ${String(opts.pattern)} within ${opts.timeoutMs}ms.`)),
|
|
2235
|
+
opts.timeoutMs
|
|
2236
|
+
);
|
|
2237
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2238
|
+
const spawnStream = async () => {
|
|
2239
|
+
if (settled) return;
|
|
2240
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2241
|
+
if (settled) return;
|
|
2242
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2243
|
+
child = stream;
|
|
2244
|
+
stream.stdout.on("data", (d) => {
|
|
2245
|
+
buf += d.toString();
|
|
2246
|
+
let idx;
|
|
2247
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2248
|
+
const line = buf.slice(0, idx);
|
|
2249
|
+
buf = buf.slice(idx + 1);
|
|
2250
|
+
const entry = parseHilogLine(line);
|
|
2251
|
+
if (entry && opts.pattern.test(entryText(entry))) {
|
|
2252
|
+
finish(entry);
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
const onEnd = () => {
|
|
2258
|
+
if (settled || child !== stream) return;
|
|
2259
|
+
child = null;
|
|
2260
|
+
if (Date.now() < deadline) {
|
|
2261
|
+
reconnectTimer = setTimeout(() => void spawnStream(), 1e3);
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
stream.once("close", onEnd);
|
|
2265
|
+
stream.once("error", onEnd);
|
|
2266
|
+
};
|
|
2267
|
+
void spawnStream();
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
function watchLog(opts) {
|
|
2271
|
+
return new Promise((resolve2, reject) => {
|
|
2272
|
+
const matches = [];
|
|
2273
|
+
let settled = false;
|
|
2274
|
+
let child = null;
|
|
2275
|
+
let buf = "";
|
|
2276
|
+
const cleanup = () => {
|
|
2277
|
+
clearTimeout(timer);
|
|
2278
|
+
opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2279
|
+
if (child) {
|
|
2280
|
+
try {
|
|
2281
|
+
child.kill("SIGKILL");
|
|
2282
|
+
} catch {
|
|
2283
|
+
}
|
|
2284
|
+
child = null;
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
const done = () => {
|
|
2288
|
+
if (settled) return;
|
|
2289
|
+
settled = true;
|
|
2290
|
+
cleanup();
|
|
2291
|
+
resolve2(opts.dedup === false ? matches : dedupEntries(matches));
|
|
2292
|
+
};
|
|
2293
|
+
const onAbort = () => {
|
|
2294
|
+
if (settled) return;
|
|
2295
|
+
settled = true;
|
|
2296
|
+
cleanup();
|
|
2297
|
+
reject(new CancelledError("watchLog cancelled."));
|
|
2298
|
+
};
|
|
2299
|
+
const timer = setTimeout(done, opts.durationMs);
|
|
2300
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
2301
|
+
void (async () => {
|
|
2302
|
+
const processId = await resolveProcessId(opts.config, opts.bundle, opts.deviceSerial);
|
|
2303
|
+
if (settled) return;
|
|
2304
|
+
const stream = streamHilog({ config: opts.config, processId, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
2305
|
+
child = stream;
|
|
2306
|
+
stream.stdout.on("data", (d) => {
|
|
2307
|
+
buf += d.toString();
|
|
2308
|
+
let idx;
|
|
2309
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
2310
|
+
const line = buf.slice(0, idx);
|
|
2311
|
+
buf = buf.slice(idx + 1);
|
|
2312
|
+
const entry = parseHilogLine(line);
|
|
2313
|
+
if (entry && opts.pattern.test(entryText(entry))) matches.push(entry);
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
2316
|
+
})();
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
async function dumpLog(opts) {
|
|
2320
|
+
let pipe = "hilog -x";
|
|
2321
|
+
if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
|
|
2322
|
+
if (opts.bundle) {
|
|
2323
|
+
const proc = await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial });
|
|
2324
|
+
if (!proc) return [];
|
|
2325
|
+
pipe += ` | grep -E '\\b${proc.pid}\\b'`;
|
|
2326
|
+
}
|
|
2327
|
+
if (opts.grep) {
|
|
2328
|
+
const safeGrep = opts.grep.replace(/'/g, `'\\''`);
|
|
2329
|
+
pipe += ` | grep -E '${safeGrep}'`;
|
|
2330
|
+
}
|
|
2331
|
+
if (opts.lines && opts.lines > 0) pipe += ` | tail -n ${Math.floor(opts.lines)}`;
|
|
2332
|
+
const res = await shell({
|
|
2333
|
+
config: opts.config,
|
|
2334
|
+
command: pipe,
|
|
2335
|
+
deviceSerial: opts.deviceSerial,
|
|
2336
|
+
timeoutMs: opts.timeoutMs ?? 3e4,
|
|
2337
|
+
logger: opts.logger
|
|
2338
|
+
});
|
|
2339
|
+
return res.stdout.split("\n").map((line) => parseHilogLine(line)).filter((e) => e !== null);
|
|
2340
|
+
}
|
|
1488
2341
|
|
|
1489
2342
|
// src/build/runHvigorw.ts
|
|
1490
|
-
import * as
|
|
1491
|
-
import * as
|
|
1492
|
-
import { spawn as
|
|
2343
|
+
import * as fs18 from "fs";
|
|
2344
|
+
import * as path17 from "path";
|
|
2345
|
+
import { spawn as spawn5 } from "child_process";
|
|
2346
|
+
function buildHvigorwArgs(opts) {
|
|
2347
|
+
const args = [opts.task ?? "assembleHap", "--mode", "module"];
|
|
2348
|
+
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
2349
|
+
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
2350
|
+
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
2351
|
+
args.push("--stacktrace", "--no-daemon");
|
|
2352
|
+
if (opts.parallel === false) args.push("--no-parallel");
|
|
2353
|
+
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
2354
|
+
return args;
|
|
2355
|
+
}
|
|
1493
2356
|
function runHvigorw(opts) {
|
|
1494
2357
|
const { config, projectDir } = opts;
|
|
1495
2358
|
const logger = opts.logger ?? noopLogger;
|
|
1496
|
-
if (!
|
|
2359
|
+
if (!fs18.existsSync(path17.join(projectDir, "build-profile.json5"))) {
|
|
1497
2360
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
1498
2361
|
}
|
|
1499
|
-
if (!
|
|
2362
|
+
if (!fs18.existsSync(getCmdToolsPath(config))) {
|
|
1500
2363
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
1501
2364
|
}
|
|
1502
2365
|
const hvigorw = getHvigorwPath(config, projectDir);
|
|
1503
|
-
const args =
|
|
1504
|
-
args.push("-p", `product=${opts.product ?? "default"}`);
|
|
1505
|
-
if (opts.module) args.push("-p", `module=${opts.module}`);
|
|
1506
|
-
if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
|
|
1507
|
-
args.push("--stacktrace", "--no-parallel", "--no-daemon");
|
|
1508
|
-
if (opts.extraArgs?.length) args.push(...opts.extraArgs);
|
|
2366
|
+
const args = buildHvigorwArgs(opts);
|
|
1509
2367
|
if (process.platform !== "win32") {
|
|
1510
2368
|
try {
|
|
1511
|
-
|
|
2369
|
+
fs18.chmodSync(hvigorw, 493);
|
|
1512
2370
|
} catch {
|
|
1513
2371
|
}
|
|
1514
2372
|
}
|
|
@@ -1517,8 +2375,25 @@ function runHvigorw(opts) {
|
|
|
1517
2375
|
OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
|
|
1518
2376
|
};
|
|
1519
2377
|
return new Promise((resolve2, reject) => {
|
|
2378
|
+
if (opts.abortSignal?.aborted) {
|
|
2379
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
1520
2382
|
logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
|
|
1521
|
-
const child =
|
|
2383
|
+
const child = spawn5(hvigorw, args, { cwd: projectDir, env, shell: false });
|
|
2384
|
+
let settled = false;
|
|
2385
|
+
const cleanup = () => opts.abortSignal?.removeEventListener("abort", onAbort);
|
|
2386
|
+
const onAbort = () => {
|
|
2387
|
+
if (settled) return;
|
|
2388
|
+
settled = true;
|
|
2389
|
+
cleanup();
|
|
2390
|
+
try {
|
|
2391
|
+
child.kill("SIGKILL");
|
|
2392
|
+
} catch {
|
|
2393
|
+
}
|
|
2394
|
+
reject(new CancelledError("hvigorw build cancelled."));
|
|
2395
|
+
};
|
|
2396
|
+
opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1522
2397
|
child.stdout.on("data", (chunk) => {
|
|
1523
2398
|
const text = chunk.toString();
|
|
1524
2399
|
opts.onOutput?.(text, "stdout");
|
|
@@ -1529,8 +2404,16 @@ function runHvigorw(opts) {
|
|
|
1529
2404
|
opts.onOutput?.(text, "stderr");
|
|
1530
2405
|
logger.warn(text.trimEnd());
|
|
1531
2406
|
});
|
|
1532
|
-
child.on("error", (err) =>
|
|
2407
|
+
child.on("error", (err) => {
|
|
2408
|
+
if (settled) return;
|
|
2409
|
+
settled = true;
|
|
2410
|
+
cleanup();
|
|
2411
|
+
reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err));
|
|
2412
|
+
});
|
|
1533
2413
|
child.on("close", (code) => {
|
|
2414
|
+
if (settled) return;
|
|
2415
|
+
settled = true;
|
|
2416
|
+
cleanup();
|
|
1534
2417
|
const exitCode = code ?? 1;
|
|
1535
2418
|
if (exitCode === 0) resolve2({ exitCode });
|
|
1536
2419
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
@@ -1538,6 +2421,137 @@ function runHvigorw(opts) {
|
|
|
1538
2421
|
});
|
|
1539
2422
|
}
|
|
1540
2423
|
|
|
2424
|
+
// src/build/discoverHaps.ts
|
|
2425
|
+
import * as fs19 from "fs";
|
|
2426
|
+
import * as path18 from "path";
|
|
2427
|
+
function moduleOf(projectDir, hapPath) {
|
|
2428
|
+
const rel = path18.relative(projectDir, hapPath);
|
|
2429
|
+
const first = rel.split(path18.sep)[0];
|
|
2430
|
+
return first && first !== ".." ? first : "(root)";
|
|
2431
|
+
}
|
|
2432
|
+
async function discoverHaps(opts) {
|
|
2433
|
+
const { projectDir } = opts;
|
|
2434
|
+
const maxDepth = opts.maxDepth ?? 8;
|
|
2435
|
+
const result = {};
|
|
2436
|
+
const walk = async (dir, depth) => {
|
|
2437
|
+
if (depth > maxDepth) return;
|
|
2438
|
+
const entries = await fs19.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2439
|
+
for (const e of entries) {
|
|
2440
|
+
const full = path18.join(dir, e.name);
|
|
2441
|
+
if (e.isDirectory()) {
|
|
2442
|
+
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2443
|
+
await walk(full, depth + 1);
|
|
2444
|
+
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2445
|
+
const moduleName = moduleOf(projectDir, full);
|
|
2446
|
+
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2447
|
+
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2448
|
+
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
await walk(projectDir, 0);
|
|
2453
|
+
return result;
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
// src/build/ohpm.ts
|
|
2457
|
+
import * as fs20 from "fs";
|
|
2458
|
+
import * as path19 from "path";
|
|
2459
|
+
function runOhpm(opts) {
|
|
2460
|
+
return runProcess({
|
|
2461
|
+
command: getOhpmPath(opts.config),
|
|
2462
|
+
args: [...opts.args],
|
|
2463
|
+
cwd: opts.projectDir,
|
|
2464
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2465
|
+
abortSignal: opts.abortSignal,
|
|
2466
|
+
onOutput: opts.onOutput,
|
|
2467
|
+
logger: opts.logger
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
async function ensureOhModules(opts) {
|
|
2471
|
+
if (fs20.existsSync(path19.join(opts.projectDir, "oh_modules"))) {
|
|
2472
|
+
return { installed: false };
|
|
2473
|
+
}
|
|
2474
|
+
const res = await runOhpm({
|
|
2475
|
+
config: opts.config,
|
|
2476
|
+
projectDir: opts.projectDir,
|
|
2477
|
+
args: ["install", "--all"],
|
|
2478
|
+
abortSignal: opts.abortSignal,
|
|
2479
|
+
onOutput: opts.onOutput,
|
|
2480
|
+
logger: opts.logger
|
|
2481
|
+
});
|
|
2482
|
+
ensureOk(res, "ohpm install --all");
|
|
2483
|
+
return { installed: true };
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// src/build/codelinter.ts
|
|
2487
|
+
var FINDING_RE = /^(.*?):(\d+):(?:\d+:)?\s*(error|warning|warn|info)\b[:\s-]*(.*)$/i;
|
|
2488
|
+
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
2489
|
+
function parseCodelinterFindings(output) {
|
|
2490
|
+
const findings = [];
|
|
2491
|
+
for (const raw of output.split("\n")) {
|
|
2492
|
+
const m = FINDING_RE.exec(raw.trim());
|
|
2493
|
+
if (!m) continue;
|
|
2494
|
+
let message = m[4].trim();
|
|
2495
|
+
let code = "";
|
|
2496
|
+
const codeMatch = TRAILING_CODE_RE.exec(message);
|
|
2497
|
+
if (codeMatch) {
|
|
2498
|
+
code = codeMatch[1];
|
|
2499
|
+
message = message.slice(0, codeMatch.index).trim();
|
|
2500
|
+
}
|
|
2501
|
+
findings.push({ file: m[1].trim(), line: Number(m[2]), severity: m[3].toLowerCase(), code, message });
|
|
2502
|
+
}
|
|
2503
|
+
return findings;
|
|
2504
|
+
}
|
|
2505
|
+
async function runCodelinter(opts) {
|
|
2506
|
+
const res = await runProcess({
|
|
2507
|
+
command: getCmdToolsBin(opts.config, "codelinter"),
|
|
2508
|
+
args: opts.files?.length ? [...opts.files] : [],
|
|
2509
|
+
cwd: opts.projectDir,
|
|
2510
|
+
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
2511
|
+
abortSignal: opts.abortSignal,
|
|
2512
|
+
logger: opts.logger
|
|
2513
|
+
});
|
|
2514
|
+
const raw = `${res.stdout}${res.stderr}`;
|
|
2515
|
+
return { code: res.code, findings: parseCodelinterFindings(raw), raw };
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// src/build/buildHap.ts
|
|
2519
|
+
async function buildHap(opts) {
|
|
2520
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
2521
|
+
const warnings = [];
|
|
2522
|
+
const start = Date.now();
|
|
2523
|
+
if (opts.autoInstallDeps !== false) {
|
|
2524
|
+
const r = await ensureOhModules({
|
|
2525
|
+
config: opts.config,
|
|
2526
|
+
projectDir: opts.projectDir,
|
|
2527
|
+
abortSignal: opts.abortSignal,
|
|
2528
|
+
onOutput: opts.onOutput,
|
|
2529
|
+
logger: opts.logger
|
|
2530
|
+
});
|
|
2531
|
+
if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
|
|
2532
|
+
}
|
|
2533
|
+
const { exitCode } = await runHvigorw({
|
|
2534
|
+
config: opts.config,
|
|
2535
|
+
projectDir: opts.projectDir,
|
|
2536
|
+
product: opts.product,
|
|
2537
|
+
module: opts.module,
|
|
2538
|
+
buildMode: opts.mode,
|
|
2539
|
+
task: opts.task,
|
|
2540
|
+
parallel: opts.parallel,
|
|
2541
|
+
abortSignal: opts.abortSignal,
|
|
2542
|
+
onOutput: opts.onOutput,
|
|
2543
|
+
logger: opts.logger
|
|
2544
|
+
});
|
|
2545
|
+
const discoveredHaps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2546
|
+
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
2547
|
+
if (!anySigned) {
|
|
2548
|
+
warnings.push(
|
|
2549
|
+
"No signed HAPs found \u2014 configure signingConfigs in build-profile.json5 (unsigned HAPs are rejected by most devices)."
|
|
2550
|
+
);
|
|
2551
|
+
}
|
|
2552
|
+
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
2553
|
+
}
|
|
2554
|
+
|
|
1541
2555
|
// src/project/validators.ts
|
|
1542
2556
|
function isValidProjectName(name) {
|
|
1543
2557
|
if (!name) return false;
|
|
@@ -1549,21 +2563,21 @@ function isValidBundleName(bundleName) {
|
|
|
1549
2563
|
}
|
|
1550
2564
|
|
|
1551
2565
|
// src/project/templates.ts
|
|
1552
|
-
import * as
|
|
1553
|
-
import * as
|
|
2566
|
+
import * as fs21 from "fs";
|
|
2567
|
+
import * as path20 from "path";
|
|
1554
2568
|
function toHumanTemplateName(folderName) {
|
|
1555
2569
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
1556
2570
|
}
|
|
1557
2571
|
function listTemplates(templateRoot) {
|
|
1558
|
-
if (!
|
|
1559
|
-
const entries =
|
|
2572
|
+
if (!fs21.existsSync(templateRoot)) return [];
|
|
2573
|
+
const entries = fs21.readdirSync(templateRoot, { withFileTypes: true });
|
|
1560
2574
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
1561
|
-
const dir =
|
|
1562
|
-
const metaPath =
|
|
2575
|
+
const dir = path20.join(templateRoot, e.name);
|
|
2576
|
+
const metaPath = path20.join(dir, "template.json");
|
|
1563
2577
|
let meta = {};
|
|
1564
|
-
if (
|
|
2578
|
+
if (fs21.existsSync(metaPath)) {
|
|
1565
2579
|
try {
|
|
1566
|
-
meta = JSON.parse(
|
|
2580
|
+
meta = JSON.parse(fs21.readFileSync(metaPath, "utf8"));
|
|
1567
2581
|
} catch {
|
|
1568
2582
|
}
|
|
1569
2583
|
}
|
|
@@ -1578,57 +2592,57 @@ function listTemplates(templateRoot) {
|
|
|
1578
2592
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
1579
2593
|
const required = [
|
|
1580
2594
|
"build-profile.json5",
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
2595
|
+
path20.join("AppScope", "app.json5"),
|
|
2596
|
+
path20.join(defaultModuleName, "src", "main", "module.json5"),
|
|
2597
|
+
path20.join(defaultModuleName, "oh-package.json5"),
|
|
1584
2598
|
"hvigorfile.ts"
|
|
1585
2599
|
];
|
|
1586
|
-
return required.filter((rel) => !
|
|
2600
|
+
return required.filter((rel) => !fs21.existsSync(path20.join(templateDir, rel)));
|
|
1587
2601
|
}
|
|
1588
2602
|
|
|
1589
2603
|
// src/project/createScaffold.ts
|
|
1590
|
-
import * as
|
|
1591
|
-
import * as
|
|
2604
|
+
import * as fs23 from "fs";
|
|
2605
|
+
import * as path21 from "path";
|
|
1592
2606
|
|
|
1593
2607
|
// src/project/jsonHelpers.ts
|
|
1594
|
-
import * as
|
|
2608
|
+
import * as fs22 from "fs";
|
|
1595
2609
|
import JSON54 from "json5";
|
|
1596
2610
|
function readJson5File(filePath) {
|
|
1597
|
-
return JSON54.parse(
|
|
2611
|
+
return JSON54.parse(fs22.readFileSync(filePath, "utf8"));
|
|
1598
2612
|
}
|
|
1599
2613
|
function writeJson5File(filePath, value) {
|
|
1600
|
-
|
|
2614
|
+
fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1601
2615
|
}
|
|
1602
2616
|
function readJsonFile(filePath) {
|
|
1603
|
-
return JSON.parse(
|
|
2617
|
+
return JSON.parse(fs22.readFileSync(filePath, "utf8"));
|
|
1604
2618
|
}
|
|
1605
2619
|
function writeJsonFile(filePath, value) {
|
|
1606
|
-
|
|
2620
|
+
fs22.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
1607
2621
|
}
|
|
1608
2622
|
|
|
1609
2623
|
// src/project/createScaffold.ts
|
|
1610
2624
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
1611
2625
|
async function pathExists(p) {
|
|
1612
2626
|
try {
|
|
1613
|
-
await
|
|
2627
|
+
await fs23.promises.access(p);
|
|
1614
2628
|
return true;
|
|
1615
2629
|
} catch {
|
|
1616
2630
|
return false;
|
|
1617
2631
|
}
|
|
1618
2632
|
}
|
|
1619
2633
|
async function copyDirRecursive(srcDir, destDir) {
|
|
1620
|
-
await
|
|
1621
|
-
const entries = await
|
|
2634
|
+
await fs23.promises.mkdir(destDir, { recursive: true });
|
|
2635
|
+
const entries = await fs23.promises.readdir(srcDir, { withFileTypes: true });
|
|
1622
2636
|
for (const entry of entries) {
|
|
1623
|
-
const src =
|
|
1624
|
-
const dest =
|
|
2637
|
+
const src = path21.join(srcDir, entry.name);
|
|
2638
|
+
const dest = path21.join(destDir, entry.name);
|
|
1625
2639
|
if (entry.isDirectory()) {
|
|
1626
2640
|
await copyDirRecursive(src, dest);
|
|
1627
2641
|
} else if (entry.isSymbolicLink()) {
|
|
1628
2642
|
continue;
|
|
1629
2643
|
} else if (entry.isFile()) {
|
|
1630
2644
|
if (entry.name === "template.json") continue;
|
|
1631
|
-
await
|
|
2645
|
+
await fs23.promises.copyFile(src, dest);
|
|
1632
2646
|
}
|
|
1633
2647
|
}
|
|
1634
2648
|
}
|
|
@@ -1636,9 +2650,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
1636
2650
|
const stack = [projectDir];
|
|
1637
2651
|
while (stack.length > 0) {
|
|
1638
2652
|
const currentDir = stack.pop();
|
|
1639
|
-
const entries = await
|
|
2653
|
+
const entries = await fs23.promises.readdir(currentDir, { withFileTypes: true });
|
|
1640
2654
|
for (const entry of entries) {
|
|
1641
|
-
const fullPath =
|
|
2655
|
+
const fullPath = path21.join(currentDir, entry.name);
|
|
1642
2656
|
if (entry.isDirectory()) {
|
|
1643
2657
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1644
2658
|
stack.push(fullPath);
|
|
@@ -1660,23 +2674,23 @@ function toJavaPropertiesPath(p) {
|
|
|
1660
2674
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
1661
2675
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
1662
2676
|
`;
|
|
1663
|
-
|
|
2677
|
+
fs23.writeFileSync(path21.join(projectDir, "local.properties"), content, "utf8");
|
|
1664
2678
|
}
|
|
1665
2679
|
function renameIfExists(fromPath, toPath) {
|
|
1666
|
-
if (
|
|
1667
|
-
|
|
2680
|
+
if (fs23.existsSync(fromPath) && fromPath !== toPath) {
|
|
2681
|
+
fs23.renameSync(fromPath, toPath);
|
|
1668
2682
|
}
|
|
1669
2683
|
}
|
|
1670
2684
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
1671
|
-
const appJsonPath =
|
|
1672
|
-
if (
|
|
2685
|
+
const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
|
|
2686
|
+
if (fs23.existsSync(appJsonPath)) {
|
|
1673
2687
|
const appJson = readJson5File(appJsonPath);
|
|
1674
2688
|
appJson.app = appJson.app ?? {};
|
|
1675
2689
|
appJson.app.bundleName = args.bundleName;
|
|
1676
2690
|
writeJson5File(appJsonPath, appJson);
|
|
1677
2691
|
}
|
|
1678
|
-
const stringsPath =
|
|
1679
|
-
if (
|
|
2692
|
+
const stringsPath = path21.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
2693
|
+
if (fs23.existsSync(stringsPath)) {
|
|
1680
2694
|
const strings = readJsonFile(stringsPath);
|
|
1681
2695
|
if (Array.isArray(strings.string)) {
|
|
1682
2696
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -1684,8 +2698,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1684
2698
|
}
|
|
1685
2699
|
writeJsonFile(stringsPath, strings);
|
|
1686
2700
|
}
|
|
1687
|
-
const buildProfilePath =
|
|
1688
|
-
if (
|
|
2701
|
+
const buildProfilePath = path21.join(projectDir, "build-profile.json5");
|
|
2702
|
+
if (fs23.existsSync(buildProfilePath)) {
|
|
1689
2703
|
const buildProfile = readJson5File(buildProfilePath);
|
|
1690
2704
|
buildProfile.app = buildProfile.app ?? {};
|
|
1691
2705
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -1698,23 +2712,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
1698
2712
|
}
|
|
1699
2713
|
writeJson5File(buildProfilePath, buildProfile);
|
|
1700
2714
|
}
|
|
1701
|
-
const moduleJsonPath =
|
|
1702
|
-
if (
|
|
2715
|
+
const moduleJsonPath = path21.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
2716
|
+
if (fs23.existsSync(moduleJsonPath)) {
|
|
1703
2717
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
1704
2718
|
moduleJson.module = moduleJson.module ?? {};
|
|
1705
2719
|
moduleJson.module.name = args.moduleName;
|
|
1706
2720
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
1707
2721
|
}
|
|
1708
|
-
const moduleOhPackagePath =
|
|
1709
|
-
if (
|
|
2722
|
+
const moduleOhPackagePath = path21.join(projectDir, args.moduleName, "oh-package.json5");
|
|
2723
|
+
if (fs23.existsSync(moduleOhPackagePath)) {
|
|
1710
2724
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
1711
2725
|
pkg.name = args.moduleName;
|
|
1712
2726
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
1713
2727
|
}
|
|
1714
|
-
const vscodeDir =
|
|
1715
|
-
|
|
2728
|
+
const vscodeDir = path21.join(projectDir, ".vscode");
|
|
2729
|
+
fs23.mkdirSync(vscodeDir, { recursive: true });
|
|
1716
2730
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
1717
|
-
writeJsonFile(
|
|
2731
|
+
writeJsonFile(path21.join(vscodeDir, "settings.json"), {
|
|
1718
2732
|
"oniro.hapPath": hapPath,
|
|
1719
2733
|
"files.associations": { "*.json5": "jsonc" }
|
|
1720
2734
|
});
|
|
@@ -1731,7 +2745,7 @@ async function createScaffold(opts) {
|
|
|
1731
2745
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
1732
2746
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
1733
2747
|
}
|
|
1734
|
-
const templateDir =
|
|
2748
|
+
const templateDir = path21.join(opts.templateRoot, opts.templateId);
|
|
1735
2749
|
if (!await pathExists(templateDir)) {
|
|
1736
2750
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
1737
2751
|
}
|
|
@@ -1744,17 +2758,17 @@ async function createScaffold(opts) {
|
|
|
1744
2758
|
throw new OniroError(`Template '${opts.templateId}' is missing required files:
|
|
1745
2759
|
- ${missing.join("\n- ")}`);
|
|
1746
2760
|
}
|
|
1747
|
-
const projectDir =
|
|
2761
|
+
const projectDir = path21.join(opts.location, opts.projectName);
|
|
1748
2762
|
if (await pathExists(projectDir)) {
|
|
1749
2763
|
if (!opts.overwrite) {
|
|
1750
2764
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
1751
2765
|
}
|
|
1752
|
-
await
|
|
2766
|
+
await fs23.promises.rm(projectDir, { recursive: true, force: true });
|
|
1753
2767
|
}
|
|
1754
2768
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
1755
2769
|
await copyDirRecursive(templateDir, projectDir);
|
|
1756
2770
|
if (moduleName !== defaultModuleName) {
|
|
1757
|
-
renameIfExists(
|
|
2771
|
+
renameIfExists(path21.join(projectDir, defaultModuleName), path21.join(projectDir, moduleName));
|
|
1758
2772
|
}
|
|
1759
2773
|
updateTemplateConfigs(
|
|
1760
2774
|
projectDir,
|
|
@@ -1770,8 +2784,225 @@ async function createScaffold(opts) {
|
|
|
1770
2784
|
return { projectDir };
|
|
1771
2785
|
}
|
|
1772
2786
|
|
|
2787
|
+
// src/project/listModules.ts
|
|
2788
|
+
import * as fs24 from "fs";
|
|
2789
|
+
import * as path22 from "path";
|
|
2790
|
+
function listModules(opts) {
|
|
2791
|
+
const buildProfile = path22.join(opts.projectDir, "build-profile.json5");
|
|
2792
|
+
if (!fs24.existsSync(buildProfile)) {
|
|
2793
|
+
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
2794
|
+
}
|
|
2795
|
+
const parsed = readJson5File(buildProfile);
|
|
2796
|
+
return (parsed.modules ?? []).filter((m) => Boolean(m.name)).map((m) => ({
|
|
2797
|
+
name: m.name,
|
|
2798
|
+
srcPath: m.srcPath ?? `./${m.name}`,
|
|
2799
|
+
targets: (m.targets ?? []).map((t) => t.name).filter((n) => Boolean(n))
|
|
2800
|
+
}));
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/project/abilities.ts
|
|
2804
|
+
import * as fs25 from "fs";
|
|
2805
|
+
import * as path23 from "path";
|
|
2806
|
+
function listAbilities(opts) {
|
|
2807
|
+
const moduleName = opts.moduleName ?? "entry";
|
|
2808
|
+
const moduleJsonPath = path23.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
2809
|
+
if (!fs25.existsSync(moduleJsonPath)) {
|
|
2810
|
+
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2811
|
+
}
|
|
2812
|
+
const parsed = readJson5File(moduleJsonPath);
|
|
2813
|
+
return (parsed.module?.abilities ?? []).filter((a) => Boolean(a.name)).map((a) => ({ name: a.name, type: a.type, visible: a.visible, srcEntry: a.srcEntry }));
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// src/project/prepareSigning.ts
|
|
2817
|
+
import * as fs26 from "fs";
|
|
2818
|
+
import * as path24 from "path";
|
|
2819
|
+
function hasSigningArtifacts(dir) {
|
|
2820
|
+
if (!fs26.existsSync(path24.join(dir, "signatures"))) return false;
|
|
2821
|
+
const buildProfile = path24.join(dir, "build-profile.json5");
|
|
2822
|
+
if (!fs26.existsSync(buildProfile)) return false;
|
|
2823
|
+
try {
|
|
2824
|
+
const parsed = readJson5File(buildProfile);
|
|
2825
|
+
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
2826
|
+
} catch {
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
function prepareSigning(opts) {
|
|
2831
|
+
if (hasSigningArtifacts(opts.projectDir)) {
|
|
2832
|
+
return { source: "present" };
|
|
2833
|
+
}
|
|
2834
|
+
generateSigningConfigs({
|
|
2835
|
+
projectDir: opts.projectDir,
|
|
2836
|
+
sdkHome: getOhosBaseSdkHome(opts.config),
|
|
2837
|
+
apl: opts.apl,
|
|
2838
|
+
appFeature: opts.appFeature,
|
|
2839
|
+
acls: opts.acls,
|
|
2840
|
+
passwords: opts.passwords,
|
|
2841
|
+
logger: opts.logger
|
|
2842
|
+
});
|
|
2843
|
+
return { source: "fresh" };
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/device/hidumper.ts
|
|
2847
|
+
async function dumpScreen(opts) {
|
|
2848
|
+
const res = await shell({ config: opts.config, command: "hidumper -s 10 -a screen", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
2849
|
+
if (res.code !== 0) return null;
|
|
2850
|
+
const m = res.stdout.match(/render resolution=(\d+)x(\d+)/);
|
|
2851
|
+
if (!m) return null;
|
|
2852
|
+
return { width: Number(m[1]), height: Number(m[2]), raw: res.stdout };
|
|
2853
|
+
}
|
|
2854
|
+
async function dumpWindow(opts) {
|
|
2855
|
+
const res = await shell({ config: opts.config, command: "hidumper -s WindowManagerService -a -a", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
2856
|
+
return res.stdout;
|
|
2857
|
+
}
|
|
2858
|
+
async function dumpRenderService(opts) {
|
|
2859
|
+
const res = await shell({ config: opts.config, command: "hidumper -s RenderService -a allInfo", deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 15e3, logger: opts.logger });
|
|
2860
|
+
return res.stdout;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
// src/device/info.ts
|
|
2864
|
+
var PROPS = [
|
|
2865
|
+
["model", "const.product.model"],
|
|
2866
|
+
["manufacturer", "const.product.manufacturer"],
|
|
2867
|
+
["brand", "const.product.brand"],
|
|
2868
|
+
["osFullName", "const.ohos.fullname"],
|
|
2869
|
+
["osReleaseType", "const.ohos.releasetype"],
|
|
2870
|
+
["sdkApiVersion", "const.ohos.apiversion"],
|
|
2871
|
+
["buildVersion", "const.product.software.version"]
|
|
2872
|
+
];
|
|
2873
|
+
async function getDeviceInfo(opts) {
|
|
2874
|
+
const info = { serial: opts.deviceSerial ?? "(default)", display: null };
|
|
2875
|
+
for (const [field, prop] of PROPS) {
|
|
2876
|
+
try {
|
|
2877
|
+
const value = await paramGet(opts.config, prop, { deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger });
|
|
2878
|
+
if (value) info[field] = value;
|
|
2879
|
+
} catch {
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
2883
|
+
if (screen) info.display = { width: screen.width, height: screen.height };
|
|
2884
|
+
return info;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
// src/install/diffHapAssets.ts
|
|
2888
|
+
import StreamZip2 from "node-stream-zip";
|
|
2889
|
+
function diffEntryNames(installed, next) {
|
|
2890
|
+
const installedSet = new Set(installed);
|
|
2891
|
+
const nextSet = new Set(next);
|
|
2892
|
+
return {
|
|
2893
|
+
addedAssetPaths: [...nextSet].filter((n) => !installedSet.has(n)).sort(),
|
|
2894
|
+
removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
async function listFileEntries(hapPath) {
|
|
2898
|
+
const zip = new StreamZip2.async({ file: hapPath });
|
|
2899
|
+
try {
|
|
2900
|
+
const entries = await zip.entries();
|
|
2901
|
+
return Object.values(entries).filter((e) => !e.isDirectory).map((e) => e.name);
|
|
2902
|
+
} finally {
|
|
2903
|
+
await zip.close();
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
async function diffHapAssets(opts) {
|
|
2907
|
+
const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
|
|
2908
|
+
return diffEntryNames(installed, next);
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/install/apply.ts
|
|
2912
|
+
import * as fs27 from "fs";
|
|
2913
|
+
import * as path25 from "path";
|
|
2914
|
+
function decideInstallMethod(s) {
|
|
2915
|
+
if (!s.signInfoInconsistent) return "replace";
|
|
2916
|
+
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
2917
|
+
return "uninstall-install";
|
|
2918
|
+
}
|
|
2919
|
+
function decideReboot(s) {
|
|
2920
|
+
if (s.assetsChanged) return { reboot: true, reason: "asset-cache" };
|
|
2921
|
+
if (s.isSystemBundle && s.preInstallPid !== null && s.postInstallPid === s.preInstallPid) {
|
|
2922
|
+
return { reboot: true, reason: "pid-unchanged" };
|
|
2923
|
+
}
|
|
2924
|
+
return { reboot: false, reason: "none" };
|
|
2925
|
+
}
|
|
2926
|
+
var SIGN_INFO_INCONSISTENT = /9568332|sign.*info.*inconsistent/i;
|
|
2927
|
+
async function pidOf(config, bundle, deviceSerial) {
|
|
2928
|
+
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
2929
|
+
return proc ? Number(proc.pid) : null;
|
|
2930
|
+
}
|
|
2931
|
+
async function resolveHapPath(opts) {
|
|
2932
|
+
let hapPath = opts.hapPath;
|
|
2933
|
+
if (hapPath) {
|
|
2934
|
+
if (!path25.isAbsolute(hapPath) && opts.projectDir) hapPath = path25.join(opts.projectDir, hapPath);
|
|
2935
|
+
} else {
|
|
2936
|
+
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
2937
|
+
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2938
|
+
if (opts.module) {
|
|
2939
|
+
const signed = haps[opts.module]?.signed ?? [];
|
|
2940
|
+
if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
|
|
2941
|
+
hapPath = signed[0];
|
|
2942
|
+
} else {
|
|
2943
|
+
const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
|
|
2944
|
+
if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
|
|
2945
|
+
if (withSigned.length > 1) {
|
|
2946
|
+
throw new OniroError(
|
|
2947
|
+
`Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
|
|
2948
|
+
);
|
|
2949
|
+
}
|
|
2950
|
+
hapPath = withSigned[0][1].signed[0];
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
if (!fs27.existsSync(hapPath)) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
2954
|
+
return hapPath;
|
|
2955
|
+
}
|
|
2956
|
+
async function applyChanges(opts) {
|
|
2957
|
+
const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
|
|
2958
|
+
const { config, bundle, deviceSerial } = opts;
|
|
2959
|
+
const isSystemBundle = opts.isSystemBundle ?? false;
|
|
2960
|
+
const allowUninstall = opts.allowUninstall ?? false;
|
|
2961
|
+
const hapPath = await resolveHapPath(opts);
|
|
2962
|
+
let assetsChanged = false;
|
|
2963
|
+
if (opts.installedHapPath && fs27.existsSync(opts.installedHapPath)) {
|
|
2964
|
+
try {
|
|
2965
|
+
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
2966
|
+
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
2967
|
+
} catch (err) {
|
|
2968
|
+
logger.debug(`asset diff skipped: ${err.message}`);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
const preInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
2972
|
+
const replaceRes = await hdcExec({ config, args: ["install", "-r", hapPath], deviceSerial, timeoutMs: 3e5 });
|
|
2973
|
+
const signInfoInconsistent = SIGN_INFO_INCONSISTENT.test(`${replaceRes.stdout}${replaceRes.stderr}`);
|
|
2974
|
+
const decision = decideInstallMethod({ signInfoInconsistent, isSystemBundle, allowUninstall });
|
|
2975
|
+
let installMethod;
|
|
2976
|
+
if (decision === "refuse") {
|
|
2977
|
+
throw new OniroError(
|
|
2978
|
+
`Refusing to reinstall system bundle ${bundle}: signing certs are inconsistent (9568332). Align the signing certs first \u2014 uninstalling a system bundle can brick the device. Pass allowUninstall to override.`
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
if (decision === "uninstall-install") {
|
|
2982
|
+
logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
|
|
2983
|
+
await uninstallApp({ config, bundle, deviceSerial });
|
|
2984
|
+
ensureOk(await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }), `hdc install ${hapPath}`);
|
|
2985
|
+
installMethod = "uninstall-install";
|
|
2986
|
+
} else {
|
|
2987
|
+
ensureOk(replaceRes, `hdc install -r ${hapPath}`);
|
|
2988
|
+
installMethod = "replace";
|
|
2989
|
+
}
|
|
2990
|
+
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
2991
|
+
const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
|
|
2992
|
+
let method = installMethod;
|
|
2993
|
+
let cacheCleared = false;
|
|
2994
|
+
if (rebootDecision.reboot) {
|
|
2995
|
+
logger.info(`rebooting (${rebootDecision.reason}) to apply changes to ${bundle}.`);
|
|
2996
|
+
await reboot({ config, waitForBundle: bundle, deviceSerial, abortSignal: opts.abortSignal, logger: opts.logger });
|
|
2997
|
+
postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
2998
|
+
method = "reboot";
|
|
2999
|
+
cacheCleared = rebootDecision.reason === "asset-cache";
|
|
3000
|
+
}
|
|
3001
|
+
return { method, preInstallPid, postInstallPid, replaced: installMethod === "replace", cacheCleared };
|
|
3002
|
+
}
|
|
3003
|
+
|
|
1773
3004
|
// src/index.ts
|
|
1774
|
-
var VERSION = "0.
|
|
3005
|
+
var VERSION = "0.7.0";
|
|
1775
3006
|
export {
|
|
1776
3007
|
ALL_SDKS,
|
|
1777
3008
|
APL_VALUES,
|
|
@@ -1779,33 +3010,59 @@ export {
|
|
|
1779
3010
|
CancelledError,
|
|
1780
3011
|
ChecksumMismatchError,
|
|
1781
3012
|
CmdToolsNotInstalledError,
|
|
3013
|
+
CommandFailedError,
|
|
1782
3014
|
OHOS_URL_BASE,
|
|
1783
3015
|
OniroError,
|
|
1784
3016
|
SdkNotInstalledError,
|
|
1785
3017
|
UnsupportedPlatformError,
|
|
1786
3018
|
VERSION,
|
|
3019
|
+
applyChanges,
|
|
1787
3020
|
attemptHdcConnection,
|
|
3021
|
+
buildGestureCommands,
|
|
3022
|
+
buildHap,
|
|
3023
|
+
buildHvigorwArgs,
|
|
3024
|
+
buildInputCommand,
|
|
3025
|
+
buildRawTouchCommand,
|
|
3026
|
+
captureBurst,
|
|
1788
3027
|
consoleLogger,
|
|
1789
3028
|
createMaterial,
|
|
1790
3029
|
createScaffold,
|
|
3030
|
+
decideInstallMethod,
|
|
3031
|
+
decideReboot,
|
|
1791
3032
|
decryptPwd,
|
|
3033
|
+
dedupEntries,
|
|
1792
3034
|
defaultContext,
|
|
1793
3035
|
defaultPaths,
|
|
3036
|
+
detectAaStartFailure,
|
|
1794
3037
|
detectProjectSdkVersion,
|
|
1795
3038
|
detectSigningConfigNames,
|
|
3039
|
+
diffEntryNames,
|
|
3040
|
+
diffHapAssets,
|
|
3041
|
+
discoverHaps,
|
|
1796
3042
|
downloadAndInstallSdk,
|
|
1797
3043
|
downloadFile,
|
|
3044
|
+
dumpLayout,
|
|
3045
|
+
dumpLog,
|
|
3046
|
+
dumpRenderService,
|
|
3047
|
+
dumpScreen,
|
|
3048
|
+
dumpWindow,
|
|
1798
3049
|
encryptPwd,
|
|
3050
|
+
ensureOhModules,
|
|
3051
|
+
ensureOk,
|
|
1799
3052
|
extractTarball,
|
|
1800
3053
|
extractZipWithProgress,
|
|
1801
3054
|
findAppProcessId,
|
|
1802
3055
|
findCmdToolsSourceDir,
|
|
3056
|
+
findRunningProcess,
|
|
3057
|
+
forceStop,
|
|
1803
3058
|
generateSigningConfigs,
|
|
1804
3059
|
getBundleName,
|
|
1805
3060
|
getCmdToolsBin,
|
|
1806
3061
|
getCmdToolsDownloadUrl,
|
|
1807
3062
|
getCmdToolsPath,
|
|
1808
3063
|
getCmdToolsStatus,
|
|
3064
|
+
getDeviceInfo,
|
|
3065
|
+
getDisplaySize,
|
|
1809
3066
|
getEmulatorDir,
|
|
1810
3067
|
getHdcPath,
|
|
1811
3068
|
getHvigorwPath,
|
|
@@ -1813,10 +3070,12 @@ export {
|
|
|
1813
3070
|
getKey,
|
|
1814
3071
|
getMainAbility,
|
|
1815
3072
|
getOhosBaseSdkHome,
|
|
3073
|
+
getOhpmPath,
|
|
1816
3074
|
getOsFolder,
|
|
1817
3075
|
getSdkFilename,
|
|
1818
3076
|
getSdkRootDir,
|
|
1819
3077
|
getSupportedSdksForUi,
|
|
3078
|
+
hdcExec,
|
|
1820
3079
|
installApp,
|
|
1821
3080
|
installCmdTools,
|
|
1822
3081
|
installEmulator,
|
|
@@ -1825,26 +3084,55 @@ export {
|
|
|
1825
3084
|
isValidBundleName,
|
|
1826
3085
|
isValidProjectName,
|
|
1827
3086
|
launchApp,
|
|
3087
|
+
listAbilities,
|
|
3088
|
+
listDevices,
|
|
3089
|
+
listModules,
|
|
1828
3090
|
listRunningProcesses,
|
|
1829
3091
|
listTemplates,
|
|
1830
3092
|
nonInteractivePrompter,
|
|
1831
3093
|
noopLogger,
|
|
1832
3094
|
noopProgress,
|
|
3095
|
+
paramGet,
|
|
3096
|
+
paramSet,
|
|
3097
|
+
parseBounds,
|
|
3098
|
+
parseCodelinterFindings,
|
|
3099
|
+
parseDeviceList,
|
|
1833
3100
|
parseHilogLine,
|
|
1834
3101
|
pickSigningKind,
|
|
3102
|
+
prepareSigning,
|
|
3103
|
+
pruneLayout,
|
|
1835
3104
|
readJson5File,
|
|
1836
3105
|
readJsonFile,
|
|
3106
|
+
reboot,
|
|
3107
|
+
recvFile,
|
|
1837
3108
|
removeCmdTools,
|
|
1838
3109
|
removeEmulator,
|
|
1839
3110
|
removeSdk,
|
|
3111
|
+
runCodelinter,
|
|
1840
3112
|
runHvigorw,
|
|
3113
|
+
runOhpm,
|
|
3114
|
+
runProcess,
|
|
3115
|
+
scopedLogger,
|
|
3116
|
+
selectDevice,
|
|
3117
|
+
sendFile,
|
|
3118
|
+
sendGesture,
|
|
3119
|
+
sendInput,
|
|
3120
|
+
sendRawTouch,
|
|
1841
3121
|
setHilogLevel,
|
|
3122
|
+
shell,
|
|
1842
3123
|
startEmulator,
|
|
1843
3124
|
staticConfig,
|
|
1844
3125
|
stopEmulator,
|
|
1845
3126
|
streamHilog,
|
|
3127
|
+
takeScreenshot,
|
|
3128
|
+
uninstallApp,
|
|
1846
3129
|
validateTemplateLayout,
|
|
1847
3130
|
verifySha256,
|
|
3131
|
+
waitForBoot,
|
|
3132
|
+
waitForBundle,
|
|
3133
|
+
waitForCondition,
|
|
3134
|
+
waitForLog,
|
|
3135
|
+
watchLog,
|
|
1848
3136
|
writeJson5File,
|
|
1849
3137
|
writeJsonFile
|
|
1850
3138
|
};
|