@oniroproject/core 0.15.1 → 0.16.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/dist/index.cjs +672 -358
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -1
- package/dist/index.d.ts +107 -1
- package/dist/index.js +665 -357
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -581,14 +581,14 @@ var pipelineAsync = promisify(pipeline);
|
|
|
581
581
|
async function downloadFile(opts) {
|
|
582
582
|
const { url, dest, progress, abortSignal } = opts;
|
|
583
583
|
const proto = url.startsWith("https") ? https : http;
|
|
584
|
-
return new Promise((
|
|
584
|
+
return new Promise((resolve9, reject) => {
|
|
585
585
|
const s = Math.max(0, Math.min(100, opts.start ?? 0));
|
|
586
586
|
const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
|
|
587
587
|
let settled = false;
|
|
588
588
|
const done = (err) => {
|
|
589
589
|
if (settled) return;
|
|
590
590
|
settled = true;
|
|
591
|
-
err ? reject(err) :
|
|
591
|
+
err ? reject(err) : resolve9();
|
|
592
592
|
};
|
|
593
593
|
if (abortSignal?.aborted) {
|
|
594
594
|
done(new CancelledError("Download cancelled before start."));
|
|
@@ -766,15 +766,26 @@ async function extractZipWithProgress(opts) {
|
|
|
766
766
|
async function extractTarball(tarPath, dest, strip = 0, filter) {
|
|
767
767
|
await tar.x({ file: tarPath, cwd: dest, strip, ...filter ? { filter: (p) => filter(p) } : {} });
|
|
768
768
|
}
|
|
769
|
-
|
|
770
|
-
|
|
769
|
+
var SIGNED_HAP_CHUNK_SIZE = 128 * 1024;
|
|
770
|
+
async function withHapZip(zipPath, use) {
|
|
771
|
+
const zip = new StreamZip({ file: zipPath, chunkSize: SIGNED_HAP_CHUNK_SIZE, storeEntries: true });
|
|
772
|
+
const close = () => new Promise((resolve9) => zip.close(() => resolve9()));
|
|
771
773
|
try {
|
|
772
|
-
|
|
773
|
-
|
|
774
|
+
await new Promise((resolve9, reject) => {
|
|
775
|
+
zip.once("ready", () => resolve9());
|
|
776
|
+
zip.once("error", (err) => reject(err));
|
|
777
|
+
});
|
|
778
|
+
return await use({ entries: () => zip.entries(), entryData: (name) => zip.entryDataSync(name) });
|
|
774
779
|
} finally {
|
|
775
|
-
await
|
|
780
|
+
await close();
|
|
776
781
|
}
|
|
777
782
|
}
|
|
783
|
+
function readZipEntry(zipPath, match) {
|
|
784
|
+
return withHapZip(zipPath, (zip) => {
|
|
785
|
+
const entry = Object.values(zip.entries()).find((e) => !e.isDirectory && match(e.name));
|
|
786
|
+
return entry ? zip.entryData(entry.name) : void 0;
|
|
787
|
+
});
|
|
788
|
+
}
|
|
778
789
|
|
|
779
790
|
// src/sdk/list.ts
|
|
780
791
|
import * as fs7 from "fs";
|
|
@@ -1425,9 +1436,16 @@ var SIGNING_KIND_CONFIG = {
|
|
|
1425
1436
|
certBasename: "OpenHarmonyApplication.cer"
|
|
1426
1437
|
}
|
|
1427
1438
|
};
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
|
|
1439
|
+
function sdkSigningFiles(sdkPath) {
|
|
1440
|
+
return {
|
|
1441
|
+
signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1442
|
+
keystore: path14.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1443
|
+
profileCert: path14.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1444
|
+
unsignedProfileTemplate: path14.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function copySigningFiles(signaturesDir, paths, kind, logger, applicationCertPath) {
|
|
1448
|
+
logger.info(`[sign] Copying signing material into ${signaturesDir}...`);
|
|
1431
1449
|
fs13.mkdirSync(signaturesDir, { recursive: true });
|
|
1432
1450
|
fs13.copyFileSync(paths.keystore, path14.join(signaturesDir, "OpenHarmony.p12"));
|
|
1433
1451
|
fs13.copyFileSync(paths.profileCert, path14.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
|
|
@@ -1454,10 +1472,7 @@ function extractLeafCertificate(chainPath) {
|
|
|
1454
1472
|
return parts[2].trim() + "\n-----END CERTIFICATE-----\n";
|
|
1455
1473
|
}
|
|
1456
1474
|
function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}) {
|
|
1457
|
-
const kind = options.kind ?? pickSigningKind(apl);
|
|
1458
|
-
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
1459
1475
|
const appJsonPath = path14.join(projectDir, "AppScope/app.json5");
|
|
1460
|
-
const profileTemplatePath = path14.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
|
|
1461
1476
|
if (!fs13.existsSync(appJsonPath)) {
|
|
1462
1477
|
throw new OniroError(`${appJsonPath} does not exist.`);
|
|
1463
1478
|
}
|
|
@@ -1467,26 +1482,28 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1467
1482
|
} catch (e) {
|
|
1468
1483
|
throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
|
|
1469
1484
|
}
|
|
1485
|
+
if (!appJson.app?.bundleName) {
|
|
1486
|
+
throw new OniroError("app.json5 does not contain app.bundleName.");
|
|
1487
|
+
}
|
|
1488
|
+
applyProfileTemplate(path14.join(projectDir, "signatures"), appJson.app.bundleName, apl, appFeature, logger, options);
|
|
1489
|
+
}
|
|
1490
|
+
function applyProfileTemplate(signaturesDir, bundleName, apl, appFeature, logger, options = {}) {
|
|
1491
|
+
const kind = options.kind ?? pickSigningKind(apl);
|
|
1492
|
+
logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature}, kind=${kind})...`);
|
|
1493
|
+
const profileTemplatePath = path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
1470
1494
|
let profileTemplate;
|
|
1471
1495
|
try {
|
|
1472
1496
|
profileTemplate = JSON.parse(fs13.readFileSync(profileTemplatePath, "utf-8"));
|
|
1473
1497
|
} catch (e) {
|
|
1474
1498
|
throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
|
|
1475
1499
|
}
|
|
1476
|
-
if (!appJson.app?.bundleName) {
|
|
1477
|
-
throw new OniroError("app.json5 does not contain app.bundleName.");
|
|
1478
|
-
}
|
|
1479
1500
|
if (!profileTemplate["bundle-info"]) {
|
|
1480
1501
|
throw new OniroError("UnsgnedReleasedProfileTemplate.json is missing the bundle-info section.");
|
|
1481
1502
|
}
|
|
1482
|
-
profileTemplate["bundle-info"]["bundle-name"] =
|
|
1503
|
+
profileTemplate["bundle-info"]["bundle-name"] = bundleName;
|
|
1483
1504
|
profileTemplate["bundle-info"]["apl"] = apl;
|
|
1484
1505
|
profileTemplate["bundle-info"]["app-feature"] = appFeature;
|
|
1485
|
-
const distCertChainPath = path14.join(
|
|
1486
|
-
projectDir,
|
|
1487
|
-
"signatures",
|
|
1488
|
-
SIGNING_KIND_CONFIG[kind].certBasename
|
|
1489
|
-
);
|
|
1506
|
+
const distCertChainPath = path14.join(signaturesDir, SIGNING_KIND_CONFIG[kind].certBasename);
|
|
1490
1507
|
profileTemplate["bundle-info"]["distribution-certificate"] = extractLeafCertificate(distCertChainPath);
|
|
1491
1508
|
if (options.acls !== void 0) {
|
|
1492
1509
|
profileTemplate.acls = profileTemplate.acls ?? {};
|
|
@@ -1497,9 +1514,8 @@ function modifyProfileTemplate(projectDir, apl, appFeature, logger, options = {}
|
|
|
1497
1514
|
}
|
|
1498
1515
|
fs13.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
|
|
1499
1516
|
}
|
|
1500
|
-
function generateP7bFile(
|
|
1517
|
+
function generateP7bFile(signaturesDir, paths, logger, passwords) {
|
|
1501
1518
|
logger.info("[sign] Generating P7b profile via hap-sign-tool...");
|
|
1502
|
-
const signaturesDir = path14.join(projectDir, "signatures");
|
|
1503
1519
|
const profileTemplatePath = path14.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
|
|
1504
1520
|
const outputProfilePath = path14.join(signaturesDir, "app1-profile.p7b");
|
|
1505
1521
|
const args = [
|
|
@@ -1623,30 +1639,201 @@ function generateSigningConfigs(options) {
|
|
|
1623
1639
|
if (!fs13.existsSync(sdkPath)) {
|
|
1624
1640
|
throw new SdkNotInstalledError(String(sdkVersion), sdkPath, ALL_SDKS.find((s) => s.api === String(sdkVersion))?.version);
|
|
1625
1641
|
}
|
|
1626
|
-
const paths =
|
|
1627
|
-
signTool: path14.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
|
|
1628
|
-
keystore: path14.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
|
|
1629
|
-
profileCert: path14.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
|
|
1630
|
-
unsignedProfileTemplate: path14.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
|
|
1631
|
-
};
|
|
1642
|
+
const paths = sdkSigningFiles(sdkPath);
|
|
1632
1643
|
const kind = pickSigningKind(apl);
|
|
1633
1644
|
const passwords = options.passwords ?? DEFAULT_PASSWORDS;
|
|
1634
1645
|
logger.info("[sign] Starting signing configuration generation...");
|
|
1635
|
-
|
|
1646
|
+
const signaturesDir = path14.join(projectDir, "signatures");
|
|
1647
|
+
copySigningFiles(signaturesDir, paths, kind, logger, options.applicationCertPath);
|
|
1636
1648
|
modifyProfileTemplate(projectDir, apl, appFeature, logger, {
|
|
1637
1649
|
kind,
|
|
1638
1650
|
acls: options.acls,
|
|
1639
1651
|
appPrivilegeCapabilities: options.appPrivilegeCapabilities
|
|
1640
1652
|
});
|
|
1641
|
-
generateP7bFile(
|
|
1653
|
+
generateP7bFile(signaturesDir, paths, logger, passwords);
|
|
1642
1654
|
prepareMaterialDirectory(projectDir, logger);
|
|
1643
1655
|
updateBuildProfile(projectDir, kind, logger, passwords);
|
|
1644
1656
|
logger.info("[sign] Signing configuration generated successfully.");
|
|
1645
1657
|
}
|
|
1646
1658
|
|
|
1647
|
-
// src/
|
|
1659
|
+
// src/sign/hapInfo.ts
|
|
1660
|
+
import * as crypto3 from "crypto";
|
|
1648
1661
|
import * as fs14 from "fs";
|
|
1649
1662
|
import * as path15 from "path";
|
|
1663
|
+
function parseHapModuleJson(text, hapPath) {
|
|
1664
|
+
let parsed;
|
|
1665
|
+
try {
|
|
1666
|
+
parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
1667
|
+
} catch (err) {
|
|
1668
|
+
throw new OniroError(`The module.json in ${hapPath} is not valid JSON: ${err.message}`, err);
|
|
1669
|
+
}
|
|
1670
|
+
const bundleName = parsed.app?.bundleName;
|
|
1671
|
+
if (typeof bundleName !== "string" || !bundleName) {
|
|
1672
|
+
throw new OniroError(`The module.json in ${hapPath} names no app.bundleName.`);
|
|
1673
|
+
}
|
|
1674
|
+
const requested = Array.isArray(parsed.module?.requestPermissions) ? parsed.module.requestPermissions : [];
|
|
1675
|
+
const { compileSdkType, targetAPIVersion } = parsed.app ?? {};
|
|
1676
|
+
return {
|
|
1677
|
+
bundleName,
|
|
1678
|
+
requestedPermissions: requested.map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0),
|
|
1679
|
+
compileSdkType: typeof compileSdkType === "string" && compileSdkType ? compileSdkType : void 0,
|
|
1680
|
+
targetApiVersion: typeof targetAPIVersion === "number" ? targetAPIVersion : void 0
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
async function readHapSigningInfo(hapPath) {
|
|
1684
|
+
if (!fs14.existsSync(hapPath)) throw new OniroError(`${hapPath} does not exist.`);
|
|
1685
|
+
let moduleJson;
|
|
1686
|
+
try {
|
|
1687
|
+
moduleJson = await readZipEntry(hapPath, (n) => n === "module.json");
|
|
1688
|
+
if (!moduleJson && await readZipEntry(hapPath, (n) => n === "config.json")) {
|
|
1689
|
+
throw new OniroError(
|
|
1690
|
+
`${hapPath} is an FA-model HAP (config.json, no module.json). Only stage-model HAPs can be signed without their project.`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
if (err instanceof OniroError) throw err;
|
|
1695
|
+
throw new OniroError(`Could not read ${hapPath} as a HAP: ${err.message}`, err);
|
|
1696
|
+
}
|
|
1697
|
+
if (!moduleJson) throw new OniroError(`${hapPath} holds no module.json, so it is not a HAP.`);
|
|
1698
|
+
return parseHapModuleJson(moduleJson.toString("utf8"), hapPath);
|
|
1699
|
+
}
|
|
1700
|
+
function defaultSignedHapPath(hapPath) {
|
|
1701
|
+
const dir = path15.dirname(hapPath);
|
|
1702
|
+
const name = path15.basename(hapPath);
|
|
1703
|
+
if (/-signed\.hap$/i.test(name)) return path15.join(dir, name.replace(/-signed\.hap$/i, "-resigned.hap"));
|
|
1704
|
+
return path15.join(dir, `${name.replace(/(-unsigned)?\.hap$/i, "")}-signed.hap`);
|
|
1705
|
+
}
|
|
1706
|
+
async function writeHapAtomically(outPath, sign) {
|
|
1707
|
+
const dir = path15.dirname(outPath);
|
|
1708
|
+
try {
|
|
1709
|
+
fs14.mkdirSync(dir, { recursive: true });
|
|
1710
|
+
} catch (err) {
|
|
1711
|
+
throw new OniroError(`Cannot create ${dir} for the signed HAP: ${err.message}`, err);
|
|
1712
|
+
}
|
|
1713
|
+
const tmpPath = path15.join(dir, `.${path15.basename(outPath)}.${crypto3.randomBytes(4).toString("hex")}.tmp`);
|
|
1714
|
+
try {
|
|
1715
|
+
await sign(tmpPath);
|
|
1716
|
+
fs14.renameSync(tmpPath, outPath);
|
|
1717
|
+
} finally {
|
|
1718
|
+
fs14.rmSync(tmpPath, { force: true });
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
// src/sign/signHap.ts
|
|
1723
|
+
import * as fs15 from "fs";
|
|
1724
|
+
import * as os8 from "os";
|
|
1725
|
+
import * as path16 from "path";
|
|
1726
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1727
|
+
|
|
1728
|
+
// src/project/validators.ts
|
|
1729
|
+
function isValidProjectName(name) {
|
|
1730
|
+
if (!name) return false;
|
|
1731
|
+
if (name.includes("/") || name.includes("\\")) return false;
|
|
1732
|
+
return /^[A-Za-z0-9._-]+$/.test(name);
|
|
1733
|
+
}
|
|
1734
|
+
function isValidBundleName(bundleName) {
|
|
1735
|
+
return /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(bundleName);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/sign/signHap.ts
|
|
1739
|
+
function pickSigningSdk(sdkHome, targetApiVersion) {
|
|
1740
|
+
let apis;
|
|
1741
|
+
try {
|
|
1742
|
+
apis = fs15.readdirSync(sdkHome).filter((api2) => /^\d+$/.test(api2));
|
|
1743
|
+
} catch {
|
|
1744
|
+
return null;
|
|
1745
|
+
}
|
|
1746
|
+
const usable = apis.filter((api2) => fs15.existsSync(sdkSigningFiles(path16.join(sdkHome, api2)).signTool)).sort((a, b) => Number(b) - Number(a));
|
|
1747
|
+
const api = usable.find((a) => targetApiVersion !== void 0 && Number(a) === targetApiVersion) ?? usable[0];
|
|
1748
|
+
return api ? { api, sdkPath: path16.join(sdkHome, api) } : null;
|
|
1749
|
+
}
|
|
1750
|
+
async function signOpenHarmonyHap(opts) {
|
|
1751
|
+
const logger = opts.logger ?? noopLogger;
|
|
1752
|
+
const hapPath = path16.resolve(opts.hapPath);
|
|
1753
|
+
const outPath = path16.resolve(opts.outPath ?? defaultSignedHapPath(hapPath));
|
|
1754
|
+
const apl = opts.apl ?? "normal";
|
|
1755
|
+
if (!APL_VALUES.includes(apl)) {
|
|
1756
|
+
throw new OniroError(`Invalid apl '${apl}'. Expected one of: ${APL_VALUES.join(", ")}.`);
|
|
1757
|
+
}
|
|
1758
|
+
const appFeature = resolveAppFeature(apl, opts.appFeature);
|
|
1759
|
+
if (!APP_FEATURE_VALUES.includes(appFeature)) {
|
|
1760
|
+
throw new OniroError(`Invalid appFeature '${appFeature}'. Expected one of: ${APP_FEATURE_VALUES.join(", ")}.`);
|
|
1761
|
+
}
|
|
1762
|
+
const info = await readHapSigningInfo(hapPath);
|
|
1763
|
+
if (info.compileSdkType === "HarmonyOS") {
|
|
1764
|
+
throw new OniroError(
|
|
1765
|
+
`${hapPath} was built for HarmonyOS, which needs a certificate from AppGallery Connect: run \`oniro-app auth login\` once, then \`oniro-app sign --harmonyos --hap\`.`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
if (!isValidBundleName(info.bundleName)) {
|
|
1769
|
+
throw new OniroError(`${hapPath} names an invalid bundle name '${info.bundleName}'.`);
|
|
1770
|
+
}
|
|
1771
|
+
const sdk = pickSigningSdk(opts.sdkHome, info.targetApiVersion);
|
|
1772
|
+
if (!sdk) {
|
|
1773
|
+
throw new OniroError(
|
|
1774
|
+
`No OpenHarmony SDK with hap-sign-tool.jar under ${opts.sdkHome}; install one with \`oniro-app sdk install\`.`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
const files = sdkSigningFiles(sdk.sdkPath);
|
|
1778
|
+
const kind = pickSigningKind(apl);
|
|
1779
|
+
const passwords = opts.passwords ?? DEFAULT_PASSWORDS;
|
|
1780
|
+
logger.info(`[sign] Signing ${path16.basename(hapPath)} as ${info.bundleName} with the API ${sdk.api} SDK's material...`);
|
|
1781
|
+
const signaturesDir = fs15.mkdtempSync(path16.join(os8.tmpdir(), "oniro-sign-hap-"));
|
|
1782
|
+
try {
|
|
1783
|
+
copySigningFiles(signaturesDir, files, kind, logger, opts.applicationCertPath);
|
|
1784
|
+
applyProfileTemplate(signaturesDir, info.bundleName, apl, appFeature, logger, {
|
|
1785
|
+
kind,
|
|
1786
|
+
acls: opts.acls,
|
|
1787
|
+
appPrivilegeCapabilities: opts.appPrivilegeCapabilities
|
|
1788
|
+
});
|
|
1789
|
+
generateP7bFile(signaturesDir, files, logger, passwords);
|
|
1790
|
+
logger.info("[sign] Signing the HAP via hap-sign-tool...");
|
|
1791
|
+
const { hapKeyAlias, certBasename } = SIGNING_KIND_CONFIG[kind];
|
|
1792
|
+
await writeHapAtomically(outPath, (tmpPath) => {
|
|
1793
|
+
const args = [
|
|
1794
|
+
"-jar",
|
|
1795
|
+
files.signTool,
|
|
1796
|
+
"sign-app",
|
|
1797
|
+
"-mode",
|
|
1798
|
+
"localSign",
|
|
1799
|
+
"-keyAlias",
|
|
1800
|
+
hapKeyAlias,
|
|
1801
|
+
"-signAlg",
|
|
1802
|
+
"SHA256withECDSA",
|
|
1803
|
+
"-appCertFile",
|
|
1804
|
+
path16.join(signaturesDir, certBasename),
|
|
1805
|
+
"-profileFile",
|
|
1806
|
+
path16.join(signaturesDir, "app1-profile.p7b"),
|
|
1807
|
+
"-inFile",
|
|
1808
|
+
hapPath,
|
|
1809
|
+
"-outFile",
|
|
1810
|
+
tmpPath,
|
|
1811
|
+
"-keystoreFile",
|
|
1812
|
+
path16.join(signaturesDir, "OpenHarmony.p12"),
|
|
1813
|
+
"-keyPwd",
|
|
1814
|
+
passwords.key,
|
|
1815
|
+
"-keystorePwd",
|
|
1816
|
+
passwords.store
|
|
1817
|
+
];
|
|
1818
|
+
try {
|
|
1819
|
+
execFileSync2("java", args, { stdio: "inherit" });
|
|
1820
|
+
} catch (e) {
|
|
1821
|
+
throw new OniroError(
|
|
1822
|
+
`hap-sign-tool failed to sign ${hapPath}. Ensure a JDK (with java) is on PATH. Underlying error: ${e.message}`,
|
|
1823
|
+
e
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
});
|
|
1827
|
+
} finally {
|
|
1828
|
+
fs15.rmSync(signaturesDir, { recursive: true, force: true });
|
|
1829
|
+
}
|
|
1830
|
+
logger.info(`[sign] Wrote ${outPath}.`);
|
|
1831
|
+
return { bundleName: info.bundleName, outPath, sdkApi: sdk.api, kind };
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/emulator/install.ts
|
|
1835
|
+
import * as fs16 from "fs";
|
|
1836
|
+
import * as path17 from "path";
|
|
1650
1837
|
async function installEmulator(opts) {
|
|
1651
1838
|
const { config, progress, abortSignal } = opts;
|
|
1652
1839
|
const logger = opts.logger ?? noopLogger;
|
|
@@ -1656,20 +1843,20 @@ async function installEmulator(opts) {
|
|
|
1656
1843
|
const { dir: tmpDir, root: tmpRoot } = createInstallTempDir({
|
|
1657
1844
|
config,
|
|
1658
1845
|
override: opts.tmpDir,
|
|
1659
|
-
installRoot:
|
|
1846
|
+
installRoot: path17.dirname(emulatorDir),
|
|
1660
1847
|
prefix: "oniro-emulator-",
|
|
1661
1848
|
logger
|
|
1662
1849
|
});
|
|
1663
|
-
const tmpZip =
|
|
1850
|
+
const tmpZip = path17.join(tmpDir, "oniro_emulator.zip");
|
|
1664
1851
|
try {
|
|
1665
|
-
|
|
1852
|
+
fs16.mkdirSync(emulatorDir, { recursive: true });
|
|
1666
1853
|
progress?.report({ message: "Downloading emulator...", increment: 0 });
|
|
1667
1854
|
await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50, what, tmpRoot });
|
|
1668
1855
|
progress?.report({ message: "Extracting emulator...", increment: 0 });
|
|
1669
1856
|
await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
|
|
1670
|
-
const runSh =
|
|
1671
|
-
if (
|
|
1672
|
-
|
|
1857
|
+
const runSh = path17.join(emulatorDir, "images", "run.sh");
|
|
1858
|
+
if (fs16.existsSync(runSh)) {
|
|
1859
|
+
fs16.chmodSync(runSh, 493);
|
|
1673
1860
|
}
|
|
1674
1861
|
progress?.report({ message: "Finalizing installation...", increment: 5 });
|
|
1675
1862
|
} catch (err) {
|
|
@@ -1679,24 +1866,24 @@ async function installEmulator(opts) {
|
|
|
1679
1866
|
}
|
|
1680
1867
|
}
|
|
1681
1868
|
function isEmulatorInstalled(config) {
|
|
1682
|
-
const imagesDir =
|
|
1683
|
-
return
|
|
1869
|
+
const imagesDir = path17.join(getEmulatorDir(config), "images");
|
|
1870
|
+
return fs16.existsSync(path17.join(imagesDir, "run.sh")) || fs16.existsSync(path17.join(imagesDir, "run.bat"));
|
|
1684
1871
|
}
|
|
1685
1872
|
function removeEmulator(config) {
|
|
1686
1873
|
const emulatorDir = getEmulatorDir(config);
|
|
1687
|
-
if (
|
|
1688
|
-
|
|
1874
|
+
if (fs16.existsSync(emulatorDir)) {
|
|
1875
|
+
fs16.rmSync(emulatorDir, { recursive: true, force: true });
|
|
1689
1876
|
}
|
|
1690
1877
|
}
|
|
1691
1878
|
|
|
1692
1879
|
// src/emulator/lifecycle.ts
|
|
1693
|
-
import * as
|
|
1694
|
-
import * as
|
|
1695
|
-
import * as
|
|
1880
|
+
import * as fs18 from "fs";
|
|
1881
|
+
import * as os9 from "os";
|
|
1882
|
+
import * as path18 from "path";
|
|
1696
1883
|
import { spawn as spawn2 } from "child_process";
|
|
1697
1884
|
|
|
1698
1885
|
// src/hdc/exec.ts
|
|
1699
|
-
import * as
|
|
1886
|
+
import * as fs17 from "fs";
|
|
1700
1887
|
import { StringDecoder } from "string_decoder";
|
|
1701
1888
|
function errorMessage(err) {
|
|
1702
1889
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -1705,12 +1892,12 @@ function runProcess(opts) {
|
|
|
1705
1892
|
const { command, args, cwd, env, onOutput } = opts;
|
|
1706
1893
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1707
1894
|
const { abortSignal } = opts;
|
|
1708
|
-
return new Promise((
|
|
1895
|
+
return new Promise((resolve9, reject) => {
|
|
1709
1896
|
if (abortSignal?.aborted) {
|
|
1710
1897
|
reject(new CancelledError(`Command aborted before start: ${command}`));
|
|
1711
1898
|
return;
|
|
1712
1899
|
}
|
|
1713
|
-
if (needsCmdWrapper(command) && !
|
|
1900
|
+
if (needsCmdWrapper(command) && !fs17.existsSync(command)) {
|
|
1714
1901
|
reject(new OniroError(`Failed to spawn ${command}: ENOENT (no such file)`));
|
|
1715
1902
|
return;
|
|
1716
1903
|
}
|
|
@@ -1778,7 +1965,7 @@ ${stderr.trim()}` : "")
|
|
|
1778
1965
|
);
|
|
1779
1966
|
return;
|
|
1780
1967
|
}
|
|
1781
|
-
|
|
1968
|
+
resolve9({ code: code ?? -1, stdout, stderr });
|
|
1782
1969
|
});
|
|
1783
1970
|
});
|
|
1784
1971
|
}
|
|
@@ -1851,7 +2038,7 @@ function ensureRemoteOk(result, command) {
|
|
|
1851
2038
|
}
|
|
1852
2039
|
|
|
1853
2040
|
// src/emulator/lifecycle.ts
|
|
1854
|
-
var PID_FILE =
|
|
2041
|
+
var PID_FILE = path18.join(os9.tmpdir(), "oniro_emulator.pid");
|
|
1855
2042
|
async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
|
|
1856
2043
|
try {
|
|
1857
2044
|
await hdcExec({ config, args: ["start", "-r"], logger });
|
|
@@ -1864,24 +2051,24 @@ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger
|
|
|
1864
2051
|
}
|
|
1865
2052
|
}
|
|
1866
2053
|
function resolveLauncher(imagesPath, headless, connect) {
|
|
1867
|
-
const runSh =
|
|
1868
|
-
const runBat =
|
|
2054
|
+
const runSh = path18.join(imagesPath, "run.sh");
|
|
2055
|
+
const runBat = path18.join(imagesPath, "run.bat");
|
|
1869
2056
|
const extra = [];
|
|
1870
2057
|
if (headless) extra.push("--headless");
|
|
1871
2058
|
if (connect) extra.push("--connect", connect);
|
|
1872
|
-
if (
|
|
1873
|
-
if (
|
|
2059
|
+
if (os9.platform() === "win32") {
|
|
2060
|
+
if (fs18.existsSync(runBat)) {
|
|
1874
2061
|
return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
|
|
1875
2062
|
}
|
|
1876
|
-
if (
|
|
2063
|
+
if (fs18.existsSync(runSh)) {
|
|
1877
2064
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1878
2065
|
}
|
|
1879
2066
|
throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
|
|
1880
2067
|
}
|
|
1881
|
-
if (
|
|
2068
|
+
if (fs18.existsSync(runSh)) {
|
|
1882
2069
|
return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
|
|
1883
2070
|
}
|
|
1884
|
-
if (
|
|
2071
|
+
if (fs18.existsSync(runBat)) {
|
|
1885
2072
|
return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
|
|
1886
2073
|
}
|
|
1887
2074
|
throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
|
|
@@ -1889,9 +2076,9 @@ function resolveLauncher(imagesPath, headless, connect) {
|
|
|
1889
2076
|
async function startEmulator(opts) {
|
|
1890
2077
|
const { config } = opts;
|
|
1891
2078
|
const logger = opts.logger ?? noopLogger;
|
|
1892
|
-
if (
|
|
2079
|
+
if (os9.platform() !== "win32" && fs18.existsSync(PID_FILE)) {
|
|
1893
2080
|
try {
|
|
1894
|
-
const pid = Number(
|
|
2081
|
+
const pid = Number(fs18.readFileSync(PID_FILE, "utf8").trim());
|
|
1895
2082
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1896
2083
|
try {
|
|
1897
2084
|
process.kill(pid, 0);
|
|
@@ -1907,13 +2094,13 @@ async function startEmulator(opts) {
|
|
|
1907
2094
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
1908
2095
|
}
|
|
1909
2096
|
}
|
|
1910
|
-
const imagesPath =
|
|
1911
|
-
if (!
|
|
2097
|
+
const imagesPath = path18.join(getEmulatorDir(config), "images");
|
|
2098
|
+
if (!fs18.existsSync(imagesPath)) {
|
|
1912
2099
|
throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
|
|
1913
2100
|
}
|
|
1914
2101
|
const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
|
|
1915
|
-
const logPath = opts.logFile ?? (
|
|
1916
|
-
const logFd =
|
|
2102
|
+
const logPath = opts.logFile ?? (os9.platform() === "win32" ? "NUL" : "/dev/null");
|
|
2103
|
+
const logFd = fs18.openSync(logPath, "a");
|
|
1917
2104
|
let child;
|
|
1918
2105
|
try {
|
|
1919
2106
|
child = spawn2(launcher.command, [...launcher.args], {
|
|
@@ -1923,19 +2110,19 @@ async function startEmulator(opts) {
|
|
|
1923
2110
|
windowsHide: true
|
|
1924
2111
|
});
|
|
1925
2112
|
} finally {
|
|
1926
|
-
|
|
2113
|
+
fs18.closeSync(logFd);
|
|
1927
2114
|
}
|
|
1928
2115
|
if (!child.pid) {
|
|
1929
2116
|
throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
|
|
1930
2117
|
}
|
|
1931
|
-
|
|
2118
|
+
fs18.writeFileSync(PID_FILE, String(child.pid));
|
|
1932
2119
|
child.unref();
|
|
1933
2120
|
logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
|
|
1934
|
-
const earlyExit = new Promise((
|
|
1935
|
-
child.once("exit", (code) =>
|
|
1936
|
-
child.once("error", () =>
|
|
2121
|
+
const earlyExit = new Promise((resolve9) => {
|
|
2122
|
+
child.once("exit", (code) => resolve9(code));
|
|
2123
|
+
child.once("error", () => resolve9(-1));
|
|
1937
2124
|
});
|
|
1938
|
-
const liveness = new Promise((
|
|
2125
|
+
const liveness = new Promise((resolve9) => setTimeout(() => resolve9("alive"), 2e3));
|
|
1939
2126
|
const outcome = await Promise.race([earlyExit, liveness]);
|
|
1940
2127
|
if (outcome !== "alive") {
|
|
1941
2128
|
const tail = readTail(logPath, 4e3);
|
|
@@ -1975,24 +2162,24 @@ ${tail}` : ""}`);
|
|
|
1975
2162
|
}
|
|
1976
2163
|
function readTail(filePath, bytes) {
|
|
1977
2164
|
try {
|
|
1978
|
-
const stat =
|
|
2165
|
+
const stat = fs18.statSync(filePath);
|
|
1979
2166
|
const start = Math.max(0, stat.size - bytes);
|
|
1980
|
-
const fd =
|
|
2167
|
+
const fd = fs18.openSync(filePath, "r");
|
|
1981
2168
|
try {
|
|
1982
2169
|
const buf = Buffer.alloc(stat.size - start);
|
|
1983
|
-
|
|
2170
|
+
fs18.readSync(fd, buf, 0, buf.length, start);
|
|
1984
2171
|
return buf.toString("utf8");
|
|
1985
2172
|
} finally {
|
|
1986
|
-
|
|
2173
|
+
fs18.closeSync(fd);
|
|
1987
2174
|
}
|
|
1988
2175
|
} catch {
|
|
1989
2176
|
return "";
|
|
1990
2177
|
}
|
|
1991
2178
|
}
|
|
1992
2179
|
async function stopEmulator(logger = noopLogger) {
|
|
1993
|
-
if (
|
|
2180
|
+
if (fs18.existsSync(PID_FILE)) {
|
|
1994
2181
|
try {
|
|
1995
|
-
const pid = Number(
|
|
2182
|
+
const pid = Number(fs18.readFileSync(PID_FILE, "utf8").trim());
|
|
1996
2183
|
if (Number.isFinite(pid) && pid > 0) {
|
|
1997
2184
|
try {
|
|
1998
2185
|
process.kill(pid, "SIGTERM");
|
|
@@ -2005,20 +2192,20 @@ async function stopEmulator(logger = noopLogger) {
|
|
|
2005
2192
|
logger.warn(`Could not read PID file: ${err.message}`);
|
|
2006
2193
|
}
|
|
2007
2194
|
}
|
|
2008
|
-
const [killCmd, ...killArgs] =
|
|
2195
|
+
const [killCmd, ...killArgs] = os9.platform() === "win32" ? ["taskkill", "/IM", "qemu-system-x86_64.exe", "/F"] : ["pkill", "-f", "qemu-system-x86_64"];
|
|
2009
2196
|
try {
|
|
2010
2197
|
await runProcess({ command: killCmd, args: killArgs, logger });
|
|
2011
2198
|
} catch (err) {
|
|
2012
2199
|
logger.warn(`[emulator] could not run ${killCmd}: ${err.message}`);
|
|
2013
2200
|
}
|
|
2014
|
-
|
|
2201
|
+
fs18.rm(PID_FILE, { force: true }, (err) => {
|
|
2015
2202
|
if (err) logger.warn(`Could not remove PID file: ${err.message}`);
|
|
2016
2203
|
});
|
|
2017
2204
|
}
|
|
2018
2205
|
async function isQemuRunning(logger) {
|
|
2019
|
-
if (
|
|
2206
|
+
if (os9.platform() !== "win32") {
|
|
2020
2207
|
try {
|
|
2021
|
-
const pid = Number(
|
|
2208
|
+
const pid = Number(fs18.readFileSync(PID_FILE, "utf8").trim());
|
|
2022
2209
|
if (Number.isFinite(pid) && pid > 0) {
|
|
2023
2210
|
process.kill(pid, 0);
|
|
2024
2211
|
return true;
|
|
@@ -2026,10 +2213,10 @@ async function isQemuRunning(logger) {
|
|
|
2026
2213
|
} catch {
|
|
2027
2214
|
}
|
|
2028
2215
|
}
|
|
2029
|
-
const [cmd, ...args] =
|
|
2216
|
+
const [cmd, ...args] = os9.platform() === "win32" ? ["tasklist", "/FI", "IMAGENAME eq qemu-system-x86_64.exe", "/NH"] : ["pgrep", "-f", "qemu-system-x86_64"];
|
|
2030
2217
|
try {
|
|
2031
2218
|
const res = await runProcess({ command: cmd, args, timeoutMs: 1e4, logger });
|
|
2032
|
-
return
|
|
2219
|
+
return os9.platform() === "win32" ? /qemu-system-x86_64/i.test(res.stdout) : res.code === 0;
|
|
2033
2220
|
} catch {
|
|
2034
2221
|
return false;
|
|
2035
2222
|
}
|
|
@@ -2154,11 +2341,11 @@ async function paramSet(config, key, value, opts = {}) {
|
|
|
2154
2341
|
}
|
|
2155
2342
|
|
|
2156
2343
|
// src/hdc/files.ts
|
|
2157
|
-
import * as
|
|
2344
|
+
import * as path19 from "path";
|
|
2158
2345
|
function normaliseLocalPath(local) {
|
|
2159
|
-
const abs =
|
|
2346
|
+
const abs = path19.resolve(local);
|
|
2160
2347
|
const long = toLongPath(abs);
|
|
2161
|
-
return long !== abs ? long :
|
|
2348
|
+
return long !== abs ? long : path19.join(toLongPath(path19.dirname(abs)), path19.basename(abs));
|
|
2162
2349
|
}
|
|
2163
2350
|
function assertDevicePath(remote) {
|
|
2164
2351
|
if (/^[A-Za-z]:[\\/]/.test(remote)) {
|
|
@@ -2177,13 +2364,13 @@ async function sendFile(opts) {
|
|
|
2177
2364
|
assertDevicePath(opts.remote);
|
|
2178
2365
|
const local = normaliseLocalPath(opts.local);
|
|
2179
2366
|
const sandbox = opts.bundle ? appSandboxDir(opts.bundle) : void 0;
|
|
2180
|
-
const remote = sandbox && !opts.remote.startsWith("/") ?
|
|
2367
|
+
const remote = sandbox && !opts.remote.startsWith("/") ? path19.posix.join(sandbox, opts.remote) : opts.remote;
|
|
2181
2368
|
if (sandbox && !remote.startsWith(`${sandbox}/`)) {
|
|
2182
2369
|
throw new OniroError(`${remote} is outside the sandbox of ${opts.bundle} (${sandbox}).`);
|
|
2183
2370
|
}
|
|
2184
2371
|
const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
2185
2372
|
if (sandbox) {
|
|
2186
|
-
const mkdir = `mkdir -p ${shQuote(
|
|
2373
|
+
const mkdir = `mkdir -p ${shQuote(path19.posix.dirname(remote))}`;
|
|
2187
2374
|
ensureRemoteOk(await shellChecked({ ...device, command: mkdir }), `hdc shell ${mkdir}`);
|
|
2188
2375
|
}
|
|
2189
2376
|
const res = await hdcExec({
|
|
@@ -2195,7 +2382,7 @@ async function sendFile(opts) {
|
|
|
2195
2382
|
});
|
|
2196
2383
|
ensureOk(res, `hdc file send ${local} ${remote}`);
|
|
2197
2384
|
if (sandbox) {
|
|
2198
|
-
const top =
|
|
2385
|
+
const top = path19.posix.join(sandbox, path19.posix.relative(sandbox, remote).split("/")[0]);
|
|
2199
2386
|
const chown = `chown -R "$(stat -c %u:%g ${shQuote(sandbox)})" ${shQuote(top)}`;
|
|
2200
2387
|
ensureRemoteOk(await shellChecked({ ...device, command: chown }), `hdc shell ${chown}`);
|
|
2201
2388
|
}
|
|
@@ -2233,7 +2420,7 @@ async function getDisplaySize(config, opts = {}) {
|
|
|
2233
2420
|
|
|
2234
2421
|
// src/hdc/wait.ts
|
|
2235
2422
|
function delay(ms, signal) {
|
|
2236
|
-
return new Promise((
|
|
2423
|
+
return new Promise((resolve9, reject) => {
|
|
2237
2424
|
if (signal?.aborted) {
|
|
2238
2425
|
reject(new CancelledError("Wait cancelled."));
|
|
2239
2426
|
return;
|
|
@@ -2248,7 +2435,7 @@ function delay(ms, signal) {
|
|
|
2248
2435
|
};
|
|
2249
2436
|
const timer = setTimeout(() => {
|
|
2250
2437
|
cleanup();
|
|
2251
|
-
|
|
2438
|
+
resolve9();
|
|
2252
2439
|
}, ms);
|
|
2253
2440
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2254
2441
|
});
|
|
@@ -2277,18 +2464,18 @@ async function waitForCondition(opts) {
|
|
|
2277
2464
|
}
|
|
2278
2465
|
|
|
2279
2466
|
// src/hdc/app.ts
|
|
2280
|
-
import * as
|
|
2281
|
-
import * as
|
|
2467
|
+
import * as fs21 from "fs";
|
|
2468
|
+
import * as path22 from "path";
|
|
2282
2469
|
|
|
2283
2470
|
// src/build/discoverHaps.ts
|
|
2284
|
-
import * as
|
|
2285
|
-
import * as
|
|
2471
|
+
import * as fs19 from "fs";
|
|
2472
|
+
import * as path20 from "path";
|
|
2286
2473
|
function moduleOfHap(hapFileName) {
|
|
2287
2474
|
const dash = hapFileName.indexOf("-");
|
|
2288
2475
|
return dash > 0 ? hapFileName.slice(0, dash) : hapFileName.replace(/\.hap$/i, "");
|
|
2289
2476
|
}
|
|
2290
2477
|
function isAppProjectRoot(dir) {
|
|
2291
|
-
return
|
|
2478
|
+
return fs19.existsSync(path20.join(dir, "AppScope", "app.json5"));
|
|
2292
2479
|
}
|
|
2293
2480
|
async function discoverHaps(opts) {
|
|
2294
2481
|
const { projectDir } = opts;
|
|
@@ -2297,9 +2484,9 @@ async function discoverHaps(opts) {
|
|
|
2297
2484
|
const result = {};
|
|
2298
2485
|
const walk = async (dir, depth) => {
|
|
2299
2486
|
if (depth > maxDepth) return;
|
|
2300
|
-
const entries = await
|
|
2487
|
+
const entries = await fs19.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
2301
2488
|
for (const e of entries) {
|
|
2302
|
-
const full =
|
|
2489
|
+
const full = path20.join(dir, e.name);
|
|
2303
2490
|
if (e.isDirectory()) {
|
|
2304
2491
|
if (e.name === "node_modules" || e.name === "oh_modules" || e.name === ".git") continue;
|
|
2305
2492
|
if (skipNestedProjects && isAppProjectRoot(full)) continue;
|
|
@@ -2313,7 +2500,7 @@ async function discoverHaps(opts) {
|
|
|
2313
2500
|
}
|
|
2314
2501
|
};
|
|
2315
2502
|
await walk(projectDir, 0);
|
|
2316
|
-
const mtime = (p) =>
|
|
2503
|
+
const mtime = (p) => fs19.statSync(p).mtimeMs;
|
|
2317
2504
|
for (const bucket of Object.values(result)) {
|
|
2318
2505
|
bucket.signed.sort((a, b) => mtime(b) - mtime(a));
|
|
2319
2506
|
bucket.unsigned.sort((a, b) => mtime(b) - mtime(a));
|
|
@@ -2339,16 +2526,16 @@ function pickHap(haps, module) {
|
|
|
2339
2526
|
}
|
|
2340
2527
|
|
|
2341
2528
|
// src/hdc/project.ts
|
|
2342
|
-
import * as
|
|
2343
|
-
import * as
|
|
2529
|
+
import * as fs20 from "fs";
|
|
2530
|
+
import * as path21 from "path";
|
|
2344
2531
|
import JSON53 from "json5";
|
|
2345
2532
|
function readJson5(filePath) {
|
|
2346
|
-
const content =
|
|
2533
|
+
const content = fs20.readFileSync(filePath, "utf-8");
|
|
2347
2534
|
return JSON53.parse(content);
|
|
2348
2535
|
}
|
|
2349
2536
|
function getBundleName(projectDir) {
|
|
2350
|
-
const appJsonPath =
|
|
2351
|
-
if (!
|
|
2537
|
+
const appJsonPath = path21.join(projectDir, "AppScope", "app.json5");
|
|
2538
|
+
if (!fs20.existsSync(appJsonPath)) {
|
|
2352
2539
|
throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
|
|
2353
2540
|
}
|
|
2354
2541
|
const appJson = readJson5(appJsonPath);
|
|
@@ -2358,8 +2545,8 @@ function getBundleName(projectDir) {
|
|
|
2358
2545
|
return appJson.app.bundleName;
|
|
2359
2546
|
}
|
|
2360
2547
|
function getMainAbility(projectDir, moduleName = "entry", abilityName) {
|
|
2361
|
-
const moduleJsonPath =
|
|
2362
|
-
if (!
|
|
2548
|
+
const moduleJsonPath = path21.join(projectDir, moduleName, "src", "main", "module.json5");
|
|
2549
|
+
if (!fs20.existsSync(moduleJsonPath)) {
|
|
2363
2550
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
2364
2551
|
}
|
|
2365
2552
|
const moduleJson = readJson5(moduleJsonPath);
|
|
@@ -2383,7 +2570,7 @@ function detectInstallFailure(output) {
|
|
|
2383
2570
|
return /\[Fail\]|msg:\s*error|error:\s*failed to install|code:\s*95\d{5}/i.test(output) ? output.trim() : null;
|
|
2384
2571
|
}
|
|
2385
2572
|
function isUnsignedHap(hapPath) {
|
|
2386
|
-
return
|
|
2573
|
+
return path22.basename(hapPath).includes("unsigned");
|
|
2387
2574
|
}
|
|
2388
2575
|
function installFailureReason(output) {
|
|
2389
2576
|
const code = /code:\s*(\d+)/i.exec(output);
|
|
@@ -2392,7 +2579,7 @@ function installFailureReason(output) {
|
|
|
2392
2579
|
return message ? `code ${code[1]}: ${message}` : `code ${code[1]}`;
|
|
2393
2580
|
}
|
|
2394
2581
|
function unsignedHapRejection(output, hap) {
|
|
2395
|
-
const name =
|
|
2582
|
+
const name = path22.basename(hap.path);
|
|
2396
2583
|
const reason = installFailureReason(output);
|
|
2397
2584
|
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}).`;
|
|
2398
2585
|
if (!/9568320|signature|pkcs7/i.test(reason)) return what;
|
|
@@ -2410,24 +2597,24 @@ ${result.stderr}`;
|
|
|
2410
2597
|
return result;
|
|
2411
2598
|
}
|
|
2412
2599
|
function existingHapOrUnsigned(hapPath) {
|
|
2413
|
-
if (
|
|
2600
|
+
if (fs21.existsSync(hapPath)) return { path: hapPath };
|
|
2414
2601
|
const sibling = hapPath.replace(/-signed\.hap$/i, "-unsigned.hap");
|
|
2415
|
-
return sibling !== hapPath &&
|
|
2602
|
+
return sibling !== hapPath && fs21.existsSync(sibling) ? { path: sibling, fallback: true } : void 0;
|
|
2416
2603
|
}
|
|
2417
2604
|
async function resolveInstallHap(opts, logger) {
|
|
2418
2605
|
const warnIfFallback = (hap) => {
|
|
2419
|
-
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${
|
|
2606
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path22.basename(hap.path)}.`);
|
|
2420
2607
|
return hap;
|
|
2421
2608
|
};
|
|
2422
2609
|
const configured = opts.hapPath ?? opts.config.get("hapPath", "");
|
|
2423
2610
|
const relative3 = configured || defaultPaths.hapPath;
|
|
2424
|
-
const hapPath =
|
|
2611
|
+
const hapPath = path22.isAbsolute(relative3) ? relative3 : path22.join(opts.projectDir, relative3);
|
|
2425
2612
|
if (configured) {
|
|
2426
2613
|
const hap = existingHapOrUnsigned(hapPath);
|
|
2427
2614
|
if (!hap) throw new OniroError(`HAP file not found at: ${hapPath}. Build the app first.`);
|
|
2428
2615
|
return warnIfFallback(hap);
|
|
2429
2616
|
}
|
|
2430
|
-
if (
|
|
2617
|
+
if (fs21.existsSync(hapPath)) return { path: hapPath };
|
|
2431
2618
|
const haps = await discoverHaps({ projectDir: opts.projectDir });
|
|
2432
2619
|
const anySigned = Object.values(haps).some((m) => m.signed.length > 0);
|
|
2433
2620
|
const entryUnsigned = anySigned ? void 0 : existingHapOrUnsigned(hapPath);
|
|
@@ -2538,7 +2725,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2538
2725
|
const logger = options.logger ?? noopLogger;
|
|
2539
2726
|
const timeoutMs = options.timeoutMs ?? 1e3;
|
|
2540
2727
|
const hdc = getHdcPath(config);
|
|
2541
|
-
return new Promise((
|
|
2728
|
+
return new Promise((resolve9, reject) => {
|
|
2542
2729
|
let proc;
|
|
2543
2730
|
try {
|
|
2544
2731
|
proc = spawnCompat(hdc, [...hdcTargetArgs(options.deviceSerial), "track-jpid"]);
|
|
@@ -2558,7 +2745,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2558
2745
|
if (options.targetProcessName && name === options.targetProcessName && !resolved) {
|
|
2559
2746
|
resolved = true;
|
|
2560
2747
|
killProcessTree(proc);
|
|
2561
|
-
|
|
2748
|
+
resolve9(pid);
|
|
2562
2749
|
return;
|
|
2563
2750
|
}
|
|
2564
2751
|
}
|
|
@@ -2579,7 +2766,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2579
2766
|
if (options.targetProcessName) {
|
|
2580
2767
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2581
2768
|
} else {
|
|
2582
|
-
|
|
2769
|
+
resolve9(processes);
|
|
2583
2770
|
}
|
|
2584
2771
|
}, timeoutMs);
|
|
2585
2772
|
proc.on("close", () => {
|
|
@@ -2589,7 +2776,7 @@ function listRunningProcesses(config, options = {}) {
|
|
|
2589
2776
|
if (options.targetProcessName) {
|
|
2590
2777
|
reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
|
|
2591
2778
|
} else {
|
|
2592
|
-
|
|
2779
|
+
resolve9(processes);
|
|
2593
2780
|
}
|
|
2594
2781
|
});
|
|
2595
2782
|
});
|
|
@@ -2758,12 +2945,12 @@ async function reboot(opts) {
|
|
|
2758
2945
|
}
|
|
2759
2946
|
|
|
2760
2947
|
// src/hdc/screen.ts
|
|
2761
|
-
import * as
|
|
2762
|
-
import * as
|
|
2763
|
-
import * as
|
|
2764
|
-
import { randomBytes as
|
|
2948
|
+
import * as fs22 from "fs";
|
|
2949
|
+
import * as os10 from "os";
|
|
2950
|
+
import * as path23 from "path";
|
|
2951
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2765
2952
|
function tmpName(ext) {
|
|
2766
|
-
return
|
|
2953
|
+
return path23.join(os10.tmpdir(), `oniro_screen_${randomBytes3(6).toString("hex")}.${ext}`);
|
|
2767
2954
|
}
|
|
2768
2955
|
async function takeScreenshot(opts) {
|
|
2769
2956
|
const remote = "/data/local/tmp/oniro_screenshot.jpeg";
|
|
@@ -2773,11 +2960,11 @@ async function takeScreenshot(opts) {
|
|
|
2773
2960
|
const local = tmpName("jpeg");
|
|
2774
2961
|
try {
|
|
2775
2962
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
2776
|
-
const pixels = await
|
|
2963
|
+
const pixels = await fs22.promises.readFile(local);
|
|
2777
2964
|
const size = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial }) ?? { width: 0, height: 0 };
|
|
2778
2965
|
return { pixels, width: size.width, height: size.height };
|
|
2779
2966
|
} finally {
|
|
2780
|
-
|
|
2967
|
+
fs22.promises.unlink(local).catch(() => {
|
|
2781
2968
|
});
|
|
2782
2969
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
2783
2970
|
});
|
|
@@ -2785,7 +2972,7 @@ async function takeScreenshot(opts) {
|
|
|
2785
2972
|
}
|
|
2786
2973
|
async function captureBurst(opts) {
|
|
2787
2974
|
const count = Math.max(1, Math.floor(opts.count));
|
|
2788
|
-
const remoteDir = `/data/local/tmp/oniro_burst_${
|
|
2975
|
+
const remoteDir = `/data/local/tmp/oniro_burst_${randomBytes3(6).toString("hex")}`;
|
|
2789
2976
|
const sleepSec = opts.intervalMs > 0 ? (opts.intervalMs / 1e3).toFixed(3) : "0";
|
|
2790
2977
|
const loop = `mkdir -p ${remoteDir}; i=0; while [ $i -lt ${count} ]; do snapshot_display -f ${remoteDir}/f$i.jpeg; [ ${count} -gt 1 ] && sleep ${sleepSec}; i=$((i+1)); done`;
|
|
2791
2978
|
const cap = await shell({ config: opts.config, command: loop, deviceSerial: opts.deviceSerial, timeoutMs: opts.timeoutMs ?? 12e4 });
|
|
@@ -2796,9 +2983,9 @@ async function captureBurst(opts) {
|
|
|
2796
2983
|
const local = tmpName(`burst_${i}.jpeg`);
|
|
2797
2984
|
try {
|
|
2798
2985
|
await recvFile({ config: opts.config, remote: `${remoteDir}/f${i}.jpeg`, local, deviceSerial: opts.deviceSerial });
|
|
2799
|
-
frames.push(await
|
|
2986
|
+
frames.push(await fs22.promises.readFile(local));
|
|
2800
2987
|
} finally {
|
|
2801
|
-
|
|
2988
|
+
fs22.promises.unlink(local).catch(() => {
|
|
2802
2989
|
});
|
|
2803
2990
|
}
|
|
2804
2991
|
}
|
|
@@ -2973,10 +3160,10 @@ async function sendRawTouch(opts) {
|
|
|
2973
3160
|
}
|
|
2974
3161
|
|
|
2975
3162
|
// src/hdc/dump.ts
|
|
2976
|
-
import * as
|
|
2977
|
-
import * as
|
|
2978
|
-
import * as
|
|
2979
|
-
import { randomBytes as
|
|
3163
|
+
import * as fs23 from "fs";
|
|
3164
|
+
import * as os11 from "os";
|
|
3165
|
+
import * as path24 from "path";
|
|
3166
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
2980
3167
|
|
|
2981
3168
|
// src/hdc/ime.ts
|
|
2982
3169
|
var INPUT_METHOD_SA_ID = 3703;
|
|
@@ -3238,10 +3425,10 @@ async function dumpLayout(opts) {
|
|
|
3238
3425
|
if (!m) throw new OniroError(`Could not parse dumpLayout output:
|
|
3239
3426
|
${dump.stdout}`);
|
|
3240
3427
|
const remote = m[1];
|
|
3241
|
-
const local =
|
|
3428
|
+
const local = path24.join(os11.tmpdir(), `oniro_layout_${randomBytes4(6).toString("hex")}.json`);
|
|
3242
3429
|
try {
|
|
3243
3430
|
await recvFile({ config: opts.config, remote, local, deviceSerial: opts.deviceSerial, timeoutMs });
|
|
3244
|
-
const parsed = JSON.parse(await
|
|
3431
|
+
const parsed = JSON.parse(await fs23.promises.readFile(local, "utf8"));
|
|
3245
3432
|
const display = await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial });
|
|
3246
3433
|
if (opts.prune === false) {
|
|
3247
3434
|
return { display, tree: parsed };
|
|
@@ -3254,7 +3441,7 @@ ${dump.stdout}`);
|
|
|
3254
3441
|
const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
|
|
3255
3442
|
return { display, tree, keyboard };
|
|
3256
3443
|
} finally {
|
|
3257
|
-
|
|
3444
|
+
fs23.promises.unlink(local).catch(() => {
|
|
3258
3445
|
});
|
|
3259
3446
|
void shell({ config: opts.config, command: `rm -f ${remote}`, deviceSerial: opts.deviceSerial }).catch(() => {
|
|
3260
3447
|
});
|
|
@@ -3264,7 +3451,7 @@ ${dump.stdout}`);
|
|
|
3264
3451
|
// src/hdc/uiActions.ts
|
|
3265
3452
|
var FOCUS_SETTLE_MS = 400;
|
|
3266
3453
|
var KEYBOARD_SETTLE_MS = 400;
|
|
3267
|
-
var sleep = (ms) => new Promise((
|
|
3454
|
+
var sleep = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
3268
3455
|
function targetTree(layout) {
|
|
3269
3456
|
const kb = layout.keyboard;
|
|
3270
3457
|
return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
|
|
@@ -3417,11 +3604,11 @@ async function waitForTarget(opts) {
|
|
|
3417
3604
|
}
|
|
3418
3605
|
|
|
3419
3606
|
// src/hdc/hilog.ts
|
|
3420
|
-
import * as
|
|
3607
|
+
import * as fs24 from "fs";
|
|
3421
3608
|
function setHilogLevel(opts) {
|
|
3422
3609
|
const logger = opts.logger ?? noopLogger;
|
|
3423
3610
|
const hdc = getHdcPath(opts.config);
|
|
3424
|
-
return new Promise((
|
|
3611
|
+
return new Promise((resolve9, reject) => {
|
|
3425
3612
|
let child;
|
|
3426
3613
|
try {
|
|
3427
3614
|
child = spawnCompat(hdc, [...hdcTargetArgs(opts.deviceSerial), "shell", "hilog", "-b", opts.level]);
|
|
@@ -3441,7 +3628,7 @@ function setHilogLevel(opts) {
|
|
|
3441
3628
|
});
|
|
3442
3629
|
child.once("close", (code) => {
|
|
3443
3630
|
if (code === 0) {
|
|
3444
|
-
|
|
3631
|
+
resolve9();
|
|
3445
3632
|
} else {
|
|
3446
3633
|
const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
|
|
3447
3634
|
logger.error(`[hilog] ${msg}`);
|
|
@@ -3543,7 +3730,7 @@ async function recentEntryFilter(opts) {
|
|
|
3543
3730
|
};
|
|
3544
3731
|
}
|
|
3545
3732
|
function waitForLog(opts) {
|
|
3546
|
-
return new Promise((
|
|
3733
|
+
return new Promise((resolve9, reject) => {
|
|
3547
3734
|
const deadline = Date.now() + opts.timeoutMs;
|
|
3548
3735
|
let settled = false;
|
|
3549
3736
|
let child = null;
|
|
@@ -3564,7 +3751,7 @@ function waitForLog(opts) {
|
|
|
3564
3751
|
if (settled) return;
|
|
3565
3752
|
settled = true;
|
|
3566
3753
|
cleanup();
|
|
3567
|
-
|
|
3754
|
+
resolve9(entry);
|
|
3568
3755
|
};
|
|
3569
3756
|
const fail = (err) => {
|
|
3570
3757
|
if (settled) return;
|
|
@@ -3619,7 +3806,7 @@ function waitForLog(opts) {
|
|
|
3619
3806
|
});
|
|
3620
3807
|
}
|
|
3621
3808
|
function watchLog(opts) {
|
|
3622
|
-
return new Promise((
|
|
3809
|
+
return new Promise((resolve9, reject) => {
|
|
3623
3810
|
const matches = [];
|
|
3624
3811
|
let settled = false;
|
|
3625
3812
|
let child = null;
|
|
@@ -3638,7 +3825,7 @@ function watchLog(opts) {
|
|
|
3638
3825
|
if (settled) return;
|
|
3639
3826
|
settled = true;
|
|
3640
3827
|
cleanup();
|
|
3641
|
-
|
|
3828
|
+
resolve9(opts.dedup === false ? matches : dedupEntries(matches));
|
|
3642
3829
|
};
|
|
3643
3830
|
const onAbort = () => {
|
|
3644
3831
|
if (settled) return;
|
|
@@ -3683,11 +3870,11 @@ async function captureLog(opts) {
|
|
|
3683
3870
|
const res = await shell({ config: opts.config, command: "hilog -r", deviceSerial: opts.deviceSerial, timeoutMs: 1e4, logger });
|
|
3684
3871
|
ensureOk(res, "hdc shell hilog -r");
|
|
3685
3872
|
}
|
|
3686
|
-
const fd =
|
|
3873
|
+
const fd = fs24.openSync(opts.file, "a");
|
|
3687
3874
|
let lines = 0;
|
|
3688
3875
|
let reconnects = 0;
|
|
3689
3876
|
try {
|
|
3690
|
-
await new Promise((
|
|
3877
|
+
await new Promise((resolve9, reject) => {
|
|
3691
3878
|
const deadline = opts.durationMs !== void 0 ? Date.now() + opts.durationMs : Infinity;
|
|
3692
3879
|
let settled = false;
|
|
3693
3880
|
let child = null;
|
|
@@ -3696,7 +3883,7 @@ async function captureLog(opts) {
|
|
|
3696
3883
|
let open = true;
|
|
3697
3884
|
const write = (line) => {
|
|
3698
3885
|
if (!open || opts.pattern && !opts.pattern.test(line)) return;
|
|
3699
|
-
|
|
3886
|
+
fs24.writeSync(fd, `${line}
|
|
3700
3887
|
`);
|
|
3701
3888
|
lines++;
|
|
3702
3889
|
};
|
|
@@ -3717,7 +3904,7 @@ async function captureLog(opts) {
|
|
|
3717
3904
|
if (settled) return;
|
|
3718
3905
|
settled = true;
|
|
3719
3906
|
cleanup();
|
|
3720
|
-
|
|
3907
|
+
resolve9();
|
|
3721
3908
|
}
|
|
3722
3909
|
const fail = (err) => {
|
|
3723
3910
|
if (settled) return;
|
|
@@ -3761,7 +3948,7 @@ async function captureLog(opts) {
|
|
|
3761
3948
|
spawnStream();
|
|
3762
3949
|
});
|
|
3763
3950
|
} finally {
|
|
3764
|
-
|
|
3951
|
+
fs24.closeSync(fd);
|
|
3765
3952
|
}
|
|
3766
3953
|
return { file: opts.file, lines, reconnects };
|
|
3767
3954
|
}
|
|
@@ -3789,17 +3976,17 @@ async function dumpLog(opts) {
|
|
|
3789
3976
|
}
|
|
3790
3977
|
|
|
3791
3978
|
// src/build/runHvigorw.ts
|
|
3792
|
-
import * as
|
|
3793
|
-
import * as
|
|
3979
|
+
import * as fs26 from "fs";
|
|
3980
|
+
import * as path26 from "path";
|
|
3794
3981
|
|
|
3795
3982
|
// src/harmonyos/target.ts
|
|
3796
|
-
import * as
|
|
3797
|
-
import * as
|
|
3983
|
+
import * as fs25 from "fs";
|
|
3984
|
+
import * as path25 from "path";
|
|
3798
3985
|
import JSON54 from "json5";
|
|
3799
3986
|
function detectRuntimeOs(projectDir, productName) {
|
|
3800
3987
|
let products = [];
|
|
3801
3988
|
try {
|
|
3802
|
-
const parsed = JSON54.parse(
|
|
3989
|
+
const parsed = JSON54.parse(fs25.readFileSync(path25.join(projectDir, "build-profile.json5"), "utf-8"));
|
|
3803
3990
|
if (Array.isArray(parsed.app?.products)) products = parsed.app.products;
|
|
3804
3991
|
} catch {
|
|
3805
3992
|
return "OpenHarmony";
|
|
@@ -3840,7 +4027,7 @@ function runHvigorw(opts) {
|
|
|
3840
4027
|
const { config } = opts;
|
|
3841
4028
|
const projectDir = toLongPath(opts.projectDir);
|
|
3842
4029
|
const logger = opts.logger ?? noopLogger;
|
|
3843
|
-
if (!
|
|
4030
|
+
if (!fs26.existsSync(path26.join(projectDir, "build-profile.json5"))) {
|
|
3844
4031
|
throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
|
|
3845
4032
|
}
|
|
3846
4033
|
let hvigorw;
|
|
@@ -3852,24 +4039,24 @@ function runHvigorw(opts) {
|
|
|
3852
4039
|
hvigorw = resolved.startsWith(projectDir) ? resolved : findHarmonyOsTool(sdk, "hvigorw") ?? resolved;
|
|
3853
4040
|
logger.info(`[build] Building for HarmonyOS against the SDK at ${sdk.sdkPath}.`);
|
|
3854
4041
|
} else {
|
|
3855
|
-
if (!
|
|
4042
|
+
if (!fs26.existsSync(getCmdToolsPath(config))) {
|
|
3856
4043
|
throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
|
|
3857
4044
|
}
|
|
3858
4045
|
sdkEnv = { OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config) };
|
|
3859
4046
|
hvigorw = getHvigorwPath(config, projectDir);
|
|
3860
4047
|
}
|
|
3861
|
-
if (!
|
|
4048
|
+
if (!fs26.existsSync(hvigorw)) {
|
|
3862
4049
|
throw new OniroError(`hvigorw wrapper not found at ${hvigorw}.`);
|
|
3863
4050
|
}
|
|
3864
4051
|
const args = buildHvigorwArgs(opts);
|
|
3865
4052
|
if (process.platform !== "win32") {
|
|
3866
4053
|
try {
|
|
3867
|
-
|
|
4054
|
+
fs26.chmodSync(hvigorw, 493);
|
|
3868
4055
|
} catch {
|
|
3869
4056
|
}
|
|
3870
4057
|
}
|
|
3871
4058
|
const env = { ...process.env, ...sdkEnv };
|
|
3872
|
-
return new Promise((
|
|
4059
|
+
return new Promise((resolve9, reject) => {
|
|
3873
4060
|
if (opts.abortSignal?.aborted) {
|
|
3874
4061
|
reject(new CancelledError("hvigorw build cancelled."));
|
|
3875
4062
|
return;
|
|
@@ -3913,15 +4100,15 @@ function runHvigorw(opts) {
|
|
|
3913
4100
|
settled = true;
|
|
3914
4101
|
cleanup();
|
|
3915
4102
|
const exitCode = code ?? 1;
|
|
3916
|
-
if (exitCode === 0)
|
|
4103
|
+
if (exitCode === 0) resolve9({ exitCode });
|
|
3917
4104
|
else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
|
|
3918
4105
|
});
|
|
3919
4106
|
});
|
|
3920
4107
|
}
|
|
3921
4108
|
|
|
3922
4109
|
// src/build/ohpm.ts
|
|
3923
|
-
import * as
|
|
3924
|
-
import * as
|
|
4110
|
+
import * as fs27 from "fs";
|
|
4111
|
+
import * as path27 from "path";
|
|
3925
4112
|
function runOhpm(opts) {
|
|
3926
4113
|
return runProcess({
|
|
3927
4114
|
command: opts.ohpmPath ?? getOhpmPath(opts.config),
|
|
@@ -3934,7 +4121,7 @@ function runOhpm(opts) {
|
|
|
3934
4121
|
});
|
|
3935
4122
|
}
|
|
3936
4123
|
async function ensureOhModules(opts) {
|
|
3937
|
-
if (
|
|
4124
|
+
if (fs27.existsSync(path27.join(opts.projectDir, "oh_modules"))) {
|
|
3938
4125
|
return { installed: false };
|
|
3939
4126
|
}
|
|
3940
4127
|
const res = await runOhpm({
|
|
@@ -3951,8 +4138,8 @@ async function ensureOhModules(opts) {
|
|
|
3951
4138
|
}
|
|
3952
4139
|
|
|
3953
4140
|
// src/build/codelinter.ts
|
|
3954
|
-
import * as
|
|
3955
|
-
import * as
|
|
4141
|
+
import * as fs28 from "fs";
|
|
4142
|
+
import * as path28 from "path";
|
|
3956
4143
|
var FINDING_RE = /^(.*?):(\d+):(?:(\d+):)?\s*(error|warning|warn|suggestion|info)\b[:\s-]*(.*)$/i;
|
|
3957
4144
|
var TRAILING_CODE_RE = /[([]([\w@/.\-]+)[)\]]\s*$/;
|
|
3958
4145
|
var FILE_HEADER_RE = /^(.+\.\w+)\((\d+)\)$/;
|
|
@@ -4042,10 +4229,10 @@ function parseCodelinterFindings(output) {
|
|
|
4042
4229
|
var PROJECT_ROOT_FILES = [
|
|
4043
4230
|
["build-profile.json5"],
|
|
4044
4231
|
["hvigorfile.ts", "hvigorfile.js"],
|
|
4045
|
-
[
|
|
4232
|
+
[path28.join("hvigor", "hvigor-config.json5")]
|
|
4046
4233
|
];
|
|
4047
4234
|
function missingProjectRootFiles(projectDir) {
|
|
4048
|
-
return PROJECT_ROOT_FILES.filter((names) => !names.some((f) =>
|
|
4235
|
+
return PROJECT_ROOT_FILES.filter((names) => !names.some((f) => fs28.existsSync(path28.join(projectDir, f)))).map(
|
|
4049
4236
|
(names) => names[0]
|
|
4050
4237
|
);
|
|
4051
4238
|
}
|
|
@@ -4127,32 +4314,22 @@ ${hint}`, err);
|
|
|
4127
4314
|
return { exitCode, durationMs: Date.now() - start, discoveredHaps, warnings };
|
|
4128
4315
|
}
|
|
4129
4316
|
|
|
4130
|
-
// src/project/validators.ts
|
|
4131
|
-
function isValidProjectName(name) {
|
|
4132
|
-
if (!name) return false;
|
|
4133
|
-
if (name.includes("/") || name.includes("\\")) return false;
|
|
4134
|
-
return /^[A-Za-z0-9._-]+$/.test(name);
|
|
4135
|
-
}
|
|
4136
|
-
function isValidBundleName(bundleName) {
|
|
4137
|
-
return /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(bundleName);
|
|
4138
|
-
}
|
|
4139
|
-
|
|
4140
4317
|
// src/project/templates.ts
|
|
4141
|
-
import * as
|
|
4142
|
-
import * as
|
|
4318
|
+
import * as fs29 from "fs";
|
|
4319
|
+
import * as path29 from "path";
|
|
4143
4320
|
function toHumanTemplateName(folderName) {
|
|
4144
4321
|
return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
|
|
4145
4322
|
}
|
|
4146
4323
|
function listTemplates(templateRoot) {
|
|
4147
|
-
if (!
|
|
4148
|
-
const entries =
|
|
4324
|
+
if (!fs29.existsSync(templateRoot)) return [];
|
|
4325
|
+
const entries = fs29.readdirSync(templateRoot, { withFileTypes: true });
|
|
4149
4326
|
return entries.filter((e) => e.isDirectory()).map((e) => {
|
|
4150
|
-
const dir =
|
|
4151
|
-
const metaPath =
|
|
4327
|
+
const dir = path29.join(templateRoot, e.name);
|
|
4328
|
+
const metaPath = path29.join(dir, "template.json");
|
|
4152
4329
|
let meta = {};
|
|
4153
|
-
if (
|
|
4330
|
+
if (fs29.existsSync(metaPath)) {
|
|
4154
4331
|
try {
|
|
4155
|
-
meta = JSON.parse(
|
|
4332
|
+
meta = JSON.parse(fs29.readFileSync(metaPath, "utf8"));
|
|
4156
4333
|
} catch {
|
|
4157
4334
|
}
|
|
4158
4335
|
}
|
|
@@ -4167,57 +4344,57 @@ function listTemplates(templateRoot) {
|
|
|
4167
4344
|
function validateTemplateLayout(templateDir, defaultModuleName) {
|
|
4168
4345
|
const required = [
|
|
4169
4346
|
"build-profile.json5",
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4347
|
+
path29.join("AppScope", "app.json5"),
|
|
4348
|
+
path29.join(defaultModuleName, "src", "main", "module.json5"),
|
|
4349
|
+
path29.join(defaultModuleName, "oh-package.json5"),
|
|
4173
4350
|
"hvigorfile.ts"
|
|
4174
4351
|
];
|
|
4175
|
-
return required.filter((rel) => !
|
|
4352
|
+
return required.filter((rel) => !fs29.existsSync(path29.join(templateDir, rel)));
|
|
4176
4353
|
}
|
|
4177
4354
|
|
|
4178
4355
|
// src/project/createScaffold.ts
|
|
4179
|
-
import * as
|
|
4180
|
-
import * as
|
|
4356
|
+
import * as fs31 from "fs";
|
|
4357
|
+
import * as path30 from "path";
|
|
4181
4358
|
|
|
4182
4359
|
// src/project/jsonHelpers.ts
|
|
4183
|
-
import * as
|
|
4360
|
+
import * as fs30 from "fs";
|
|
4184
4361
|
import JSON55 from "json5";
|
|
4185
4362
|
function readJson5File(filePath) {
|
|
4186
|
-
return JSON55.parse(
|
|
4363
|
+
return JSON55.parse(fs30.readFileSync(filePath, "utf8"));
|
|
4187
4364
|
}
|
|
4188
4365
|
function writeJson5File(filePath, value) {
|
|
4189
|
-
|
|
4366
|
+
fs30.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
4190
4367
|
}
|
|
4191
4368
|
function readJsonFile(filePath) {
|
|
4192
|
-
return JSON.parse(
|
|
4369
|
+
return JSON.parse(fs30.readFileSync(filePath, "utf8"));
|
|
4193
4370
|
}
|
|
4194
4371
|
function writeJsonFile(filePath, value) {
|
|
4195
|
-
|
|
4372
|
+
fs30.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
4196
4373
|
}
|
|
4197
4374
|
|
|
4198
4375
|
// src/project/createScaffold.ts
|
|
4199
4376
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
|
|
4200
4377
|
async function pathExists(p) {
|
|
4201
4378
|
try {
|
|
4202
|
-
await
|
|
4379
|
+
await fs31.promises.access(p);
|
|
4203
4380
|
return true;
|
|
4204
4381
|
} catch {
|
|
4205
4382
|
return false;
|
|
4206
4383
|
}
|
|
4207
4384
|
}
|
|
4208
4385
|
async function copyDirRecursive(srcDir, destDir) {
|
|
4209
|
-
await
|
|
4210
|
-
const entries = await
|
|
4386
|
+
await fs31.promises.mkdir(destDir, { recursive: true });
|
|
4387
|
+
const entries = await fs31.promises.readdir(srcDir, { withFileTypes: true });
|
|
4211
4388
|
for (const entry of entries) {
|
|
4212
|
-
const src =
|
|
4213
|
-
const dest =
|
|
4389
|
+
const src = path30.join(srcDir, entry.name);
|
|
4390
|
+
const dest = path30.join(destDir, entry.name);
|
|
4214
4391
|
if (entry.isDirectory()) {
|
|
4215
4392
|
await copyDirRecursive(src, dest);
|
|
4216
4393
|
} else if (entry.isSymbolicLink()) {
|
|
4217
4394
|
continue;
|
|
4218
4395
|
} else if (entry.isFile()) {
|
|
4219
4396
|
if (entry.name === "template.json") continue;
|
|
4220
|
-
await
|
|
4397
|
+
await fs31.promises.copyFile(src, dest);
|
|
4221
4398
|
}
|
|
4222
4399
|
}
|
|
4223
4400
|
}
|
|
@@ -4225,9 +4402,9 @@ async function normalizeJson5ToJson(projectDir, logger) {
|
|
|
4225
4402
|
const stack = [projectDir];
|
|
4226
4403
|
while (stack.length > 0) {
|
|
4227
4404
|
const currentDir = stack.pop();
|
|
4228
|
-
const entries = await
|
|
4405
|
+
const entries = await fs31.promises.readdir(currentDir, { withFileTypes: true });
|
|
4229
4406
|
for (const entry of entries) {
|
|
4230
|
-
const fullPath =
|
|
4407
|
+
const fullPath = path30.join(currentDir, entry.name);
|
|
4231
4408
|
if (entry.isDirectory()) {
|
|
4232
4409
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4233
4410
|
stack.push(fullPath);
|
|
@@ -4249,23 +4426,23 @@ function toJavaPropertiesPath(p) {
|
|
|
4249
4426
|
function createOrUpdateLocalProperties(projectDir, sdkDir) {
|
|
4250
4427
|
const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
|
|
4251
4428
|
`;
|
|
4252
|
-
|
|
4429
|
+
fs31.writeFileSync(path30.join(projectDir, "local.properties"), content, "utf8");
|
|
4253
4430
|
}
|
|
4254
4431
|
function renameIfExists(fromPath, toPath) {
|
|
4255
|
-
if (
|
|
4256
|
-
|
|
4432
|
+
if (fs31.existsSync(fromPath) && fromPath !== toPath) {
|
|
4433
|
+
fs31.renameSync(fromPath, toPath);
|
|
4257
4434
|
}
|
|
4258
4435
|
}
|
|
4259
4436
|
function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
4260
|
-
const appJsonPath =
|
|
4261
|
-
if (
|
|
4437
|
+
const appJsonPath = path30.join(projectDir, "AppScope", "app.json5");
|
|
4438
|
+
if (fs31.existsSync(appJsonPath)) {
|
|
4262
4439
|
const appJson = readJson5File(appJsonPath);
|
|
4263
4440
|
appJson.app = appJson.app ?? {};
|
|
4264
4441
|
appJson.app.bundleName = args.bundleName;
|
|
4265
4442
|
writeJson5File(appJsonPath, appJson);
|
|
4266
4443
|
}
|
|
4267
|
-
const stringsPath =
|
|
4268
|
-
if (
|
|
4444
|
+
const stringsPath = path30.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
|
|
4445
|
+
if (fs31.existsSync(stringsPath)) {
|
|
4269
4446
|
const strings = readJsonFile(stringsPath);
|
|
4270
4447
|
if (Array.isArray(strings.string)) {
|
|
4271
4448
|
const appName = strings.string.find((s) => s.name === "app_name");
|
|
@@ -4273,8 +4450,8 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
4273
4450
|
}
|
|
4274
4451
|
writeJsonFile(stringsPath, strings);
|
|
4275
4452
|
}
|
|
4276
|
-
const buildProfilePath =
|
|
4277
|
-
if (
|
|
4453
|
+
const buildProfilePath = path30.join(projectDir, "build-profile.json5");
|
|
4454
|
+
if (fs31.existsSync(buildProfilePath)) {
|
|
4278
4455
|
const buildProfile = readJson5File(buildProfilePath);
|
|
4279
4456
|
buildProfile.app = buildProfile.app ?? {};
|
|
4280
4457
|
if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
|
|
@@ -4287,23 +4464,23 @@ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
|
|
|
4287
4464
|
}
|
|
4288
4465
|
writeJson5File(buildProfilePath, buildProfile);
|
|
4289
4466
|
}
|
|
4290
|
-
const moduleJsonPath =
|
|
4291
|
-
if (
|
|
4467
|
+
const moduleJsonPath = path30.join(projectDir, args.moduleName, "src", "main", "module.json5");
|
|
4468
|
+
if (fs31.existsSync(moduleJsonPath)) {
|
|
4292
4469
|
const moduleJson = readJson5File(moduleJsonPath);
|
|
4293
4470
|
moduleJson.module = moduleJson.module ?? {};
|
|
4294
4471
|
moduleJson.module.name = args.moduleName;
|
|
4295
4472
|
writeJson5File(moduleJsonPath, moduleJson);
|
|
4296
4473
|
}
|
|
4297
|
-
const moduleOhPackagePath =
|
|
4298
|
-
if (
|
|
4474
|
+
const moduleOhPackagePath = path30.join(projectDir, args.moduleName, "oh-package.json5");
|
|
4475
|
+
if (fs31.existsSync(moduleOhPackagePath)) {
|
|
4299
4476
|
const pkg = readJson5File(moduleOhPackagePath);
|
|
4300
4477
|
pkg.name = args.moduleName;
|
|
4301
4478
|
writeJson5File(moduleOhPackagePath, pkg);
|
|
4302
4479
|
}
|
|
4303
|
-
const vscodeDir =
|
|
4304
|
-
|
|
4480
|
+
const vscodeDir = path30.join(projectDir, ".vscode");
|
|
4481
|
+
fs31.mkdirSync(vscodeDir, { recursive: true });
|
|
4305
4482
|
const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
|
|
4306
|
-
writeJsonFile(
|
|
4483
|
+
writeJsonFile(path30.join(vscodeDir, "settings.json"), {
|
|
4307
4484
|
"oniro.hapPath": hapPath,
|
|
4308
4485
|
"files.associations": { "*.json5": "jsonc" }
|
|
4309
4486
|
});
|
|
@@ -4320,7 +4497,7 @@ async function createScaffold(opts) {
|
|
|
4320
4497
|
if (!opts.location || !await pathExists(opts.location)) {
|
|
4321
4498
|
throw new OniroError(`Location does not exist: ${opts.location}`);
|
|
4322
4499
|
}
|
|
4323
|
-
const templateDir =
|
|
4500
|
+
const templateDir = path30.join(opts.templateRoot, opts.templateId);
|
|
4324
4501
|
if (!await pathExists(templateDir)) {
|
|
4325
4502
|
throw new OniroError(`Template not found: ${templateDir}`);
|
|
4326
4503
|
}
|
|
@@ -4355,17 +4532,17 @@ async function createScaffold(opts) {
|
|
|
4355
4532
|
}
|
|
4356
4533
|
}
|
|
4357
4534
|
}
|
|
4358
|
-
const projectDir =
|
|
4535
|
+
const projectDir = path30.join(opts.location, opts.projectName);
|
|
4359
4536
|
if (await pathExists(projectDir)) {
|
|
4360
4537
|
if (!opts.overwrite) {
|
|
4361
4538
|
throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
|
|
4362
4539
|
}
|
|
4363
|
-
await
|
|
4540
|
+
await fs31.promises.rm(projectDir, { recursive: true, force: true });
|
|
4364
4541
|
}
|
|
4365
4542
|
logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
|
|
4366
4543
|
await copyDirRecursive(templateDir, projectDir);
|
|
4367
4544
|
if (moduleName !== defaultModuleName) {
|
|
4368
|
-
renameIfExists(
|
|
4545
|
+
renameIfExists(path30.join(projectDir, defaultModuleName), path30.join(projectDir, moduleName));
|
|
4369
4546
|
}
|
|
4370
4547
|
updateTemplateConfigs(
|
|
4371
4548
|
projectDir,
|
|
@@ -4382,11 +4559,11 @@ async function createScaffold(opts) {
|
|
|
4382
4559
|
}
|
|
4383
4560
|
|
|
4384
4561
|
// src/project/listModules.ts
|
|
4385
|
-
import * as
|
|
4386
|
-
import * as
|
|
4562
|
+
import * as fs32 from "fs";
|
|
4563
|
+
import * as path31 from "path";
|
|
4387
4564
|
function listModules(opts) {
|
|
4388
|
-
const buildProfile =
|
|
4389
|
-
if (!
|
|
4565
|
+
const buildProfile = path31.join(opts.projectDir, "build-profile.json5");
|
|
4566
|
+
if (!fs32.existsSync(buildProfile)) {
|
|
4390
4567
|
throw new OniroError(`build-profile.json5 not found at ${buildProfile}`);
|
|
4391
4568
|
}
|
|
4392
4569
|
const parsed = readJson5File(buildProfile);
|
|
@@ -4398,12 +4575,12 @@ function listModules(opts) {
|
|
|
4398
4575
|
}
|
|
4399
4576
|
|
|
4400
4577
|
// src/project/abilities.ts
|
|
4401
|
-
import * as
|
|
4402
|
-
import * as
|
|
4578
|
+
import * as fs33 from "fs";
|
|
4579
|
+
import * as path32 from "path";
|
|
4403
4580
|
function listAbilities(opts) {
|
|
4404
4581
|
const moduleName = opts.moduleName ?? "entry";
|
|
4405
|
-
const moduleJsonPath =
|
|
4406
|
-
if (!
|
|
4582
|
+
const moduleJsonPath = path32.join(opts.projectDir, moduleName, "src", "main", "module.json5");
|
|
4583
|
+
if (!fs33.existsSync(moduleJsonPath)) {
|
|
4407
4584
|
throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
|
|
4408
4585
|
}
|
|
4409
4586
|
const parsed = readJson5File(moduleJsonPath);
|
|
@@ -4411,12 +4588,12 @@ function listAbilities(opts) {
|
|
|
4411
4588
|
}
|
|
4412
4589
|
|
|
4413
4590
|
// src/project/prepareSigning.ts
|
|
4414
|
-
import * as
|
|
4415
|
-
import * as
|
|
4591
|
+
import * as fs34 from "fs";
|
|
4592
|
+
import * as path33 from "path";
|
|
4416
4593
|
function hasSigningArtifacts(dir) {
|
|
4417
|
-
if (!
|
|
4418
|
-
const buildProfile =
|
|
4419
|
-
if (!
|
|
4594
|
+
if (!fs34.existsSync(path33.join(dir, "signatures"))) return false;
|
|
4595
|
+
const buildProfile = path33.join(dir, "build-profile.json5");
|
|
4596
|
+
if (!fs34.existsSync(buildProfile)) return false;
|
|
4420
4597
|
try {
|
|
4421
4598
|
const parsed = readJson5File(buildProfile);
|
|
4422
4599
|
return Array.isArray(parsed.app?.signingConfigs) && parsed.app.signingConfigs.length > 0;
|
|
@@ -4495,7 +4672,6 @@ async function getDeviceInfo(opts) {
|
|
|
4495
4672
|
}
|
|
4496
4673
|
|
|
4497
4674
|
// src/install/diffHapAssets.ts
|
|
4498
|
-
import StreamZip2 from "node-stream-zip";
|
|
4499
4675
|
function diffEntryNames(installed, next) {
|
|
4500
4676
|
const installedSet = new Set(installed);
|
|
4501
4677
|
const nextSet = new Set(next);
|
|
@@ -4504,14 +4680,11 @@ function diffEntryNames(installed, next) {
|
|
|
4504
4680
|
removedAssetPaths: [...installedSet].filter((n) => !nextSet.has(n)).sort()
|
|
4505
4681
|
};
|
|
4506
4682
|
}
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
} finally {
|
|
4513
|
-
await zip.close();
|
|
4514
|
-
}
|
|
4683
|
+
function listFileEntries(hapPath) {
|
|
4684
|
+
return withHapZip(
|
|
4685
|
+
hapPath,
|
|
4686
|
+
(zip) => Object.values(zip.entries()).filter((e) => !e.isDirectory).map((e) => e.name)
|
|
4687
|
+
);
|
|
4515
4688
|
}
|
|
4516
4689
|
async function diffHapAssets(opts) {
|
|
4517
4690
|
const [installed, next] = await Promise.all([listFileEntries(opts.installedHap), listFileEntries(opts.newHap)]);
|
|
@@ -4519,8 +4692,8 @@ async function diffHapAssets(opts) {
|
|
|
4519
4692
|
}
|
|
4520
4693
|
|
|
4521
4694
|
// src/install/apply.ts
|
|
4522
|
-
import * as
|
|
4523
|
-
import * as
|
|
4695
|
+
import * as fs35 from "fs";
|
|
4696
|
+
import * as path34 from "path";
|
|
4524
4697
|
|
|
4525
4698
|
// src/install/hapBundleName.ts
|
|
4526
4699
|
function bundleNameFromHapConfig(text) {
|
|
@@ -4558,7 +4731,7 @@ async function pidOf(config, bundle, deviceSerial) {
|
|
|
4558
4731
|
async function resolveHap(opts, logger) {
|
|
4559
4732
|
let hap;
|
|
4560
4733
|
if (opts.hapPath) {
|
|
4561
|
-
const hapPath = !
|
|
4734
|
+
const hapPath = !path34.isAbsolute(opts.hapPath) && opts.projectDir ? path34.join(opts.projectDir, opts.hapPath) : opts.hapPath;
|
|
4562
4735
|
hap = existingHapOrUnsigned(hapPath);
|
|
4563
4736
|
if (!hap) throw new OniroError(`HAP not found at ${hapPath}.`);
|
|
4564
4737
|
} else {
|
|
@@ -4567,7 +4740,7 @@ async function resolveHap(opts, logger) {
|
|
|
4567
4740
|
if (!picked) throw new OniroError(`No HAP found${opts.module ? ` for module ${opts.module}` : ""}. Build first.`);
|
|
4568
4741
|
hap = { path: picked.path, fallback: !picked.signed };
|
|
4569
4742
|
}
|
|
4570
|
-
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${
|
|
4743
|
+
if (hap.fallback) logger.warn(`No signed HAP found; installing the unsigned ${path34.basename(hap.path)}.`);
|
|
4571
4744
|
return hap;
|
|
4572
4745
|
}
|
|
4573
4746
|
async function applyChanges(opts) {
|
|
@@ -4583,12 +4756,12 @@ async function applyChanges(opts) {
|
|
|
4583
4756
|
});
|
|
4584
4757
|
if (hapBundle && hapBundle !== bundle) {
|
|
4585
4758
|
throw new OniroError(
|
|
4586
|
-
`${
|
|
4759
|
+
`${path34.basename(hapPath)} (${hapPath}) is bundle ${hapBundle}, not ${bundle}, so installing it would not update ${bundle}. Choose the HAP explicitly, or name ${hapBundle} as the bundle if it is the app to update.`
|
|
4587
4760
|
);
|
|
4588
4761
|
}
|
|
4589
4762
|
logger.info(`installing ${hapPath}`);
|
|
4590
4763
|
let assetsChanged = false;
|
|
4591
|
-
if (opts.installedHapPath &&
|
|
4764
|
+
if (opts.installedHapPath && fs35.existsSync(opts.installedHapPath)) {
|
|
4592
4765
|
try {
|
|
4593
4766
|
const diff = await diffHapAssets({ installedHap: opts.installedHapPath, newHap: hapPath });
|
|
4594
4767
|
assetsChanged = diff.addedAssetPaths.length > 0 || diff.removedAssetPaths.length > 0;
|
|
@@ -4621,7 +4794,7 @@ async function applyChanges(opts) {
|
|
|
4621
4794
|
}
|
|
4622
4795
|
if (!await isBundleInstalled({ config, bundle, deviceSerial })) {
|
|
4623
4796
|
throw new OniroError(
|
|
4624
|
-
`hdc reported ${
|
|
4797
|
+
`hdc reported ${path34.basename(hapPath)} as installed, but bm dump does not list ${bundle}. Check that the HAP was built from this project (its bundleName must match).`
|
|
4625
4798
|
);
|
|
4626
4799
|
}
|
|
4627
4800
|
let postInstallPid = await pidOf(config, bundle, deviceSerial);
|
|
@@ -4698,10 +4871,10 @@ var AUTH_PATHS = {
|
|
|
4698
4871
|
};
|
|
4699
4872
|
|
|
4700
4873
|
// src/harmonyos/auth/tokenStore.ts
|
|
4701
|
-
import * as
|
|
4702
|
-
import * as
|
|
4703
|
-
import * as
|
|
4704
|
-
import * as
|
|
4874
|
+
import * as fs36 from "fs";
|
|
4875
|
+
import * as os12 from "os";
|
|
4876
|
+
import * as path35 from "path";
|
|
4877
|
+
import * as crypto4 from "crypto";
|
|
4705
4878
|
var ALGORITHM = "aes-256-gcm";
|
|
4706
4879
|
var KEY_LENGTH = 32;
|
|
4707
4880
|
var IV_LENGTH = 12;
|
|
@@ -4710,8 +4883,8 @@ var KEY_FILE = "kek.bin";
|
|
|
4710
4883
|
var DIR_MODE = 448;
|
|
4711
4884
|
var FILE_MODE = 384;
|
|
4712
4885
|
function encrypt2(key, plaintext) {
|
|
4713
|
-
const iv =
|
|
4714
|
-
const cipher =
|
|
4886
|
+
const iv = crypto4.randomBytes(IV_LENGTH);
|
|
4887
|
+
const cipher = crypto4.createCipheriv(ALGORITHM, key, iv);
|
|
4715
4888
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
4716
4889
|
return {
|
|
4717
4890
|
version: 1,
|
|
@@ -4722,7 +4895,7 @@ function encrypt2(key, plaintext) {
|
|
|
4722
4895
|
};
|
|
4723
4896
|
}
|
|
4724
4897
|
function decrypt2(key, blob) {
|
|
4725
|
-
const decipher =
|
|
4898
|
+
const decipher = crypto4.createDecipheriv(ALGORITHM, key, Buffer.from(blob.iv, "base64"));
|
|
4726
4899
|
decipher.setAuthTag(Buffer.from(blob.authTag, "base64"));
|
|
4727
4900
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "base64")), decipher.final()]);
|
|
4728
4901
|
}
|
|
@@ -4736,7 +4909,7 @@ function permissionHint(dir) {
|
|
|
4736
4909
|
}
|
|
4737
4910
|
function ensureDir(dir) {
|
|
4738
4911
|
try {
|
|
4739
|
-
|
|
4912
|
+
fs36.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
4740
4913
|
} catch (err) {
|
|
4741
4914
|
if (err.code === "EACCES") {
|
|
4742
4915
|
throw new OniroError(permissionHint(dir), err);
|
|
@@ -4747,17 +4920,17 @@ function ensureDir(dir) {
|
|
|
4747
4920
|
function createTokenStore(opts) {
|
|
4748
4921
|
const logger = opts.logger ?? noopLogger;
|
|
4749
4922
|
const authDir = opts.config.get("harmonyosAuthDir", defaultPaths.harmonyosAuthDir());
|
|
4750
|
-
const tokenPath =
|
|
4751
|
-
const keyPath =
|
|
4923
|
+
const tokenPath = path35.join(authDir, TOKEN_FILE);
|
|
4924
|
+
const keyPath = path35.join(os12.homedir(), ".local", "share", "oniro-app", "keys", KEY_FILE);
|
|
4752
4925
|
function loadOrCreateKey() {
|
|
4753
|
-
if (
|
|
4754
|
-
const existing =
|
|
4926
|
+
if (fs36.existsSync(keyPath)) {
|
|
4927
|
+
const existing = fs36.readFileSync(keyPath);
|
|
4755
4928
|
if (existing.length === KEY_LENGTH) return existing;
|
|
4756
4929
|
logger.warn("[harmonyos] The token key file was malformed; regenerating (you will need to log in again).");
|
|
4757
4930
|
}
|
|
4758
|
-
ensureDir(
|
|
4759
|
-
const key =
|
|
4760
|
-
|
|
4931
|
+
ensureDir(path35.dirname(keyPath));
|
|
4932
|
+
const key = crypto4.randomBytes(KEY_LENGTH);
|
|
4933
|
+
fs36.writeFileSync(keyPath, key, { mode: FILE_MODE });
|
|
4761
4934
|
return key;
|
|
4762
4935
|
}
|
|
4763
4936
|
return {
|
|
@@ -4767,12 +4940,12 @@ function createTokenStore(opts) {
|
|
|
4767
4940
|
const payload = Buffer.from(JSON.stringify(session), "utf8");
|
|
4768
4941
|
const blob = encrypt2(loadOrCreateKey(), payload);
|
|
4769
4942
|
ensureDir(authDir);
|
|
4770
|
-
|
|
4943
|
+
fs36.writeFileSync(tokenPath, JSON.stringify(blob, null, 2), { mode: FILE_MODE });
|
|
4771
4944
|
},
|
|
4772
4945
|
load() {
|
|
4773
|
-
if (!
|
|
4946
|
+
if (!fs36.existsSync(tokenPath)) return null;
|
|
4774
4947
|
try {
|
|
4775
|
-
const blob = JSON.parse(
|
|
4948
|
+
const blob = JSON.parse(fs36.readFileSync(tokenPath, "utf8"));
|
|
4776
4949
|
if (!isEncryptedBlob(blob)) return null;
|
|
4777
4950
|
const { jwtToken, siteId } = JSON.parse(decrypt2(loadOrCreateKey(), blob).toString("utf8"));
|
|
4778
4951
|
return typeof jwtToken === "string" && jwtToken && typeof siteId === "string" ? { jwtToken, siteId } : null;
|
|
@@ -4784,7 +4957,7 @@ function createTokenStore(opts) {
|
|
|
4784
4957
|
}
|
|
4785
4958
|
logger.debug(`[harmonyos] Discarding unreadable token at ${tokenPath}.`);
|
|
4786
4959
|
try {
|
|
4787
|
-
|
|
4960
|
+
fs36.rmSync(tokenPath, { force: true });
|
|
4788
4961
|
} catch {
|
|
4789
4962
|
}
|
|
4790
4963
|
return null;
|
|
@@ -4792,7 +4965,7 @@ function createTokenStore(opts) {
|
|
|
4792
4965
|
},
|
|
4793
4966
|
clear() {
|
|
4794
4967
|
try {
|
|
4795
|
-
|
|
4968
|
+
fs36.rmSync(tokenPath, { force: true });
|
|
4796
4969
|
} catch (err) {
|
|
4797
4970
|
throw new OniroError(`Failed to remove ${tokenPath}.`, err);
|
|
4798
4971
|
}
|
|
@@ -4801,7 +4974,7 @@ function createTokenStore(opts) {
|
|
|
4801
4974
|
}
|
|
4802
4975
|
|
|
4803
4976
|
// src/harmonyos/auth/session.ts
|
|
4804
|
-
import * as
|
|
4977
|
+
import * as crypto6 from "crypto";
|
|
4805
4978
|
|
|
4806
4979
|
// src/harmonyos/http.ts
|
|
4807
4980
|
import { EnvHttpProxyAgent, fetch } from "undici";
|
|
@@ -4892,13 +5065,13 @@ function createHttpClient(opts = {}) {
|
|
|
4892
5065
|
|
|
4893
5066
|
// src/harmonyos/auth/callbackServer.ts
|
|
4894
5067
|
import * as http2 from "http";
|
|
4895
|
-
import * as
|
|
5068
|
+
import * as crypto5 from "crypto";
|
|
4896
5069
|
var CALLBACK_PATH = "/callback";
|
|
4897
5070
|
var MAX_BODY_BYTES = 65536;
|
|
4898
5071
|
function codeMatches(code, secret) {
|
|
4899
5072
|
const a = Buffer.from(code ?? "", "utf8");
|
|
4900
5073
|
const b = Buffer.from(secret, "utf8");
|
|
4901
|
-
return a.length === b.length &&
|
|
5074
|
+
return a.length === b.length && crypto5.timingSafeEqual(a, b);
|
|
4902
5075
|
}
|
|
4903
5076
|
function isQuit(quit) {
|
|
4904
5077
|
return quit === "true" || quit === "access_denied" || quit === "quit";
|
|
@@ -4906,8 +5079,8 @@ function isQuit(quit) {
|
|
|
4906
5079
|
async function startCallbackServer(opts) {
|
|
4907
5080
|
let resolveCallback;
|
|
4908
5081
|
let rejectCallback;
|
|
4909
|
-
const callback = new Promise((
|
|
4910
|
-
resolveCallback =
|
|
5082
|
+
const callback = new Promise((resolve9, reject) => {
|
|
5083
|
+
resolveCallback = resolve9;
|
|
4911
5084
|
rejectCallback = reject;
|
|
4912
5085
|
});
|
|
4913
5086
|
callback.catch(() => {
|
|
@@ -4966,9 +5139,9 @@ async function startCallbackServer(opts) {
|
|
|
4966
5139
|
}
|
|
4967
5140
|
});
|
|
4968
5141
|
server.keepAliveTimeout = 1;
|
|
4969
|
-
await new Promise((
|
|
5142
|
+
await new Promise((resolve9, reject) => {
|
|
4970
5143
|
server.once("error", (err) => reject(new OniroError("Failed to start the local login callback server.", err)));
|
|
4971
|
-
server.listen(0, "127.0.0.1",
|
|
5144
|
+
server.listen(0, "127.0.0.1", resolve9);
|
|
4972
5145
|
});
|
|
4973
5146
|
const port = server.address().port;
|
|
4974
5147
|
return {
|
|
@@ -4984,12 +5157,12 @@ async function startCallbackServer(opts) {
|
|
|
4984
5157
|
return Promise.race([callback, timeout]).finally(() => clearTimeout(timer));
|
|
4985
5158
|
},
|
|
4986
5159
|
stop() {
|
|
4987
|
-
return new Promise((
|
|
5160
|
+
return new Promise((resolve9) => {
|
|
4988
5161
|
server.closeAllConnections?.();
|
|
4989
|
-
const timer = setTimeout(
|
|
5162
|
+
const timer = setTimeout(resolve9, 100);
|
|
4990
5163
|
server.close(() => {
|
|
4991
5164
|
clearTimeout(timer);
|
|
4992
|
-
|
|
5165
|
+
resolve9();
|
|
4993
5166
|
});
|
|
4994
5167
|
});
|
|
4995
5168
|
}
|
|
@@ -5021,11 +5194,11 @@ function openBrowser(url) {
|
|
|
5021
5194
|
windowsHide: true
|
|
5022
5195
|
});
|
|
5023
5196
|
child.unref();
|
|
5024
|
-
return new Promise((
|
|
5197
|
+
return new Promise((resolve9, reject) => {
|
|
5025
5198
|
child.on("error", (err) => reject(new OniroError(`Failed to open a browser (${command}).`, err)));
|
|
5026
5199
|
child.on(
|
|
5027
5200
|
"exit",
|
|
5028
|
-
(code) => code === 0 ?
|
|
5201
|
+
(code) => code === 0 ? resolve9() : reject(new OniroError(`Browser launcher '${command}' exited with code ${code}.`))
|
|
5029
5202
|
);
|
|
5030
5203
|
});
|
|
5031
5204
|
}
|
|
@@ -5107,7 +5280,7 @@ function createHarmonyOsSession(opts) {
|
|
|
5107
5280
|
getDeveloperAgreement,
|
|
5108
5281
|
async login(o = {}) {
|
|
5109
5282
|
const start = HUAWEI_SITES.CN.loginUrl;
|
|
5110
|
-
const clientSecret =
|
|
5283
|
+
const clientSecret = crypto6.randomUUID().replace(/-/g, "");
|
|
5111
5284
|
const server = await startCallbackServer({
|
|
5112
5285
|
clientSecret,
|
|
5113
5286
|
baseUrl: start,
|
|
@@ -5273,7 +5446,7 @@ function mapAgcError(operation, statusCode, reasonPhrase, body) {
|
|
|
5273
5446
|
}
|
|
5274
5447
|
|
|
5275
5448
|
// src/harmonyos/signing/profile.ts
|
|
5276
|
-
import * as
|
|
5449
|
+
import * as fs37 from "fs";
|
|
5277
5450
|
import { X509Certificate } from "crypto";
|
|
5278
5451
|
import forge from "node-forge";
|
|
5279
5452
|
function extractProfileJson(buffer) {
|
|
@@ -5305,7 +5478,7 @@ function extractProfileJson(buffer) {
|
|
|
5305
5478
|
function readProvisionProfile(profilePath) {
|
|
5306
5479
|
let raw;
|
|
5307
5480
|
try {
|
|
5308
|
-
const json = extractProfileJson(
|
|
5481
|
+
const json = extractProfileJson(fs37.readFileSync(profilePath));
|
|
5309
5482
|
if (!json) return null;
|
|
5310
5483
|
raw = JSON.parse(json);
|
|
5311
5484
|
} catch {
|
|
@@ -5334,7 +5507,7 @@ function parseCertificate(pem) {
|
|
|
5334
5507
|
}
|
|
5335
5508
|
function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
5336
5509
|
try {
|
|
5337
|
-
const der = forge.util.createBuffer(
|
|
5510
|
+
const der = forge.util.createBuffer(fs37.readFileSync(p12Path).toString("binary"));
|
|
5338
5511
|
const p12 = forge.pkcs12.pkcs12FromAsn1(forge.asn1.fromDer(der), keyPwd);
|
|
5339
5512
|
const certBag = forge.pki.oids.certBag ?? "1.2.840.113549.1.12.10.1.3";
|
|
5340
5513
|
const wanted = certificate.publicKey.export({ type: "spki", format: "der" });
|
|
@@ -5350,7 +5523,7 @@ function keystoreHoldsKey(p12Path, keyAlias, keyPwd, certificate) {
|
|
|
5350
5523
|
}
|
|
5351
5524
|
}
|
|
5352
5525
|
function verifySigningMaterial(opts) {
|
|
5353
|
-
const pems =
|
|
5526
|
+
const pems = fs37.readFileSync(opts.cerPath, "utf8").match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
|
|
5354
5527
|
const chain = (pems ?? []).map(parseCertificate);
|
|
5355
5528
|
const leaf = chain.find((c) => !c.ca) ?? chain.at(-1);
|
|
5356
5529
|
if (!leaf) throw new AgcError(SIGNING_ERRORS.CERT_INVALID);
|
|
@@ -5368,15 +5541,15 @@ function verifySigningMaterial(opts) {
|
|
|
5368
5541
|
}
|
|
5369
5542
|
|
|
5370
5543
|
// src/harmonyos/signing/autoSign.ts
|
|
5371
|
-
import * as
|
|
5372
|
-
import * as
|
|
5373
|
-
import * as
|
|
5544
|
+
import * as crypto10 from "crypto";
|
|
5545
|
+
import * as fs42 from "fs";
|
|
5546
|
+
import * as path41 from "path";
|
|
5374
5547
|
import JSON57 from "json5";
|
|
5375
5548
|
|
|
5376
5549
|
// src/harmonyos/signing/agc.ts
|
|
5377
|
-
import * as
|
|
5378
|
-
import * as
|
|
5379
|
-
import * as
|
|
5550
|
+
import * as crypto7 from "crypto";
|
|
5551
|
+
import * as fs38 from "fs";
|
|
5552
|
+
import * as path36 from "path";
|
|
5380
5553
|
var ENDPOINTS = {
|
|
5381
5554
|
CERT_LIST: "/api/cps/harmony-cert-manage/v1/cert/list",
|
|
5382
5555
|
CERT_ADD: "/api/cps/harmony-cert-manage/v1/cert/add",
|
|
@@ -5424,7 +5597,7 @@ function createAgcClient(http3, auth) {
|
|
|
5424
5597
|
}
|
|
5425
5598
|
},
|
|
5426
5599
|
async addDevice(udid, deviceKind) {
|
|
5427
|
-
const deviceName = `oniro_app_device_${
|
|
5600
|
+
const deviceName = `oniro_app_device_${crypto7.randomBytes(4).toString("hex")}`;
|
|
5428
5601
|
await call("device", "post", ENDPOINTS.DEVICE_ADD, {
|
|
5429
5602
|
body: { deviceName, udid, deviceType: String(DEVICE_TYPE_CODES[deviceKind] ?? DEVICE_TYPE_CODES.phone) }
|
|
5430
5603
|
});
|
|
@@ -5458,12 +5631,12 @@ function createAgcClient(http3, auth) {
|
|
|
5458
5631
|
const target = urlsInfo?.[0];
|
|
5459
5632
|
if (!target) throw mapAgcError(operation, 200, "", "");
|
|
5460
5633
|
const buffer = await http3.getBinary(target.newUrl, { timeoutMs: 6e4 });
|
|
5461
|
-
const actual =
|
|
5634
|
+
const actual = crypto7.createHash("sha256").update(buffer).digest("hex");
|
|
5462
5635
|
if (target.sha256 && actual !== target.sha256.toLowerCase()) {
|
|
5463
5636
|
throw new ChecksumMismatchError(target.sha256, actual);
|
|
5464
5637
|
}
|
|
5465
|
-
|
|
5466
|
-
|
|
5638
|
+
fs38.mkdirSync(path36.dirname(dest), { recursive: true, mode: 448 });
|
|
5639
|
+
fs38.writeFileSync(dest, buffer, { mode: 384 });
|
|
5467
5640
|
}
|
|
5468
5641
|
};
|
|
5469
5642
|
}
|
|
@@ -5518,23 +5691,23 @@ async function registerDevices(agc, connected, logger) {
|
|
|
5518
5691
|
}
|
|
5519
5692
|
|
|
5520
5693
|
// src/harmonyos/signing/keystore.ts
|
|
5521
|
-
import * as
|
|
5522
|
-
import * as
|
|
5523
|
-
import * as
|
|
5694
|
+
import * as crypto8 from "crypto";
|
|
5695
|
+
import * as fs40 from "fs";
|
|
5696
|
+
import * as path38 from "path";
|
|
5524
5697
|
|
|
5525
5698
|
// src/harmonyos/java.ts
|
|
5526
|
-
import * as
|
|
5527
|
-
import * as
|
|
5699
|
+
import * as fs39 from "fs";
|
|
5700
|
+
import * as path37 from "path";
|
|
5528
5701
|
var JAVA_BIN = process.platform === "win32" ? "java.exe" : "java";
|
|
5529
5702
|
function findJava(sdk, env = process.env) {
|
|
5530
5703
|
const candidates = [
|
|
5531
|
-
env.JAVA_HOME &&
|
|
5532
|
-
sdk &&
|
|
5704
|
+
env.JAVA_HOME && path37.join(env.JAVA_HOME, "bin", JAVA_BIN),
|
|
5705
|
+
sdk && path37.join(sdk.installRoot, "jbr", "bin", JAVA_BIN),
|
|
5533
5706
|
// macOS: the JBR is itself a bundle inside Studio's Contents.
|
|
5534
|
-
sdk &&
|
|
5535
|
-
...(env.PATH ?? "").split(
|
|
5707
|
+
sdk && path37.join(sdk.installRoot, "jbr", "Contents", "Home", "bin", JAVA_BIN),
|
|
5708
|
+
...(env.PATH ?? "").split(path37.delimiter).map((dir) => dir && path37.join(dir, JAVA_BIN))
|
|
5536
5709
|
];
|
|
5537
|
-
const found = candidates.find((c) => !!c &&
|
|
5710
|
+
const found = candidates.find((c) => !!c && fs39.existsSync(c));
|
|
5538
5711
|
if (found) return found;
|
|
5539
5712
|
throw new OniroError(
|
|
5540
5713
|
"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."
|
|
@@ -5547,49 +5720,77 @@ var SIGN_ALG = "SHA256withECDSA";
|
|
|
5547
5720
|
function redactArgs(args) {
|
|
5548
5721
|
return args.map((arg, i) => args[i - 1] === "-keyPwd" || args[i - 1] === "-keystorePwd" ? "******" : arg);
|
|
5549
5722
|
}
|
|
5550
|
-
|
|
5551
|
-
const jar =
|
|
5552
|
-
if (!
|
|
5723
|
+
function hapSignToolJar(sdk) {
|
|
5724
|
+
const jar = path38.join(sdk.sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
|
|
5725
|
+
if (!fs40.existsSync(jar)) {
|
|
5553
5726
|
throw new OniroError(`hap-sign-tool.jar not found at ${jar}; the HarmonyOS SDK looks incomplete.`);
|
|
5554
5727
|
}
|
|
5728
|
+
return jar;
|
|
5729
|
+
}
|
|
5730
|
+
async function runHapSignTool(opts) {
|
|
5731
|
+
const jar = hapSignToolJar(opts.sdk);
|
|
5555
5732
|
const java = findJava(opts.sdk);
|
|
5556
|
-
const
|
|
5557
|
-
|
|
5558
|
-
const
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
if (result.code !== 0) {
|
|
5563
|
-
const detail = (result.stderr || result.stdout || "").trim();
|
|
5564
|
-
throw new OniroError(`hap-sign-tool failed to ${what} (exit ${result.code}).${detail ? `
|
|
5733
|
+
const argv = ["-jar", jar, ...opts.args, "-keyAlias", KEY_ALIAS, "-keystoreFile", opts.p12Path, "-keystorePwd", opts.keyPwd, "-keyPwd", opts.keyPwd];
|
|
5734
|
+
opts.logger.debug(`[harmonyos] ${java} ${redactArgs(argv).join(" ")}`);
|
|
5735
|
+
const result = await runProcess({ command: java, args: argv, timeoutMs: 12e4, logger: opts.logger });
|
|
5736
|
+
if (result.code !== 0) {
|
|
5737
|
+
const detail = (result.stderr || result.stdout || "").trim();
|
|
5738
|
+
throw new OniroError(`hap-sign-tool failed to ${opts.what} (exit ${result.code}).${detail ? `
|
|
5565
5739
|
${detail}` : ""}`);
|
|
5566
|
-
|
|
5567
|
-
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
async function generateKeystoreAndCsr(opts) {
|
|
5743
|
+
hapSignToolJar(opts.sdk);
|
|
5744
|
+
findJava(opts.sdk);
|
|
5745
|
+
const keyPwd = crypto8.randomBytes(12).toString("base64url").replaceAll(/[-_]/g, "");
|
|
5746
|
+
fs40.mkdirSync(path38.dirname(opts.p12Path), { recursive: true, mode: 448 });
|
|
5747
|
+
const run = (what, args) => runHapSignTool({ ...opts, what, args, keyPwd });
|
|
5568
5748
|
opts.logger.info("[harmonyos] Generating the local keystore and certificate signing request...");
|
|
5569
5749
|
await run("generate the key pair", ["generate-keypair", "-keyAlg", "ECC", "-keySize", "NIST-P-256"]);
|
|
5570
5750
|
await run("generate the CSR", ["generate-csr", "-subject", "CN=DebugKey", "-signAlg", SIGN_ALG, "-outFile", opts.csrPath]);
|
|
5571
|
-
return { keyPwd, csr:
|
|
5751
|
+
return { keyPwd, csr: fs40.readFileSync(opts.csrPath, "utf8") };
|
|
5752
|
+
}
|
|
5753
|
+
async function signHapFile(opts) {
|
|
5754
|
+
await runHapSignTool({
|
|
5755
|
+
...opts,
|
|
5756
|
+
what: `sign ${opts.inFile}`,
|
|
5757
|
+
args: [
|
|
5758
|
+
"sign-app",
|
|
5759
|
+
"-mode",
|
|
5760
|
+
"localSign",
|
|
5761
|
+
"-signAlg",
|
|
5762
|
+
SIGN_ALG,
|
|
5763
|
+
"-appCertFile",
|
|
5764
|
+
opts.cerPath,
|
|
5765
|
+
"-profileFile",
|
|
5766
|
+
opts.profilePath,
|
|
5767
|
+
"-inFile",
|
|
5768
|
+
opts.inFile,
|
|
5769
|
+
"-outFile",
|
|
5770
|
+
opts.outFile
|
|
5771
|
+
]
|
|
5772
|
+
});
|
|
5572
5773
|
}
|
|
5573
5774
|
|
|
5574
5775
|
// src/harmonyos/signing/paths.ts
|
|
5575
|
-
import * as
|
|
5576
|
-
import * as
|
|
5776
|
+
import * as crypto9 from "crypto";
|
|
5777
|
+
import * as path39 from "path";
|
|
5577
5778
|
function sanitizeNamePart(raw) {
|
|
5578
5779
|
const safe = (raw?.trim() ?? "").replace(/[\\/:*?"<>|=.-]+/g, "_");
|
|
5579
5780
|
return safe.slice(0, 64) || "default";
|
|
5580
5781
|
}
|
|
5581
5782
|
function resolveSigningMaterialPaths(config, productName, projectRoot) {
|
|
5582
|
-
const hash =
|
|
5583
|
-
const base =
|
|
5783
|
+
const hash = crypto9.createHash("sha256").update(projectRoot, "utf8").digest("base64url").replaceAll(/[-_]/g, "");
|
|
5784
|
+
const base = path39.join(
|
|
5584
5785
|
config.get("harmonyosSigningDir", defaultPaths.harmonyosSigningDir()),
|
|
5585
|
-
`${sanitizeNamePart(productName)}_${
|
|
5786
|
+
`${sanitizeNamePart(productName)}_${path39.basename(projectRoot)}_${hash}=`
|
|
5586
5787
|
);
|
|
5587
5788
|
return { p12Path: `${base}.p12`, csrPath: `${base}.csr`, cerPath: `${base}.cer`, profilePath: `${base}.p7b` };
|
|
5588
5789
|
}
|
|
5589
5790
|
|
|
5590
5791
|
// src/harmonyos/signing/project.ts
|
|
5591
|
-
import * as
|
|
5592
|
-
import * as
|
|
5792
|
+
import * as fs41 from "fs";
|
|
5793
|
+
import * as path40 from "path";
|
|
5593
5794
|
import JSON56 from "json5";
|
|
5594
5795
|
|
|
5595
5796
|
// src/harmonyos/signing/aclPermissionList.ts
|
|
@@ -5657,9 +5858,9 @@ var ACL_PERMISSIONS = /* @__PURE__ */ new Set([
|
|
|
5657
5858
|
|
|
5658
5859
|
// src/harmonyos/signing/project.ts
|
|
5659
5860
|
function readRequestedPermissions(moduleConfigPath) {
|
|
5660
|
-
if (!
|
|
5861
|
+
if (!fs41.existsSync(moduleConfigPath)) return [];
|
|
5661
5862
|
try {
|
|
5662
|
-
const parsed = JSON56.parse(
|
|
5863
|
+
const parsed = JSON56.parse(fs41.readFileSync(moduleConfigPath, "utf-8"));
|
|
5663
5864
|
return (parsed.module?.requestPermissions ?? []).map((p) => p?.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
5664
5865
|
} catch {
|
|
5665
5866
|
return [];
|
|
@@ -5668,32 +5869,38 @@ function readRequestedPermissions(moduleConfigPath) {
|
|
|
5668
5869
|
function collectAclPermissions(projectDir, logger = noopLogger) {
|
|
5669
5870
|
const requested = /* @__PURE__ */ new Set();
|
|
5670
5871
|
for (const module of listModules({ projectDir })) {
|
|
5671
|
-
const moduleRoot =
|
|
5872
|
+
const moduleRoot = path40.resolve(projectDir, module.srcPath);
|
|
5672
5873
|
for (const sourceSet of ["main", "ohosTest"]) {
|
|
5673
5874
|
for (const name of readRequestedPermissions(
|
|
5674
|
-
|
|
5875
|
+
path40.join(moduleRoot, "src", sourceSet, "module.json5")
|
|
5675
5876
|
)) {
|
|
5676
5877
|
requested.add(name);
|
|
5677
5878
|
}
|
|
5678
5879
|
}
|
|
5679
5880
|
}
|
|
5680
|
-
const acls =
|
|
5881
|
+
const acls = aclPermissionsAmong(requested);
|
|
5681
5882
|
if (acls.length > 0) {
|
|
5682
5883
|
logger.info(`[harmonyos] Project requests ${acls.length} ACL permission(s): ${acls.join(", ")}`);
|
|
5683
5884
|
}
|
|
5684
5885
|
return acls;
|
|
5685
5886
|
}
|
|
5887
|
+
function aclPermissionsAmong(names) {
|
|
5888
|
+
return [...new Set(names)].filter((p) => ACL_PERMISSIONS.has(p)).sort();
|
|
5889
|
+
}
|
|
5686
5890
|
|
|
5687
5891
|
// src/harmonyos/signing/autoSign.ts
|
|
5688
5892
|
function shouldRegenerate(input) {
|
|
5689
5893
|
if (input.force) return "--force was passed";
|
|
5690
|
-
const missing = Object.values(input.paths).find((file) => !
|
|
5894
|
+
const missing = Object.values(input.paths).find((file) => !fs42.existsSync(file));
|
|
5691
5895
|
if (missing) return `${missing} is missing`;
|
|
5692
5896
|
const config = input.buildProfile.app?.signingConfigs?.find((c) => c.name === input.productName);
|
|
5693
5897
|
if (config?.type !== "HarmonyOS" || config.material?.profile !== input.paths.profilePath) {
|
|
5694
5898
|
return "build-profile.json5 has no signingConfig for this material";
|
|
5695
5899
|
}
|
|
5696
|
-
|
|
5900
|
+
return profileProblem({ ...input, profilePath: input.paths.profilePath });
|
|
5901
|
+
}
|
|
5902
|
+
function profileProblem(input) {
|
|
5903
|
+
const profile = readProvisionProfile(input.profilePath);
|
|
5697
5904
|
if (!profile) return "the provisioning profile could not be read";
|
|
5698
5905
|
if (profile.bundleName !== input.bundleName) return `the profile is for bundle '${profile.bundleName}'`;
|
|
5699
5906
|
if (profile.developerId && profile.developerId !== input.teamId) {
|
|
@@ -5726,25 +5933,23 @@ function writeSigningConfig(opts) {
|
|
|
5726
5933
|
for (const product of app.products ?? []) {
|
|
5727
5934
|
if (product.name === opts.productName) product.signingConfig = opts.productName;
|
|
5728
5935
|
}
|
|
5729
|
-
|
|
5936
|
+
fs42.writeFileSync(opts.buildProfilePath, JSON.stringify(opts.buildProfile, null, 2));
|
|
5730
5937
|
}
|
|
5731
5938
|
async function harmonyOsAutoSign(opts) {
|
|
5732
5939
|
const logger = opts.logger ?? noopLogger;
|
|
5733
5940
|
const productName = opts.productName ?? "default";
|
|
5734
|
-
const projectDir =
|
|
5735
|
-
const buildProfilePath =
|
|
5941
|
+
const projectDir = path41.resolve(opts.projectDir);
|
|
5942
|
+
const buildProfilePath = path41.join(projectDir, "build-profile.json5");
|
|
5736
5943
|
let buildProfile;
|
|
5737
5944
|
try {
|
|
5738
|
-
buildProfile = JSON57.parse(
|
|
5945
|
+
buildProfile = JSON57.parse(fs42.readFileSync(buildProfilePath, "utf-8"));
|
|
5739
5946
|
} catch (err) {
|
|
5740
5947
|
throw new OniroError(`Could not read ${buildProfilePath}: ${err.message}`, err);
|
|
5741
5948
|
}
|
|
5742
5949
|
const product = buildProfile.app?.products?.find((p) => p.name === productName);
|
|
5743
5950
|
if (!product) throw new OniroError(`${buildProfilePath} declares no product named '${productName}'.`);
|
|
5744
5951
|
const bundleName = product.bundleName || getBundleName(projectDir);
|
|
5745
|
-
|
|
5746
|
-
throw new AgcError(SIGNING_ERRORS.BUNDLE_NAME_INVALID);
|
|
5747
|
-
}
|
|
5952
|
+
assertAgcBundleName(bundleName);
|
|
5748
5953
|
const sdk = requireHarmonyOsSdk({ config: opts.config, logger });
|
|
5749
5954
|
const auth = await opts.session.resolveAgcAuth({ teamId: opts.teamId });
|
|
5750
5955
|
const aclPermissions = collectAclPermissions(projectDir, logger);
|
|
@@ -5773,14 +5978,47 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5773
5978
|
return result(false);
|
|
5774
5979
|
}
|
|
5775
5980
|
logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
|
|
5776
|
-
const
|
|
5981
|
+
const keyPwd = await issueSigningMaterial({
|
|
5982
|
+
agc: createAgcClient(opts.http ?? createHttpClient({ logger }), auth),
|
|
5983
|
+
local,
|
|
5984
|
+
sdk,
|
|
5985
|
+
paths,
|
|
5986
|
+
bundleName,
|
|
5987
|
+
aclPermissions,
|
|
5988
|
+
teamId: auth.teamId,
|
|
5989
|
+
logger
|
|
5990
|
+
});
|
|
5991
|
+
const encryptedPassword = encryptPwd(keyPwd, sharedMaterialDir(paths));
|
|
5992
|
+
writeSigningConfig({ buildProfilePath, buildProfile, productName, paths, encryptedPassword });
|
|
5993
|
+
logger.info(`[harmonyos] Wrote signingConfigs['${productName}'] to ${buildProfilePath}.`);
|
|
5994
|
+
return result(true);
|
|
5995
|
+
}
|
|
5996
|
+
function assertAgcBundleName(bundleName) {
|
|
5997
|
+
if (!isValidBundleName(bundleName) || bundleName.split(".").length < 3 || bundleName.length < 7 || bundleName.length > 128) {
|
|
5998
|
+
throw new AgcError(SIGNING_ERRORS.BUNDLE_NAME_INVALID);
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
function materialDirFor(paths) {
|
|
6002
|
+
return path41.join(path41.dirname(paths.p12Path), "material");
|
|
6003
|
+
}
|
|
6004
|
+
function sharedMaterialDir(paths) {
|
|
6005
|
+
const materialDir = materialDirFor(paths);
|
|
6006
|
+
try {
|
|
6007
|
+
getKey(materialDir);
|
|
6008
|
+
} catch {
|
|
6009
|
+
createMaterial(materialDir);
|
|
6010
|
+
}
|
|
6011
|
+
return materialDir;
|
|
6012
|
+
}
|
|
6013
|
+
async function issueSigningMaterial(opts) {
|
|
6014
|
+
const { agc, local, sdk, paths, logger } = opts;
|
|
5777
6015
|
const deviceIds = await registerDevices(agc, local.devices, logger);
|
|
5778
6016
|
if (local.devices.length === 0) {
|
|
5779
6017
|
const cause = local.skipped.length > 0 ? "No connected device could be registered" : "No device is connected";
|
|
5780
6018
|
logger.warn(`[harmonyos] ${cause}; the profile will name the ${deviceIds.length} device(s) already registered to this team.`);
|
|
5781
6019
|
}
|
|
5782
|
-
for (const file of Object.values(paths))
|
|
5783
|
-
const certName = `oniro_debug_${
|
|
6020
|
+
for (const file of Object.values(paths)) fs42.rmSync(file, { force: true });
|
|
6021
|
+
const certName = `oniro_debug_${opts.teamId.replace(/[\\/.:]/g, "")}.cer`;
|
|
5784
6022
|
const stale = (await agc.listCertificates()).find((c) => c.certName === certName);
|
|
5785
6023
|
if (stale) {
|
|
5786
6024
|
logger.info(`[harmonyos] Replacing the previous '${certName}' certificate in AppGallery Connect...`);
|
|
@@ -5794,11 +6032,11 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5794
6032
|
await agc.download("cert", cert.certObjectId, paths.cerPath);
|
|
5795
6033
|
logger.info("[harmonyos] Issuing the debug provisioning profile...");
|
|
5796
6034
|
const profile = await agc.addDebugProfile({
|
|
5797
|
-
provisionName: `oniro_${
|
|
5798
|
-
bundleName,
|
|
6035
|
+
provisionName: `oniro_${crypto10.randomBytes(6).toString("hex")}`,
|
|
6036
|
+
bundleName: opts.bundleName,
|
|
5799
6037
|
certId: cert.id,
|
|
5800
6038
|
deviceIds,
|
|
5801
|
-
aclPermissions
|
|
6039
|
+
aclPermissions: opts.aclPermissions
|
|
5802
6040
|
});
|
|
5803
6041
|
try {
|
|
5804
6042
|
await agc.download("profile", profile.provisionFileUrl, paths.profilePath);
|
|
@@ -5810,22 +6048,86 @@ async function harmonyOsAutoSign(opts) {
|
|
|
5810
6048
|
try {
|
|
5811
6049
|
verifySigningMaterial({ ...paths, keyAlias: KEY_ALIAS, keyPwd });
|
|
5812
6050
|
} catch (err) {
|
|
5813
|
-
|
|
6051
|
+
fs42.rmSync(paths.profilePath, { force: true });
|
|
5814
6052
|
throw err;
|
|
5815
6053
|
}
|
|
5816
|
-
|
|
6054
|
+
return keyPwd;
|
|
6055
|
+
}
|
|
6056
|
+
|
|
6057
|
+
// src/harmonyos/signing/signHap.ts
|
|
6058
|
+
import * as fs43 from "fs";
|
|
6059
|
+
import * as path42 from "path";
|
|
6060
|
+
function resolveHapSigningMaterialPaths(config, bundleName) {
|
|
6061
|
+
const paths = resolveSigningMaterialPaths(config, "hap", bundleName);
|
|
6062
|
+
return { ...paths, pwdPath: paths.p12Path.replace(/\.p12$/, ".pwd") };
|
|
6063
|
+
}
|
|
6064
|
+
function readStoredPassword(paths) {
|
|
5817
6065
|
try {
|
|
5818
|
-
|
|
6066
|
+
return decryptPwd(fs43.readFileSync(paths.pwdPath, "utf8").trim(), materialDirFor(paths));
|
|
5819
6067
|
} catch {
|
|
5820
|
-
|
|
6068
|
+
return null;
|
|
5821
6069
|
}
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
6070
|
+
}
|
|
6071
|
+
async function harmonyOsSignHap(opts) {
|
|
6072
|
+
const logger = opts.logger ?? noopLogger;
|
|
6073
|
+
const hapPath = path42.resolve(opts.hapPath);
|
|
6074
|
+
const outPath = path42.resolve(opts.outPath ?? defaultSignedHapPath(hapPath));
|
|
6075
|
+
const info = await readHapSigningInfo(hapPath);
|
|
6076
|
+
const { bundleName } = info;
|
|
6077
|
+
if (info.compileSdkType === "OpenHarmony") {
|
|
6078
|
+
throw new OniroError(
|
|
6079
|
+
`${hapPath} was built for OpenHarmony, so a HarmonyOS device would not run it; sign it without --harmonyos.`
|
|
6080
|
+
);
|
|
6081
|
+
}
|
|
6082
|
+
assertAgcBundleName(bundleName);
|
|
6083
|
+
const aclPermissions = aclPermissionsAmong(info.requestedPermissions);
|
|
6084
|
+
if (aclPermissions.length > 0) {
|
|
6085
|
+
logger.info(`[harmonyos] HAP requests ${aclPermissions.length} ACL permission(s): ${aclPermissions.join(", ")}`);
|
|
6086
|
+
}
|
|
6087
|
+
const sdk = requireHarmonyOsSdk({ config: opts.config, logger });
|
|
6088
|
+
const auth = await opts.session.resolveAgcAuth({ teamId: opts.teamId });
|
|
6089
|
+
const paths = resolveHapSigningMaterialPaths(opts.config, bundleName);
|
|
6090
|
+
const { pwdPath, ...material } = paths;
|
|
6091
|
+
logger.info(`[harmonyos] Signing ${path42.basename(hapPath)} as ${bundleName} (team ${auth.teamId}).`);
|
|
6092
|
+
const local = await collectLocalDevices(opts.config, logger);
|
|
6093
|
+
let keyPwd = opts.force ? null : readStoredPassword(paths);
|
|
6094
|
+
const missing = Object.values(material).find((file) => !fs43.existsSync(file));
|
|
6095
|
+
const reason = opts.force ? "--force was passed" : missing ? `${missing} is missing` : keyPwd === null ? "the stored keystore password could not be read" : profileProblem({
|
|
6096
|
+
profilePath: material.profilePath,
|
|
6097
|
+
bundleName,
|
|
6098
|
+
teamId: auth.teamId,
|
|
6099
|
+
connectedUdids: local.devices.map((d) => d.udid),
|
|
6100
|
+
aclPermissions
|
|
6101
|
+
});
|
|
6102
|
+
if (!reason && keyPwd !== null) {
|
|
6103
|
+
logger.info("[harmonyos] Reusing the existing signing material, which is still valid.");
|
|
6104
|
+
} else {
|
|
6105
|
+
logger.info(`[harmonyos] Regenerating signing material: ${reason}.`);
|
|
6106
|
+
fs43.rmSync(pwdPath, { force: true });
|
|
6107
|
+
keyPwd = await issueSigningMaterial({
|
|
6108
|
+
agc: createAgcClient(opts.http ?? createHttpClient({ logger }), auth),
|
|
6109
|
+
local,
|
|
6110
|
+
sdk,
|
|
6111
|
+
paths: material,
|
|
6112
|
+
bundleName,
|
|
6113
|
+
aclPermissions,
|
|
6114
|
+
teamId: auth.teamId,
|
|
6115
|
+
logger
|
|
6116
|
+
});
|
|
6117
|
+
fs43.writeFileSync(pwdPath, encryptPwd(keyPwd, sharedMaterialDir(material)), { mode: 384 });
|
|
6118
|
+
}
|
|
6119
|
+
const password = keyPwd;
|
|
6120
|
+
await writeHapAtomically(
|
|
6121
|
+
outPath,
|
|
6122
|
+
(tmpPath) => signHapFile({ sdk, inFile: hapPath, outFile: tmpPath, ...material, keyPwd: password, logger })
|
|
6123
|
+
);
|
|
6124
|
+
const deviceIds = readProvisionProfile(material.profilePath)?.deviceIds ?? [];
|
|
6125
|
+
logger.info(`[harmonyos] Wrote ${outPath}.`);
|
|
6126
|
+
return { bundleName, teamId: auth.teamId, paths: material, regenerated: !!reason, deviceIds, outPath };
|
|
5825
6127
|
}
|
|
5826
6128
|
|
|
5827
6129
|
// src/index.ts
|
|
5828
|
-
var VERSION = "0.
|
|
6130
|
+
var VERSION = "0.16.0";
|
|
5829
6131
|
export {
|
|
5830
6132
|
ALL_SDKS,
|
|
5831
6133
|
APL_VALUES,
|
|
@@ -5842,6 +6144,7 @@ export {
|
|
|
5842
6144
|
OHOS_DAILY_BUILDS_URL,
|
|
5843
6145
|
OHOS_URL_BASE,
|
|
5844
6146
|
OniroError,
|
|
6147
|
+
SIGNED_HAP_CHUNK_SIZE,
|
|
5845
6148
|
SIGNING_ERRORS,
|
|
5846
6149
|
SdkNotInstalledError,
|
|
5847
6150
|
TMP_DIR_HINT,
|
|
@@ -5877,6 +6180,7 @@ export {
|
|
|
5877
6180
|
dedupEntries,
|
|
5878
6181
|
defaultContext,
|
|
5879
6182
|
defaultPaths,
|
|
6183
|
+
defaultSignedHapPath,
|
|
5880
6184
|
describeLayoutNode,
|
|
5881
6185
|
detectAaStartFailure,
|
|
5882
6186
|
detectCmdToolsFlavour,
|
|
@@ -5943,6 +6247,7 @@ export {
|
|
|
5943
6247
|
getSupportedSdksForUi,
|
|
5944
6248
|
harmonyOsAutoSign,
|
|
5945
6249
|
harmonyOsSdkLabelForApi,
|
|
6250
|
+
harmonyOsSignHap,
|
|
5946
6251
|
hdcClientFailure,
|
|
5947
6252
|
hdcExec,
|
|
5948
6253
|
hdcTargetArgs,
|
|
@@ -5996,6 +6301,7 @@ export {
|
|
|
5996
6301
|
prepareSigning,
|
|
5997
6302
|
pruneLayout,
|
|
5998
6303
|
readHapBundleName,
|
|
6304
|
+
readHapSigningInfo,
|
|
5999
6305
|
readJson5File,
|
|
6000
6306
|
readJsonFile,
|
|
6001
6307
|
readProvisionProfile,
|
|
@@ -6023,6 +6329,7 @@ export {
|
|
|
6023
6329
|
setHilogLevel,
|
|
6024
6330
|
shell,
|
|
6025
6331
|
shellChecked,
|
|
6332
|
+
signOpenHarmonyHap,
|
|
6026
6333
|
spawnCompat,
|
|
6027
6334
|
startEmulator,
|
|
6028
6335
|
staticConfig,
|
|
@@ -6042,6 +6349,7 @@ export {
|
|
|
6042
6349
|
waitForTarget,
|
|
6043
6350
|
watchLog,
|
|
6044
6351
|
waypointsToRawEvents,
|
|
6352
|
+
withHapZip,
|
|
6045
6353
|
withoutWindow,
|
|
6046
6354
|
writeJson5File,
|
|
6047
6355
|
writeJsonFile
|