@oniroproject/core 0.13.0 → 0.14.1
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 +16 -9
- package/dist/index.cjs +818 -310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -25
- package/dist/index.d.ts +276 -25
- package/dist/index.js +794 -310
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -89,14 +89,19 @@ var OniroError = class extends Error {
|
|
|
89
89
|
cause;
|
|
90
90
|
};
|
|
91
91
|
var SdkNotInstalledError = class extends OniroError {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
/** `version` is the release that ships `api` (e.g. `6.0` for API 20), when one is known. */
|
|
93
|
+
constructor(api, expectedPath, version) {
|
|
94
|
+
super(
|
|
95
|
+
`SDK API ${api} is not installed at ${expectedPath}.` + (version ? ` Install it with \`oniro-app sdk install ${version}\`.` : "")
|
|
96
|
+
);
|
|
94
97
|
this.api = api;
|
|
95
98
|
this.expectedPath = expectedPath;
|
|
99
|
+
this.version = version;
|
|
96
100
|
this.name = "SdkNotInstalledError";
|
|
97
101
|
}
|
|
98
102
|
api;
|
|
99
103
|
expectedPath;
|
|
104
|
+
version;
|
|
100
105
|
};
|
|
101
106
|
var CmdToolsNotInstalledError = class extends OniroError {
|
|
102
107
|
constructor(expectedPath) {
|
|
@@ -372,6 +377,24 @@ function isProjectLocalHvigorwWorking(projectDir) {
|
|
|
372
377
|
import * as fs3 from "fs";
|
|
373
378
|
import * as path4 from "path";
|
|
374
379
|
import JSON5 from "json5";
|
|
380
|
+
function localPropertiesSdkDir(projectRoot) {
|
|
381
|
+
try {
|
|
382
|
+
const content = fs3.readFileSync(path4.join(projectRoot, "local.properties"), "utf-8");
|
|
383
|
+
const m = /^\s*sdk\.dir\s*[=:]\s*(.+?)\s*$/m.exec(content);
|
|
384
|
+
return m ? m[1].replace(/\\(.)/g, "$1") : void 0;
|
|
385
|
+
} catch {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function findMissingProjectSdk(config, projectRoot) {
|
|
390
|
+
const api = detectProjectSdkVersion(projectRoot);
|
|
391
|
+
if (api === void 0) return null;
|
|
392
|
+
const expected = path4.join(getOhosBaseSdkHome(config), String(api));
|
|
393
|
+
if (fs3.existsSync(expected)) return null;
|
|
394
|
+
const localSdkDir = localPropertiesSdkDir(projectRoot);
|
|
395
|
+
if (localSdkDir && fs3.existsSync(path4.join(localSdkDir, String(api)))) return null;
|
|
396
|
+
return new SdkNotInstalledError(String(api), expected, ALL_SDKS.find((s) => s.api === String(api))?.version);
|
|
397
|
+
}
|
|
375
398
|
function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
|
|
376
399
|
const buildProfilePath = path4.join(projectRoot, "build-profile.json5");
|
|
377
400
|
if (!fs3.existsSync(buildProfilePath)) {
|
|
@@ -557,14 +580,14 @@ var pipelineAsync = promisify(pipeline);
|
|
|
557
580
|
async function downloadFile(opts) {
|
|
558
581
|
const { url, dest, progress, abortSignal } = opts;
|
|
559
582
|
const proto = url.startsWith("https") ? https : http;
|
|
560
|
-
return new Promise((
|
|
583
|
+
return new Promise((resolve7, reject) => {
|
|
561
584
|
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
562
585
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
563
586
|
let settled = false;
|
|
564
587
|
const done = (err) => {
|
|
565
588
|
if (settled) return;
|
|
566
589
|
settled = true;
|
|
567
|
-
err ? reject(err) :
|
|
590
|
+
err ? reject(err) : resolve7();
|
|
568
591
|
};
|
|
569
592
|
if (abortSignal?.aborted) {
|
|
570
593
|
done(new CancelledError("Download cancelled before start."));
|
|
@@ -1051,21 +1074,27 @@ async function installCmdTools(opts) {
|
|
|
1051
1074
|
removeTempWorkDir(tmpDir, tmpRoot);
|
|
1052
1075
|
}
|
|
1053
1076
|
}
|
|
1077
|
+
function detectCmdToolsFlavour(cmdToolsPath) {
|
|
1078
|
+
return fs11.existsSync(path12.join(cmdToolsPath, "sdk", "default", "hms")) ? "HarmonyOS" : "OpenHarmony";
|
|
1079
|
+
}
|
|
1054
1080
|
function getCmdToolsStatus(config) {
|
|
1055
1081
|
const cmdPath = getCmdToolsPath(config);
|
|
1082
|
+
const hdcPath = getHdcPath(config);
|
|
1083
|
+
const base = { path: cmdPath, hdc: { path: hdcPath, exists: fs11.existsSync(hdcPath) } };
|
|
1056
1084
|
const binDir = path12.join(cmdPath, "bin");
|
|
1057
1085
|
const candidates = os7.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
|
|
1058
1086
|
const bin = candidates.map((c) => path12.join(binDir, c)).find((p) => fs11.existsSync(p));
|
|
1059
1087
|
if (!bin) {
|
|
1060
|
-
return { installed: false, status: "Not installed" };
|
|
1088
|
+
return { installed: false, status: "Not installed", ...base };
|
|
1061
1089
|
}
|
|
1090
|
+
const installed = { installed: true, ...base, flavour: detectCmdToolsFlavour(cmdPath) };
|
|
1062
1091
|
try {
|
|
1063
1092
|
const versionFile = path12.join(cmdPath, "version.txt");
|
|
1064
1093
|
if (fs11.existsSync(versionFile)) {
|
|
1065
1094
|
const content = fs11.readFileSync(versionFile, "utf8");
|
|
1066
1095
|
for (const line of content.split(/\r?\n/)) {
|
|
1067
1096
|
const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
|
|
1068
|
-
if (m?.[1]) return { installed
|
|
1097
|
+
if (m?.[1]) return { ...installed, status: `Installed (${m[1].trim()})` };
|
|
1069
1098
|
}
|
|
1070
1099
|
}
|
|
1071
1100
|
} catch {
|
|
@@ -1079,9 +1108,9 @@ function getCmdToolsStatus(config) {
|
|
|
1079
1108
|
});
|
|
1080
1109
|
if (res.error || res.status !== 0) throw res.error ?? new Error(`exit ${String(res.status)}`);
|
|
1081
1110
|
const version = res.stdout.trim();
|
|
1082
|
-
return { installed
|
|
1111
|
+
return { ...installed, status: `Installed (${version})` };
|
|
1083
1112
|
} catch {
|
|
1084
|
-
return { installed
|
|
1113
|
+
return { ...installed, status: "Installed (version unknown)" };
|
|
1085
1114
|
}
|
|
1086
1115
|
}
|
|
1087
1116
|
function isCmdToolsInstalled(config) {
|
|
@@ -1371,6 +1400,9 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1371
1400
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
1372
1401
|
profileTemplate.acls["allowed-acls"] = options.acls;
|
|
1373
1402
|
}
|
|
1403
|
+
if (options.appPrivilegeCapabilities !== void 0) {
|
|
1404
|
+
profileTemplate["app-privilege-capabilities"] = options.appPrivilegeCapabilities;
|
|
1405
|
+
}
|
|
1374
1406
|
fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1375
1407
|
}
|
|
1376
1408
|
function generateP7bFile(projectDir, paths, logger, passwords) {
|
|
@@ -1460,6 +1492,12 @@ function updateBuildProfile(projectDir, kind, logger, passwords) {
|
|
|
1460
1492
|
storeFile: "./signatures/OpenHarmony.p12"
|
|
1461
1493
|
}
|
|
1462
1494
|
}));
|
|
1495
|
+
for (const product of buildProfile.app.products ?? []) {
|
|
1496
|
+
if (typeof product.signingConfig !== "string" || product.signingConfig === "") {
|
|
1497
|
+
product.signingConfig = configNames[0];
|
|
1498
|
+
logger.info(`[sign] Pointing a product with no signingConfig at "${configNames[0]}".`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1463
1501
|
fs13.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
|
|
1464
1502
|
}
|
|
1465
1503
|
function prepareMaterialDirectory(projectDir, logger) {
|
|
@@ -1491,7 +1529,7 @@ function generateSigningConfigs(options) {
|
|
|
1491
1529
|
}
|
|
1492
1530
|
const sdkPath = path14.join(sdkHome, String(sdkVersion));
|
|
1493
1531
|
if (!fs13.existsSync(sdkPath)) {
|
|
1494
|
-
throw new
|
|
1532
|
+
throw new SdkNotInstalledError(String(sdkVersion), sdkPath, ALL_SDKS.find((s) => s.api === String(sdkVersion))?.version);
|
|
1495
1533
|
}
|
|
1496
1534
|
const paths = {
|
|
1497
1535
|
signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
@@ -1503,7 +1541,11 @@ function generateSigningConfigs(options) {
|
|
|
1503
1541
|
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
1504
1542
|
logger.info("[sign] Starting signing configuration generation...");
|
|
1505
1543
|
copyFilesToProject(projectDir, paths, kind, logger, options.applicationCertPath);
|
|
1506
|
-
modifyProfileTemplate(projectDir, apl, appFeature, logger, {
|
|
1544
|
+
modifyProfileTemplate(projectDir, apl, appFeature, logger, {
|
|
1545
|
+
kind,
|
|
1546
|
+
acls: options.acls,
|
|
1547
|
+
appPrivilegeCapabilities: options.appPrivilegeCapabilities
|
|
1548
|
+
});
|
|
1507
1549
|
generateP7bFile(projectDir, paths, logger, passwords);
|
|
1508
1550
|
prepareMaterialDirectory(projectDir, logger);
|
|
1509
1551
|
updateBuildProfile(projectDir, kind, logger, passwords);
|
|
@@ -1563,6 +1605,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
1563
1605
|
|
|
1564
1606
|
// src/hdc/exec.ts
|
|
1565
1607
|
import * as fs15 from "fs";
|
|
1608
|
+
import { StringDecoder } from "string_decoder";
|
|
1566
1609
|
function errorMessage(err) {
|
|
1567
1610
|
return err instanceof Error ? err.message : String(err);
|
|
1568
1611
|
}
|
|
@@ -1570,7 +1613,7 @@ function runProcess(opts) {
|
|
|
1570
1613
|
const { command, args, cwd, env, onOutput } = opts;
|
|
1571
1614
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1572
1615
|
const { abortSignal } = opts;
|
|
1573
|
-
return new Promise((
|
|
1616
|
+
return new Promise((resolve7, reject) => {
|
|
1574
1617
|
if (abortSignal?.aborted) {
|
|
1575
1618
|
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1576
1619
|
return;
|
|
@@ -1590,6 +1633,8 @@ function runProcess(opts) {
|
|
|
1590
1633
|
let stderr = "";
|
|
1591
1634
|
let settled = false;
|
|
1592
1635
|
let timedOut = false;
|
|
1636
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
1637
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
1593
1638
|
const cleanup = () => {
|
|
1594
1639
|
clearTimeout(timer);
|
|
1595
1640
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
@@ -1607,14 +1652,14 @@ function runProcess(opts) {
|
|
|
1607
1652
|
}, timeoutMs);
|
|
1608
1653
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
1609
1654
|
child.stdout?.on("data", (d) => {
|
|
1610
|
-
const s =
|
|
1655
|
+
const s = stdoutDecoder.write(d);
|
|
1611
1656
|
stdout += s;
|
|
1612
|
-
onOutput?.(s, "stdout");
|
|
1657
|
+
if (s) onOutput?.(s, "stdout");
|
|
1613
1658
|
});
|
|
1614
1659
|
child.stderr?.on("data", (d) => {
|
|
1615
|
-
const s =
|
|
1660
|
+
const s = stderrDecoder.write(d);
|
|
1616
1661
|
stderr += s;
|
|
1617
|
-
onOutput?.(s, "stderr");
|
|
1662
|
+
if (s) onOutput?.(s, "stderr");
|
|
1618
1663
|
});
|
|
1619
1664
|
child.on("error", (err) => {
|
|
1620
1665
|
if (settled) return;
|
|
@@ -1626,6 +1671,12 @@ function runProcess(opts) {
|
|
|
1626
1671
|
if (settled) return;
|
|
1627
1672
|
settled = true;
|
|
1628
1673
|
cleanup();
|
|
1674
|
+
const stdoutTail = stdoutDecoder.end();
|
|
1675
|
+
const stderrTail = stderrDecoder.end();
|
|
1676
|
+
stdout += stdoutTail;
|
|
1677
|
+
stderr += stderrTail;
|
|
1678
|
+
if (stdoutTail) onOutput?.(stdoutTail, "stdout");
|
|
1679
|
+
if (stderrTail) onOutput?.(stderrTail, "stderr");
|
|
1629
1680
|
if (timedOut) {
|
|
1630
1681
|
reject(
|
|
1631
1682
|
new OniroError(
|
|
@@ -1635,21 +1686,41 @@ ${stderr.trim()}` : "")
|
|
|
1635
1686
|
);
|
|
1636
1687
|
return;
|
|
1637
1688
|
}
|
|
1638
|
-
|
|
1689
|
+
resolve7({ code: code ?? -1, stdout, stderr });
|
|
1639
1690
|
});
|
|
1640
1691
|
});
|
|
1641
1692
|
}
|
|
1642
|
-
function
|
|
1693
|
+
function resolveDeviceSerial(explicit, env = process.env) {
|
|
1694
|
+
if (explicit && explicit !== "auto") return explicit;
|
|
1695
|
+
const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
|
|
1696
|
+
return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
|
|
1697
|
+
}
|
|
1698
|
+
function hdcTargetArgs(explicit) {
|
|
1699
|
+
const serial = resolveDeviceSerial(explicit);
|
|
1700
|
+
return serial ? ["-t", serial] : [];
|
|
1701
|
+
}
|
|
1702
|
+
var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
|
|
1703
|
+
function hdcTargetArgsFor(args, explicit) {
|
|
1704
|
+
if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
|
|
1705
|
+
return hdcTargetArgs(explicit);
|
|
1706
|
+
}
|
|
1707
|
+
function hdcClientFailure(output) {
|
|
1708
|
+
const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
|
|
1709
|
+
return first.startsWith("[Fail]") ? first : null;
|
|
1710
|
+
}
|
|
1711
|
+
async function hdcExec(opts) {
|
|
1643
1712
|
const hdc = getHdcPath(opts.config);
|
|
1644
|
-
const
|
|
1645
|
-
return runProcess({
|
|
1713
|
+
const res = await runProcess({
|
|
1646
1714
|
command: hdc,
|
|
1647
|
-
args:
|
|
1715
|
+
args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
|
|
1648
1716
|
timeoutMs: opts.timeoutMs,
|
|
1649
1717
|
abortSignal: opts.abortSignal,
|
|
1650
1718
|
onOutput: opts.onOutput,
|
|
1651
1719
|
logger: opts.logger
|
|
1652
1720
|
});
|
|
1721
|
+
if (res.code === 0 && hdcClientFailure(`${res.stdout}
|
|
1722
|
+
${res.stderr}`)) return { ...res, code: 1 };
|
|
1723
|
+
return res;
|
|
1653
1724
|
}
|
|
1654
1725
|
function shell(opts) {
|
|
1655
1726
|
const { command, ...rest } = opts;
|
|
@@ -1657,10 +1728,15 @@ function shell(opts) {
|
|
|
1657
1728
|
}
|
|
1658
1729
|
function ensureOk(result, command) {
|
|
1659
1730
|
if (result.code !== 0) {
|
|
1660
|
-
throw new CommandFailedError(command, result.code, result.stderr.trim());
|
|
1731
|
+
throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
|
|
1661
1732
|
}
|
|
1662
1733
|
return result;
|
|
1663
1734
|
}
|
|
1735
|
+
function withTargetHint(detail) {
|
|
1736
|
+
if (!/need connect-key|Not match target/i.test(detail)) return detail;
|
|
1737
|
+
return `${detail}
|
|
1738
|
+
More than one target is connected, or the serial is unknown: pass --device <serial> or set ONIRO_DEVICE_SERIAL to one listed by \`oniro-app devices\`.`;
|
|
1739
|
+
}
|
|
1664
1740
|
var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
|
|
1665
1741
|
function parseRemoteExit(stdout) {
|
|
1666
1742
|
let code = null;
|
|
@@ -1677,7 +1753,7 @@ async function shellChecked(opts) {
|
|
|
1677
1753
|
}
|
|
1678
1754
|
function ensureRemoteOk(result, command) {
|
|
1679
1755
|
if (result.code !== 0) {
|
|
1680
|
-
throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
|
|
1756
|
+
throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
|
|
1681
1757
|
}
|
|
1682
1758
|
return result;
|
|
1683
1759
|
}
|
|
@@ -1763,11 +1839,11 @@ async function startEmulator(opts) {
|
|
|
1763
1839
|
fs16.writeFileSync(PID_FILE, String(child.pid));
|
|
1764
1840
|
child.unref();
|
|
1765
1841
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1766
|
-
const earlyExit = new Promise((
|
|
1767
|
-
child.once("exit", (code) =>
|
|
1768
|
-
child.once("error", () =>
|
|
1842
|
+
const earlyExit = new Promise((resolve7) => {
|
|
1843
|
+
child.once("exit", (code) => resolve7(code));
|
|
1844
|
+
child.once("error", () => resolve7(-1));
|
|
1769
1845
|
});
|
|
1770
|
-
const liveness = new Promise((
|
|
1846
|
+
const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
|
|
1771
1847
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1772
1848
|
if (outcome !== "alive") {
|
|
1773
1849
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1847,10 +1923,48 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1847
1923
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1848
1924
|
});
|
|
1849
1925
|
}
|
|
1926
|
+
async function isQemuRunning(logger) {
|
|
1927
|
+
if (os8.platform() !== "win32") {
|
|
1928
|
+
try {
|
|
1929
|
+
const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
|
|
1930
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
1931
|
+
process.kill(pid, 0);
|
|
1932
|
+
return true;
|
|
1933
|
+
}
|
|
1934
|
+
} catch {
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
|
|
1938
|
+
try {
|
|
1939
|
+
const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
|
|
1940
|
+
return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
|
|
1941
|
+
} catch {
|
|
1942
|
+
return false;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
async function getEmulatorStatus(config, opts = {}) {
|
|
1946
|
+
const logger = opts.logger ?? noopLogger;
|
|
1947
|
+
const address = opts.address ?? "127.0.0.1:55555";
|
|
1948
|
+
const dir = getEmulatorDir(config);
|
|
1949
|
+
let connected = false;
|
|
1950
|
+
try {
|
|
1951
|
+
const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
|
|
1952
|
+
connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
return {
|
|
1956
|
+
installed: isEmulatorInstalled(config),
|
|
1957
|
+
dir,
|
|
1958
|
+
running: await isQemuRunning(logger),
|
|
1959
|
+
address,
|
|
1960
|
+
connected
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1850
1963
|
|
|
1851
1964
|
// src/hdc/devices.ts
|
|
1852
1965
|
var STATUS_TOKENS = /* @__PURE__ */ new Set([
|
|
1853
1966
|
"Connected",
|
|
1967
|
+
"Ready",
|
|
1854
1968
|
"Offline",
|
|
1855
1969
|
"Unauthorized",
|
|
1856
1970
|
"Authorizing",
|
|
@@ -1885,9 +1999,8 @@ async function listDevices(config, opts = {}) {
|
|
|
1885
1999
|
return parseDeviceList(res.stdout);
|
|
1886
2000
|
}
|
|
1887
2001
|
async function selectDevice(config, serial, opts = {}) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
if (envSerial && envSerial !== "auto") return envSerial;
|
|
2002
|
+
const resolved = resolveDeviceSerial(serial);
|
|
2003
|
+
if (resolved) return resolved;
|
|
1891
2004
|
const usable = (await listDevices(config, opts)).filter(
|
|
1892
2005
|
(d) => d.status === "Connected" || d.status === "Unknown"
|
|
1893
2006
|
);
|
|
@@ -1901,6 +2014,7 @@ async function selectDevice(config, serial, opts = {}) {
|
|
|
1901
2014
|
}
|
|
1902
2015
|
|
|
1903
2016
|
// src/hdc/param.ts
|
|
2017
|
+
var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
|
|
1904
2018
|
async function paramGet(config, key, opts = {}) {
|
|
1905
2019
|
const res = await shell({
|
|
1906
2020
|
config,
|
|
@@ -1910,7 +2024,30 @@ async function paramGet(config, key, opts = {}) {
|
|
|
1910
2024
|
logger: opts.logger
|
|
1911
2025
|
});
|
|
1912
2026
|
ensureOk(res, `hdc shell param get ${key}`);
|
|
1913
|
-
|
|
2027
|
+
const value = res.stdout.trim();
|
|
2028
|
+
if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
|
|
2029
|
+
return value;
|
|
2030
|
+
}
|
|
2031
|
+
var PARAM_SEPARATOR = "__ONIRO_PARAM__";
|
|
2032
|
+
function parseParamValues(keys, stdout) {
|
|
2033
|
+
const parts = stdout.split(PARAM_SEPARATOR);
|
|
2034
|
+
const values = {};
|
|
2035
|
+
keys.forEach((key, i) => {
|
|
2036
|
+
const value = parts[i]?.trim();
|
|
2037
|
+
values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
|
|
2038
|
+
});
|
|
2039
|
+
return values;
|
|
2040
|
+
}
|
|
2041
|
+
async function paramGetMany(config, keys, opts = {}) {
|
|
2042
|
+
const res = await shell({
|
|
2043
|
+
config,
|
|
2044
|
+
command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
|
|
2045
|
+
deviceSerial: opts.deviceSerial,
|
|
2046
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
2047
|
+
logger: opts.logger
|
|
2048
|
+
});
|
|
2049
|
+
ensureOk(res, "hdc shell param get \u2026");
|
|
2050
|
+
return parseParamValues(keys, res.stdout);
|
|
1914
2051
|
}
|
|
1915
2052
|
async function paramSet(config, key, value, opts = {}) {
|
|
1916
2053
|
const res = await shell({
|
|
@@ -1924,29 +2061,66 @@ async function paramSet(config, key, value, opts = {}) {
|
|
|
1924
2061
|
}
|
|
1925
2062
|
|
|
1926
2063
|
// src/hdc/files.ts
|
|
2064
|
+
import * as path17 from "path";
|
|
2065
|
+
function normaliseLocalPath(local) {
|
|
2066
|
+
const abs = path17.resolve(local);
|
|
2067
|
+
const long = toLongPath(abs);
|
|
2068
|
+
return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
|
|
2069
|
+
}
|
|
2070
|
+
function assertDevicePath(remote) {
|
|
2071
|
+
if (/^[A-Za-z]:[\\/]/.test(remote)) {
|
|
2072
|
+
throw new OniroError(
|
|
2073
|
+
`The device path ${remote} is a Windows path. Git Bash rewrites arguments that start with /: run the command with MSYS_NO_PATHCONV=1, or from PowerShell or cmd.`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
function appSandboxDir(bundle) {
|
|
2078
|
+
return `/data/app/el2/100/base/${bundle}`;
|
|
2079
|
+
}
|
|
2080
|
+
function shQuote(s) {
|
|
2081
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
2082
|
+
}
|
|
1927
2083
|
async function sendFile(opts) {
|
|
2084
|
+
assertDevicePath(opts.remote);
|
|
2085
|
+
const local = normaliseLocalPath(opts.local);
|
|
2086
|
+
const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
|
|
2087
|
+
const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
|
|
2088
|
+
if (sandbox && !remote.startsWith(`${sandbox}/`)) {
|
|
2089
|
+
throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
|
|
2090
|
+
}
|
|
2091
|
+
const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
2092
|
+
if (sandbox) {
|
|
2093
|
+
const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
|
|
2094
|
+
ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
|
|
2095
|
+
}
|
|
1928
2096
|
const res = await hdcExec({
|
|
1929
|
-
|
|
1930
|
-
args: ["file", "send",
|
|
1931
|
-
deviceSerial: opts.deviceSerial,
|
|
2097
|
+
...device,
|
|
2098
|
+
args: ["file", "send", local, remote],
|
|
1932
2099
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1933
2100
|
abortSignal: opts.abortSignal,
|
|
1934
|
-
onOutput: opts.onOutput
|
|
1935
|
-
logger: opts.logger
|
|
2101
|
+
onOutput: opts.onOutput
|
|
1936
2102
|
});
|
|
1937
|
-
ensureOk(res, `hdc file send ${
|
|
2103
|
+
ensureOk(res, `hdc file send ${local} ${remote}`);
|
|
2104
|
+
if (sandbox) {
|
|
2105
|
+
const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
|
|
2106
|
+
const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
|
|
2107
|
+
ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
|
|
2108
|
+
}
|
|
2109
|
+
return remote;
|
|
1938
2110
|
}
|
|
1939
2111
|
async function recvFile(opts) {
|
|
2112
|
+
assertDevicePath(opts.remote);
|
|
2113
|
+
const local = normaliseLocalPath(opts.local);
|
|
1940
2114
|
const res = await hdcExec({
|
|
1941
2115
|
config: opts.config,
|
|
1942
|
-
args: ["file", "recv", opts.remote,
|
|
2116
|
+
args: ["file", "recv", opts.remote, local],
|
|
1943
2117
|
deviceSerial: opts.deviceSerial,
|
|
1944
2118
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1945
2119
|
abortSignal: opts.abortSignal,
|
|
1946
2120
|
onOutput: opts.onOutput,
|
|
1947
2121
|
logger: opts.logger
|
|
1948
2122
|
});
|
|
1949
|
-
ensureOk(res, `hdc file recv ${opts.remote} ${
|
|
2123
|
+
ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
|
|
1950
2124
|
}
|
|
1951
2125
|
|
|
1952
2126
|
// src/hdc/display.ts
|
|
@@ -1966,7 +2140,7 @@ async function getDisplaySize(config, opts = {}) {
|
|
|
1966
2140
|
|
|
1967
2141
|
// src/hdc/wait.ts
|
|
1968
2142
|
function delay(ms, signal) {
|
|
1969
|
-
return new Promise((
|
|
2143
|
+
return new Promise((resolve7, reject) => {
|
|
1970
2144
|
if (signal?.aborted) {
|
|
1971
2145
|
reject(new CancelledError("Wait cancelled."));
|
|
1972
2146
|
return;
|
|
@@ -1981,7 +2155,7 @@ function delay(ms, signal) {
|
|
|
1981
2155
|
};
|
|
1982
2156
|
const timer = setTimeout(() => {
|
|
1983
2157
|
cleanup();
|
|
1984
|
-
|
|
2158
|
+
resolve7();
|
|
1985
2159
|
}, ms);
|
|
1986
2160
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1987
2161
|
});
|
|
@@ -2010,20 +2184,73 @@ async function waitForCondition(opts) {
|
|
|
2010
2184
|
}
|
|
2011
2185
|
|
|
2012
2186
|
// src/hdc/app.ts
|
|
2013
|
-
import * as
|
|
2187
|
+
import * as fs19 from "fs";
|
|
2188
|
+
import * as path20 from "path";
|
|
2189
|
+
|
|
2190
|
+
// src/build/discoverHaps.ts
|
|
2191
|
+
import * as fs17 from "fs";
|
|
2014
2192
|
import * as path18 from "path";
|
|
2193
|
+
function moduleOfHap(hapFileName) {
|
|
2194
|
+
const dash = hapFileName.indexOf("-");
|
|
2195
|
+
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
2196
|
+
}
|
|
2197
|
+
async function discoverHaps(opts) {
|
|
2198
|
+
const { projectDir } = opts;
|
|
2199
|
+
const maxDepth = opts.maxDepth ?? 8;
|
|
2200
|
+
const result = {};
|
|
2201
|
+
const walk = async (dir, depth) => {
|
|
2202
|
+
if (depth > maxDepth) return;
|
|
2203
|
+
const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2204
|
+
for (const e of entries) {
|
|
2205
|
+
const full = path18.join(dir, e.name);
|
|
2206
|
+
if (e.isDirectory()) {
|
|
2207
|
+
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2208
|
+
await walk(full, depth + 1);
|
|
2209
|
+
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2210
|
+
const moduleName = moduleOfHap(e.name);
|
|
2211
|
+
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2212
|
+
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2213
|
+
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
await walk(projectDir, 0);
|
|
2218
|
+
const mtime = (p) => fs17.statSync(p).mtimeMs;
|
|
2219
|
+
for (const bucket of Object.values(result)) {
|
|
2220
|
+
bucket.signed.sort((a, b) => mtime(b) - mtime(a));
|
|
2221
|
+
bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
|
|
2222
|
+
}
|
|
2223
|
+
return result;
|
|
2224
|
+
}
|
|
2225
|
+
function pickHap(haps, module) {
|
|
2226
|
+
const pick = (name) => {
|
|
2227
|
+
const signed = haps[name]?.signed[0];
|
|
2228
|
+
if (signed) return { module: name, path: signed, signed: true };
|
|
2229
|
+
const unsigned = haps[name]?.unsigned[0];
|
|
2230
|
+
return unsigned ? { module: name, path: unsigned, signed: false } : null;
|
|
2231
|
+
};
|
|
2232
|
+
if (module) return pick(module);
|
|
2233
|
+
const names = Object.keys(haps);
|
|
2234
|
+
const withSigned = names.filter((n) => haps[n].signed.length > 0);
|
|
2235
|
+
const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
|
|
2236
|
+
if (candidates.length === 0) return null;
|
|
2237
|
+
if (candidates.length > 1) {
|
|
2238
|
+
throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
|
|
2239
|
+
}
|
|
2240
|
+
return pick(candidates[0]);
|
|
2241
|
+
}
|
|
2015
2242
|
|
|
2016
2243
|
// src/hdc/project.ts
|
|
2017
|
-
import * as
|
|
2018
|
-
import * as
|
|
2244
|
+
import * as fs18 from "fs";
|
|
2245
|
+
import * as path19 from "path";
|
|
2019
2246
|
import JSON53 from "json5";
|
|
2020
2247
|
function readJson5(filePath) {
|
|
2021
|
-
const content =
|
|
2248
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
2022
2249
|
return JSON53.parse(content);
|
|
2023
2250
|
}
|
|
2024
2251
|
function getBundleName(projectDir) {
|
|
2025
|
-
const appJsonPath =
|
|
2026
|
-
if (!
|
|
2252
|
+
const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
|
|
2253
|
+
if (!fs18.existsSync(appJsonPath)) {
|
|
2027
2254
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
2028
2255
|
}
|
|
2029
2256
|
const appJson = readJson5(appJsonPath);
|
|
@@ -2033,8 +2260,8 @@ function getBundleName(projectDir) {
|
|
|
2033
2260
|
return appJson.app.bundleName;
|
|
2034
2261
|
}
|
|
2035
2262
|
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
2036
|
-
const moduleJsonPath =
|
|
2037
|
-
if (!
|
|
2263
|
+
const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
2264
|
+
if (!fs18.existsSync(moduleJsonPath)) {
|
|
2038
2265
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2039
2266
|
}
|
|
2040
2267
|
const moduleJson = readJson5(moduleJsonPath);
|
|
@@ -2054,13 +2281,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
|
2054
2281
|
}
|
|
2055
2282
|
|
|
2056
2283
|
// src/hdc/app.ts
|
|
2284
|
+
function detectInstallFailure(output) {
|
|
2285
|
+
return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
|
|
2286
|
+
}
|
|
2287
|
+
function isUnsignedHap(hapPath) {
|
|
2288
|
+
return path20.basename(hapPath).includes("unsigned");
|
|
2289
|
+
}
|
|
2290
|
+
function installFailureReason(output) {
|
|
2291
|
+
const code = /code:\s*(\d+)/i.exec(output);
|
|
2292
|
+
if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
|
|
2293
|
+
const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
|
|
2294
|
+
return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
|
|
2295
|
+
}
|
|
2296
|
+
function unsignedHapRejection(output, hap) {
|
|
2297
|
+
const name = path20.basename(hap.path);
|
|
2298
|
+
const reason = installFailureReason(output);
|
|
2299
|
+
const what = hap.fallback ? `No signed HAP was found, so the unsigned ${name} was tried, and the target rejected it (${reason}).` : `The target rejected the unsigned ${name} (${reason}).`;
|
|
2300
|
+
if (!/9568320|signature|pkcs7/i.test(reason)) return what;
|
|
2301
|
+
return `${what} This target installs only signed HAPs. Sign the project for it, build again, and install the signed HAP: \`oniro-app sign\` for an OpenHarmony device or the Oniro emulator, \`oniro-app sign --harmonyos\` for a HarmonyOS device.`;
|
|
2302
|
+
}
|
|
2303
|
+
function ensureInstallOk(result, command, hap) {
|
|
2304
|
+
const output = `${result.stdout}
|
|
2305
|
+
${result.stderr}`;
|
|
2306
|
+
if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
|
|
2307
|
+
throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
|
|
2308
|
+
}
|
|
2309
|
+
ensureOk(result, command);
|
|
2310
|
+
const failure = detectInstallFailure(output);
|
|
2311
|
+
if (failure) throw new CommandFailedError(command, 1, failure);
|
|
2312
|
+
return result;
|
|
2313
|
+
}
|
|
2314
|
+
function existingHapOrUnsigned(hapPath) {
|
|
2315
|
+
if (fs19.existsSync(hapPath)) return { path: hapPath };
|
|
2316
|
+
const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
|
|
2317
|
+
return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
|
|
2318
|
+
}
|
|
2319
|
+
async function resolveInstallHap(opts, logger) {
|
|
2320
|
+
const warnIfFallback = (hap) => {
|
|
2321
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
|
|
2322
|
+
return hap;
|
|
2323
|
+
};
|
|
2324
|
+
const configured = opts.hapPath ?? opts.config.get("hapPath", "");
|
|
2325
|
+
const relative3 = configured || defaultPaths.hapPath;
|
|
2326
|
+
const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
|
|
2327
|
+
if (configured) {
|
|
2328
|
+
const hap = existingHapOrUnsigned(hapPath);
|
|
2329
|
+
if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
|
|
2330
|
+
return warnIfFallback(hap);
|
|
2331
|
+
}
|
|
2332
|
+
if (fs19.existsSync(hapPath)) return { path: hapPath };
|
|
2333
|
+
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2334
|
+
const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
|
|
2335
|
+
const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
|
|
2336
|
+
if (entryUnsigned) return warnIfFallback(entryUnsigned);
|
|
2337
|
+
const picked = pickHap(haps);
|
|
2338
|
+
if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
|
|
2339
|
+
return warnIfFallback({ path: picked.path, fallback: !picked.signed });
|
|
2340
|
+
}
|
|
2057
2341
|
async function installApp(opts) {
|
|
2058
2342
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2059
|
-
const
|
|
2060
|
-
const hapPath =
|
|
2061
|
-
if (!fs18.existsSync(hapPath)) {
|
|
2062
|
-
throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
|
|
2063
|
-
}
|
|
2343
|
+
const hap = await resolveInstallHap(opts, logger);
|
|
2344
|
+
const hapPath = hap.path;
|
|
2064
2345
|
const result = await hdcExec({
|
|
2065
2346
|
config: opts.config,
|
|
2066
2347
|
args: ["install", hapPath],
|
|
@@ -2069,7 +2350,7 @@ async function installApp(opts) {
|
|
|
2069
2350
|
});
|
|
2070
2351
|
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
2071
2352
|
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
2072
|
-
|
|
2353
|
+
ensureInstallOk(result, `hdc install ${hapPath}`, hap);
|
|
2073
2354
|
let bundleName = "";
|
|
2074
2355
|
try {
|
|
2075
2356
|
bundleName = getBundleName(opts.projectDir);
|
|
@@ -2077,6 +2358,18 @@ async function installApp(opts) {
|
|
|
2077
2358
|
}
|
|
2078
2359
|
return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
|
|
2079
2360
|
}
|
|
2361
|
+
async function isBundleInstalled(opts) {
|
|
2362
|
+
const safe = opts.bundle.replace(/'/g, `'\\''`);
|
|
2363
|
+
const res = await shell({
|
|
2364
|
+
config: opts.config,
|
|
2365
|
+
command: `bm dump -n '${safe}'`,
|
|
2366
|
+
deviceSerial: opts.deviceSerial,
|
|
2367
|
+
timeoutMs: opts.timeoutMs ?? 15e3,
|
|
2368
|
+
logger: opts.logger
|
|
2369
|
+
});
|
|
2370
|
+
ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
|
|
2371
|
+
return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
|
|
2372
|
+
}
|
|
2080
2373
|
function detectAaStartFailure(output) {
|
|
2081
2374
|
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
2082
2375
|
}
|
|
@@ -2147,10 +2440,10 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2147
2440
|
const logger = options.logger ?? noopLogger;
|
|
2148
2441
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
2149
2442
|
const hdc = getHdcPath(config);
|
|
2150
|
-
return new Promise((
|
|
2443
|
+
return new Promise((resolve7, reject) => {
|
|
2151
2444
|
let proc;
|
|
2152
2445
|
try {
|
|
2153
|
-
proc = spawnCompat(hdc, ["track-jpid"]);
|
|
2446
|
+
proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
|
|
2154
2447
|
} catch (err) {
|
|
2155
2448
|
reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
|
|
2156
2449
|
return;
|
|
@@ -2167,7 +2460,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2167
2460
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
2168
2461
|
resolved = true;
|
|
2169
2462
|
killProcessTree(proc);
|
|
2170
|
-
|
|
2463
|
+
resolve7(pid);
|
|
2171
2464
|
return;
|
|
2172
2465
|
}
|
|
2173
2466
|
}
|
|
@@ -2188,7 +2481,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2188
2481
|
if (options.targetProcessName) {
|
|
2189
2482
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2190
2483
|
} else {
|
|
2191
|
-
|
|
2484
|
+
resolve7(processes);
|
|
2192
2485
|
}
|
|
2193
2486
|
}, timeoutMs);
|
|
2194
2487
|
proc.on("close", () => {
|
|
@@ -2198,7 +2491,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2198
2491
|
if (options.targetProcessName) {
|
|
2199
2492
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2200
2493
|
} else {
|
|
2201
|
-
|
|
2494
|
+
resolve7(processes);
|
|
2202
2495
|
}
|
|
2203
2496
|
});
|
|
2204
2497
|
});
|
|
@@ -2220,7 +2513,11 @@ async function findRunningProcess(opts) {
|
|
|
2220
2513
|
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
2221
2514
|
if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
|
|
2222
2515
|
try {
|
|
2223
|
-
const list = await listRunningProcesses(opts.config, {
|
|
2516
|
+
const list = await listRunningProcesses(opts.config, {
|
|
2517
|
+
timeoutMs: 1200,
|
|
2518
|
+
deviceSerial: opts.deviceSerial,
|
|
2519
|
+
logger: opts.logger
|
|
2520
|
+
});
|
|
2224
2521
|
if (Array.isArray(list)) {
|
|
2225
2522
|
const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
|
|
2226
2523
|
const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
|
|
@@ -2363,12 +2660,12 @@ async function reboot(opts) {
|
|
|
2363
2660
|
}
|
|
2364
2661
|
|
|
2365
2662
|
// src/hdc/screen.ts
|
|
2366
|
-
import * as
|
|
2663
|
+
import * as fs20 from "fs";
|
|
2367
2664
|
import * as os9 from "os";
|
|
2368
|
-
import * as
|
|
2665
|
+
import * as path21 from "path";
|
|
2369
2666
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2370
2667
|
function tmpName(ext) {
|
|
2371
|
-
return
|
|
2668
|
+
return path21.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
|
|
2372
2669
|
}
|
|
2373
2670
|
async function takeScreenshot(opts) {
|
|
2374
2671
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -2378,11 +2675,11 @@ async function takeScreenshot(opts) {
|
|
|
2378
2675
|
const local = tmpName("jpeg");
|
|
2379
2676
|
try {
|
|
2380
2677
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2381
|
-
const pixels = await
|
|
2678
|
+
const pixels = await fs20.promises.readFile(local);
|
|
2382
2679
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2383
2680
|
return { pixels, width: size.width, height: size.height };
|
|
2384
2681
|
} finally {
|
|
2385
|
-
|
|
2682
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2386
2683
|
});
|
|
2387
2684
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2388
2685
|
});
|
|
@@ -2401,9 +2698,9 @@ async function captureBurst(opts) {
|
|
|
2401
2698
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
2402
2699
|
try {
|
|
2403
2700
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2404
|
-
frames.push(await
|
|
2701
|
+
frames.push(await fs20.promises.readFile(local));
|
|
2405
2702
|
} finally {
|
|
2406
|
-
|
|
2703
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2407
2704
|
});
|
|
2408
2705
|
}
|
|
2409
2706
|
}
|
|
@@ -2578,9 +2875,9 @@ async function sendRawTouch(opts) {
|
|
|
2578
2875
|
}
|
|
2579
2876
|
|
|
2580
2877
|
// src/hdc/dump.ts
|
|
2581
|
-
import * as
|
|
2878
|
+
import * as fs21 from "fs";
|
|
2582
2879
|
import * as os10 from "os";
|
|
2583
|
-
import * as
|
|
2880
|
+
import * as path22 from "path";
|
|
2584
2881
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
2585
2882
|
|
|
2586
2883
|
// src/hdc/ime.ts
|
|
@@ -2843,10 +3140,10 @@ async function dumpLayout(opts) {
|
|
|
2843
3140
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2844
3141
|
${dump.stdout}`);
|
|
2845
3142
|
const remote = m[1];
|
|
2846
|
-
const local =
|
|
3143
|
+
const local = path22.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
|
|
2847
3144
|
try {
|
|
2848
3145
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2849
|
-
const parsed = JSON.parse(await
|
|
3146
|
+
const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
|
|
2850
3147
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2851
3148
|
if (opts.prune === false) {
|
|
2852
3149
|
return { display, tree: parsed };
|
|
@@ -2859,7 +3156,7 @@ ${dump.stdout}`);
|
|
|
2859
3156
|
const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
|
|
2860
3157
|
return { display, tree, keyboard };
|
|
2861
3158
|
} finally {
|
|
2862
|
-
|
|
3159
|
+
fs21.promises.unlink(local).catch(() => {
|
|
2863
3160
|
});
|
|
2864
3161
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2865
3162
|
});
|
|
@@ -2869,7 +3166,7 @@ ${dump.stdout}`);
|
|
|
2869
3166
|
// src/hdc/uiActions.ts
|
|
2870
3167
|
var FOCUS_SETTLE_MS = 400;
|
|
2871
3168
|
var KEYBOARD_SETTLE_MS = 400;
|
|
2872
|
-
var sleep = (ms) => new Promise((
|
|
3169
|
+
var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
2873
3170
|
function targetTree(layout) {
|
|
2874
3171
|
const kb = layout.keyboard;
|
|
2875
3172
|
return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
|
|
@@ -3022,13 +3319,14 @@ async function waitForTarget(opts) {
|
|
|
3022
3319
|
}
|
|
3023
3320
|
|
|
3024
3321
|
// src/hdc/hilog.ts
|
|
3322
|
+
import * as fs22 from "fs";
|
|
3025
3323
|
function setHilogLevel(opts) {
|
|
3026
3324
|
const logger = opts.logger ?? noopLogger;
|
|
3027
3325
|
const hdc = getHdcPath(opts.config);
|
|
3028
|
-
return new Promise((
|
|
3326
|
+
return new Promise((resolve7, reject) => {
|
|
3029
3327
|
let child;
|
|
3030
3328
|
try {
|
|
3031
|
-
child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
3329
|
+
child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
|
|
3032
3330
|
} catch (err) {
|
|
3033
3331
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3034
3332
|
logger.error(`[hilog] failed to spawn hdc: ${msg}`);
|
|
@@ -3045,7 +3343,7 @@ function setHilogLevel(opts) {
|
|
|
3045
3343
|
});
|
|
3046
3344
|
child.once("close", (code) => {
|
|
3047
3345
|
if (code === 0) {
|
|
3048
|
-
|
|
3346
|
+
resolve7();
|
|
3049
3347
|
} else {
|
|
3050
3348
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
3051
3349
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -3056,8 +3354,7 @@ function setHilogLevel(opts) {
|
|
|
3056
3354
|
}
|
|
3057
3355
|
function streamHilog(opts) {
|
|
3058
3356
|
const hdc = getHdcPath(opts.config);
|
|
3059
|
-
const args = [];
|
|
3060
|
-
if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
|
|
3357
|
+
const args = [...hdcTargetArgs(opts.deviceSerial)];
|
|
3061
3358
|
args.push("shell", "hilog");
|
|
3062
3359
|
if (opts.processId && opts.processId.trim() !== "") {
|
|
3063
3360
|
args.push("-P", opts.processId.trim());
|
|
@@ -3100,7 +3397,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
|
|
|
3100
3397
|
return proc?.pid;
|
|
3101
3398
|
}
|
|
3102
3399
|
function waitForLog(opts) {
|
|
3103
|
-
return new Promise((
|
|
3400
|
+
return new Promise((resolve7, reject) => {
|
|
3104
3401
|
const deadline = Date.now() + opts.timeoutMs;
|
|
3105
3402
|
let settled = false;
|
|
3106
3403
|
let child = null;
|
|
@@ -3119,7 +3416,7 @@ function waitForLog(opts) {
|
|
|
3119
3416
|
if (settled) return;
|
|
3120
3417
|
settled = true;
|
|
3121
3418
|
cleanup();
|
|
3122
|
-
|
|
3419
|
+
resolve7(entry);
|
|
3123
3420
|
};
|
|
3124
3421
|
const fail = (err) => {
|
|
3125
3422
|
if (settled) return;
|
|
@@ -3166,7 +3463,7 @@ function waitForLog(opts) {
|
|
|
3166
3463
|
});
|
|
3167
3464
|
}
|
|
3168
3465
|
function watchLog(opts) {
|
|
3169
|
-
return new Promise((
|
|
3466
|
+
return new Promise((resolve7, reject) => {
|
|
3170
3467
|
const matches = [];
|
|
3171
3468
|
let settled = false;
|
|
3172
3469
|
let child = null;
|
|
@@ -3183,7 +3480,7 @@ function watchLog(opts) {
|
|
|
3183
3480
|
if (settled) return;
|
|
3184
3481
|
settled = true;
|
|
3185
3482
|
cleanup();
|
|
3186
|
-
|
|
3483
|
+
resolve7(opts.dedup === false ? matches : dedupEntries(matches));
|
|
3187
3484
|
};
|
|
3188
3485
|
const onAbort = () => {
|
|
3189
3486
|
if (settled) return;
|
|
@@ -3215,6 +3512,94 @@ function watchLog(opts) {
|
|
|
3215
3512
|
});
|
|
3216
3513
|
});
|
|
3217
3514
|
}
|
|
3515
|
+
async function captureLog(opts) {
|
|
3516
|
+
const logger = opts.logger ?? noopLogger;
|
|
3517
|
+
if (opts.clear) {
|
|
3518
|
+
const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
|
|
3519
|
+
ensureOk(res, "hdc shell hilog -r");
|
|
3520
|
+
}
|
|
3521
|
+
const fd = fs22.openSync(opts.file, "a");
|
|
3522
|
+
let lines = 0;
|
|
3523
|
+
let reconnects = 0;
|
|
3524
|
+
try {
|
|
3525
|
+
await new Promise((resolve7, reject) => {
|
|
3526
|
+
const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
|
|
3527
|
+
let settled = false;
|
|
3528
|
+
let child = null;
|
|
3529
|
+
let reconnectTimer;
|
|
3530
|
+
let buf = "";
|
|
3531
|
+
let open = true;
|
|
3532
|
+
const write = (line) => {
|
|
3533
|
+
if (!open || opts.pattern && !opts.pattern.test(line)) return;
|
|
3534
|
+
fs22.writeSync(fd, `${line}
|
|
3535
|
+
`);
|
|
3536
|
+
lines++;
|
|
3537
|
+
};
|
|
3538
|
+
const cleanup = () => {
|
|
3539
|
+
if (timer) clearTimeout(timer);
|
|
3540
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
3541
|
+
opts.abortSignal?.removeEventListener("abort", finish);
|
|
3542
|
+
if (buf.trim()) write(buf.trimEnd());
|
|
3543
|
+
buf = "";
|
|
3544
|
+
open = false;
|
|
3545
|
+
if (child) {
|
|
3546
|
+
child.stdout.removeAllListeners("data");
|
|
3547
|
+
killProcessTree(child);
|
|
3548
|
+
child = null;
|
|
3549
|
+
}
|
|
3550
|
+
};
|
|
3551
|
+
function finish() {
|
|
3552
|
+
if (settled) return;
|
|
3553
|
+
settled = true;
|
|
3554
|
+
cleanup();
|
|
3555
|
+
resolve7();
|
|
3556
|
+
}
|
|
3557
|
+
const fail = (err) => {
|
|
3558
|
+
if (settled) return;
|
|
3559
|
+
settled = true;
|
|
3560
|
+
cleanup();
|
|
3561
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3562
|
+
};
|
|
3563
|
+
const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
|
|
3564
|
+
if (opts.abortSignal?.aborted) {
|
|
3565
|
+
finish();
|
|
3566
|
+
return;
|
|
3567
|
+
}
|
|
3568
|
+
opts.abortSignal?.addEventListener("abort", finish, { once: true });
|
|
3569
|
+
const spawnStream = () => {
|
|
3570
|
+
if (settled) return;
|
|
3571
|
+
const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
3572
|
+
child = stream;
|
|
3573
|
+
stream.stdout.on("data", (d) => {
|
|
3574
|
+
buf += d.toString();
|
|
3575
|
+
const parts = buf.split(/\r?\n/);
|
|
3576
|
+
buf = parts.pop() ?? "";
|
|
3577
|
+
for (const line of parts) write(line);
|
|
3578
|
+
});
|
|
3579
|
+
const onEnd = () => {
|
|
3580
|
+
if (settled || child !== stream) return;
|
|
3581
|
+
child = null;
|
|
3582
|
+
if (Date.now() >= deadline) return;
|
|
3583
|
+
reconnects++;
|
|
3584
|
+
logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
|
|
3585
|
+
reconnectTimer = setTimeout(() => {
|
|
3586
|
+
try {
|
|
3587
|
+
spawnStream();
|
|
3588
|
+
} catch (err) {
|
|
3589
|
+
fail(err);
|
|
3590
|
+
}
|
|
3591
|
+
}, 2e3);
|
|
3592
|
+
};
|
|
3593
|
+
stream.once("close", onEnd);
|
|
3594
|
+
stream.once("error", onEnd);
|
|
3595
|
+
};
|
|
3596
|
+
spawnStream();
|
|
3597
|
+
});
|
|
3598
|
+
} finally {
|
|
3599
|
+
fs22.closeSync(fd);
|
|
3600
|
+
}
|
|
3601
|
+
return { file: opts.file, lines, reconnects };
|
|
3602
|
+
}
|
|
3218
3603
|
async function dumpLog(opts) {
|
|
3219
3604
|
let pipe = "hilog -x";
|
|
3220
3605
|
if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
|
|
@@ -3239,17 +3624,17 @@ async function dumpLog(opts) {
|
|
|
3239
3624
|
}
|
|
3240
3625
|
|
|
3241
3626
|
// src/build/runHvigorw.ts
|
|
3242
|
-
import * as
|
|
3243
|
-
import * as
|
|
3627
|
+
import * as fs24 from "fs";
|
|
3628
|
+
import * as path24 from "path";
|
|
3244
3629
|
|
|
3245
3630
|
// src/harmonyos/target.ts
|
|
3246
|
-
import * as
|
|
3247
|
-
import * as
|
|
3631
|
+
import * as fs23 from "fs";
|
|
3632
|
+
import * as path23 from "path";
|
|
3248
3633
|
import JSON54 from "json5";
|
|
3249
3634
|
function detectRuntimeOs(projectDir, productName) {
|
|
3250
3635
|
let products = [];
|
|
3251
3636
|
try {
|
|
3252
|
-
const parsed = JSON54.parse(
|
|
3637
|
+
const parsed = JSON54.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
|
|
3253
3638
|
if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
|
|
3254
3639
|
} catch {
|
|
3255
3640
|
return "OpenHarmony";
|
|
@@ -3290,7 +3675,7 @@ function runHvigorw(opts) {
|
|
|
3290
3675
|
const { config } = opts;
|
|
3291
3676
|
const projectDir = toLongPath(opts.projectDir);
|
|
3292
3677
|
const logger = opts.logger ?? noopLogger;
|
|
3293
|
-
if (!
|
|
3678
|
+
if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
|
|
3294
3679
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
3295
3680
|
}
|
|
3296
3681
|
let hvigorw;
|
|
@@ -3302,24 +3687,24 @@ function runHvigorw(opts) {
|
|
|
3302
3687
|
hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
|
|
3303
3688
|
logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
|
|
3304
3689
|
} else {
|
|
3305
|
-
if (!
|
|
3690
|
+
if (!fs24.existsSync(getCmdToolsPath(config))) {
|
|
3306
3691
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
3307
3692
|
}
|
|
3308
3693
|
sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
|
|
3309
3694
|
hvigorw = getHvigorwPath(config, projectDir);
|
|
3310
3695
|
}
|
|
3311
|
-
if (!
|
|
3696
|
+
if (!fs24.existsSync(hvigorw)) {
|
|
3312
3697
|
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
3313
3698
|
}
|
|
3314
3699
|
const args = buildHvigorwArgs(opts);
|
|
3315
3700
|
if (process.platform !== "win32") {
|
|
3316
3701
|
try {
|
|
3317
|
-
|
|
3702
|
+
fs24.chmodSync(hvigorw, 493);
|
|
3318
3703
|
} catch {
|
|
3319
3704
|
}
|
|
3320
3705
|
}
|
|
3321
3706
|
const env = { ...process.env, ...sdkEnv };
|
|
3322
|
-
return new Promise((
|
|
3707
|
+
return new Promise((resolve7, reject) => {
|
|
3323
3708
|
if (opts.abortSignal?.aborted) {
|
|
3324
3709
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
3325
3710
|
return;
|
|
@@ -3363,46 +3748,15 @@ function runHvigorw(opts) {
|
|
|
3363
3748
|
settled = true;
|
|
3364
3749
|
cleanup();
|
|
3365
3750
|
const exitCode = code ?? 1;
|
|
3366
|
-
if (exitCode === 0)
|
|
3751
|
+
if (exitCode === 0) resolve7({ exitCode });
|
|
3367
3752
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
3368
3753
|
});
|
|
3369
3754
|
});
|
|
3370
3755
|
}
|
|
3371
3756
|
|
|
3372
|
-
// src/build/discoverHaps.ts
|
|
3373
|
-
import * as fs23 from "fs";
|
|
3374
|
-
import * as path23 from "path";
|
|
3375
|
-
function moduleOfHap(hapFileName) {
|
|
3376
|
-
const dash = hapFileName.indexOf("-");
|
|
3377
|
-
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
3378
|
-
}
|
|
3379
|
-
async function discoverHaps(opts) {
|
|
3380
|
-
const { projectDir } = opts;
|
|
3381
|
-
const maxDepth = opts.maxDepth ?? 8;
|
|
3382
|
-
const result = {};
|
|
3383
|
-
const walk = async (dir, depth) => {
|
|
3384
|
-
if (depth > maxDepth) return;
|
|
3385
|
-
const entries = await fs23.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
3386
|
-
for (const e of entries) {
|
|
3387
|
-
const full = path23.join(dir, e.name);
|
|
3388
|
-
if (e.isDirectory()) {
|
|
3389
|
-
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
3390
|
-
await walk(full, depth + 1);
|
|
3391
|
-
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
3392
|
-
const moduleName = moduleOfHap(e.name);
|
|
3393
|
-
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
3394
|
-
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
3395
|
-
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
};
|
|
3399
|
-
await walk(projectDir, 0);
|
|
3400
|
-
return result;
|
|
3401
|
-
}
|
|
3402
|
-
|
|
3403
3757
|
// src/build/ohpm.ts
|
|
3404
|
-
import * as
|
|
3405
|
-
import * as
|
|
3758
|
+
import * as fs25 from "fs";
|
|
3759
|
+
import * as path25 from "path";
|
|
3406
3760
|
function runOhpm(opts) {
|
|
3407
3761
|
return runProcess({
|
|
3408
3762
|
command: opts.ohpmPath ?? getOhpmPath(opts.config),
|
|
@@ -3415,7 +3769,7 @@ function runOhpm(opts) {
|
|
|
3415
3769
|
});
|
|
3416
3770
|
}
|
|
3417
3771
|
async function ensureOhModules(opts) {
|
|
3418
|
-
if (
|
|
3772
|
+
if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
|
|
3419
3773
|
return { installed: false };
|
|
3420
3774
|
}
|
|
3421
3775
|
const res = await runOhpm({
|
|
@@ -3432,23 +3786,91 @@ async function ensureOhModules(opts) {
|
|
|
3432
3786
|
}
|
|
3433
3787
|
|
|
3434
3788
|
// src/build/codelinter.ts
|
|
3435
|
-
var FINDING_RE = /^(.*?):(\d+):(
|
|
3789
|
+
var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
|
|
3436
3790
|
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
3437
|
-
|
|
3791
|
+
var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
|
|
3792
|
+
var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
|
|
3793
|
+
var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
|
|
3794
|
+
var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
|
|
3795
|
+
var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
|
|
3796
|
+
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
3797
|
+
function cleanCodelinterOutput(output) {
|
|
3798
|
+
return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
|
|
3799
|
+
}
|
|
3800
|
+
function normaliseSeverity(s) {
|
|
3801
|
+
const lower = s.toLowerCase();
|
|
3802
|
+
return lower === "warn" ? "warning" : lower;
|
|
3803
|
+
}
|
|
3804
|
+
function parseCodelinterReport(output) {
|
|
3438
3805
|
const findings = [];
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3806
|
+
const notices = [];
|
|
3807
|
+
let summary = null;
|
|
3808
|
+
let file = "";
|
|
3809
|
+
for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
|
|
3810
|
+
const line = rawLine.trim();
|
|
3811
|
+
if (!line) continue;
|
|
3812
|
+
const row = ROW_RE.exec(line);
|
|
3813
|
+
if (row) {
|
|
3814
|
+
if (!file) continue;
|
|
3815
|
+
const columns = row[4].trim().split(/\s{2,}/);
|
|
3816
|
+
const last = columns[columns.length - 1];
|
|
3817
|
+
const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
|
|
3818
|
+
const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
|
|
3819
|
+
findings.push({
|
|
3820
|
+
file,
|
|
3821
|
+
line: Number(row[1]),
|
|
3822
|
+
column: Number(row[2]),
|
|
3823
|
+
severity: normaliseSeverity(row[3]),
|
|
3824
|
+
code,
|
|
3825
|
+
message
|
|
3826
|
+
});
|
|
3827
|
+
continue;
|
|
3828
|
+
}
|
|
3829
|
+
const header = FILE_HEADER_RE.exec(line);
|
|
3830
|
+
if (header) {
|
|
3831
|
+
file = header[1];
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
const legacy = FINDING_RE.exec(line);
|
|
3835
|
+
if (legacy) {
|
|
3836
|
+
let message = legacy[5].trim();
|
|
3837
|
+
let code = "";
|
|
3838
|
+
const codeMatch = TRAILING_CODE_RE.exec(message);
|
|
3839
|
+
if (codeMatch) {
|
|
3840
|
+
code = codeMatch[1];
|
|
3841
|
+
message = message.slice(0, codeMatch.index).trim();
|
|
3842
|
+
}
|
|
3843
|
+
const finding = {
|
|
3844
|
+
file: legacy[1].trim(),
|
|
3845
|
+
line: Number(legacy[2]),
|
|
3846
|
+
severity: normaliseSeverity(legacy[4]),
|
|
3847
|
+
code,
|
|
3848
|
+
message
|
|
3849
|
+
};
|
|
3850
|
+
if (legacy[3]) finding.column = Number(legacy[3]);
|
|
3851
|
+
findings.push(finding);
|
|
3852
|
+
continue;
|
|
3448
3853
|
}
|
|
3449
|
-
|
|
3854
|
+
const totals = SUMMARY_RE.exec(line);
|
|
3855
|
+
if (totals) {
|
|
3856
|
+
summary = {
|
|
3857
|
+
defects: Number(totals[1]),
|
|
3858
|
+
errors: Number(totals[2]),
|
|
3859
|
+
warnings: Number(totals[3]),
|
|
3860
|
+
suggestions: Number(totals[4])
|
|
3861
|
+
};
|
|
3862
|
+
continue;
|
|
3863
|
+
}
|
|
3864
|
+
if (/no defects found/i.test(line)) {
|
|
3865
|
+
summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
|
|
3866
|
+
continue;
|
|
3867
|
+
}
|
|
3868
|
+
if (NOTICE_RE.test(line)) notices.push(line);
|
|
3450
3869
|
}
|
|
3451
|
-
return findings;
|
|
3870
|
+
return { findings, summary, notices, incomplete: notices.length > 0 };
|
|
3871
|
+
}
|
|
3872
|
+
function parseCodelinterFindings(output) {
|
|
3873
|
+
return parseCodelinterReport(output).findings;
|
|
3452
3874
|
}
|
|
3453
3875
|
async function runCodelinter(opts) {
|
|
3454
3876
|
const res = await runProcess({
|
|
@@ -3460,16 +3882,28 @@ async function runCodelinter(opts) {
|
|
|
3460
3882
|
logger: opts.logger
|
|
3461
3883
|
});
|
|
3462
3884
|
const raw = `${res.stdout}${res.stderr}`;
|
|
3463
|
-
return { code: res.code,
|
|
3885
|
+
return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
|
|
3464
3886
|
}
|
|
3465
3887
|
|
|
3466
3888
|
// src/build/buildHap.ts
|
|
3889
|
+
var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
|
|
3890
|
+
function diagnoseBuildFailure(output) {
|
|
3891
|
+
if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
|
|
3892
|
+
return "The project calls system APIs (`@systemapi`), which the public SDK leaves out. Build against the full OpenHarmony SDK: unpack its components into <root>/<os>/<api> of a separate SDK root and set ONIRO_SDK_ROOT_DIR to that root. Installing such an app also needs privileged signing (`oniro-app sign --apl system_core --acls \u2026 --app-privilege-capabilities \u2026`).";
|
|
3893
|
+
}
|
|
3894
|
+
if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
|
|
3895
|
+
return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
|
|
3896
|
+
}
|
|
3897
|
+
return void 0;
|
|
3898
|
+
}
|
|
3467
3899
|
async function buildHap(opts) {
|
|
3468
3900
|
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
3469
3901
|
const warnings = [];
|
|
3470
3902
|
const start = Date.now();
|
|
3471
3903
|
const projectDir = toLongPath(opts.projectDir);
|
|
3472
3904
|
const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
|
|
3905
|
+
const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
|
|
3906
|
+
if (missingSdk) throw missingSdk;
|
|
3473
3907
|
if (opts.autoInstallDeps !== false) {
|
|
3474
3908
|
const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
|
|
3475
3909
|
const r = await ensureOhModules({
|
|
@@ -3482,23 +3916,35 @@ async function buildHap(opts) {
|
|
|
3482
3916
|
});
|
|
3483
3917
|
if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
|
|
3484
3918
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3919
|
+
let output = "";
|
|
3920
|
+
let exitCode;
|
|
3921
|
+
try {
|
|
3922
|
+
({ exitCode } = await runHvigorw({
|
|
3923
|
+
config: opts.config,
|
|
3924
|
+
projectDir,
|
|
3925
|
+
product: opts.product,
|
|
3926
|
+
module: opts.module,
|
|
3927
|
+
buildMode: opts.mode,
|
|
3928
|
+
task: opts.task,
|
|
3929
|
+
parallel: opts.parallel,
|
|
3930
|
+
abortSignal: opts.abortSignal,
|
|
3931
|
+
onOutput: (chunk, stream) => {
|
|
3932
|
+
output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
|
|
3933
|
+
opts.onOutput?.(chunk, stream);
|
|
3934
|
+
},
|
|
3935
|
+
logger: opts.logger
|
|
3936
|
+
}));
|
|
3937
|
+
} catch (err) {
|
|
3938
|
+
const hint = diagnoseBuildFailure(output);
|
|
3939
|
+
if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
|
|
3940
|
+
${hint}`, err);
|
|
3941
|
+
throw err;
|
|
3942
|
+
}
|
|
3497
3943
|
const discoveredHaps = await discoverHaps({ projectDir });
|
|
3498
3944
|
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
3499
3945
|
if (!anySigned) {
|
|
3500
3946
|
warnings.push(
|
|
3501
|
-
isHarmonyOs ? "No signed HAPs found
|
|
3947
|
+
isHarmonyOs ? "No signed HAPs found. The HarmonyOS emulator installs the unsigned HAP as it is; for a device, run `oniro-app sign --harmonyos` to have AppGallery Connect issue a certificate and profile." : "No signed HAPs found: most OpenHarmony targets reject unsigned HAPs. Run `oniro-app sign` to generate local signing material, then build again."
|
|
3502
3948
|
);
|
|
3503
3949
|
}
|
|
3504
3950
|
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
@@ -3515,21 +3961,21 @@ function isValidBundleName(bundleName) {
|
|
|
3515
3961
|
}
|
|
3516
3962
|
|
|
3517
3963
|
// src/project/templates.ts
|
|
3518
|
-
import * as
|
|
3519
|
-
import * as
|
|
3964
|
+
import * as fs26 from "fs";
|
|
3965
|
+
import * as path26 from "path";
|
|
3520
3966
|
function toHumanTemplateName(folderName) {
|
|
3521
3967
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
3522
3968
|
}
|
|
3523
3969
|
function listTemplates(templateRoot) {
|
|
3524
|
-
if (!
|
|
3525
|
-
const entries =
|
|
3970
|
+
if (!fs26.existsSync(templateRoot)) return [];
|
|
3971
|
+
const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
|
|
3526
3972
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
3527
|
-
const dir =
|
|
3528
|
-
const metaPath =
|
|
3973
|
+
const dir = path26.join(templateRoot, e.name);
|
|
3974
|
+
const metaPath = path26.join(dir, "template.json");
|
|
3529
3975
|
let meta = {};
|
|
3530
|
-
if (
|
|
3976
|
+
if (fs26.existsSync(metaPath)) {
|
|
3531
3977
|
try {
|
|
3532
|
-
meta = JSON.parse(
|
|
3978
|
+
meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
|
|
3533
3979
|
} catch {
|
|
3534
3980
|
}
|
|
3535
3981
|
}
|
|
@@ -3544,57 +3990,57 @@ function listTemplates(templateRoot) {
|
|
|
3544
3990
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
3545
3991
|
const required = [
|
|
3546
3992
|
"build-profile.json5",
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3993
|
+
path26.join("AppScope", "app.json5"),
|
|
3994
|
+
path26.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3995
|
+
path26.join(defaultModuleName, "oh-package.json5"),
|
|
3550
3996
|
"hvigorfile.ts"
|
|
3551
3997
|
];
|
|
3552
|
-
return required.filter((rel) => !
|
|
3998
|
+
return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
|
|
3553
3999
|
}
|
|
3554
4000
|
|
|
3555
4001
|
// src/project/createScaffold.ts
|
|
3556
|
-
import * as
|
|
3557
|
-
import * as
|
|
4002
|
+
import * as fs28 from "fs";
|
|
4003
|
+
import * as path27 from "path";
|
|
3558
4004
|
|
|
3559
4005
|
// src/project/jsonHelpers.ts
|
|
3560
|
-
import * as
|
|
4006
|
+
import * as fs27 from "fs";
|
|
3561
4007
|
import JSON55 from "json5";
|
|
3562
4008
|
function readJson5File(filePath) {
|
|
3563
|
-
return JSON55.parse(
|
|
4009
|
+
return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
|
|
3564
4010
|
}
|
|
3565
4011
|
function writeJson5File(filePath, value) {
|
|
3566
|
-
|
|
4012
|
+
fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3567
4013
|
}
|
|
3568
4014
|
function readJsonFile(filePath) {
|
|
3569
|
-
return JSON.parse(
|
|
4015
|
+
return JSON.parse(fs27.readFileSync(filePath, "utf8"));
|
|
3570
4016
|
}
|
|
3571
4017
|
function writeJsonFile(filePath, value) {
|
|
3572
|
-
|
|
4018
|
+
fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3573
4019
|
}
|
|
3574
4020
|
|
|
3575
4021
|
// src/project/createScaffold.ts
|
|
3576
4022
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
3577
4023
|
async function pathExists(p) {
|
|
3578
4024
|
try {
|
|
3579
|
-
await
|
|
4025
|
+
await fs28.promises.access(p);
|
|
3580
4026
|
return true;
|
|
3581
4027
|
} catch {
|
|
3582
4028
|
return false;
|
|
3583
4029
|
}
|
|
3584
4030
|
}
|
|
3585
4031
|
async function copyDirRecursive(srcDir, destDir) {
|
|
3586
|
-
await
|
|
3587
|
-
const entries = await
|
|
4032
|
+
await fs28.promises.mkdir(destDir, { recursive: true });
|
|
4033
|
+
const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
|
|
3588
4034
|
for (const entry of entries) {
|
|
3589
|
-
const src =
|
|
3590
|
-
const dest =
|
|
4035
|
+
const src = path27.join(srcDir, entry.name);
|
|
4036
|
+
const dest = path27.join(destDir, entry.name);
|
|
3591
4037
|
if (entry.isDirectory()) {
|
|
3592
4038
|
await copyDirRecursive(src, dest);
|
|
3593
4039
|
} else if (entry.isSymbolicLink()) {
|
|
3594
4040
|
continue;
|
|
3595
4041
|
} else if (entry.isFile()) {
|
|
3596
4042
|
if (entry.name === "template.json") continue;
|
|
3597
|
-
await
|
|
4043
|
+
await fs28.promises.copyFile(src, dest);
|
|
3598
4044
|
}
|
|
3599
4045
|
}
|
|
3600
4046
|
}
|
|
@@ -3602,9 +4048,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
3602
4048
|
const stack = [projectDir];
|
|
3603
4049
|
while (stack.length > 0) {
|
|
3604
4050
|
const currentDir = stack.pop();
|
|
3605
|
-
const entries = await
|
|
4051
|
+
const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
|
|
3606
4052
|
for (const entry of entries) {
|
|
3607
|
-
const fullPath =
|
|
4053
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
3608
4054
|
if (entry.isDirectory()) {
|
|
3609
4055
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3610
4056
|
stack.push(fullPath);
|
|
@@ -3626,23 +4072,23 @@ function toJavaPropertiesPath(p) {
|
|
|
3626
4072
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
3627
4073
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
3628
4074
|
`;
|
|
3629
|
-
|
|
4075
|
+
fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
|
|
3630
4076
|
}
|
|
3631
4077
|
function renameIfExists(fromPath, toPath) {
|
|
3632
|
-
if (
|
|
3633
|
-
|
|
4078
|
+
if (fs28.existsSync(fromPath) && fromPath !== toPath) {
|
|
4079
|
+
fs28.renameSync(fromPath, toPath);
|
|
3634
4080
|
}
|
|
3635
4081
|
}
|
|
3636
4082
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
3637
|
-
const appJsonPath =
|
|
3638
|
-
if (
|
|
4083
|
+
const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
|
|
4084
|
+
if (fs28.existsSync(appJsonPath)) {
|
|
3639
4085
|
const appJson = readJson5File(appJsonPath);
|
|
3640
4086
|
appJson.app = appJson.app ?? {};
|
|
3641
4087
|
appJson.app.bundleName = args.bundleName;
|
|
3642
4088
|
writeJson5File(appJsonPath, appJson);
|
|
3643
4089
|
}
|
|
3644
|
-
const stringsPath =
|
|
3645
|
-
if (
|
|
4090
|
+
const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
4091
|
+
if (fs28.existsSync(stringsPath)) {
|
|
3646
4092
|
const strings = readJsonFile(stringsPath);
|
|
3647
4093
|
if (Array.isArray(strings.string)) {
|
|
3648
4094
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -3650,8 +4096,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3650
4096
|
}
|
|
3651
4097
|
writeJsonFile(stringsPath, strings);
|
|
3652
4098
|
}
|
|
3653
|
-
const buildProfilePath =
|
|
3654
|
-
if (
|
|
4099
|
+
const buildProfilePath = path27.join(projectDir, "build-profile.json5");
|
|
4100
|
+
if (fs28.existsSync(buildProfilePath)) {
|
|
3655
4101
|
const buildProfile = readJson5File(buildProfilePath);
|
|
3656
4102
|
buildProfile.app = buildProfile.app ?? {};
|
|
3657
4103
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -3664,23 +4110,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3664
4110
|
}
|
|
3665
4111
|
writeJson5File(buildProfilePath, buildProfile);
|
|
3666
4112
|
}
|
|
3667
|
-
const moduleJsonPath =
|
|
3668
|
-
if (
|
|
4113
|
+
const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
4114
|
+
if (fs28.existsSync(moduleJsonPath)) {
|
|
3669
4115
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
3670
4116
|
moduleJson.module = moduleJson.module ?? {};
|
|
3671
4117
|
moduleJson.module.name = args.moduleName;
|
|
3672
4118
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
3673
4119
|
}
|
|
3674
|
-
const moduleOhPackagePath =
|
|
3675
|
-
if (
|
|
4120
|
+
const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
|
|
4121
|
+
if (fs28.existsSync(moduleOhPackagePath)) {
|
|
3676
4122
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
3677
4123
|
pkg.name = args.moduleName;
|
|
3678
4124
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
3679
4125
|
}
|
|
3680
|
-
const vscodeDir =
|
|
3681
|
-
|
|
4126
|
+
const vscodeDir = path27.join(projectDir, ".vscode");
|
|
4127
|
+
fs28.mkdirSync(vscodeDir, { recursive: true });
|
|
3682
4128
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
3683
|
-
writeJsonFile(
|
|
4129
|
+
writeJsonFile(path27.join(vscodeDir, "settings.json"), {
|
|
3684
4130
|
"oniro.hapPath": hapPath,
|
|
3685
4131
|
"files.associations": { "*.json5": "jsonc" }
|
|
3686
4132
|
});
|
|
@@ -3697,7 +4143,7 @@ async function createScaffold(opts) {
|
|
|
3697
4143
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
3698
4144
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
3699
4145
|
}
|
|
3700
|
-
const templateDir =
|
|
4146
|
+
const templateDir = path27.join(opts.templateRoot, opts.templateId);
|
|
3701
4147
|
if (!await pathExists(templateDir)) {
|
|
3702
4148
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
3703
4149
|
}
|
|
@@ -3732,17 +4178,17 @@ async function createScaffold(opts) {
|
|
|
3732
4178
|
}
|
|
3733
4179
|
}
|
|
3734
4180
|
}
|
|
3735
|
-
const projectDir =
|
|
4181
|
+
const projectDir = path27.join(opts.location, opts.projectName);
|
|
3736
4182
|
if (await pathExists(projectDir)) {
|
|
3737
4183
|
if (!opts.overwrite) {
|
|
3738
4184
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
3739
4185
|
}
|
|
3740
|
-
await
|
|
4186
|
+
await fs28.promises.rm(projectDir, { recursive: true, force: true });
|
|
3741
4187
|
}
|
|
3742
4188
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
3743
4189
|
await copyDirRecursive(templateDir, projectDir);
|
|
3744
4190
|
if (moduleName !== defaultModuleName) {
|
|
3745
|
-
renameIfExists(
|
|
4191
|
+
renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
|
|
3746
4192
|
}
|
|
3747
4193
|
updateTemplateConfigs(
|
|
3748
4194
|
projectDir,
|
|
@@ -3759,11 +4205,11 @@ async function createScaffold(opts) {
|
|
|
3759
4205
|
}
|
|
3760
4206
|
|
|
3761
4207
|
// src/project/listModules.ts
|
|
3762
|
-
import * as
|
|
3763
|
-
import * as
|
|
4208
|
+
import * as fs29 from "fs";
|
|
4209
|
+
import * as path28 from "path";
|
|
3764
4210
|
function listModules(opts) {
|
|
3765
|
-
const buildProfile =
|
|
3766
|
-
if (!
|
|
4211
|
+
const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
|
|
4212
|
+
if (!fs29.existsSync(buildProfile)) {
|
|
3767
4213
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
3768
4214
|
}
|
|
3769
4215
|
const parsed = readJson5File(buildProfile);
|
|
@@ -3775,12 +4221,12 @@ function listModules(opts) {
|
|
|
3775
4221
|
}
|
|
3776
4222
|
|
|
3777
4223
|
// src/project/abilities.ts
|
|
3778
|
-
import * as
|
|
3779
|
-
import * as
|
|
4224
|
+
import * as fs30 from "fs";
|
|
4225
|
+
import * as path29 from "path";
|
|
3780
4226
|
function listAbilities(opts) {
|
|
3781
4227
|
const moduleName = opts.moduleName ?? "entry";
|
|
3782
|
-
const moduleJsonPath =
|
|
3783
|
-
if (!
|
|
4228
|
+
const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
4229
|
+
if (!fs30.existsSync(moduleJsonPath)) {
|
|
3784
4230
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
3785
4231
|
}
|
|
3786
4232
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -3788,12 +4234,12 @@ function listAbilities(opts) {
|
|
|
3788
4234
|
}
|
|
3789
4235
|
|
|
3790
4236
|
// src/project/prepareSigning.ts
|
|
3791
|
-
import * as
|
|
3792
|
-
import * as
|
|
4237
|
+
import * as fs31 from "fs";
|
|
4238
|
+
import * as path30 from "path";
|
|
3793
4239
|
function hasSigningArtifacts(dir) {
|
|
3794
|
-
if (!
|
|
3795
|
-
const buildProfile =
|
|
3796
|
-
if (!
|
|
4240
|
+
if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
|
|
4241
|
+
const buildProfile = path30.join(dir, "build-profile.json5");
|
|
4242
|
+
if (!fs31.existsSync(buildProfile)) return false;
|
|
3797
4243
|
try {
|
|
3798
4244
|
const parsed = readJson5File(buildProfile);
|
|
3799
4245
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -3811,6 +4257,7 @@ function prepareSigning(opts) {
|
|
|
3811
4257
|
apl: opts.apl,
|
|
3812
4258
|
appFeature: opts.appFeature,
|
|
3813
4259
|
acls: opts.acls,
|
|
4260
|
+
appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
|
|
3814
4261
|
passwords: opts.passwords,
|
|
3815
4262
|
logger: opts.logger
|
|
3816
4263
|
});
|
|
@@ -3839,19 +4286,31 @@ var PROPS = [
|
|
|
3839
4286
|
["model", "const.product.model"],
|
|
3840
4287
|
["manufacturer", "const.product.manufacturer"],
|
|
3841
4288
|
["brand", "const.product.brand"],
|
|
4289
|
+
["osDistName", "const.product.os.dist.name"],
|
|
4290
|
+
["osDistVersion", "const.product.os.dist.version"],
|
|
3842
4291
|
["osFullName", "const.ohos.fullname"],
|
|
3843
4292
|
["osReleaseType", "const.ohos.releasetype"],
|
|
3844
4293
|
["sdkApiVersion", "const.ohos.apiversion"],
|
|
3845
|
-
["buildVersion", "const.product.software.version"]
|
|
4294
|
+
["buildVersion", "const.product.software.version"],
|
|
4295
|
+
["abi", "const.product.cpu.abilist"],
|
|
4296
|
+
["deviceType", "const.product.devicetype"]
|
|
3846
4297
|
];
|
|
3847
4298
|
async function getDeviceInfo(opts) {
|
|
3848
4299
|
const info = { serial: opts.deviceSerial ?? "(default)", display: null };
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4300
|
+
try {
|
|
4301
|
+
const values = await paramGetMany(
|
|
4302
|
+
opts.config,
|
|
4303
|
+
PROPS.map(([, prop]) => prop),
|
|
4304
|
+
{ deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
|
|
4305
|
+
);
|
|
4306
|
+
for (const [field, prop] of PROPS) {
|
|
4307
|
+
const value = values[prop];
|
|
3852
4308
|
if (value) info[field] = value;
|
|
3853
|
-
} catch {
|
|
3854
4309
|
}
|
|
4310
|
+
} catch {
|
|
4311
|
+
}
|
|
4312
|
+
if (info.osDistName || info.osFullName) {
|
|
4313
|
+
info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
|
|
3855
4314
|
}
|
|
3856
4315
|
const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3857
4316
|
if (screen) info.display = { width: screen.width, height: screen.height };
|
|
@@ -3883,8 +4342,8 @@ async function diffHapAssets(opts) {
|
|
|
3883
4342
|
}
|
|
3884
4343
|
|
|
3885
4344
|
// src/install/apply.ts
|
|
3886
|
-
import * as
|
|
3887
|
-
import * as
|
|
4345
|
+
import * as fs32 from "fs";
|
|
4346
|
+
import * as path31 from "path";
|
|
3888
4347
|
function decideInstallMethod(s) {
|
|
3889
4348
|
if (!s.signInfoInconsistent) return "replace";
|
|
3890
4349
|
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
@@ -3902,39 +4361,31 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
3902
4361
|
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
3903
4362
|
return proc ? Number(proc.pid) : null;
|
|
3904
4363
|
}
|
|
3905
|
-
async function
|
|
3906
|
-
let
|
|
3907
|
-
if (hapPath) {
|
|
3908
|
-
|
|
4364
|
+
async function resolveHap(opts, logger) {
|
|
4365
|
+
let hap;
|
|
4366
|
+
if (opts.hapPath) {
|
|
4367
|
+
const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
|
|
4368
|
+
hap = existingHapOrUnsigned(hapPath);
|
|
4369
|
+
if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3909
4370
|
} else {
|
|
3910
4371
|
if (!opts.projectDir) throw new OniroError("applyChanges requires hapPath or projectDir.");
|
|
3911
|
-
const
|
|
3912
|
-
if (opts.module
|
|
3913
|
-
|
|
3914
|
-
if (signed.length === 0) throw new OniroError(`No signed HAP for module ${opts.module}. Build first.`);
|
|
3915
|
-
hapPath = signed[0];
|
|
3916
|
-
} else {
|
|
3917
|
-
const withSigned = Object.entries(haps).filter(([, m]) => m.signed.length > 0);
|
|
3918
|
-
if (withSigned.length === 0) throw new OniroError("No signed HAP found. Build first.");
|
|
3919
|
-
if (withSigned.length > 1) {
|
|
3920
|
-
throw new OniroError(
|
|
3921
|
-
`Multiple modules have signed HAPs (${withSigned.map(([k]) => k).join(", ")}). Pass module to choose.`
|
|
3922
|
-
);
|
|
3923
|
-
}
|
|
3924
|
-
hapPath = withSigned[0][1].signed[0];
|
|
3925
|
-
}
|
|
4372
|
+
const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
|
|
4373
|
+
if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
|
|
4374
|
+
hap = { path: picked.path, fallback: !picked.signed };
|
|
3926
4375
|
}
|
|
3927
|
-
if (
|
|
3928
|
-
return
|
|
4376
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
|
|
4377
|
+
return hap;
|
|
3929
4378
|
}
|
|
3930
4379
|
async function applyChanges(opts) {
|
|
3931
4380
|
const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
|
|
3932
4381
|
const { config, bundle, deviceSerial } = opts;
|
|
3933
4382
|
const isSystemBundle = opts.isSystemBundle ?? false;
|
|
3934
4383
|
const allowUninstall = opts.allowUninstall ?? false;
|
|
3935
|
-
const
|
|
4384
|
+
const hap = await resolveHap(opts, logger);
|
|
4385
|
+
const hapPath = hap.path;
|
|
4386
|
+
logger.info(`installing ${hapPath}`);
|
|
3936
4387
|
let assetsChanged = false;
|
|
3937
|
-
if (opts.installedHapPath &&
|
|
4388
|
+
if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
|
|
3938
4389
|
try {
|
|
3939
4390
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3940
4391
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -3955,12 +4406,21 @@ async function applyChanges(opts) {
|
|
|
3955
4406
|
if (decision === "uninstall-install") {
|
|
3956
4407
|
logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
|
|
3957
4408
|
await uninstallApp({ config, bundle, deviceSerial });
|
|
3958
|
-
|
|
4409
|
+
ensureInstallOk(
|
|
4410
|
+
await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
|
|
4411
|
+
`hdc install ${hapPath}`,
|
|
4412
|
+
hap
|
|
4413
|
+
);
|
|
3959
4414
|
installMethod = "uninstall-install";
|
|
3960
4415
|
} else {
|
|
3961
|
-
|
|
4416
|
+
ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
|
|
3962
4417
|
installMethod = "replace";
|
|
3963
4418
|
}
|
|
4419
|
+
if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
|
|
4420
|
+
throw new OniroError(
|
|
4421
|
+
`hdc reported ${path31.basename(hapPath)} as installed, but bm dump does not list ${bundle}. Check that the HAP was built from this project (its bundleName must match).`
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
3964
4424
|
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3965
4425
|
const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
|
|
3966
4426
|
let method = installMethod;
|
|
@@ -4035,9 +4495,9 @@ var AUTH_PATHS = {
|
|
|
4035
4495
|
};
|
|
4036
4496
|
|
|
4037
4497
|
// src/harmonyos/auth/tokenStore.ts
|
|
4038
|
-
import * as
|
|
4498
|
+
import * as fs33 from "fs";
|
|
4039
4499
|
import * as os11 from "os";
|
|
4040
|
-
import * as
|
|
4500
|
+
import * as path32 from "path";
|
|
4041
4501
|
import * as crypto3 from "crypto";
|
|
4042
4502
|
var ALGORITHM = "aes-256-gcm";
|
|
4043
4503
|
var KEY_LENGTH = 32;
|
|
@@ -4073,7 +4533,7 @@ function permissionHint(dir) {
|
|
|
4073
4533
|
}
|
|
4074
4534
|
function ensureDir(dir) {
|
|
4075
4535
|
try {
|
|
4076
|
-
|
|
4536
|
+
fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
4077
4537
|
} catch (err) {
|
|
4078
4538
|
if (err.code === "EACCES") {
|
|
4079
4539
|
throw new OniroError(permissionHint(dir), err);
|
|
@@ -4084,17 +4544,17 @@ function ensureDir(dir) {
|
|
|
4084
4544
|
function createTokenStore(opts) {
|
|
4085
4545
|
const logger = opts.logger ?? noopLogger;
|
|
4086
4546
|
const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
|
|
4087
|
-
const tokenPath =
|
|
4088
|
-
const keyPath =
|
|
4547
|
+
const tokenPath = path32.join(authDir, TOKEN_FILE);
|
|
4548
|
+
const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
|
|
4089
4549
|
function loadOrCreateKey() {
|
|
4090
|
-
if (
|
|
4091
|
-
const existing =
|
|
4550
|
+
if (fs33.existsSync(keyPath)) {
|
|
4551
|
+
const existing = fs33.readFileSync(keyPath);
|
|
4092
4552
|
if (existing.length === KEY_LENGTH) return existing;
|
|
4093
4553
|
logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
|
|
4094
4554
|
}
|
|
4095
|
-
ensureDir(
|
|
4555
|
+
ensureDir(path32.dirname(keyPath));
|
|
4096
4556
|
const key = crypto3.randomBytes(KEY_LENGTH);
|
|
4097
|
-
|
|
4557
|
+
fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
|
|
4098
4558
|
return key;
|
|
4099
4559
|
}
|
|
4100
4560
|
return {
|
|
@@ -4104,12 +4564,12 @@ function createTokenStore(opts) {
|
|
|
4104
4564
|
const payload = Buffer.from(JSON.stringify(session), "utf8");
|
|
4105
4565
|
const blob = encrypt2(loadOrCreateKey(), payload);
|
|
4106
4566
|
ensureDir(authDir);
|
|
4107
|
-
|
|
4567
|
+
fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
|
|
4108
4568
|
},
|
|
4109
4569
|
load() {
|
|
4110
|
-
if (!
|
|
4570
|
+
if (!fs33.existsSync(tokenPath)) return null;
|
|
4111
4571
|
try {
|
|
4112
|
-
const blob = JSON.parse(
|
|
4572
|
+
const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
|
|
4113
4573
|
if (!isEncryptedBlob(blob)) return null;
|
|
4114
4574
|
const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
|
|
4115
4575
|
return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
|
|
@@ -4121,7 +4581,7 @@ function createTokenStore(opts) {
|
|
|
4121
4581
|
}
|
|
4122
4582
|
logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
|
|
4123
4583
|
try {
|
|
4124
|
-
|
|
4584
|
+
fs33.rmSync(tokenPath, { force: true });
|
|
4125
4585
|
} catch {
|
|
4126
4586
|
}
|
|
4127
4587
|
return null;
|
|
@@ -4129,7 +4589,7 @@ function createTokenStore(opts) {
|
|
|
4129
4589
|
},
|
|
4130
4590
|
clear() {
|
|
4131
4591
|
try {
|
|
4132
|
-
|
|
4592
|
+
fs33.rmSync(tokenPath, { force: true });
|
|
4133
4593
|
} catch (err) {
|
|
4134
4594
|
throw new OniroError(`Failed to remove ${tokenPath}.`, err);
|
|
4135
4595
|
}
|
|
@@ -4243,8 +4703,8 @@ function isQuit(quit) {
|
|
|
4243
4703
|
async function startCallbackServer(opts) {
|
|
4244
4704
|
let resolveCallback;
|
|
4245
4705
|
let rejectCallback;
|
|
4246
|
-
const callback = new Promise((
|
|
4247
|
-
resolveCallback =
|
|
4706
|
+
const callback = new Promise((resolve7, reject) => {
|
|
4707
|
+
resolveCallback = resolve7;
|
|
4248
4708
|
rejectCallback = reject;
|
|
4249
4709
|
});
|
|
4250
4710
|
callback.catch(() => {
|
|
@@ -4303,9 +4763,9 @@ async function startCallbackServer(opts) {
|
|
|
4303
4763
|
}
|
|
4304
4764
|
});
|
|
4305
4765
|
server.keepAliveTimeout = 1;
|
|
4306
|
-
await new Promise((
|
|
4766
|
+
await new Promise((resolve7, reject) => {
|
|
4307
4767
|
server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
|
|
4308
|
-
server.listen(0, "127.0.0.1",
|
|
4768
|
+
server.listen(0, "127.0.0.1", resolve7);
|
|
4309
4769
|
});
|
|
4310
4770
|
const port = server.address().port;
|
|
4311
4771
|
return {
|
|
@@ -4321,12 +4781,12 @@ async function startCallbackServer(opts) {
|
|
|
4321
4781
|
return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
|
|
4322
4782
|
},
|
|
4323
4783
|
stop() {
|
|
4324
|
-
return new Promise((
|
|
4784
|
+
return new Promise((resolve7) => {
|
|
4325
4785
|
server.closeAllConnections?.();
|
|
4326
|
-
const timer = setTimeout(
|
|
4786
|
+
const timer = setTimeout(resolve7, 100);
|
|
4327
4787
|
server.close(() => {
|
|
4328
4788
|
clearTimeout(timer);
|
|
4329
|
-
|
|
4789
|
+
resolve7();
|
|
4330
4790
|
});
|
|
4331
4791
|
});
|
|
4332
4792
|
}
|
|
@@ -4358,11 +4818,11 @@ function openBrowser(url) {
|
|
|
4358
4818
|
windowsHide: true
|
|
4359
4819
|
});
|
|
4360
4820
|
child.unref();
|
|
4361
|
-
return new Promise((
|
|
4821
|
+
return new Promise((resolve7, reject) => {
|
|
4362
4822
|
child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
|
|
4363
4823
|
child.on(
|
|
4364
4824
|
"exit",
|
|
4365
|
-
(code) => code === 0 ?
|
|
4825
|
+
(code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
|
|
4366
4826
|
);
|
|
4367
4827
|
});
|
|
4368
4828
|
}
|
|
@@ -4610,7 +5070,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
|
|
|
4610
5070
|
}
|
|
4611
5071
|
|
|
4612
5072
|
// src/harmonyos/signing/profile.ts
|
|
4613
|
-
import * as
|
|
5073
|
+
import * as fs34 from "fs";
|
|
4614
5074
|
import { X509Certificate } from "crypto";
|
|
4615
5075
|
import forge from "node-forge";
|
|
4616
5076
|
function extractProfileJson(buffer) {
|
|
@@ -4642,7 +5102,7 @@ function extractProfileJson(buffer) {
|
|
|
4642
5102
|
function readProvisionProfile(profilePath) {
|
|
4643
5103
|
let raw;
|
|
4644
5104
|
try {
|
|
4645
|
-
const json = extractProfileJson(
|
|
5105
|
+
const json = extractProfileJson(fs34.readFileSync(profilePath));
|
|
4646
5106
|
if (!json) return null;
|
|
4647
5107
|
raw = JSON.parse(json);
|
|
4648
5108
|
} catch {
|
|
@@ -4671,7 +5131,7 @@ function parseCertificate(pem) {
|
|
|
4671
5131
|
}
|
|
4672
5132
|
function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
4673
5133
|
try {
|
|
4674
|
-
const der = forge.util.createBuffer(
|
|
5134
|
+
const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
|
|
4675
5135
|
const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
|
|
4676
5136
|
const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
|
|
4677
5137
|
const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
|
|
@@ -4687,7 +5147,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
|
4687
5147
|
}
|
|
4688
5148
|
}
|
|
4689
5149
|
function verifySigningMaterial(opts) {
|
|
4690
|
-
const pems =
|
|
5150
|
+
const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
4691
5151
|
const chain = (pems ?? []).map(parseCertificate);
|
|
4692
5152
|
const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
|
|
4693
5153
|
if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
|
|
@@ -4706,14 +5166,14 @@ function verifySigningMaterial(opts) {
|
|
|
4706
5166
|
|
|
4707
5167
|
// src/harmonyos/signing/autoSign.ts
|
|
4708
5168
|
import * as crypto9 from "crypto";
|
|
4709
|
-
import * as
|
|
4710
|
-
import * as
|
|
5169
|
+
import * as fs39 from "fs";
|
|
5170
|
+
import * as path38 from "path";
|
|
4711
5171
|
import JSON57 from "json5";
|
|
4712
5172
|
|
|
4713
5173
|
// src/harmonyos/signing/agc.ts
|
|
4714
5174
|
import * as crypto6 from "crypto";
|
|
4715
|
-
import * as
|
|
4716
|
-
import * as
|
|
5175
|
+
import * as fs35 from "fs";
|
|
5176
|
+
import * as path33 from "path";
|
|
4717
5177
|
var ENDPOINTS = {
|
|
4718
5178
|
CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
|
|
4719
5179
|
CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
|
|
@@ -4799,8 +5259,8 @@ function createAgcClient(http3, auth) {
|
|
|
4799
5259
|
if (target.sha256 && actual !== target.sha256.toLowerCase()) {
|
|
4800
5260
|
throw new ChecksumMismatchError(target.sha256, actual);
|
|
4801
5261
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
5262
|
+
fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
|
|
5263
|
+
fs35.writeFileSync(dest, buffer, { mode: 384 });
|
|
4804
5264
|
}
|
|
4805
5265
|
};
|
|
4806
5266
|
}
|
|
@@ -4851,22 +5311,22 @@ async function registerDevices(agc, connected, logger) {
|
|
|
4851
5311
|
|
|
4852
5312
|
// src/harmonyos/signing/keystore.ts
|
|
4853
5313
|
import * as crypto7 from "crypto";
|
|
4854
|
-
import * as
|
|
4855
|
-
import * as
|
|
5314
|
+
import * as fs37 from "fs";
|
|
5315
|
+
import * as path35 from "path";
|
|
4856
5316
|
|
|
4857
5317
|
// src/harmonyos/java.ts
|
|
4858
|
-
import * as
|
|
4859
|
-
import * as
|
|
5318
|
+
import * as fs36 from "fs";
|
|
5319
|
+
import * as path34 from "path";
|
|
4860
5320
|
var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
|
|
4861
5321
|
function findJava(sdk, env = process.env) {
|
|
4862
5322
|
const candidates = [
|
|
4863
|
-
env.JAVA_HOME &&
|
|
4864
|
-
sdk &&
|
|
5323
|
+
env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
|
|
5324
|
+
sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
|
|
4865
5325
|
// macOS: the JBR is itself a bundle inside Studio's Contents.
|
|
4866
|
-
sdk &&
|
|
4867
|
-
...(env.PATH ?? "").split(
|
|
5326
|
+
sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
|
|
5327
|
+
...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
|
|
4868
5328
|
];
|
|
4869
|
-
const found = candidates.find((c) => !!c &&
|
|
5329
|
+
const found = candidates.find((c) => !!c && fs36.existsSync(c));
|
|
4870
5330
|
if (found) return found;
|
|
4871
5331
|
throw new OniroError(
|
|
4872
5332
|
"No Java runtime found. HarmonyOS signing runs the SDK's hap-sign-tool.jar, which needs one: install a JDK and set JAVA_HOME, or install DevEco Studio, whose bundled runtime is used automatically."
|
|
@@ -4880,13 +5340,13 @@ function redactArgs(args) {
|
|
|
4880
5340
|
return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
|
|
4881
5341
|
}
|
|
4882
5342
|
async function generateKeystoreAndCsr(opts) {
|
|
4883
|
-
const jar =
|
|
4884
|
-
if (!
|
|
5343
|
+
const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
|
|
5344
|
+
if (!fs37.existsSync(jar)) {
|
|
4885
5345
|
throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
|
|
4886
5346
|
}
|
|
4887
5347
|
const java = findJava(opts.sdk);
|
|
4888
5348
|
const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
|
|
4889
|
-
|
|
5349
|
+
fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
|
|
4890
5350
|
const run = async (what, args) => {
|
|
4891
5351
|
const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
|
|
4892
5352
|
opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
|
|
@@ -4900,28 +5360,28 @@ ${detail}` : ""}`);
|
|
|
4900
5360
|
opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
|
|
4901
5361
|
await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
|
|
4902
5362
|
await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
|
|
4903
|
-
return { keyPwd, csr:
|
|
5363
|
+
return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
|
|
4904
5364
|
}
|
|
4905
5365
|
|
|
4906
5366
|
// src/harmonyos/signing/paths.ts
|
|
4907
5367
|
import * as crypto8 from "crypto";
|
|
4908
|
-
import * as
|
|
5368
|
+
import * as path36 from "path";
|
|
4909
5369
|
function sanitizeNamePart(raw) {
|
|
4910
5370
|
const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
|
|
4911
5371
|
return safe.slice(0, 64) || "default";
|
|
4912
5372
|
}
|
|
4913
5373
|
function resolveSigningMaterialPaths(config, productName, projectRoot) {
|
|
4914
5374
|
const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
|
|
4915
|
-
const base =
|
|
5375
|
+
const base = path36.join(
|
|
4916
5376
|
config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
|
|
4917
|
-
`${sanitizeNamePart(productName)}_${
|
|
5377
|
+
`${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
|
|
4918
5378
|
);
|
|
4919
5379
|
return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
|
|
4920
5380
|
}
|
|
4921
5381
|
|
|
4922
5382
|
// src/harmonyos/signing/project.ts
|
|
4923
|
-
import * as
|
|
4924
|
-
import * as
|
|
5383
|
+
import * as fs38 from "fs";
|
|
5384
|
+
import * as path37 from "path";
|
|
4925
5385
|
import JSON56 from "json5";
|
|
4926
5386
|
|
|
4927
5387
|
// src/harmonyos/signing/aclPermissionList.ts
|
|
@@ -4989,9 +5449,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
|
4989
5449
|
|
|
4990
5450
|
// src/harmonyos/signing/project.ts
|
|
4991
5451
|
function readRequestedPermissions(moduleConfigPath) {
|
|
4992
|
-
if (!
|
|
5452
|
+
if (!fs38.existsSync(moduleConfigPath)) return [];
|
|
4993
5453
|
try {
|
|
4994
|
-
const parsed = JSON56.parse(
|
|
5454
|
+
const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
|
|
4995
5455
|
return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
4996
5456
|
} catch {
|
|
4997
5457
|
return [];
|
|
@@ -5000,10 +5460,10 @@ function readRequestedPermissions(moduleConfigPath) {
|
|
|
5000
5460
|
function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
5001
5461
|
const requested = /* @__PURE__ */ new Set();
|
|
5002
5462
|
for (const module of listModules({ projectDir })) {
|
|
5003
|
-
const moduleRoot =
|
|
5463
|
+
const moduleRoot = path37.resolve(projectDir, module.srcPath);
|
|
5004
5464
|
for (const sourceSet of ["main", "ohosTest"]) {
|
|
5005
5465
|
for (const name of readRequestedPermissions(
|
|
5006
|
-
|
|
5466
|
+
path37.join(moduleRoot, "src", sourceSet, "module.json5")
|
|
5007
5467
|
)) {
|
|
5008
5468
|
requested.add(name);
|
|
5009
5469
|
}
|
|
@@ -5019,7 +5479,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
|
5019
5479
|
// src/harmonyos/signing/autoSign.ts
|
|
5020
5480
|
function shouldRegenerate(input) {
|
|
5021
5481
|
if (input.force) return "--force was passed";
|
|
5022
|
-
const missing = Object.values(input.paths).find((file) => !
|
|
5482
|
+
const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
|
|
5023
5483
|
if (missing) return `${missing} is missing`;
|
|
5024
5484
|
const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
|
|
5025
5485
|
if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
|
|
@@ -5058,16 +5518,16 @@ function writeSigningConfig(opts) {
|
|
|
5058
5518
|
for (const product of app.products ?? []) {
|
|
5059
5519
|
if (product.name === opts.productName) product.signingConfig = opts.productName;
|
|
5060
5520
|
}
|
|
5061
|
-
|
|
5521
|
+
fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
|
|
5062
5522
|
}
|
|
5063
5523
|
async function harmonyOsAutoSign(opts) {
|
|
5064
5524
|
const logger = opts.logger ?? noopLogger;
|
|
5065
5525
|
const productName = opts.productName ?? "default";
|
|
5066
|
-
const projectDir =
|
|
5067
|
-
const buildProfilePath =
|
|
5526
|
+
const projectDir = path38.resolve(opts.projectDir);
|
|
5527
|
+
const buildProfilePath = path38.join(projectDir, "build-profile.json5");
|
|
5068
5528
|
let buildProfile;
|
|
5069
5529
|
try {
|
|
5070
|
-
buildProfile = JSON57.parse(
|
|
5530
|
+
buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
|
|
5071
5531
|
} catch (err) {
|
|
5072
5532
|
throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
|
|
5073
5533
|
}
|
|
@@ -5107,7 +5567,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5107
5567
|
logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
|
|
5108
5568
|
const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
|
|
5109
5569
|
const deviceIds = await registerDevices(agc, connected, logger);
|
|
5110
|
-
for (const file of Object.values(paths))
|
|
5570
|
+
for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
|
|
5111
5571
|
const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
|
|
5112
5572
|
const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
|
|
5113
5573
|
if (stale) {
|
|
@@ -5138,10 +5598,10 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5138
5598
|
try {
|
|
5139
5599
|
verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
|
|
5140
5600
|
} catch (err) {
|
|
5141
|
-
|
|
5601
|
+
fs39.rmSync(paths.profilePath, { force: true });
|
|
5142
5602
|
throw err;
|
|
5143
5603
|
}
|
|
5144
|
-
const materialDir =
|
|
5604
|
+
const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
|
|
5145
5605
|
try {
|
|
5146
5606
|
getKey(materialDir);
|
|
5147
5607
|
} catch {
|
|
@@ -5153,7 +5613,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5153
5613
|
}
|
|
5154
5614
|
|
|
5155
5615
|
// src/index.ts
|
|
5156
|
-
var VERSION = "0.
|
|
5616
|
+
var VERSION = "0.14.1";
|
|
5157
5617
|
export {
|
|
5158
5618
|
ALL_SDKS,
|
|
5159
5619
|
APL_VALUES,
|
|
@@ -5175,7 +5635,9 @@ export {
|
|
|
5175
5635
|
TargetNotFoundError,
|
|
5176
5636
|
UnsupportedPlatformError,
|
|
5177
5637
|
VERSION,
|
|
5638
|
+
appSandboxDir,
|
|
5178
5639
|
applyChanges,
|
|
5640
|
+
assertDevicePath,
|
|
5179
5641
|
assertLayoutSelector,
|
|
5180
5642
|
attemptHdcConnection,
|
|
5181
5643
|
buildCmdWrapper,
|
|
@@ -5186,6 +5648,8 @@ export {
|
|
|
5186
5648
|
buildInputCommand,
|
|
5187
5649
|
buildRawTouchCommand,
|
|
5188
5650
|
captureBurst,
|
|
5651
|
+
captureLog,
|
|
5652
|
+
cleanCodelinterOutput,
|
|
5189
5653
|
consoleLogger,
|
|
5190
5654
|
createHarmonyOsSession,
|
|
5191
5655
|
createInstallTempDir,
|
|
@@ -5201,9 +5665,12 @@ export {
|
|
|
5201
5665
|
defaultPaths,
|
|
5202
5666
|
describeLayoutNode,
|
|
5203
5667
|
detectAaStartFailure,
|
|
5668
|
+
detectCmdToolsFlavour,
|
|
5669
|
+
detectInstallFailure,
|
|
5204
5670
|
detectProjectSdkVersion,
|
|
5205
5671
|
detectRuntimeOs,
|
|
5206
5672
|
detectSigningConfigNames,
|
|
5673
|
+
diagnoseBuildFailure,
|
|
5207
5674
|
diffEntryNames,
|
|
5208
5675
|
diffHapAssets,
|
|
5209
5676
|
discoverHaps,
|
|
@@ -5216,11 +5683,13 @@ export {
|
|
|
5216
5683
|
dumpWindow,
|
|
5217
5684
|
encryptPwd,
|
|
5218
5685
|
ensureFreeSpace,
|
|
5686
|
+
ensureInstallOk,
|
|
5219
5687
|
ensureOhModules,
|
|
5220
5688
|
ensureOk,
|
|
5221
5689
|
ensureRemoteOk,
|
|
5222
5690
|
escapeCmdArgument,
|
|
5223
5691
|
escapeCmdCommand,
|
|
5692
|
+
existingHapOrUnsigned,
|
|
5224
5693
|
extractTarball,
|
|
5225
5694
|
extractZipWithProgress,
|
|
5226
5695
|
findAppProcessId,
|
|
@@ -5229,6 +5698,7 @@ export {
|
|
|
5229
5698
|
findHarmonyOsSdk,
|
|
5230
5699
|
findKeyboardWindow,
|
|
5231
5700
|
findLayoutNodes,
|
|
5701
|
+
findMissingProjectSdk,
|
|
5232
5702
|
findRunningProcess,
|
|
5233
5703
|
flattenLayout,
|
|
5234
5704
|
forceStop,
|
|
@@ -5242,6 +5712,7 @@ export {
|
|
|
5242
5712
|
getDeviceInfo,
|
|
5243
5713
|
getDisplaySize,
|
|
5244
5714
|
getEmulatorDir,
|
|
5715
|
+
getEmulatorStatus,
|
|
5245
5716
|
getFreeSpaceBytes,
|
|
5246
5717
|
getHdcPath,
|
|
5247
5718
|
getHvigorwPath,
|
|
@@ -5257,15 +5728,21 @@ export {
|
|
|
5257
5728
|
getSupportedSdksForUi,
|
|
5258
5729
|
harmonyOsAutoSign,
|
|
5259
5730
|
harmonyOsSdkLabelForApi,
|
|
5731
|
+
hdcClientFailure,
|
|
5260
5732
|
hdcExec,
|
|
5733
|
+
hdcTargetArgs,
|
|
5734
|
+
hdcTargetArgsFor,
|
|
5261
5735
|
hideKeyboard,
|
|
5262
5736
|
installApp,
|
|
5263
5737
|
installCmdTools,
|
|
5264
5738
|
installEmulator,
|
|
5739
|
+
installFailureReason,
|
|
5740
|
+
isBundleInstalled,
|
|
5265
5741
|
isCmdToolsInstalled,
|
|
5266
5742
|
isEmulatorInstalled,
|
|
5267
5743
|
isOutOfSpaceError,
|
|
5268
5744
|
isRamBackedPath,
|
|
5745
|
+
isUnsignedHap,
|
|
5269
5746
|
isValidBundleName,
|
|
5270
5747
|
isValidProjectName,
|
|
5271
5748
|
killProcessTree,
|
|
@@ -5280,16 +5757,21 @@ export {
|
|
|
5280
5757
|
nonInteractivePrompter,
|
|
5281
5758
|
noopLogger,
|
|
5282
5759
|
noopProgress,
|
|
5760
|
+
normaliseLocalPath,
|
|
5283
5761
|
paramGet,
|
|
5762
|
+
paramGetMany,
|
|
5284
5763
|
paramSet,
|
|
5285
5764
|
parseBounds,
|
|
5286
5765
|
parseCodelinterFindings,
|
|
5766
|
+
parseCodelinterReport,
|
|
5287
5767
|
parseDeviceList,
|
|
5288
5768
|
parseHilogLine,
|
|
5289
5769
|
parseImeBundles,
|
|
5290
5770
|
parseMainAbilityFromBmDump,
|
|
5771
|
+
parseParamValues,
|
|
5291
5772
|
parseRemoteExit,
|
|
5292
5773
|
performInput,
|
|
5774
|
+
pickHap,
|
|
5293
5775
|
pickSigningKind,
|
|
5294
5776
|
pickTarget,
|
|
5295
5777
|
prepareSigning,
|
|
@@ -5304,6 +5786,7 @@ export {
|
|
|
5304
5786
|
removeSdk,
|
|
5305
5787
|
removeTempWorkDir,
|
|
5306
5788
|
requireHarmonyOsSdk,
|
|
5789
|
+
resolveDeviceSerial,
|
|
5307
5790
|
resolveKeyEvent,
|
|
5308
5791
|
resolveTmpRoot,
|
|
5309
5792
|
runCodelinter,
|
|
@@ -5328,6 +5811,7 @@ export {
|
|
|
5328
5811
|
toLongPath,
|
|
5329
5812
|
toSpaceError,
|
|
5330
5813
|
uninstallApp,
|
|
5814
|
+
unsignedHapRejection,
|
|
5331
5815
|
validateTemplateLayout,
|
|
5332
5816
|
verifySha256,
|
|
5333
5817
|
waitForBoot,
|