@oniroproject/core 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/index.cjs +804 -306
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +275 -24
- package/dist/index.d.ts +275 -24
- package/dist/index.js +780 -306
- 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);
|
|
@@ -1570,7 +1612,7 @@ function runProcess(opts) {
|
|
|
1570
1612
|
const { command, args, cwd, env, onOutput } = opts;
|
|
1571
1613
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1572
1614
|
const { abortSignal } = opts;
|
|
1573
|
-
return new Promise((
|
|
1615
|
+
return new Promise((resolve7, reject) => {
|
|
1574
1616
|
if (abortSignal?.aborted) {
|
|
1575
1617
|
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1576
1618
|
return;
|
|
@@ -1635,21 +1677,41 @@ ${stderr.trim()}` : "")
|
|
|
1635
1677
|
);
|
|
1636
1678
|
return;
|
|
1637
1679
|
}
|
|
1638
|
-
|
|
1680
|
+
resolve7({ code: code ?? -1, stdout, stderr });
|
|
1639
1681
|
});
|
|
1640
1682
|
});
|
|
1641
1683
|
}
|
|
1642
|
-
function
|
|
1684
|
+
function resolveDeviceSerial(explicit, env = process.env) {
|
|
1685
|
+
if (explicit && explicit !== "auto") return explicit;
|
|
1686
|
+
const fromEnv = env.ONIRO_DEVICE_SERIAL || env.DEVICE_SERIAL;
|
|
1687
|
+
return fromEnv && fromEnv !== "auto" ? fromEnv : void 0;
|
|
1688
|
+
}
|
|
1689
|
+
function hdcTargetArgs(explicit) {
|
|
1690
|
+
const serial = resolveDeviceSerial(explicit);
|
|
1691
|
+
return serial ? ["-t", serial] : [];
|
|
1692
|
+
}
|
|
1693
|
+
var HOST_COMMANDS = /* @__PURE__ */ new Set(["list", "tconn", "start", "kill", "version", "checkserver"]);
|
|
1694
|
+
function hdcTargetArgsFor(args, explicit) {
|
|
1695
|
+
if (HOST_COMMANDS.has(args[0] ?? "")) return explicit ? ["-t", explicit] : [];
|
|
1696
|
+
return hdcTargetArgs(explicit);
|
|
1697
|
+
}
|
|
1698
|
+
function hdcClientFailure(output) {
|
|
1699
|
+
const first = output.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
|
|
1700
|
+
return first.startsWith("[Fail]") ? first : null;
|
|
1701
|
+
}
|
|
1702
|
+
async function hdcExec(opts) {
|
|
1643
1703
|
const hdc = getHdcPath(opts.config);
|
|
1644
|
-
const
|
|
1645
|
-
return runProcess({
|
|
1704
|
+
const res = await runProcess({
|
|
1646
1705
|
command: hdc,
|
|
1647
|
-
args:
|
|
1706
|
+
args: [...hdcTargetArgsFor(opts.args, opts.deviceSerial), ...opts.args],
|
|
1648
1707
|
timeoutMs: opts.timeoutMs,
|
|
1649
1708
|
abortSignal: opts.abortSignal,
|
|
1650
1709
|
onOutput: opts.onOutput,
|
|
1651
1710
|
logger: opts.logger
|
|
1652
1711
|
});
|
|
1712
|
+
if (res.code === 0 && hdcClientFailure(`${res.stdout}
|
|
1713
|
+
${res.stderr}`)) return { ...res, code: 1 };
|
|
1714
|
+
return res;
|
|
1653
1715
|
}
|
|
1654
1716
|
function shell(opts) {
|
|
1655
1717
|
const { command, ...rest } = opts;
|
|
@@ -1657,10 +1719,15 @@ function shell(opts) {
|
|
|
1657
1719
|
}
|
|
1658
1720
|
function ensureOk(result, command) {
|
|
1659
1721
|
if (result.code !== 0) {
|
|
1660
|
-
throw new CommandFailedError(command, result.code, result.stderr.trim());
|
|
1722
|
+
throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
|
|
1661
1723
|
}
|
|
1662
1724
|
return result;
|
|
1663
1725
|
}
|
|
1726
|
+
function withTargetHint(detail) {
|
|
1727
|
+
if (!/need connect-key|Not match target/i.test(detail)) return detail;
|
|
1728
|
+
return `${detail}
|
|
1729
|
+
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\`.`;
|
|
1730
|
+
}
|
|
1664
1731
|
var REMOTE_RC_RE = /__ONIRO_RC=(-?\d+)__/g;
|
|
1665
1732
|
function parseRemoteExit(stdout) {
|
|
1666
1733
|
let code = null;
|
|
@@ -1677,7 +1744,7 @@ async function shellChecked(opts) {
|
|
|
1677
1744
|
}
|
|
1678
1745
|
function ensureRemoteOk(result, command) {
|
|
1679
1746
|
if (result.code !== 0) {
|
|
1680
|
-
throw new CommandFailedError(command, result.code, result.stderr.trim() || result.stdout.trim());
|
|
1747
|
+
throw new CommandFailedError(command, result.code, withTargetHint(result.stderr.trim() || result.stdout.trim()));
|
|
1681
1748
|
}
|
|
1682
1749
|
return result;
|
|
1683
1750
|
}
|
|
@@ -1763,11 +1830,11 @@ async function startEmulator(opts) {
|
|
|
1763
1830
|
fs16.writeFileSync(PID_FILE, String(child.pid));
|
|
1764
1831
|
child.unref();
|
|
1765
1832
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1766
|
-
const earlyExit = new Promise((
|
|
1767
|
-
child.once("exit", (code) =>
|
|
1768
|
-
child.once("error", () =>
|
|
1833
|
+
const earlyExit = new Promise((resolve7) => {
|
|
1834
|
+
child.once("exit", (code) => resolve7(code));
|
|
1835
|
+
child.once("error", () => resolve7(-1));
|
|
1769
1836
|
});
|
|
1770
|
-
const liveness = new Promise((
|
|
1837
|
+
const liveness = new Promise((resolve7) => setTimeout(() => resolve7("alive"), 2e3));
|
|
1771
1838
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1772
1839
|
if (outcome !== "alive") {
|
|
1773
1840
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1847,6 +1914,43 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
1847
1914
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
1848
1915
|
});
|
|
1849
1916
|
}
|
|
1917
|
+
async function isQemuRunning(logger) {
|
|
1918
|
+
if (os8.platform() !== "win32") {
|
|
1919
|
+
try {
|
|
1920
|
+
const pid = Number(fs16.readFileSync(PID_FILE, "utf8").trim());
|
|
1921
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
1922
|
+
process.kill(pid, 0);
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1925
|
+
} catch {
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
const [cmd, ...args] = os8.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
|
|
1929
|
+
try {
|
|
1930
|
+
const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
|
|
1931
|
+
return os8.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
|
|
1932
|
+
} catch {
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
async function getEmulatorStatus(config, opts = {}) {
|
|
1937
|
+
const logger = opts.logger ?? noopLogger;
|
|
1938
|
+
const address = opts.address ?? "127.0.0.1:55555";
|
|
1939
|
+
const dir = getEmulatorDir(config);
|
|
1940
|
+
let connected = false;
|
|
1941
|
+
try {
|
|
1942
|
+
const targets = await hdcExec({ config, args: ["list", "targets", "-v"], timeoutMs: 1e4, logger });
|
|
1943
|
+
connected = targets.stdout.split("\n").some((l) => l.trim().startsWith(address) && /\bConnected\b/.test(l));
|
|
1944
|
+
} catch {
|
|
1945
|
+
}
|
|
1946
|
+
return {
|
|
1947
|
+
installed: isEmulatorInstalled(config),
|
|
1948
|
+
dir,
|
|
1949
|
+
running: await isQemuRunning(logger),
|
|
1950
|
+
address,
|
|
1951
|
+
connected
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1850
1954
|
|
|
1851
1955
|
// src/hdc/devices.ts
|
|
1852
1956
|
var STATUS_TOKENS = /* @__PURE__ */ new Set([
|
|
@@ -1885,9 +1989,8 @@ async function listDevices(config, opts = {}) {
|
|
|
1885
1989
|
return parseDeviceList(res.stdout);
|
|
1886
1990
|
}
|
|
1887
1991
|
async function selectDevice(config, serial, opts = {}) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
if (envSerial && envSerial !== "auto") return envSerial;
|
|
1992
|
+
const resolved = resolveDeviceSerial(serial);
|
|
1993
|
+
if (resolved) return resolved;
|
|
1891
1994
|
const usable = (await listDevices(config, opts)).filter(
|
|
1892
1995
|
(d) => d.status === "Connected" || d.status === "Unknown"
|
|
1893
1996
|
);
|
|
@@ -1901,6 +2004,7 @@ async function selectDevice(config, serial, opts = {}) {
|
|
|
1901
2004
|
}
|
|
1902
2005
|
|
|
1903
2006
|
// src/hdc/param.ts
|
|
2007
|
+
var PARAM_MISSING_RE = /^Get parameter ".*" fail!/;
|
|
1904
2008
|
async function paramGet(config, key, opts = {}) {
|
|
1905
2009
|
const res = await shell({
|
|
1906
2010
|
config,
|
|
@@ -1910,7 +2014,30 @@ async function paramGet(config, key, opts = {}) {
|
|
|
1910
2014
|
logger: opts.logger
|
|
1911
2015
|
});
|
|
1912
2016
|
ensureOk(res, `hdc shell param get ${key}`);
|
|
1913
|
-
|
|
2017
|
+
const value = res.stdout.trim();
|
|
2018
|
+
if (PARAM_MISSING_RE.test(value)) throw new OniroError(`System parameter ${key} is not set: ${value}`);
|
|
2019
|
+
return value;
|
|
2020
|
+
}
|
|
2021
|
+
var PARAM_SEPARATOR = "__ONIRO_PARAM__";
|
|
2022
|
+
function parseParamValues(keys, stdout) {
|
|
2023
|
+
const parts = stdout.split(PARAM_SEPARATOR);
|
|
2024
|
+
const values = {};
|
|
2025
|
+
keys.forEach((key, i) => {
|
|
2026
|
+
const value = parts[i]?.trim();
|
|
2027
|
+
values[key] = value && !PARAM_MISSING_RE.test(value) ? value : void 0;
|
|
2028
|
+
});
|
|
2029
|
+
return values;
|
|
2030
|
+
}
|
|
2031
|
+
async function paramGetMany(config, keys, opts = {}) {
|
|
2032
|
+
const res = await shell({
|
|
2033
|
+
config,
|
|
2034
|
+
command: keys.map((k) => `param get ${k}; echo ${PARAM_SEPARATOR}`).join("; "),
|
|
2035
|
+
deviceSerial: opts.deviceSerial,
|
|
2036
|
+
timeoutMs: opts.timeoutMs ?? 1e4,
|
|
2037
|
+
logger: opts.logger
|
|
2038
|
+
});
|
|
2039
|
+
ensureOk(res, "hdc shell param get \u2026");
|
|
2040
|
+
return parseParamValues(keys, res.stdout);
|
|
1914
2041
|
}
|
|
1915
2042
|
async function paramSet(config, key, value, opts = {}) {
|
|
1916
2043
|
const res = await shell({
|
|
@@ -1924,29 +2051,66 @@ async function paramSet(config, key, value, opts = {}) {
|
|
|
1924
2051
|
}
|
|
1925
2052
|
|
|
1926
2053
|
// src/hdc/files.ts
|
|
2054
|
+
import * as path17 from "path";
|
|
2055
|
+
function normaliseLocalPath(local) {
|
|
2056
|
+
const abs = path17.resolve(local);
|
|
2057
|
+
const long = toLongPath(abs);
|
|
2058
|
+
return long !== abs ? long : path17.join(toLongPath(path17.dirname(abs)), path17.basename(abs));
|
|
2059
|
+
}
|
|
2060
|
+
function assertDevicePath(remote) {
|
|
2061
|
+
if (/^[A-Za-z]:[\\/]/.test(remote)) {
|
|
2062
|
+
throw new OniroError(
|
|
2063
|
+
`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.`
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
function appSandboxDir(bundle) {
|
|
2068
|
+
return `/data/app/el2/100/base/${bundle}`;
|
|
2069
|
+
}
|
|
2070
|
+
function shQuote(s) {
|
|
2071
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
2072
|
+
}
|
|
1927
2073
|
async function sendFile(opts) {
|
|
2074
|
+
assertDevicePath(opts.remote);
|
|
2075
|
+
const local = normaliseLocalPath(opts.local);
|
|
2076
|
+
const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
|
|
2077
|
+
const remote = sandbox && !opts.remote.startsWith("/") ? path17.posix.join(sandbox, opts.remote) : opts.remote;
|
|
2078
|
+
if (sandbox && !remote.startsWith(`${sandbox}/`)) {
|
|
2079
|
+
throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
|
|
2080
|
+
}
|
|
2081
|
+
const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
2082
|
+
if (sandbox) {
|
|
2083
|
+
const mkdir = `mkdir -p ${shQuote(path17.posix.dirname(remote))}`;
|
|
2084
|
+
ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
|
|
2085
|
+
}
|
|
1928
2086
|
const res = await hdcExec({
|
|
1929
|
-
|
|
1930
|
-
args: ["file", "send",
|
|
1931
|
-
deviceSerial: opts.deviceSerial,
|
|
2087
|
+
...device,
|
|
2088
|
+
args: ["file", "send", local, remote],
|
|
1932
2089
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1933
2090
|
abortSignal: opts.abortSignal,
|
|
1934
|
-
onOutput: opts.onOutput
|
|
1935
|
-
logger: opts.logger
|
|
2091
|
+
onOutput: opts.onOutput
|
|
1936
2092
|
});
|
|
1937
|
-
ensureOk(res, `hdc file send ${
|
|
2093
|
+
ensureOk(res, `hdc file send ${local} ${remote}`);
|
|
2094
|
+
if (sandbox) {
|
|
2095
|
+
const top = path17.posix.join(sandbox, path17.posix.relative(sandbox, remote).split("/")[0]);
|
|
2096
|
+
const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
|
|
2097
|
+
ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
|
|
2098
|
+
}
|
|
2099
|
+
return remote;
|
|
1938
2100
|
}
|
|
1939
2101
|
async function recvFile(opts) {
|
|
2102
|
+
assertDevicePath(opts.remote);
|
|
2103
|
+
const local = normaliseLocalPath(opts.local);
|
|
1940
2104
|
const res = await hdcExec({
|
|
1941
2105
|
config: opts.config,
|
|
1942
|
-
args: ["file", "recv", opts.remote,
|
|
2106
|
+
args: ["file", "recv", opts.remote, local],
|
|
1943
2107
|
deviceSerial: opts.deviceSerial,
|
|
1944
2108
|
timeoutMs: opts.timeoutMs ?? 6e5,
|
|
1945
2109
|
abortSignal: opts.abortSignal,
|
|
1946
2110
|
onOutput: opts.onOutput,
|
|
1947
2111
|
logger: opts.logger
|
|
1948
2112
|
});
|
|
1949
|
-
ensureOk(res, `hdc file recv ${opts.remote} ${
|
|
2113
|
+
ensureOk(res, `hdc file recv ${opts.remote} ${local}`);
|
|
1950
2114
|
}
|
|
1951
2115
|
|
|
1952
2116
|
// src/hdc/display.ts
|
|
@@ -1966,7 +2130,7 @@ async function getDisplaySize(config, opts = {}) {
|
|
|
1966
2130
|
|
|
1967
2131
|
// src/hdc/wait.ts
|
|
1968
2132
|
function delay(ms, signal) {
|
|
1969
|
-
return new Promise((
|
|
2133
|
+
return new Promise((resolve7, reject) => {
|
|
1970
2134
|
if (signal?.aborted) {
|
|
1971
2135
|
reject(new CancelledError("Wait cancelled."));
|
|
1972
2136
|
return;
|
|
@@ -1981,7 +2145,7 @@ function delay(ms, signal) {
|
|
|
1981
2145
|
};
|
|
1982
2146
|
const timer = setTimeout(() => {
|
|
1983
2147
|
cleanup();
|
|
1984
|
-
|
|
2148
|
+
resolve7();
|
|
1985
2149
|
}, ms);
|
|
1986
2150
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1987
2151
|
});
|
|
@@ -2010,20 +2174,73 @@ async function waitForCondition(opts) {
|
|
|
2010
2174
|
}
|
|
2011
2175
|
|
|
2012
2176
|
// src/hdc/app.ts
|
|
2013
|
-
import * as
|
|
2177
|
+
import * as fs19 from "fs";
|
|
2178
|
+
import * as path20 from "path";
|
|
2179
|
+
|
|
2180
|
+
// src/build/discoverHaps.ts
|
|
2181
|
+
import * as fs17 from "fs";
|
|
2014
2182
|
import * as path18 from "path";
|
|
2183
|
+
function moduleOfHap(hapFileName) {
|
|
2184
|
+
const dash = hapFileName.indexOf("-");
|
|
2185
|
+
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
2186
|
+
}
|
|
2187
|
+
async function discoverHaps(opts) {
|
|
2188
|
+
const { projectDir } = opts;
|
|
2189
|
+
const maxDepth = opts.maxDepth ?? 8;
|
|
2190
|
+
const result = {};
|
|
2191
|
+
const walk = async (dir, depth) => {
|
|
2192
|
+
if (depth > maxDepth) return;
|
|
2193
|
+
const entries = await fs17.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2194
|
+
for (const e of entries) {
|
|
2195
|
+
const full = path18.join(dir, e.name);
|
|
2196
|
+
if (e.isDirectory()) {
|
|
2197
|
+
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2198
|
+
await walk(full, depth + 1);
|
|
2199
|
+
} else if (e.isFile() && e.name.endsWith(".hap")) {
|
|
2200
|
+
const moduleName = moduleOfHap(e.name);
|
|
2201
|
+
const bucket = result[moduleName] ??= { signed: [], unsigned: [] };
|
|
2202
|
+
if (e.name.includes("unsigned")) bucket.unsigned.push(full);
|
|
2203
|
+
else if (e.name.includes("signed")) bucket.signed.push(full);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
await walk(projectDir, 0);
|
|
2208
|
+
const mtime = (p) => fs17.statSync(p).mtimeMs;
|
|
2209
|
+
for (const bucket of Object.values(result)) {
|
|
2210
|
+
bucket.signed.sort((a, b) => mtime(b) - mtime(a));
|
|
2211
|
+
bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
|
|
2212
|
+
}
|
|
2213
|
+
return result;
|
|
2214
|
+
}
|
|
2215
|
+
function pickHap(haps, module) {
|
|
2216
|
+
const pick = (name) => {
|
|
2217
|
+
const signed = haps[name]?.signed[0];
|
|
2218
|
+
if (signed) return { module: name, path: signed, signed: true };
|
|
2219
|
+
const unsigned = haps[name]?.unsigned[0];
|
|
2220
|
+
return unsigned ? { module: name, path: unsigned, signed: false } : null;
|
|
2221
|
+
};
|
|
2222
|
+
if (module) return pick(module);
|
|
2223
|
+
const names = Object.keys(haps);
|
|
2224
|
+
const withSigned = names.filter((n) => haps[n].signed.length > 0);
|
|
2225
|
+
const candidates = withSigned.length > 0 ? withSigned : names.filter((n) => haps[n].unsigned.length > 0);
|
|
2226
|
+
if (candidates.length === 0) return null;
|
|
2227
|
+
if (candidates.length > 1) {
|
|
2228
|
+
throw new OniroError(`Multiple modules have built HAPs (${candidates.join(", ")}). Pass a module to choose.`);
|
|
2229
|
+
}
|
|
2230
|
+
return pick(candidates[0]);
|
|
2231
|
+
}
|
|
2015
2232
|
|
|
2016
2233
|
// src/hdc/project.ts
|
|
2017
|
-
import * as
|
|
2018
|
-
import * as
|
|
2234
|
+
import * as fs18 from "fs";
|
|
2235
|
+
import * as path19 from "path";
|
|
2019
2236
|
import JSON53 from "json5";
|
|
2020
2237
|
function readJson5(filePath) {
|
|
2021
|
-
const content =
|
|
2238
|
+
const content = fs18.readFileSync(filePath, "utf-8");
|
|
2022
2239
|
return JSON53.parse(content);
|
|
2023
2240
|
}
|
|
2024
2241
|
function getBundleName(projectDir) {
|
|
2025
|
-
const appJsonPath =
|
|
2026
|
-
if (!
|
|
2242
|
+
const appJsonPath = path19.join(projectDir, "AppScope", "app.json5");
|
|
2243
|
+
if (!fs18.existsSync(appJsonPath)) {
|
|
2027
2244
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
2028
2245
|
}
|
|
2029
2246
|
const appJson = readJson5(appJsonPath);
|
|
@@ -2033,8 +2250,8 @@ function getBundleName(projectDir) {
|
|
|
2033
2250
|
return appJson.app.bundleName;
|
|
2034
2251
|
}
|
|
2035
2252
|
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
2036
|
-
const moduleJsonPath =
|
|
2037
|
-
if (!
|
|
2253
|
+
const moduleJsonPath = path19.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
2254
|
+
if (!fs18.existsSync(moduleJsonPath)) {
|
|
2038
2255
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2039
2256
|
}
|
|
2040
2257
|
const moduleJson = readJson5(moduleJsonPath);
|
|
@@ -2054,13 +2271,67 @@ function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
|
2054
2271
|
}
|
|
2055
2272
|
|
|
2056
2273
|
// src/hdc/app.ts
|
|
2274
|
+
function detectInstallFailure(output) {
|
|
2275
|
+
return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
|
|
2276
|
+
}
|
|
2277
|
+
function isUnsignedHap(hapPath) {
|
|
2278
|
+
return path20.basename(hapPath).includes("unsigned");
|
|
2279
|
+
}
|
|
2280
|
+
function installFailureReason(output) {
|
|
2281
|
+
const code = /code:\s*(\d+)/i.exec(output);
|
|
2282
|
+
if (!code) return output.trim().split(/\r?\n/)[0] ?? "";
|
|
2283
|
+
const message = /error:\s*([^\r\n]+)/i.exec(output.slice(code.index + code[0].length))?.[1]?.trim().replace(/\.$/, "");
|
|
2284
|
+
return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
|
|
2285
|
+
}
|
|
2286
|
+
function unsignedHapRejection(output, hap) {
|
|
2287
|
+
const name = path20.basename(hap.path);
|
|
2288
|
+
const reason = installFailureReason(output);
|
|
2289
|
+
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}).`;
|
|
2290
|
+
if (!/9568320|signature|pkcs7/i.test(reason)) return what;
|
|
2291
|
+
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.`;
|
|
2292
|
+
}
|
|
2293
|
+
function ensureInstallOk(result, command, hap) {
|
|
2294
|
+
const output = `${result.stdout}
|
|
2295
|
+
${result.stderr}`;
|
|
2296
|
+
if (hap && isUnsignedHap(hap.path) && /code:\s*95\d{5}/i.test(output)) {
|
|
2297
|
+
throw new OniroError(unsignedHapRejection(output, hap), new CommandFailedError(command, result.code || 1, output.trim()));
|
|
2298
|
+
}
|
|
2299
|
+
ensureOk(result, command);
|
|
2300
|
+
const failure = detectInstallFailure(output);
|
|
2301
|
+
if (failure) throw new CommandFailedError(command, 1, failure);
|
|
2302
|
+
return result;
|
|
2303
|
+
}
|
|
2304
|
+
function existingHapOrUnsigned(hapPath) {
|
|
2305
|
+
if (fs19.existsSync(hapPath)) return { path: hapPath };
|
|
2306
|
+
const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
|
|
2307
|
+
return sibling !== hapPath && fs19.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
|
|
2308
|
+
}
|
|
2309
|
+
async function resolveInstallHap(opts, logger) {
|
|
2310
|
+
const warnIfFallback = (hap) => {
|
|
2311
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path20.basename(hap.path)}.`);
|
|
2312
|
+
return hap;
|
|
2313
|
+
};
|
|
2314
|
+
const configured = opts.hapPath ?? opts.config.get("hapPath", "");
|
|
2315
|
+
const relative3 = configured || defaultPaths.hapPath;
|
|
2316
|
+
const hapPath = path20.isAbsolute(relative3) ? relative3 : path20.join(opts.projectDir, relative3);
|
|
2317
|
+
if (configured) {
|
|
2318
|
+
const hap = existingHapOrUnsigned(hapPath);
|
|
2319
|
+
if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
|
|
2320
|
+
return warnIfFallback(hap);
|
|
2321
|
+
}
|
|
2322
|
+
if (fs19.existsSync(hapPath)) return { path: hapPath };
|
|
2323
|
+
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2324
|
+
const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
|
|
2325
|
+
const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
|
|
2326
|
+
if (entryUnsigned) return warnIfFallback(entryUnsigned);
|
|
2327
|
+
const picked = pickHap(haps);
|
|
2328
|
+
if (!picked) throw new OniroError(`No HAP found under ${opts.projectDir}. Build the app first (\`oniro-app build\`).`);
|
|
2329
|
+
return warnIfFallback({ path: picked.path, fallback: !picked.signed });
|
|
2330
|
+
}
|
|
2057
2331
|
async function installApp(opts) {
|
|
2058
2332
|
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
|
-
}
|
|
2333
|
+
const hap = await resolveInstallHap(opts, logger);
|
|
2334
|
+
const hapPath = hap.path;
|
|
2064
2335
|
const result = await hdcExec({
|
|
2065
2336
|
config: opts.config,
|
|
2066
2337
|
args: ["install", hapPath],
|
|
@@ -2069,7 +2340,7 @@ async function installApp(opts) {
|
|
|
2069
2340
|
});
|
|
2070
2341
|
if (result.stdout.trim()) logger.info(result.stdout.trim());
|
|
2071
2342
|
if (result.stderr.trim()) logger.warn(result.stderr.trim());
|
|
2072
|
-
|
|
2343
|
+
ensureInstallOk(result, `hdc install ${hapPath}`, hap);
|
|
2073
2344
|
let bundleName = "";
|
|
2074
2345
|
try {
|
|
2075
2346
|
bundleName = getBundleName(opts.projectDir);
|
|
@@ -2077,6 +2348,18 @@ async function installApp(opts) {
|
|
|
2077
2348
|
}
|
|
2078
2349
|
return { installed: true, bundleName, hapPath, output: `${result.stdout}${result.stderr}`.trim() };
|
|
2079
2350
|
}
|
|
2351
|
+
async function isBundleInstalled(opts) {
|
|
2352
|
+
const safe = opts.bundle.replace(/'/g, `'\\''`);
|
|
2353
|
+
const res = await shell({
|
|
2354
|
+
config: opts.config,
|
|
2355
|
+
command: `bm dump -n '${safe}'`,
|
|
2356
|
+
deviceSerial: opts.deviceSerial,
|
|
2357
|
+
timeoutMs: opts.timeoutMs ?? 15e3,
|
|
2358
|
+
logger: opts.logger
|
|
2359
|
+
});
|
|
2360
|
+
ensureOk(res, `hdc shell bm dump -n ${opts.bundle}`);
|
|
2361
|
+
return res.stdout.includes("{") && !/failed to get information/i.test(res.stdout);
|
|
2362
|
+
}
|
|
2080
2363
|
function detectAaStartFailure(output) {
|
|
2081
2364
|
return /failed to start ability|Error Code:\s*\d|Error Message:/i.test(output) ? output.trim() : null;
|
|
2082
2365
|
}
|
|
@@ -2147,10 +2430,10 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2147
2430
|
const logger = options.logger ?? noopLogger;
|
|
2148
2431
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
2149
2432
|
const hdc = getHdcPath(config);
|
|
2150
|
-
return new Promise((
|
|
2433
|
+
return new Promise((resolve7, reject) => {
|
|
2151
2434
|
let proc;
|
|
2152
2435
|
try {
|
|
2153
|
-
proc = spawnCompat(hdc, ["track-jpid"]);
|
|
2436
|
+
proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
|
|
2154
2437
|
} catch (err) {
|
|
2155
2438
|
reject(new OniroError(`Failed to spawn ${hdc}: ${err instanceof Error ? err.message : String(err)}`, err));
|
|
2156
2439
|
return;
|
|
@@ -2167,7 +2450,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2167
2450
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
2168
2451
|
resolved = true;
|
|
2169
2452
|
killProcessTree(proc);
|
|
2170
|
-
|
|
2453
|
+
resolve7(pid);
|
|
2171
2454
|
return;
|
|
2172
2455
|
}
|
|
2173
2456
|
}
|
|
@@ -2188,7 +2471,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2188
2471
|
if (options.targetProcessName) {
|
|
2189
2472
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2190
2473
|
} else {
|
|
2191
|
-
|
|
2474
|
+
resolve7(processes);
|
|
2192
2475
|
}
|
|
2193
2476
|
}, timeoutMs);
|
|
2194
2477
|
proc.on("close", () => {
|
|
@@ -2198,7 +2481,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2198
2481
|
if (options.targetProcessName) {
|
|
2199
2482
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2200
2483
|
} else {
|
|
2201
|
-
|
|
2484
|
+
resolve7(processes);
|
|
2202
2485
|
}
|
|
2203
2486
|
});
|
|
2204
2487
|
});
|
|
@@ -2220,7 +2503,11 @@ async function findRunningProcess(opts) {
|
|
|
2220
2503
|
const pid = res.stdout.trim().split(/\s+/).filter(Boolean)[0];
|
|
2221
2504
|
if (res.code === 0 && pid && /^\d+$/.test(pid)) return { pid, name: opts.bundle };
|
|
2222
2505
|
try {
|
|
2223
|
-
const list = await listRunningProcesses(opts.config, {
|
|
2506
|
+
const list = await listRunningProcesses(opts.config, {
|
|
2507
|
+
timeoutMs: 1200,
|
|
2508
|
+
deviceSerial: opts.deviceSerial,
|
|
2509
|
+
logger: opts.logger
|
|
2510
|
+
});
|
|
2224
2511
|
if (Array.isArray(list)) {
|
|
2225
2512
|
const mine = (name) => name === opts.bundle || name.startsWith(`${opts.bundle}/`) || name.startsWith(`${opts.bundle}:`);
|
|
2226
2513
|
const match = list.find((p) => p.name === opts.bundle) ?? list.find((p) => mine(p.name));
|
|
@@ -2363,12 +2650,12 @@ async function reboot(opts) {
|
|
|
2363
2650
|
}
|
|
2364
2651
|
|
|
2365
2652
|
// src/hdc/screen.ts
|
|
2366
|
-
import * as
|
|
2653
|
+
import * as fs20 from "fs";
|
|
2367
2654
|
import * as os9 from "os";
|
|
2368
|
-
import * as
|
|
2655
|
+
import * as path21 from "path";
|
|
2369
2656
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2370
2657
|
function tmpName(ext) {
|
|
2371
|
-
return
|
|
2658
|
+
return path21.join(os9.tmpdir(), `oniro_screen_${randomBytes2(6).toString("hex")}.${ext}`);
|
|
2372
2659
|
}
|
|
2373
2660
|
async function takeScreenshot(opts) {
|
|
2374
2661
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -2378,11 +2665,11 @@ async function takeScreenshot(opts) {
|
|
|
2378
2665
|
const local = tmpName("jpeg");
|
|
2379
2666
|
try {
|
|
2380
2667
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2381
|
-
const pixels = await
|
|
2668
|
+
const pixels = await fs20.promises.readFile(local);
|
|
2382
2669
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2383
2670
|
return { pixels, width: size.width, height: size.height };
|
|
2384
2671
|
} finally {
|
|
2385
|
-
|
|
2672
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2386
2673
|
});
|
|
2387
2674
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2388
2675
|
});
|
|
@@ -2401,9 +2688,9 @@ async function captureBurst(opts) {
|
|
|
2401
2688
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
2402
2689
|
try {
|
|
2403
2690
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2404
|
-
frames.push(await
|
|
2691
|
+
frames.push(await fs20.promises.readFile(local));
|
|
2405
2692
|
} finally {
|
|
2406
|
-
|
|
2693
|
+
fs20.promises.unlink(local).catch(() => {
|
|
2407
2694
|
});
|
|
2408
2695
|
}
|
|
2409
2696
|
}
|
|
@@ -2578,9 +2865,9 @@ async function sendRawTouch(opts) {
|
|
|
2578
2865
|
}
|
|
2579
2866
|
|
|
2580
2867
|
// src/hdc/dump.ts
|
|
2581
|
-
import * as
|
|
2868
|
+
import * as fs21 from "fs";
|
|
2582
2869
|
import * as os10 from "os";
|
|
2583
|
-
import * as
|
|
2870
|
+
import * as path22 from "path";
|
|
2584
2871
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
2585
2872
|
|
|
2586
2873
|
// src/hdc/ime.ts
|
|
@@ -2843,10 +3130,10 @@ async function dumpLayout(opts) {
|
|
|
2843
3130
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
2844
3131
|
${dump.stdout}`);
|
|
2845
3132
|
const remote = m[1];
|
|
2846
|
-
const local =
|
|
3133
|
+
const local = path22.join(os10.tmpdir(), `oniro_layout_${randomBytes3(6).toString("hex")}.json`);
|
|
2847
3134
|
try {
|
|
2848
3135
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2849
|
-
const parsed = JSON.parse(await
|
|
3136
|
+
const parsed = JSON.parse(await fs21.promises.readFile(local, "utf8"));
|
|
2850
3137
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
2851
3138
|
if (opts.prune === false) {
|
|
2852
3139
|
return { display, tree: parsed };
|
|
@@ -2859,7 +3146,7 @@ ${dump.stdout}`);
|
|
|
2859
3146
|
const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
|
|
2860
3147
|
return { display, tree, keyboard };
|
|
2861
3148
|
} finally {
|
|
2862
|
-
|
|
3149
|
+
fs21.promises.unlink(local).catch(() => {
|
|
2863
3150
|
});
|
|
2864
3151
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2865
3152
|
});
|
|
@@ -2869,7 +3156,7 @@ ${dump.stdout}`);
|
|
|
2869
3156
|
// src/hdc/uiActions.ts
|
|
2870
3157
|
var FOCUS_SETTLE_MS = 400;
|
|
2871
3158
|
var KEYBOARD_SETTLE_MS = 400;
|
|
2872
|
-
var sleep = (ms) => new Promise((
|
|
3159
|
+
var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
2873
3160
|
function targetTree(layout) {
|
|
2874
3161
|
const kb = layout.keyboard;
|
|
2875
3162
|
return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
|
|
@@ -3022,13 +3309,14 @@ async function waitForTarget(opts) {
|
|
|
3022
3309
|
}
|
|
3023
3310
|
|
|
3024
3311
|
// src/hdc/hilog.ts
|
|
3312
|
+
import * as fs22 from "fs";
|
|
3025
3313
|
function setHilogLevel(opts) {
|
|
3026
3314
|
const logger = opts.logger ?? noopLogger;
|
|
3027
3315
|
const hdc = getHdcPath(opts.config);
|
|
3028
|
-
return new Promise((
|
|
3316
|
+
return new Promise((resolve7, reject) => {
|
|
3029
3317
|
let child;
|
|
3030
3318
|
try {
|
|
3031
|
-
child = spawnCompat(hdc, ["shell", "hilog", "-b", opts.level]);
|
|
3319
|
+
child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
|
|
3032
3320
|
} catch (err) {
|
|
3033
3321
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3034
3322
|
logger.error(`[hilog] failed to spawn hdc: ${msg}`);
|
|
@@ -3045,7 +3333,7 @@ function setHilogLevel(opts) {
|
|
|
3045
3333
|
});
|
|
3046
3334
|
child.once("close", (code) => {
|
|
3047
3335
|
if (code === 0) {
|
|
3048
|
-
|
|
3336
|
+
resolve7();
|
|
3049
3337
|
} else {
|
|
3050
3338
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
3051
3339
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -3056,8 +3344,7 @@ function setHilogLevel(opts) {
|
|
|
3056
3344
|
}
|
|
3057
3345
|
function streamHilog(opts) {
|
|
3058
3346
|
const hdc = getHdcPath(opts.config);
|
|
3059
|
-
const args = [];
|
|
3060
|
-
if (opts.deviceSerial) args.push("-t", opts.deviceSerial);
|
|
3347
|
+
const args = [...hdcTargetArgs(opts.deviceSerial)];
|
|
3061
3348
|
args.push("shell", "hilog");
|
|
3062
3349
|
if (opts.processId && opts.processId.trim() !== "") {
|
|
3063
3350
|
args.push("-P", opts.processId.trim());
|
|
@@ -3100,7 +3387,7 @@ async function resolveProcessId(config, bundle, deviceSerial) {
|
|
|
3100
3387
|
return proc?.pid;
|
|
3101
3388
|
}
|
|
3102
3389
|
function waitForLog(opts) {
|
|
3103
|
-
return new Promise((
|
|
3390
|
+
return new Promise((resolve7, reject) => {
|
|
3104
3391
|
const deadline = Date.now() + opts.timeoutMs;
|
|
3105
3392
|
let settled = false;
|
|
3106
3393
|
let child = null;
|
|
@@ -3119,7 +3406,7 @@ function waitForLog(opts) {
|
|
|
3119
3406
|
if (settled) return;
|
|
3120
3407
|
settled = true;
|
|
3121
3408
|
cleanup();
|
|
3122
|
-
|
|
3409
|
+
resolve7(entry);
|
|
3123
3410
|
};
|
|
3124
3411
|
const fail = (err) => {
|
|
3125
3412
|
if (settled) return;
|
|
@@ -3166,7 +3453,7 @@ function waitForLog(opts) {
|
|
|
3166
3453
|
});
|
|
3167
3454
|
}
|
|
3168
3455
|
function watchLog(opts) {
|
|
3169
|
-
return new Promise((
|
|
3456
|
+
return new Promise((resolve7, reject) => {
|
|
3170
3457
|
const matches = [];
|
|
3171
3458
|
let settled = false;
|
|
3172
3459
|
let child = null;
|
|
@@ -3183,7 +3470,7 @@ function watchLog(opts) {
|
|
|
3183
3470
|
if (settled) return;
|
|
3184
3471
|
settled = true;
|
|
3185
3472
|
cleanup();
|
|
3186
|
-
|
|
3473
|
+
resolve7(opts.dedup === false ? matches : dedupEntries(matches));
|
|
3187
3474
|
};
|
|
3188
3475
|
const onAbort = () => {
|
|
3189
3476
|
if (settled) return;
|
|
@@ -3215,6 +3502,94 @@ function watchLog(opts) {
|
|
|
3215
3502
|
});
|
|
3216
3503
|
});
|
|
3217
3504
|
}
|
|
3505
|
+
async function captureLog(opts) {
|
|
3506
|
+
const logger = opts.logger ?? noopLogger;
|
|
3507
|
+
if (opts.clear) {
|
|
3508
|
+
const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
|
|
3509
|
+
ensureOk(res, "hdc shell hilog -r");
|
|
3510
|
+
}
|
|
3511
|
+
const fd = fs22.openSync(opts.file, "a");
|
|
3512
|
+
let lines = 0;
|
|
3513
|
+
let reconnects = 0;
|
|
3514
|
+
try {
|
|
3515
|
+
await new Promise((resolve7, reject) => {
|
|
3516
|
+
const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
|
|
3517
|
+
let settled = false;
|
|
3518
|
+
let child = null;
|
|
3519
|
+
let reconnectTimer;
|
|
3520
|
+
let buf = "";
|
|
3521
|
+
let open = true;
|
|
3522
|
+
const write = (line) => {
|
|
3523
|
+
if (!open || opts.pattern && !opts.pattern.test(line)) return;
|
|
3524
|
+
fs22.writeSync(fd, `${line}
|
|
3525
|
+
`);
|
|
3526
|
+
lines++;
|
|
3527
|
+
};
|
|
3528
|
+
const cleanup = () => {
|
|
3529
|
+
if (timer) clearTimeout(timer);
|
|
3530
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
3531
|
+
opts.abortSignal?.removeEventListener("abort", finish);
|
|
3532
|
+
if (buf.trim()) write(buf.trimEnd());
|
|
3533
|
+
buf = "";
|
|
3534
|
+
open = false;
|
|
3535
|
+
if (child) {
|
|
3536
|
+
child.stdout.removeAllListeners("data");
|
|
3537
|
+
killProcessTree(child);
|
|
3538
|
+
child = null;
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
function finish() {
|
|
3542
|
+
if (settled) return;
|
|
3543
|
+
settled = true;
|
|
3544
|
+
cleanup();
|
|
3545
|
+
resolve7();
|
|
3546
|
+
}
|
|
3547
|
+
const fail = (err) => {
|
|
3548
|
+
if (settled) return;
|
|
3549
|
+
settled = true;
|
|
3550
|
+
cleanup();
|
|
3551
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
3552
|
+
};
|
|
3553
|
+
const timer = opts.durationMs !== void 0 ? setTimeout(finish, opts.durationMs) : void 0;
|
|
3554
|
+
if (opts.abortSignal?.aborted) {
|
|
3555
|
+
finish();
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
opts.abortSignal?.addEventListener("abort", finish, { once: true });
|
|
3559
|
+
const spawnStream = () => {
|
|
3560
|
+
if (settled) return;
|
|
3561
|
+
const stream = streamHilog({ config: opts.config, domain: opts.domain, deviceSerial: opts.deviceSerial });
|
|
3562
|
+
child = stream;
|
|
3563
|
+
stream.stdout.on("data", (d) => {
|
|
3564
|
+
buf += d.toString();
|
|
3565
|
+
const parts = buf.split(/\r?\n/);
|
|
3566
|
+
buf = parts.pop() ?? "";
|
|
3567
|
+
for (const line of parts) write(line);
|
|
3568
|
+
});
|
|
3569
|
+
const onEnd = () => {
|
|
3570
|
+
if (settled || child !== stream) return;
|
|
3571
|
+
child = null;
|
|
3572
|
+
if (Date.now() >= deadline) return;
|
|
3573
|
+
reconnects++;
|
|
3574
|
+
logger.warn(`[hilog] stream ended; reconnecting (${reconnects}).`);
|
|
3575
|
+
reconnectTimer = setTimeout(() => {
|
|
3576
|
+
try {
|
|
3577
|
+
spawnStream();
|
|
3578
|
+
} catch (err) {
|
|
3579
|
+
fail(err);
|
|
3580
|
+
}
|
|
3581
|
+
}, 2e3);
|
|
3582
|
+
};
|
|
3583
|
+
stream.once("close", onEnd);
|
|
3584
|
+
stream.once("error", onEnd);
|
|
3585
|
+
};
|
|
3586
|
+
spawnStream();
|
|
3587
|
+
});
|
|
3588
|
+
} finally {
|
|
3589
|
+
fs22.closeSync(fd);
|
|
3590
|
+
}
|
|
3591
|
+
return { file: opts.file, lines, reconnects };
|
|
3592
|
+
}
|
|
3218
3593
|
async function dumpLog(opts) {
|
|
3219
3594
|
let pipe = "hilog -x";
|
|
3220
3595
|
if (opts.domain !== void 0 && `${opts.domain}` !== "") pipe += ` -D ${opts.domain}`;
|
|
@@ -3239,17 +3614,17 @@ async function dumpLog(opts) {
|
|
|
3239
3614
|
}
|
|
3240
3615
|
|
|
3241
3616
|
// src/build/runHvigorw.ts
|
|
3242
|
-
import * as
|
|
3243
|
-
import * as
|
|
3617
|
+
import * as fs24 from "fs";
|
|
3618
|
+
import * as path24 from "path";
|
|
3244
3619
|
|
|
3245
3620
|
// src/harmonyos/target.ts
|
|
3246
|
-
import * as
|
|
3247
|
-
import * as
|
|
3621
|
+
import * as fs23 from "fs";
|
|
3622
|
+
import * as path23 from "path";
|
|
3248
3623
|
import JSON54 from "json5";
|
|
3249
3624
|
function detectRuntimeOs(projectDir, productName) {
|
|
3250
3625
|
let products = [];
|
|
3251
3626
|
try {
|
|
3252
|
-
const parsed = JSON54.parse(
|
|
3627
|
+
const parsed = JSON54.parse(fs23.readFileSync(path23.join(projectDir, "build-profile.json5"), "utf-8"));
|
|
3253
3628
|
if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
|
|
3254
3629
|
} catch {
|
|
3255
3630
|
return "OpenHarmony";
|
|
@@ -3290,7 +3665,7 @@ function runHvigorw(opts) {
|
|
|
3290
3665
|
const { config } = opts;
|
|
3291
3666
|
const projectDir = toLongPath(opts.projectDir);
|
|
3292
3667
|
const logger = opts.logger ?? noopLogger;
|
|
3293
|
-
if (!
|
|
3668
|
+
if (!fs24.existsSync(path24.join(projectDir, "build-profile.json5"))) {
|
|
3294
3669
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
3295
3670
|
}
|
|
3296
3671
|
let hvigorw;
|
|
@@ -3302,24 +3677,24 @@ function runHvigorw(opts) {
|
|
|
3302
3677
|
hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
|
|
3303
3678
|
logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
|
|
3304
3679
|
} else {
|
|
3305
|
-
if (!
|
|
3680
|
+
if (!fs24.existsSync(getCmdToolsPath(config))) {
|
|
3306
3681
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
3307
3682
|
}
|
|
3308
3683
|
sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
|
|
3309
3684
|
hvigorw = getHvigorwPath(config, projectDir);
|
|
3310
3685
|
}
|
|
3311
|
-
if (!
|
|
3686
|
+
if (!fs24.existsSync(hvigorw)) {
|
|
3312
3687
|
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
3313
3688
|
}
|
|
3314
3689
|
const args = buildHvigorwArgs(opts);
|
|
3315
3690
|
if (process.platform !== "win32") {
|
|
3316
3691
|
try {
|
|
3317
|
-
|
|
3692
|
+
fs24.chmodSync(hvigorw, 493);
|
|
3318
3693
|
} catch {
|
|
3319
3694
|
}
|
|
3320
3695
|
}
|
|
3321
3696
|
const env = { ...process.env, ...sdkEnv };
|
|
3322
|
-
return new Promise((
|
|
3697
|
+
return new Promise((resolve7, reject) => {
|
|
3323
3698
|
if (opts.abortSignal?.aborted) {
|
|
3324
3699
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
3325
3700
|
return;
|
|
@@ -3363,46 +3738,15 @@ function runHvigorw(opts) {
|
|
|
3363
3738
|
settled = true;
|
|
3364
3739
|
cleanup();
|
|
3365
3740
|
const exitCode = code ?? 1;
|
|
3366
|
-
if (exitCode === 0)
|
|
3741
|
+
if (exitCode === 0) resolve7({ exitCode });
|
|
3367
3742
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
3368
3743
|
});
|
|
3369
3744
|
});
|
|
3370
3745
|
}
|
|
3371
3746
|
|
|
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
3747
|
// src/build/ohpm.ts
|
|
3404
|
-
import * as
|
|
3405
|
-
import * as
|
|
3748
|
+
import * as fs25 from "fs";
|
|
3749
|
+
import * as path25 from "path";
|
|
3406
3750
|
function runOhpm(opts) {
|
|
3407
3751
|
return runProcess({
|
|
3408
3752
|
command: opts.ohpmPath ?? getOhpmPath(opts.config),
|
|
@@ -3415,7 +3759,7 @@ function runOhpm(opts) {
|
|
|
3415
3759
|
});
|
|
3416
3760
|
}
|
|
3417
3761
|
async function ensureOhModules(opts) {
|
|
3418
|
-
if (
|
|
3762
|
+
if (fs25.existsSync(path25.join(opts.projectDir, "oh_modules"))) {
|
|
3419
3763
|
return { installed: false };
|
|
3420
3764
|
}
|
|
3421
3765
|
const res = await runOhpm({
|
|
@@ -3432,23 +3776,91 @@ async function ensureOhModules(opts) {
|
|
|
3432
3776
|
}
|
|
3433
3777
|
|
|
3434
3778
|
// src/build/codelinter.ts
|
|
3435
|
-
var FINDING_RE = /^(.*?):(\d+):(
|
|
3779
|
+
var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
|
|
3436
3780
|
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
3437
|
-
|
|
3781
|
+
var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
|
|
3782
|
+
var ROW_RE = /^(\d+):(\d+)\s+(error|warning|warn|suggestion|info)\s+(.*)$/i;
|
|
3783
|
+
var RULE_RE = /^@?[\w.-]+(?:\/[\w.-]+)+$/;
|
|
3784
|
+
var SUMMARY_RE = /Defects:\s*(\d+);\s*Errors:\s*(\d+);\s*Warns:\s*(\d+);\s*Suggestions:\s*(\d+)/i;
|
|
3785
|
+
var NOTICE_RE = /does not match that of the project|error occurred during linting|incomplete report/i;
|
|
3786
|
+
var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
3787
|
+
function cleanCodelinterOutput(output) {
|
|
3788
|
+
return output.replace(ANSI_RE, "").split("\n").map((line) => (line.split("\r").filter((s) => s.trim() !== "").pop() ?? "").trimEnd()).join("\n");
|
|
3789
|
+
}
|
|
3790
|
+
function normaliseSeverity(s) {
|
|
3791
|
+
const lower = s.toLowerCase();
|
|
3792
|
+
return lower === "warn" ? "warning" : lower;
|
|
3793
|
+
}
|
|
3794
|
+
function parseCodelinterReport(output) {
|
|
3438
3795
|
const findings = [];
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3796
|
+
const notices = [];
|
|
3797
|
+
let summary = null;
|
|
3798
|
+
let file = "";
|
|
3799
|
+
for (const rawLine of cleanCodelinterOutput(output).split("\n")) {
|
|
3800
|
+
const line = rawLine.trim();
|
|
3801
|
+
if (!line) continue;
|
|
3802
|
+
const row = ROW_RE.exec(line);
|
|
3803
|
+
if (row) {
|
|
3804
|
+
if (!file) continue;
|
|
3805
|
+
const columns = row[4].trim().split(/\s{2,}/);
|
|
3806
|
+
const last = columns[columns.length - 1];
|
|
3807
|
+
const code = columns.length > 1 && RULE_RE.test(last) ? last : "";
|
|
3808
|
+
const message = (code ? columns.slice(0, -1) : columns).join(" ").trim();
|
|
3809
|
+
findings.push({
|
|
3810
|
+
file,
|
|
3811
|
+
line: Number(row[1]),
|
|
3812
|
+
column: Number(row[2]),
|
|
3813
|
+
severity: normaliseSeverity(row[3]),
|
|
3814
|
+
code,
|
|
3815
|
+
message
|
|
3816
|
+
});
|
|
3817
|
+
continue;
|
|
3818
|
+
}
|
|
3819
|
+
const header = FILE_HEADER_RE.exec(line);
|
|
3820
|
+
if (header) {
|
|
3821
|
+
file = header[1];
|
|
3822
|
+
continue;
|
|
3823
|
+
}
|
|
3824
|
+
const legacy = FINDING_RE.exec(line);
|
|
3825
|
+
if (legacy) {
|
|
3826
|
+
let message = legacy[5].trim();
|
|
3827
|
+
let code = "";
|
|
3828
|
+
const codeMatch = TRAILING_CODE_RE.exec(message);
|
|
3829
|
+
if (codeMatch) {
|
|
3830
|
+
code = codeMatch[1];
|
|
3831
|
+
message = message.slice(0, codeMatch.index).trim();
|
|
3832
|
+
}
|
|
3833
|
+
const finding = {
|
|
3834
|
+
file: legacy[1].trim(),
|
|
3835
|
+
line: Number(legacy[2]),
|
|
3836
|
+
severity: normaliseSeverity(legacy[4]),
|
|
3837
|
+
code,
|
|
3838
|
+
message
|
|
3839
|
+
};
|
|
3840
|
+
if (legacy[3]) finding.column = Number(legacy[3]);
|
|
3841
|
+
findings.push(finding);
|
|
3842
|
+
continue;
|
|
3448
3843
|
}
|
|
3449
|
-
|
|
3844
|
+
const totals = SUMMARY_RE.exec(line);
|
|
3845
|
+
if (totals) {
|
|
3846
|
+
summary = {
|
|
3847
|
+
defects: Number(totals[1]),
|
|
3848
|
+
errors: Number(totals[2]),
|
|
3849
|
+
warnings: Number(totals[3]),
|
|
3850
|
+
suggestions: Number(totals[4])
|
|
3851
|
+
};
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
if (/no defects found/i.test(line)) {
|
|
3855
|
+
summary = { defects: 0, errors: 0, warnings: 0, suggestions: 0 };
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
if (NOTICE_RE.test(line)) notices.push(line);
|
|
3450
3859
|
}
|
|
3451
|
-
return findings;
|
|
3860
|
+
return { findings, summary, notices, incomplete: notices.length > 0 };
|
|
3861
|
+
}
|
|
3862
|
+
function parseCodelinterFindings(output) {
|
|
3863
|
+
return parseCodelinterReport(output).findings;
|
|
3452
3864
|
}
|
|
3453
3865
|
async function runCodelinter(opts) {
|
|
3454
3866
|
const res = await runProcess({
|
|
@@ -3460,16 +3872,28 @@ async function runCodelinter(opts) {
|
|
|
3460
3872
|
logger: opts.logger
|
|
3461
3873
|
});
|
|
3462
3874
|
const raw = `${res.stdout}${res.stderr}`;
|
|
3463
|
-
return { code: res.code,
|
|
3875
|
+
return { code: res.code, ...parseCodelinterReport(raw), raw: cleanCodelinterOutput(raw) };
|
|
3464
3876
|
}
|
|
3465
3877
|
|
|
3466
3878
|
// src/build/buildHap.ts
|
|
3879
|
+
var DIAGNOSIS_OUTPUT_LIMIT = 2e6;
|
|
3880
|
+
function diagnoseBuildFailure(output) {
|
|
3881
|
+
if (/is not exported from Kit|has no exported member .*@systemapi/i.test(output)) {
|
|
3882
|
+
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`).";
|
|
3883
|
+
}
|
|
3884
|
+
if (/00308018|SDK path does not exist|SDK license agreement is not accepted/i.test(output)) {
|
|
3885
|
+
return "hvigor cannot find the SDK the project compiles against: check `oniro-app sdk list` against compileSdkVersion.";
|
|
3886
|
+
}
|
|
3887
|
+
return void 0;
|
|
3888
|
+
}
|
|
3467
3889
|
async function buildHap(opts) {
|
|
3468
3890
|
const logger = scopedLogger(opts.logger ?? noopLogger, "build");
|
|
3469
3891
|
const warnings = [];
|
|
3470
3892
|
const start = Date.now();
|
|
3471
3893
|
const projectDir = toLongPath(opts.projectDir);
|
|
3472
3894
|
const isHarmonyOs = detectRuntimeOs(projectDir, opts.product) === "HarmonyOS";
|
|
3895
|
+
const missingSdk = isHarmonyOs ? null : findMissingProjectSdk(opts.config, projectDir);
|
|
3896
|
+
if (missingSdk) throw missingSdk;
|
|
3473
3897
|
if (opts.autoInstallDeps !== false) {
|
|
3474
3898
|
const harmonySdk = isHarmonyOs ? findHarmonyOsSdk({ config: opts.config, logger: opts.logger }) : null;
|
|
3475
3899
|
const r = await ensureOhModules({
|
|
@@ -3482,23 +3906,35 @@ async function buildHap(opts) {
|
|
|
3482
3906
|
});
|
|
3483
3907
|
if (r.installed) logger.info("ran ohpm install --all (oh_modules was missing)");
|
|
3484
3908
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3909
|
+
let output = "";
|
|
3910
|
+
let exitCode;
|
|
3911
|
+
try {
|
|
3912
|
+
({ exitCode } = await runHvigorw({
|
|
3913
|
+
config: opts.config,
|
|
3914
|
+
projectDir,
|
|
3915
|
+
product: opts.product,
|
|
3916
|
+
module: opts.module,
|
|
3917
|
+
buildMode: opts.mode,
|
|
3918
|
+
task: opts.task,
|
|
3919
|
+
parallel: opts.parallel,
|
|
3920
|
+
abortSignal: opts.abortSignal,
|
|
3921
|
+
onOutput: (chunk, stream) => {
|
|
3922
|
+
output = (output + chunk).slice(-DIAGNOSIS_OUTPUT_LIMIT);
|
|
3923
|
+
opts.onOutput?.(chunk, stream);
|
|
3924
|
+
},
|
|
3925
|
+
logger: opts.logger
|
|
3926
|
+
}));
|
|
3927
|
+
} catch (err) {
|
|
3928
|
+
const hint = diagnoseBuildFailure(output);
|
|
3929
|
+
if (hint && err instanceof OniroError) throw new OniroError(`${err.message}
|
|
3930
|
+
${hint}`, err);
|
|
3931
|
+
throw err;
|
|
3932
|
+
}
|
|
3497
3933
|
const discoveredHaps = await discoverHaps({ projectDir });
|
|
3498
3934
|
const anySigned = Object.values(discoveredHaps).some((m) => m.signed.length > 0);
|
|
3499
3935
|
if (!anySigned) {
|
|
3500
3936
|
warnings.push(
|
|
3501
|
-
isHarmonyOs ? "No signed HAPs found
|
|
3937
|
+
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
3938
|
);
|
|
3503
3939
|
}
|
|
3504
3940
|
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
@@ -3515,21 +3951,21 @@ function isValidBundleName(bundleName) {
|
|
|
3515
3951
|
}
|
|
3516
3952
|
|
|
3517
3953
|
// src/project/templates.ts
|
|
3518
|
-
import * as
|
|
3519
|
-
import * as
|
|
3954
|
+
import * as fs26 from "fs";
|
|
3955
|
+
import * as path26 from "path";
|
|
3520
3956
|
function toHumanTemplateName(folderName) {
|
|
3521
3957
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
3522
3958
|
}
|
|
3523
3959
|
function listTemplates(templateRoot) {
|
|
3524
|
-
if (!
|
|
3525
|
-
const entries =
|
|
3960
|
+
if (!fs26.existsSync(templateRoot)) return [];
|
|
3961
|
+
const entries = fs26.readdirSync(templateRoot, { withFileTypes: true });
|
|
3526
3962
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
3527
|
-
const dir =
|
|
3528
|
-
const metaPath =
|
|
3963
|
+
const dir = path26.join(templateRoot, e.name);
|
|
3964
|
+
const metaPath = path26.join(dir, "template.json");
|
|
3529
3965
|
let meta = {};
|
|
3530
|
-
if (
|
|
3966
|
+
if (fs26.existsSync(metaPath)) {
|
|
3531
3967
|
try {
|
|
3532
|
-
meta = JSON.parse(
|
|
3968
|
+
meta = JSON.parse(fs26.readFileSync(metaPath, "utf8"));
|
|
3533
3969
|
} catch {
|
|
3534
3970
|
}
|
|
3535
3971
|
}
|
|
@@ -3544,57 +3980,57 @@ function listTemplates(templateRoot) {
|
|
|
3544
3980
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
3545
3981
|
const required = [
|
|
3546
3982
|
"build-profile.json5",
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3983
|
+
path26.join("AppScope", "app.json5"),
|
|
3984
|
+
path26.join(defaultModuleName, "src", "main", "module.json5"),
|
|
3985
|
+
path26.join(defaultModuleName, "oh-package.json5"),
|
|
3550
3986
|
"hvigorfile.ts"
|
|
3551
3987
|
];
|
|
3552
|
-
return required.filter((rel) => !
|
|
3988
|
+
return required.filter((rel) => !fs26.existsSync(path26.join(templateDir, rel)));
|
|
3553
3989
|
}
|
|
3554
3990
|
|
|
3555
3991
|
// src/project/createScaffold.ts
|
|
3556
|
-
import * as
|
|
3557
|
-
import * as
|
|
3992
|
+
import * as fs28 from "fs";
|
|
3993
|
+
import * as path27 from "path";
|
|
3558
3994
|
|
|
3559
3995
|
// src/project/jsonHelpers.ts
|
|
3560
|
-
import * as
|
|
3996
|
+
import * as fs27 from "fs";
|
|
3561
3997
|
import JSON55 from "json5";
|
|
3562
3998
|
function readJson5File(filePath) {
|
|
3563
|
-
return JSON55.parse(
|
|
3999
|
+
return JSON55.parse(fs27.readFileSync(filePath, "utf8"));
|
|
3564
4000
|
}
|
|
3565
4001
|
function writeJson5File(filePath, value) {
|
|
3566
|
-
|
|
4002
|
+
fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3567
4003
|
}
|
|
3568
4004
|
function readJsonFile(filePath) {
|
|
3569
|
-
return JSON.parse(
|
|
4005
|
+
return JSON.parse(fs27.readFileSync(filePath, "utf8"));
|
|
3570
4006
|
}
|
|
3571
4007
|
function writeJsonFile(filePath, value) {
|
|
3572
|
-
|
|
4008
|
+
fs27.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
3573
4009
|
}
|
|
3574
4010
|
|
|
3575
4011
|
// src/project/createScaffold.ts
|
|
3576
4012
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
3577
4013
|
async function pathExists(p) {
|
|
3578
4014
|
try {
|
|
3579
|
-
await
|
|
4015
|
+
await fs28.promises.access(p);
|
|
3580
4016
|
return true;
|
|
3581
4017
|
} catch {
|
|
3582
4018
|
return false;
|
|
3583
4019
|
}
|
|
3584
4020
|
}
|
|
3585
4021
|
async function copyDirRecursive(srcDir, destDir) {
|
|
3586
|
-
await
|
|
3587
|
-
const entries = await
|
|
4022
|
+
await fs28.promises.mkdir(destDir, { recursive: true });
|
|
4023
|
+
const entries = await fs28.promises.readdir(srcDir, { withFileTypes: true });
|
|
3588
4024
|
for (const entry of entries) {
|
|
3589
|
-
const src =
|
|
3590
|
-
const dest =
|
|
4025
|
+
const src = path27.join(srcDir, entry.name);
|
|
4026
|
+
const dest = path27.join(destDir, entry.name);
|
|
3591
4027
|
if (entry.isDirectory()) {
|
|
3592
4028
|
await copyDirRecursive(src, dest);
|
|
3593
4029
|
} else if (entry.isSymbolicLink()) {
|
|
3594
4030
|
continue;
|
|
3595
4031
|
} else if (entry.isFile()) {
|
|
3596
4032
|
if (entry.name === "template.json") continue;
|
|
3597
|
-
await
|
|
4033
|
+
await fs28.promises.copyFile(src, dest);
|
|
3598
4034
|
}
|
|
3599
4035
|
}
|
|
3600
4036
|
}
|
|
@@ -3602,9 +4038,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
3602
4038
|
const stack = [projectDir];
|
|
3603
4039
|
while (stack.length > 0) {
|
|
3604
4040
|
const currentDir = stack.pop();
|
|
3605
|
-
const entries = await
|
|
4041
|
+
const entries = await fs28.promises.readdir(currentDir, { withFileTypes: true });
|
|
3606
4042
|
for (const entry of entries) {
|
|
3607
|
-
const fullPath =
|
|
4043
|
+
const fullPath = path27.join(currentDir, entry.name);
|
|
3608
4044
|
if (entry.isDirectory()) {
|
|
3609
4045
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3610
4046
|
stack.push(fullPath);
|
|
@@ -3626,23 +4062,23 @@ function toJavaPropertiesPath(p) {
|
|
|
3626
4062
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
3627
4063
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
3628
4064
|
`;
|
|
3629
|
-
|
|
4065
|
+
fs28.writeFileSync(path27.join(projectDir, "local.properties"), content, "utf8");
|
|
3630
4066
|
}
|
|
3631
4067
|
function renameIfExists(fromPath, toPath) {
|
|
3632
|
-
if (
|
|
3633
|
-
|
|
4068
|
+
if (fs28.existsSync(fromPath) && fromPath !== toPath) {
|
|
4069
|
+
fs28.renameSync(fromPath, toPath);
|
|
3634
4070
|
}
|
|
3635
4071
|
}
|
|
3636
4072
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
3637
|
-
const appJsonPath =
|
|
3638
|
-
if (
|
|
4073
|
+
const appJsonPath = path27.join(projectDir, "AppScope", "app.json5");
|
|
4074
|
+
if (fs28.existsSync(appJsonPath)) {
|
|
3639
4075
|
const appJson = readJson5File(appJsonPath);
|
|
3640
4076
|
appJson.app = appJson.app ?? {};
|
|
3641
4077
|
appJson.app.bundleName = args.bundleName;
|
|
3642
4078
|
writeJson5File(appJsonPath, appJson);
|
|
3643
4079
|
}
|
|
3644
|
-
const stringsPath =
|
|
3645
|
-
if (
|
|
4080
|
+
const stringsPath = path27.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
4081
|
+
if (fs28.existsSync(stringsPath)) {
|
|
3646
4082
|
const strings = readJsonFile(stringsPath);
|
|
3647
4083
|
if (Array.isArray(strings.string)) {
|
|
3648
4084
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -3650,8 +4086,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3650
4086
|
}
|
|
3651
4087
|
writeJsonFile(stringsPath, strings);
|
|
3652
4088
|
}
|
|
3653
|
-
const buildProfilePath =
|
|
3654
|
-
if (
|
|
4089
|
+
const buildProfilePath = path27.join(projectDir, "build-profile.json5");
|
|
4090
|
+
if (fs28.existsSync(buildProfilePath)) {
|
|
3655
4091
|
const buildProfile = readJson5File(buildProfilePath);
|
|
3656
4092
|
buildProfile.app = buildProfile.app ?? {};
|
|
3657
4093
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -3664,23 +4100,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
3664
4100
|
}
|
|
3665
4101
|
writeJson5File(buildProfilePath, buildProfile);
|
|
3666
4102
|
}
|
|
3667
|
-
const moduleJsonPath =
|
|
3668
|
-
if (
|
|
4103
|
+
const moduleJsonPath = path27.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
4104
|
+
if (fs28.existsSync(moduleJsonPath)) {
|
|
3669
4105
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
3670
4106
|
moduleJson.module = moduleJson.module ?? {};
|
|
3671
4107
|
moduleJson.module.name = args.moduleName;
|
|
3672
4108
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
3673
4109
|
}
|
|
3674
|
-
const moduleOhPackagePath =
|
|
3675
|
-
if (
|
|
4110
|
+
const moduleOhPackagePath = path27.join(projectDir, args.moduleName, "oh-package.json5");
|
|
4111
|
+
if (fs28.existsSync(moduleOhPackagePath)) {
|
|
3676
4112
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
3677
4113
|
pkg.name = args.moduleName;
|
|
3678
4114
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
3679
4115
|
}
|
|
3680
|
-
const vscodeDir =
|
|
3681
|
-
|
|
4116
|
+
const vscodeDir = path27.join(projectDir, ".vscode");
|
|
4117
|
+
fs28.mkdirSync(vscodeDir, { recursive: true });
|
|
3682
4118
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
3683
|
-
writeJsonFile(
|
|
4119
|
+
writeJsonFile(path27.join(vscodeDir, "settings.json"), {
|
|
3684
4120
|
"oniro.hapPath": hapPath,
|
|
3685
4121
|
"files.associations": { "*.json5": "jsonc" }
|
|
3686
4122
|
});
|
|
@@ -3697,7 +4133,7 @@ async function createScaffold(opts) {
|
|
|
3697
4133
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
3698
4134
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
3699
4135
|
}
|
|
3700
|
-
const templateDir =
|
|
4136
|
+
const templateDir = path27.join(opts.templateRoot, opts.templateId);
|
|
3701
4137
|
if (!await pathExists(templateDir)) {
|
|
3702
4138
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
3703
4139
|
}
|
|
@@ -3732,17 +4168,17 @@ async function createScaffold(opts) {
|
|
|
3732
4168
|
}
|
|
3733
4169
|
}
|
|
3734
4170
|
}
|
|
3735
|
-
const projectDir =
|
|
4171
|
+
const projectDir = path27.join(opts.location, opts.projectName);
|
|
3736
4172
|
if (await pathExists(projectDir)) {
|
|
3737
4173
|
if (!opts.overwrite) {
|
|
3738
4174
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
3739
4175
|
}
|
|
3740
|
-
await
|
|
4176
|
+
await fs28.promises.rm(projectDir, { recursive: true, force: true });
|
|
3741
4177
|
}
|
|
3742
4178
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
3743
4179
|
await copyDirRecursive(templateDir, projectDir);
|
|
3744
4180
|
if (moduleName !== defaultModuleName) {
|
|
3745
|
-
renameIfExists(
|
|
4181
|
+
renameIfExists(path27.join(projectDir, defaultModuleName), path27.join(projectDir, moduleName));
|
|
3746
4182
|
}
|
|
3747
4183
|
updateTemplateConfigs(
|
|
3748
4184
|
projectDir,
|
|
@@ -3759,11 +4195,11 @@ async function createScaffold(opts) {
|
|
|
3759
4195
|
}
|
|
3760
4196
|
|
|
3761
4197
|
// src/project/listModules.ts
|
|
3762
|
-
import * as
|
|
3763
|
-
import * as
|
|
4198
|
+
import * as fs29 from "fs";
|
|
4199
|
+
import * as path28 from "path";
|
|
3764
4200
|
function listModules(opts) {
|
|
3765
|
-
const buildProfile =
|
|
3766
|
-
if (!
|
|
4201
|
+
const buildProfile = path28.join(opts.projectDir, "build-profile.json5");
|
|
4202
|
+
if (!fs29.existsSync(buildProfile)) {
|
|
3767
4203
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
3768
4204
|
}
|
|
3769
4205
|
const parsed = readJson5File(buildProfile);
|
|
@@ -3775,12 +4211,12 @@ function listModules(opts) {
|
|
|
3775
4211
|
}
|
|
3776
4212
|
|
|
3777
4213
|
// src/project/abilities.ts
|
|
3778
|
-
import * as
|
|
3779
|
-
import * as
|
|
4214
|
+
import * as fs30 from "fs";
|
|
4215
|
+
import * as path29 from "path";
|
|
3780
4216
|
function listAbilities(opts) {
|
|
3781
4217
|
const moduleName = opts.moduleName ?? "entry";
|
|
3782
|
-
const moduleJsonPath =
|
|
3783
|
-
if (!
|
|
4218
|
+
const moduleJsonPath = path29.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
4219
|
+
if (!fs30.existsSync(moduleJsonPath)) {
|
|
3784
4220
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
3785
4221
|
}
|
|
3786
4222
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -3788,12 +4224,12 @@ function listAbilities(opts) {
|
|
|
3788
4224
|
}
|
|
3789
4225
|
|
|
3790
4226
|
// src/project/prepareSigning.ts
|
|
3791
|
-
import * as
|
|
3792
|
-
import * as
|
|
4227
|
+
import * as fs31 from "fs";
|
|
4228
|
+
import * as path30 from "path";
|
|
3793
4229
|
function hasSigningArtifacts(dir) {
|
|
3794
|
-
if (!
|
|
3795
|
-
const buildProfile =
|
|
3796
|
-
if (!
|
|
4230
|
+
if (!fs31.existsSync(path30.join(dir, "signatures"))) return false;
|
|
4231
|
+
const buildProfile = path30.join(dir, "build-profile.json5");
|
|
4232
|
+
if (!fs31.existsSync(buildProfile)) return false;
|
|
3797
4233
|
try {
|
|
3798
4234
|
const parsed = readJson5File(buildProfile);
|
|
3799
4235
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -3811,6 +4247,7 @@ function prepareSigning(opts) {
|
|
|
3811
4247
|
apl: opts.apl,
|
|
3812
4248
|
appFeature: opts.appFeature,
|
|
3813
4249
|
acls: opts.acls,
|
|
4250
|
+
appPrivilegeCapabilities: opts.appPrivilegeCapabilities,
|
|
3814
4251
|
passwords: opts.passwords,
|
|
3815
4252
|
logger: opts.logger
|
|
3816
4253
|
});
|
|
@@ -3839,19 +4276,31 @@ var PROPS = [
|
|
|
3839
4276
|
["model", "const.product.model"],
|
|
3840
4277
|
["manufacturer", "const.product.manufacturer"],
|
|
3841
4278
|
["brand", "const.product.brand"],
|
|
4279
|
+
["osDistName", "const.product.os.dist.name"],
|
|
4280
|
+
["osDistVersion", "const.product.os.dist.version"],
|
|
3842
4281
|
["osFullName", "const.ohos.fullname"],
|
|
3843
4282
|
["osReleaseType", "const.ohos.releasetype"],
|
|
3844
4283
|
["sdkApiVersion", "const.ohos.apiversion"],
|
|
3845
|
-
["buildVersion", "const.product.software.version"]
|
|
4284
|
+
["buildVersion", "const.product.software.version"],
|
|
4285
|
+
["abi", "const.product.cpu.abilist"],
|
|
4286
|
+
["deviceType", "const.product.devicetype"]
|
|
3846
4287
|
];
|
|
3847
4288
|
async function getDeviceInfo(opts) {
|
|
3848
4289
|
const info = { serial: opts.deviceSerial ?? "(default)", display: null };
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4290
|
+
try {
|
|
4291
|
+
const values = await paramGetMany(
|
|
4292
|
+
opts.config,
|
|
4293
|
+
PROPS.map(([, prop]) => prop),
|
|
4294
|
+
{ deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 1e4, logger: opts.logger }
|
|
4295
|
+
);
|
|
4296
|
+
for (const [field, prop] of PROPS) {
|
|
4297
|
+
const value = values[prop];
|
|
3852
4298
|
if (value) info[field] = value;
|
|
3853
|
-
} catch {
|
|
3854
4299
|
}
|
|
4300
|
+
} catch {
|
|
4301
|
+
}
|
|
4302
|
+
if (info.osDistName || info.osFullName) {
|
|
4303
|
+
info.osFamily = /harmonyos/i.test(info.osDistName ?? "") ? "HarmonyOS" : "OpenHarmony";
|
|
3855
4304
|
}
|
|
3856
4305
|
const screen = await dumpScreen({ config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
3857
4306
|
if (screen) info.display = { width: screen.width, height: screen.height };
|
|
@@ -3883,8 +4332,8 @@ async function diffHapAssets(opts) {
|
|
|
3883
4332
|
}
|
|
3884
4333
|
|
|
3885
4334
|
// src/install/apply.ts
|
|
3886
|
-
import * as
|
|
3887
|
-
import * as
|
|
4335
|
+
import * as fs32 from "fs";
|
|
4336
|
+
import * as path31 from "path";
|
|
3888
4337
|
function decideInstallMethod(s) {
|
|
3889
4338
|
if (!s.signInfoInconsistent) return "replace";
|
|
3890
4339
|
if (s.isSystemBundle && !s.allowUninstall) return "refuse";
|
|
@@ -3902,39 +4351,31 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
3902
4351
|
const proc = await findRunningProcess({ config, bundle, deviceSerial });
|
|
3903
4352
|
return proc ? Number(proc.pid) : null;
|
|
3904
4353
|
}
|
|
3905
|
-
async function
|
|
3906
|
-
let
|
|
3907
|
-
if (hapPath) {
|
|
3908
|
-
|
|
4354
|
+
async function resolveHap(opts, logger) {
|
|
4355
|
+
let hap;
|
|
4356
|
+
if (opts.hapPath) {
|
|
4357
|
+
const hapPath = !path31.isAbsolute(opts.hapPath) && opts.projectDir ? path31.join(opts.projectDir, opts.hapPath) : opts.hapPath;
|
|
4358
|
+
hap = existingHapOrUnsigned(hapPath);
|
|
4359
|
+
if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
3909
4360
|
} else {
|
|
3910
4361
|
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
|
-
}
|
|
4362
|
+
const picked = pickHap(await discoverHaps({ projectDir: opts.projectDir }), opts.module);
|
|
4363
|
+
if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
|
|
4364
|
+
hap = { path: picked.path, fallback: !picked.signed };
|
|
3926
4365
|
}
|
|
3927
|
-
if (
|
|
3928
|
-
return
|
|
4366
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path31.basename(hap.path)}.`);
|
|
4367
|
+
return hap;
|
|
3929
4368
|
}
|
|
3930
4369
|
async function applyChanges(opts) {
|
|
3931
4370
|
const logger = scopedLogger(opts.logger ?? noopLogger, "apply");
|
|
3932
4371
|
const { config, bundle, deviceSerial } = opts;
|
|
3933
4372
|
const isSystemBundle = opts.isSystemBundle ?? false;
|
|
3934
4373
|
const allowUninstall = opts.allowUninstall ?? false;
|
|
3935
|
-
const
|
|
4374
|
+
const hap = await resolveHap(opts, logger);
|
|
4375
|
+
const hapPath = hap.path;
|
|
4376
|
+
logger.info(`installing ${hapPath}`);
|
|
3936
4377
|
let assetsChanged = false;
|
|
3937
|
-
if (opts.installedHapPath &&
|
|
4378
|
+
if (opts.installedHapPath && fs32.existsSync(opts.installedHapPath)) {
|
|
3938
4379
|
try {
|
|
3939
4380
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
3940
4381
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -3955,12 +4396,21 @@ async function applyChanges(opts) {
|
|
|
3955
4396
|
if (decision === "uninstall-install") {
|
|
3956
4397
|
logger.warn(`sign-info inconsistent for ${bundle}; uninstalling then installing fresh.`);
|
|
3957
4398
|
await uninstallApp({ config, bundle, deviceSerial });
|
|
3958
|
-
|
|
4399
|
+
ensureInstallOk(
|
|
4400
|
+
await hdcExec({ config, args: ["install", hapPath], deviceSerial, timeoutMs: 3e5 }),
|
|
4401
|
+
`hdc install ${hapPath}`,
|
|
4402
|
+
hap
|
|
4403
|
+
);
|
|
3959
4404
|
installMethod = "uninstall-install";
|
|
3960
4405
|
} else {
|
|
3961
|
-
|
|
4406
|
+
ensureInstallOk(replaceRes, `hdc install -r ${hapPath}`, hap);
|
|
3962
4407
|
installMethod = "replace";
|
|
3963
4408
|
}
|
|
4409
|
+
if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
|
|
4410
|
+
throw new OniroError(
|
|
4411
|
+
`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).`
|
|
4412
|
+
);
|
|
4413
|
+
}
|
|
3964
4414
|
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
3965
4415
|
const rebootDecision = decideReboot({ assetsChanged, isSystemBundle, preInstallPid, postInstallPid });
|
|
3966
4416
|
let method = installMethod;
|
|
@@ -4035,9 +4485,9 @@ var AUTH_PATHS = {
|
|
|
4035
4485
|
};
|
|
4036
4486
|
|
|
4037
4487
|
// src/harmonyos/auth/tokenStore.ts
|
|
4038
|
-
import * as
|
|
4488
|
+
import * as fs33 from "fs";
|
|
4039
4489
|
import * as os11 from "os";
|
|
4040
|
-
import * as
|
|
4490
|
+
import * as path32 from "path";
|
|
4041
4491
|
import * as crypto3 from "crypto";
|
|
4042
4492
|
var ALGORITHM = "aes-256-gcm";
|
|
4043
4493
|
var KEY_LENGTH = 32;
|
|
@@ -4073,7 +4523,7 @@ function permissionHint(dir) {
|
|
|
4073
4523
|
}
|
|
4074
4524
|
function ensureDir(dir) {
|
|
4075
4525
|
try {
|
|
4076
|
-
|
|
4526
|
+
fs33.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
4077
4527
|
} catch (err) {
|
|
4078
4528
|
if (err.code === "EACCES") {
|
|
4079
4529
|
throw new OniroError(permissionHint(dir), err);
|
|
@@ -4084,17 +4534,17 @@ function ensureDir(dir) {
|
|
|
4084
4534
|
function createTokenStore(opts) {
|
|
4085
4535
|
const logger = opts.logger ?? noopLogger;
|
|
4086
4536
|
const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
|
|
4087
|
-
const tokenPath =
|
|
4088
|
-
const keyPath =
|
|
4537
|
+
const tokenPath = path32.join(authDir, TOKEN_FILE);
|
|
4538
|
+
const keyPath = path32.join(os11.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
|
|
4089
4539
|
function loadOrCreateKey() {
|
|
4090
|
-
if (
|
|
4091
|
-
const existing =
|
|
4540
|
+
if (fs33.existsSync(keyPath)) {
|
|
4541
|
+
const existing = fs33.readFileSync(keyPath);
|
|
4092
4542
|
if (existing.length === KEY_LENGTH) return existing;
|
|
4093
4543
|
logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
|
|
4094
4544
|
}
|
|
4095
|
-
ensureDir(
|
|
4545
|
+
ensureDir(path32.dirname(keyPath));
|
|
4096
4546
|
const key = crypto3.randomBytes(KEY_LENGTH);
|
|
4097
|
-
|
|
4547
|
+
fs33.writeFileSync(keyPath, key, { mode: FILE_MODE });
|
|
4098
4548
|
return key;
|
|
4099
4549
|
}
|
|
4100
4550
|
return {
|
|
@@ -4104,12 +4554,12 @@ function createTokenStore(opts) {
|
|
|
4104
4554
|
const payload = Buffer.from(JSON.stringify(session), "utf8");
|
|
4105
4555
|
const blob = encrypt2(loadOrCreateKey(), payload);
|
|
4106
4556
|
ensureDir(authDir);
|
|
4107
|
-
|
|
4557
|
+
fs33.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
|
|
4108
4558
|
},
|
|
4109
4559
|
load() {
|
|
4110
|
-
if (!
|
|
4560
|
+
if (!fs33.existsSync(tokenPath)) return null;
|
|
4111
4561
|
try {
|
|
4112
|
-
const blob = JSON.parse(
|
|
4562
|
+
const blob = JSON.parse(fs33.readFileSync(tokenPath, "utf8"));
|
|
4113
4563
|
if (!isEncryptedBlob(blob)) return null;
|
|
4114
4564
|
const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
|
|
4115
4565
|
return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
|
|
@@ -4121,7 +4571,7 @@ function createTokenStore(opts) {
|
|
|
4121
4571
|
}
|
|
4122
4572
|
logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
|
|
4123
4573
|
try {
|
|
4124
|
-
|
|
4574
|
+
fs33.rmSync(tokenPath, { force: true });
|
|
4125
4575
|
} catch {
|
|
4126
4576
|
}
|
|
4127
4577
|
return null;
|
|
@@ -4129,7 +4579,7 @@ function createTokenStore(opts) {
|
|
|
4129
4579
|
},
|
|
4130
4580
|
clear() {
|
|
4131
4581
|
try {
|
|
4132
|
-
|
|
4582
|
+
fs33.rmSync(tokenPath, { force: true });
|
|
4133
4583
|
} catch (err) {
|
|
4134
4584
|
throw new OniroError(`Failed to remove ${tokenPath}.`, err);
|
|
4135
4585
|
}
|
|
@@ -4243,8 +4693,8 @@ function isQuit(quit) {
|
|
|
4243
4693
|
async function startCallbackServer(opts) {
|
|
4244
4694
|
let resolveCallback;
|
|
4245
4695
|
let rejectCallback;
|
|
4246
|
-
const callback = new Promise((
|
|
4247
|
-
resolveCallback =
|
|
4696
|
+
const callback = new Promise((resolve7, reject) => {
|
|
4697
|
+
resolveCallback = resolve7;
|
|
4248
4698
|
rejectCallback = reject;
|
|
4249
4699
|
});
|
|
4250
4700
|
callback.catch(() => {
|
|
@@ -4303,9 +4753,9 @@ async function startCallbackServer(opts) {
|
|
|
4303
4753
|
}
|
|
4304
4754
|
});
|
|
4305
4755
|
server.keepAliveTimeout = 1;
|
|
4306
|
-
await new Promise((
|
|
4756
|
+
await new Promise((resolve7, reject) => {
|
|
4307
4757
|
server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
|
|
4308
|
-
server.listen(0, "127.0.0.1",
|
|
4758
|
+
server.listen(0, "127.0.0.1", resolve7);
|
|
4309
4759
|
});
|
|
4310
4760
|
const port = server.address().port;
|
|
4311
4761
|
return {
|
|
@@ -4321,12 +4771,12 @@ async function startCallbackServer(opts) {
|
|
|
4321
4771
|
return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
|
|
4322
4772
|
},
|
|
4323
4773
|
stop() {
|
|
4324
|
-
return new Promise((
|
|
4774
|
+
return new Promise((resolve7) => {
|
|
4325
4775
|
server.closeAllConnections?.();
|
|
4326
|
-
const timer = setTimeout(
|
|
4776
|
+
const timer = setTimeout(resolve7, 100);
|
|
4327
4777
|
server.close(() => {
|
|
4328
4778
|
clearTimeout(timer);
|
|
4329
|
-
|
|
4779
|
+
resolve7();
|
|
4330
4780
|
});
|
|
4331
4781
|
});
|
|
4332
4782
|
}
|
|
@@ -4358,11 +4808,11 @@ function openBrowser(url) {
|
|
|
4358
4808
|
windowsHide: true
|
|
4359
4809
|
});
|
|
4360
4810
|
child.unref();
|
|
4361
|
-
return new Promise((
|
|
4811
|
+
return new Promise((resolve7, reject) => {
|
|
4362
4812
|
child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
|
|
4363
4813
|
child.on(
|
|
4364
4814
|
"exit",
|
|
4365
|
-
(code) => code === 0 ?
|
|
4815
|
+
(code) => code === 0 ? resolve7() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
|
|
4366
4816
|
);
|
|
4367
4817
|
});
|
|
4368
4818
|
}
|
|
@@ -4610,7 +5060,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
|
|
|
4610
5060
|
}
|
|
4611
5061
|
|
|
4612
5062
|
// src/harmonyos/signing/profile.ts
|
|
4613
|
-
import * as
|
|
5063
|
+
import * as fs34 from "fs";
|
|
4614
5064
|
import { X509Certificate } from "crypto";
|
|
4615
5065
|
import forge from "node-forge";
|
|
4616
5066
|
function extractProfileJson(buffer) {
|
|
@@ -4642,7 +5092,7 @@ function extractProfileJson(buffer) {
|
|
|
4642
5092
|
function readProvisionProfile(profilePath) {
|
|
4643
5093
|
let raw;
|
|
4644
5094
|
try {
|
|
4645
|
-
const json = extractProfileJson(
|
|
5095
|
+
const json = extractProfileJson(fs34.readFileSync(profilePath));
|
|
4646
5096
|
if (!json) return null;
|
|
4647
5097
|
raw = JSON.parse(json);
|
|
4648
5098
|
} catch {
|
|
@@ -4671,7 +5121,7 @@ function parseCertificate(pem) {
|
|
|
4671
5121
|
}
|
|
4672
5122
|
function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
4673
5123
|
try {
|
|
4674
|
-
const der = forge.util.createBuffer(
|
|
5124
|
+
const der = forge.util.createBuffer(fs34.readFileSync(p12Path).toString("binary"));
|
|
4675
5125
|
const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
|
|
4676
5126
|
const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
|
|
4677
5127
|
const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
|
|
@@ -4687,7 +5137,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
|
4687
5137
|
}
|
|
4688
5138
|
}
|
|
4689
5139
|
function verifySigningMaterial(opts) {
|
|
4690
|
-
const pems =
|
|
5140
|
+
const pems = fs34.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
4691
5141
|
const chain = (pems ?? []).map(parseCertificate);
|
|
4692
5142
|
const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
|
|
4693
5143
|
if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
|
|
@@ -4706,14 +5156,14 @@ function verifySigningMaterial(opts) {
|
|
|
4706
5156
|
|
|
4707
5157
|
// src/harmonyos/signing/autoSign.ts
|
|
4708
5158
|
import * as crypto9 from "crypto";
|
|
4709
|
-
import * as
|
|
4710
|
-
import * as
|
|
5159
|
+
import * as fs39 from "fs";
|
|
5160
|
+
import * as path38 from "path";
|
|
4711
5161
|
import JSON57 from "json5";
|
|
4712
5162
|
|
|
4713
5163
|
// src/harmonyos/signing/agc.ts
|
|
4714
5164
|
import * as crypto6 from "crypto";
|
|
4715
|
-
import * as
|
|
4716
|
-
import * as
|
|
5165
|
+
import * as fs35 from "fs";
|
|
5166
|
+
import * as path33 from "path";
|
|
4717
5167
|
var ENDPOINTS = {
|
|
4718
5168
|
CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
|
|
4719
5169
|
CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
|
|
@@ -4799,8 +5249,8 @@ function createAgcClient(http3, auth) {
|
|
|
4799
5249
|
if (target.sha256 && actual !== target.sha256.toLowerCase()) {
|
|
4800
5250
|
throw new ChecksumMismatchError(target.sha256, actual);
|
|
4801
5251
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
5252
|
+
fs35.mkdirSync(path33.dirname(dest), { recursive: true, mode: 448 });
|
|
5253
|
+
fs35.writeFileSync(dest, buffer, { mode: 384 });
|
|
4804
5254
|
}
|
|
4805
5255
|
};
|
|
4806
5256
|
}
|
|
@@ -4851,22 +5301,22 @@ async function registerDevices(agc, connected, logger) {
|
|
|
4851
5301
|
|
|
4852
5302
|
// src/harmonyos/signing/keystore.ts
|
|
4853
5303
|
import * as crypto7 from "crypto";
|
|
4854
|
-
import * as
|
|
4855
|
-
import * as
|
|
5304
|
+
import * as fs37 from "fs";
|
|
5305
|
+
import * as path35 from "path";
|
|
4856
5306
|
|
|
4857
5307
|
// src/harmonyos/java.ts
|
|
4858
|
-
import * as
|
|
4859
|
-
import * as
|
|
5308
|
+
import * as fs36 from "fs";
|
|
5309
|
+
import * as path34 from "path";
|
|
4860
5310
|
var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
|
|
4861
5311
|
function findJava(sdk, env = process.env) {
|
|
4862
5312
|
const candidates = [
|
|
4863
|
-
env.JAVA_HOME &&
|
|
4864
|
-
sdk &&
|
|
5313
|
+
env.JAVA_HOME && path34.join(env.JAVA_HOME, "bin", JAVA_BIN),
|
|
5314
|
+
sdk && path34.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
|
|
4865
5315
|
// macOS: the JBR is itself a bundle inside Studio's Contents.
|
|
4866
|
-
sdk &&
|
|
4867
|
-
...(env.PATH ?? "").split(
|
|
5316
|
+
sdk && path34.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
|
|
5317
|
+
...(env.PATH ?? "").split(path34.delimiter).map((dir) => dir && path34.join(dir, JAVA_BIN))
|
|
4868
5318
|
];
|
|
4869
|
-
const found = candidates.find((c) => !!c &&
|
|
5319
|
+
const found = candidates.find((c) => !!c && fs36.existsSync(c));
|
|
4870
5320
|
if (found) return found;
|
|
4871
5321
|
throw new OniroError(
|
|
4872
5322
|
"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 +5330,13 @@ function redactArgs(args) {
|
|
|
4880
5330
|
return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
|
|
4881
5331
|
}
|
|
4882
5332
|
async function generateKeystoreAndCsr(opts) {
|
|
4883
|
-
const jar =
|
|
4884
|
-
if (!
|
|
5333
|
+
const jar = path35.join(opts.sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
|
|
5334
|
+
if (!fs37.existsSync(jar)) {
|
|
4885
5335
|
throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
|
|
4886
5336
|
}
|
|
4887
5337
|
const java = findJava(opts.sdk);
|
|
4888
5338
|
const keyPwd = crypto7.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
|
|
4889
|
-
|
|
5339
|
+
fs37.mkdirSync(path35.dirname(opts.p12Path), { recursive: true, mode: 448 });
|
|
4890
5340
|
const run = async (what, args) => {
|
|
4891
5341
|
const argv = ["-jar", jar, ...args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", keyPwd, "-keyPwd", keyPwd];
|
|
4892
5342
|
opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
|
|
@@ -4900,28 +5350,28 @@ ${detail}` : ""}`);
|
|
|
4900
5350
|
opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
|
|
4901
5351
|
await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
|
|
4902
5352
|
await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
|
|
4903
|
-
return { keyPwd, csr:
|
|
5353
|
+
return { keyPwd, csr: fs37.readFileSync(opts.csrPath, "utf8") };
|
|
4904
5354
|
}
|
|
4905
5355
|
|
|
4906
5356
|
// src/harmonyos/signing/paths.ts
|
|
4907
5357
|
import * as crypto8 from "crypto";
|
|
4908
|
-
import * as
|
|
5358
|
+
import * as path36 from "path";
|
|
4909
5359
|
function sanitizeNamePart(raw) {
|
|
4910
5360
|
const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
|
|
4911
5361
|
return safe.slice(0, 64) || "default";
|
|
4912
5362
|
}
|
|
4913
5363
|
function resolveSigningMaterialPaths(config, productName, projectRoot) {
|
|
4914
5364
|
const hash = crypto8.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
|
|
4915
|
-
const base =
|
|
5365
|
+
const base = path36.join(
|
|
4916
5366
|
config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
|
|
4917
|
-
`${sanitizeNamePart(productName)}_${
|
|
5367
|
+
`${sanitizeNamePart(productName)}_${path36.basename(projectRoot)}_${hash}=`
|
|
4918
5368
|
);
|
|
4919
5369
|
return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
|
|
4920
5370
|
}
|
|
4921
5371
|
|
|
4922
5372
|
// src/harmonyos/signing/project.ts
|
|
4923
|
-
import * as
|
|
4924
|
-
import * as
|
|
5373
|
+
import * as fs38 from "fs";
|
|
5374
|
+
import * as path37 from "path";
|
|
4925
5375
|
import JSON56 from "json5";
|
|
4926
5376
|
|
|
4927
5377
|
// src/harmonyos/signing/aclPermissionList.ts
|
|
@@ -4989,9 +5439,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
|
4989
5439
|
|
|
4990
5440
|
// src/harmonyos/signing/project.ts
|
|
4991
5441
|
function readRequestedPermissions(moduleConfigPath) {
|
|
4992
|
-
if (!
|
|
5442
|
+
if (!fs38.existsSync(moduleConfigPath)) return [];
|
|
4993
5443
|
try {
|
|
4994
|
-
const parsed = JSON56.parse(
|
|
5444
|
+
const parsed = JSON56.parse(fs38.readFileSync(moduleConfigPath, "utf-8"));
|
|
4995
5445
|
return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
4996
5446
|
} catch {
|
|
4997
5447
|
return [];
|
|
@@ -5000,10 +5450,10 @@ function readRequestedPermissions(moduleConfigPath) {
|
|
|
5000
5450
|
function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
5001
5451
|
const requested = /* @__PURE__ */ new Set();
|
|
5002
5452
|
for (const module of listModules({ projectDir })) {
|
|
5003
|
-
const moduleRoot =
|
|
5453
|
+
const moduleRoot = path37.resolve(projectDir, module.srcPath);
|
|
5004
5454
|
for (const sourceSet of ["main", "ohosTest"]) {
|
|
5005
5455
|
for (const name of readRequestedPermissions(
|
|
5006
|
-
|
|
5456
|
+
path37.join(moduleRoot, "src", sourceSet, "module.json5")
|
|
5007
5457
|
)) {
|
|
5008
5458
|
requested.add(name);
|
|
5009
5459
|
}
|
|
@@ -5019,7 +5469,7 @@ function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
|
5019
5469
|
// src/harmonyos/signing/autoSign.ts
|
|
5020
5470
|
function shouldRegenerate(input) {
|
|
5021
5471
|
if (input.force) return "--force was passed";
|
|
5022
|
-
const missing = Object.values(input.paths).find((file) => !
|
|
5472
|
+
const missing = Object.values(input.paths).find((file) => !fs39.existsSync(file));
|
|
5023
5473
|
if (missing) return `${missing} is missing`;
|
|
5024
5474
|
const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
|
|
5025
5475
|
if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
|
|
@@ -5058,16 +5508,16 @@ function writeSigningConfig(opts) {
|
|
|
5058
5508
|
for (const product of app.products ?? []) {
|
|
5059
5509
|
if (product.name === opts.productName) product.signingConfig = opts.productName;
|
|
5060
5510
|
}
|
|
5061
|
-
|
|
5511
|
+
fs39.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
|
|
5062
5512
|
}
|
|
5063
5513
|
async function harmonyOsAutoSign(opts) {
|
|
5064
5514
|
const logger = opts.logger ?? noopLogger;
|
|
5065
5515
|
const productName = opts.productName ?? "default";
|
|
5066
|
-
const projectDir =
|
|
5067
|
-
const buildProfilePath =
|
|
5516
|
+
const projectDir = path38.resolve(opts.projectDir);
|
|
5517
|
+
const buildProfilePath = path38.join(projectDir, "build-profile.json5");
|
|
5068
5518
|
let buildProfile;
|
|
5069
5519
|
try {
|
|
5070
|
-
buildProfile = JSON57.parse(
|
|
5520
|
+
buildProfile = JSON57.parse(fs39.readFileSync(buildProfilePath, "utf-8"));
|
|
5071
5521
|
} catch (err) {
|
|
5072
5522
|
throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
|
|
5073
5523
|
}
|
|
@@ -5107,7 +5557,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5107
5557
|
logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
|
|
5108
5558
|
const agc = createAgcClient(opts.http ?? createHttpClient({ logger }), auth);
|
|
5109
5559
|
const deviceIds = await registerDevices(agc, connected, logger);
|
|
5110
|
-
for (const file of Object.values(paths))
|
|
5560
|
+
for (const file of Object.values(paths)) fs39.rmSync(file, { force: true });
|
|
5111
5561
|
const certName = `oniro_debug_${auth.teamId.replace(/[\\/.:]/g, "")}.cer`;
|
|
5112
5562
|
const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
|
|
5113
5563
|
if (stale) {
|
|
@@ -5138,10 +5588,10 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5138
5588
|
try {
|
|
5139
5589
|
verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
|
|
5140
5590
|
} catch (err) {
|
|
5141
|
-
|
|
5591
|
+
fs39.rmSync(paths.profilePath, { force: true });
|
|
5142
5592
|
throw err;
|
|
5143
5593
|
}
|
|
5144
|
-
const materialDir =
|
|
5594
|
+
const materialDir = path38.join(path38.dirname(paths.p12Path), "material");
|
|
5145
5595
|
try {
|
|
5146
5596
|
getKey(materialDir);
|
|
5147
5597
|
} catch {
|
|
@@ -5153,7 +5603,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5153
5603
|
}
|
|
5154
5604
|
|
|
5155
5605
|
// src/index.ts
|
|
5156
|
-
var VERSION = "0.
|
|
5606
|
+
var VERSION = "0.14.0";
|
|
5157
5607
|
export {
|
|
5158
5608
|
ALL_SDKS,
|
|
5159
5609
|
APL_VALUES,
|
|
@@ -5175,7 +5625,9 @@ export {
|
|
|
5175
5625
|
TargetNotFoundError,
|
|
5176
5626
|
UnsupportedPlatformError,
|
|
5177
5627
|
VERSION,
|
|
5628
|
+
appSandboxDir,
|
|
5178
5629
|
applyChanges,
|
|
5630
|
+
assertDevicePath,
|
|
5179
5631
|
assertLayoutSelector,
|
|
5180
5632
|
attemptHdcConnection,
|
|
5181
5633
|
buildCmdWrapper,
|
|
@@ -5186,6 +5638,8 @@ export {
|
|
|
5186
5638
|
buildInputCommand,
|
|
5187
5639
|
buildRawTouchCommand,
|
|
5188
5640
|
captureBurst,
|
|
5641
|
+
captureLog,
|
|
5642
|
+
cleanCodelinterOutput,
|
|
5189
5643
|
consoleLogger,
|
|
5190
5644
|
createHarmonyOsSession,
|
|
5191
5645
|
createInstallTempDir,
|
|
@@ -5201,9 +5655,12 @@ export {
|
|
|
5201
5655
|
defaultPaths,
|
|
5202
5656
|
describeLayoutNode,
|
|
5203
5657
|
detectAaStartFailure,
|
|
5658
|
+
detectCmdToolsFlavour,
|
|
5659
|
+
detectInstallFailure,
|
|
5204
5660
|
detectProjectSdkVersion,
|
|
5205
5661
|
detectRuntimeOs,
|
|
5206
5662
|
detectSigningConfigNames,
|
|
5663
|
+
diagnoseBuildFailure,
|
|
5207
5664
|
diffEntryNames,
|
|
5208
5665
|
diffHapAssets,
|
|
5209
5666
|
discoverHaps,
|
|
@@ -5216,11 +5673,13 @@ export {
|
|
|
5216
5673
|
dumpWindow,
|
|
5217
5674
|
encryptPwd,
|
|
5218
5675
|
ensureFreeSpace,
|
|
5676
|
+
ensureInstallOk,
|
|
5219
5677
|
ensureOhModules,
|
|
5220
5678
|
ensureOk,
|
|
5221
5679
|
ensureRemoteOk,
|
|
5222
5680
|
escapeCmdArgument,
|
|
5223
5681
|
escapeCmdCommand,
|
|
5682
|
+
existingHapOrUnsigned,
|
|
5224
5683
|
extractTarball,
|
|
5225
5684
|
extractZipWithProgress,
|
|
5226
5685
|
findAppProcessId,
|
|
@@ -5229,6 +5688,7 @@ export {
|
|
|
5229
5688
|
findHarmonyOsSdk,
|
|
5230
5689
|
findKeyboardWindow,
|
|
5231
5690
|
findLayoutNodes,
|
|
5691
|
+
findMissingProjectSdk,
|
|
5232
5692
|
findRunningProcess,
|
|
5233
5693
|
flattenLayout,
|
|
5234
5694
|
forceStop,
|
|
@@ -5242,6 +5702,7 @@ export {
|
|
|
5242
5702
|
getDeviceInfo,
|
|
5243
5703
|
getDisplaySize,
|
|
5244
5704
|
getEmulatorDir,
|
|
5705
|
+
getEmulatorStatus,
|
|
5245
5706
|
getFreeSpaceBytes,
|
|
5246
5707
|
getHdcPath,
|
|
5247
5708
|
getHvigorwPath,
|
|
@@ -5257,15 +5718,21 @@ export {
|
|
|
5257
5718
|
getSupportedSdksForUi,
|
|
5258
5719
|
harmonyOsAutoSign,
|
|
5259
5720
|
harmonyOsSdkLabelForApi,
|
|
5721
|
+
hdcClientFailure,
|
|
5260
5722
|
hdcExec,
|
|
5723
|
+
hdcTargetArgs,
|
|
5724
|
+
hdcTargetArgsFor,
|
|
5261
5725
|
hideKeyboard,
|
|
5262
5726
|
installApp,
|
|
5263
5727
|
installCmdTools,
|
|
5264
5728
|
installEmulator,
|
|
5729
|
+
installFailureReason,
|
|
5730
|
+
isBundleInstalled,
|
|
5265
5731
|
isCmdToolsInstalled,
|
|
5266
5732
|
isEmulatorInstalled,
|
|
5267
5733
|
isOutOfSpaceError,
|
|
5268
5734
|
isRamBackedPath,
|
|
5735
|
+
isUnsignedHap,
|
|
5269
5736
|
isValidBundleName,
|
|
5270
5737
|
isValidProjectName,
|
|
5271
5738
|
killProcessTree,
|
|
@@ -5280,16 +5747,21 @@ export {
|
|
|
5280
5747
|
nonInteractivePrompter,
|
|
5281
5748
|
noopLogger,
|
|
5282
5749
|
noopProgress,
|
|
5750
|
+
normaliseLocalPath,
|
|
5283
5751
|
paramGet,
|
|
5752
|
+
paramGetMany,
|
|
5284
5753
|
paramSet,
|
|
5285
5754
|
parseBounds,
|
|
5286
5755
|
parseCodelinterFindings,
|
|
5756
|
+
parseCodelinterReport,
|
|
5287
5757
|
parseDeviceList,
|
|
5288
5758
|
parseHilogLine,
|
|
5289
5759
|
parseImeBundles,
|
|
5290
5760
|
parseMainAbilityFromBmDump,
|
|
5761
|
+
parseParamValues,
|
|
5291
5762
|
parseRemoteExit,
|
|
5292
5763
|
performInput,
|
|
5764
|
+
pickHap,
|
|
5293
5765
|
pickSigningKind,
|
|
5294
5766
|
pickTarget,
|
|
5295
5767
|
prepareSigning,
|
|
@@ -5304,6 +5776,7 @@ export {
|
|
|
5304
5776
|
removeSdk,
|
|
5305
5777
|
removeTempWorkDir,
|
|
5306
5778
|
requireHarmonyOsSdk,
|
|
5779
|
+
resolveDeviceSerial,
|
|
5307
5780
|
resolveKeyEvent,
|
|
5308
5781
|
resolveTmpRoot,
|
|
5309
5782
|
runCodelinter,
|
|
@@ -5328,6 +5801,7 @@ export {
|
|
|
5328
5801
|
toLongPath,
|
|
5329
5802
|
toSpaceError,
|
|
5330
5803
|
uninstallApp,
|
|
5804
|
+
unsignedHapRejection,
|
|
5331
5805
|
validateTemplateLayout,
|
|
5332
5806
|
verifySha256,
|
|
5333
5807
|
waitForBoot,
|