@oniroproject/core 0.6.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.js ADDED
@@ -0,0 +1,1748 @@
1
+ // src/ports/logger.ts
2
+ var noopLogger = {
3
+ debug() {
4
+ },
5
+ info() {
6
+ },
7
+ warn() {
8
+ },
9
+ error() {
10
+ }
11
+ };
12
+ var consoleLogger = {
13
+ debug: (m) => console.debug(m),
14
+ info: (m) => console.log(m),
15
+ warn: (m) => console.warn(m),
16
+ error: (m) => console.error(m)
17
+ };
18
+
19
+ // src/ports/progress.ts
20
+ var noopProgress = {
21
+ report() {
22
+ }
23
+ };
24
+
25
+ // src/ports/config.ts
26
+ import * as os from "os";
27
+ import * as path from "path";
28
+ var defaultPaths = {
29
+ sdkRootDir: () => path.join(os.homedir(), "setup-ohos-sdk"),
30
+ cmdToolsPath: () => path.join(os.homedir(), "command-line-tools"),
31
+ emulatorDir: () => path.join(os.homedir(), "oniro-emulator"),
32
+ hapPath: "entry/build/default/outputs/default/entry-default-signed.hap",
33
+ emulatorUrl: "https://github.com/eclipse-oniro4openharmony/device_board_oniro/releases/latest/download/oniro_emulator.zip",
34
+ // The Huawei mirror only hosts a public Linux build of the command-line tools.
35
+ // Windows and macOS require a manual download from the Huawei developer portal
36
+ // (https://developer.huawei.com/...); pass the resulting ZIP to `cmdtools install --from-zip <path>`
37
+ // or set ONIRO_CMD_TOOLS_URL_WINDOWS / ONIRO_CMD_TOOLS_URL_MAC to a self-hosted URL.
38
+ cmdToolsUrlLinux: "https://repo.huaweicloud.com/harmonyos/ohpm/5.1.0/commandline-tools-linux-x64-5.1.0.840.zip"
39
+ };
40
+ function staticConfig(values = {}) {
41
+ return {
42
+ get(key, fallback) {
43
+ const v = values[key];
44
+ if (v === void 0 || v === "") {
45
+ return fallback;
46
+ }
47
+ if (typeof v === "string" && v.includes("${userHome}")) {
48
+ return v.replace(/\$\{userHome\}/g, os.homedir());
49
+ }
50
+ return v;
51
+ }
52
+ };
53
+ }
54
+
55
+ // src/ports/prompter.ts
56
+ var nonInteractivePrompter = {
57
+ async confirm(message) {
58
+ throw new Error(`Non-interactive mode: cannot confirm "${message}"`);
59
+ },
60
+ async input(message) {
61
+ throw new Error(`Non-interactive mode: cannot ask "${message}"`);
62
+ },
63
+ async selectDirectory(message) {
64
+ throw new Error(`Non-interactive mode: cannot prompt for directory "${message}"`);
65
+ },
66
+ async selectFile(message) {
67
+ throw new Error(`Non-interactive mode: cannot prompt for file "${message}"`);
68
+ }
69
+ };
70
+
71
+ // src/ports/errors.ts
72
+ var OniroError = class extends Error {
73
+ constructor(message, cause) {
74
+ super(message);
75
+ this.cause = cause;
76
+ this.name = "OniroError";
77
+ }
78
+ cause;
79
+ };
80
+ var SdkNotInstalledError = class extends OniroError {
81
+ constructor(api, expectedPath) {
82
+ super(`SDK API ${api} is not installed at ${expectedPath}.`);
83
+ this.api = api;
84
+ this.expectedPath = expectedPath;
85
+ this.name = "SdkNotInstalledError";
86
+ }
87
+ api;
88
+ expectedPath;
89
+ };
90
+ var CmdToolsNotInstalledError = class extends OniroError {
91
+ constructor(expectedPath) {
92
+ super(`OpenHarmony command-line tools are not installed at ${expectedPath}.`);
93
+ this.expectedPath = expectedPath;
94
+ this.name = "CmdToolsNotInstalledError";
95
+ }
96
+ expectedPath;
97
+ };
98
+ var UnsupportedPlatformError = class extends OniroError {
99
+ constructor(platform6) {
100
+ super(`Unsupported platform: ${platform6}.`);
101
+ this.platform = platform6;
102
+ this.name = "UnsupportedPlatformError";
103
+ }
104
+ platform;
105
+ };
106
+ var ChecksumMismatchError = class extends OniroError {
107
+ constructor(expected, actual) {
108
+ super(`SHA256 mismatch: expected ${expected}, got ${actual}.`);
109
+ this.expected = expected;
110
+ this.actual = actual;
111
+ this.name = "ChecksumMismatchError";
112
+ }
113
+ expected;
114
+ actual;
115
+ };
116
+ var CancelledError = class extends OniroError {
117
+ constructor(message = "Operation cancelled.") {
118
+ super(message);
119
+ this.name = "CancelledError";
120
+ }
121
+ };
122
+
123
+ // src/ports/context.ts
124
+ function defaultContext(overrides = {}) {
125
+ return {
126
+ logger: overrides.logger ?? noopLogger,
127
+ config: overrides.config ?? staticConfig()
128
+ };
129
+ }
130
+
131
+ // src/sdk/constants.ts
132
+ var ALL_SDKS = [
133
+ { version: "4.0", api: "10" },
134
+ { version: "4.1", api: "11" },
135
+ { version: "5.0.0", api: "12" },
136
+ { version: "5.0.1", api: "13" },
137
+ { version: "5.0.2", api: "14" },
138
+ { version: "5.0.3", api: "15" },
139
+ { version: "5.1.0", api: "18" },
140
+ { version: "6.0", api: "20" },
141
+ { version: "6.1", api: "23" }
142
+ ];
143
+ var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
144
+
145
+ // src/sdk/platform.ts
146
+ import * as os2 from "os";
147
+ function getOsFolder() {
148
+ const platform6 = os2.platform();
149
+ if (platform6 === "linux") return "linux";
150
+ if (platform6 === "darwin") return "darwin";
151
+ if (platform6 === "win32") return "windows";
152
+ throw new UnsupportedPlatformError(platform6);
153
+ }
154
+ function getSdkFilename(version) {
155
+ const platform6 = os2.platform();
156
+ const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
157
+ if (platform6 === "linux") {
158
+ const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
159
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "linux", strip };
160
+ }
161
+ if (platform6 === "darwin") {
162
+ if (os2.arch() === "arm64") {
163
+ return { filename: "L2-SDK-MAC-M1-PUBLIC.tar.gz", osFolder: "darwin", strip: 3 };
164
+ }
165
+ return { filename: "ohos-sdk-mac-public.tar.gz", osFolder: "darwin", strip: 3 };
166
+ }
167
+ if (platform6 === "win32") {
168
+ const strip = v === "5.0.0" || v === "5.0.1" || v === "6.0" || v === "6.1" ? 0 : 1;
169
+ return { filename: "ohos-sdk-windows_linux-public.tar.gz", osFolder: "windows", strip };
170
+ }
171
+ throw new UnsupportedPlatformError(platform6);
172
+ }
173
+
174
+ // src/sdk/paths.ts
175
+ import * as fs from "fs";
176
+ import * as os3 from "os";
177
+ import * as path2 from "path";
178
+ function getSdkRootDir(config) {
179
+ return config.get("sdkRootDir", defaultPaths.sdkRootDir());
180
+ }
181
+ function getOhosBaseSdkHome(config) {
182
+ return path2.join(getSdkRootDir(config), getOsFolder());
183
+ }
184
+ function getCmdToolsPath(config) {
185
+ return config.get("cmdToolsPath", defaultPaths.cmdToolsPath());
186
+ }
187
+ function getEmulatorDir(config) {
188
+ return config.get("emulatorDir", defaultPaths.emulatorDir());
189
+ }
190
+ function pickExisting(candidates) {
191
+ for (const c of candidates) {
192
+ if (fs.existsSync(c)) return c;
193
+ }
194
+ return candidates[0];
195
+ }
196
+ function getCmdToolsBin(config) {
197
+ const binDir = path2.join(getCmdToolsPath(config), "bin");
198
+ if (os3.platform() === "win32") {
199
+ return pickExisting([
200
+ path2.join(binDir, "ohpm.exe"),
201
+ path2.join(binDir, "ohpm.cmd"),
202
+ path2.join(binDir, "ohpm.bat"),
203
+ path2.join(binDir, "ohpm")
204
+ ]);
205
+ }
206
+ return path2.join(binDir, "ohpm");
207
+ }
208
+ function getHdcPath(config) {
209
+ const base = path2.join(getCmdToolsPath(config), "sdk", "default", "openharmony", "toolchains");
210
+ if (os3.platform() === "win32") {
211
+ return pickExisting([
212
+ path2.join(base, "hdc.exe"),
213
+ path2.join(base, "hdc.bat"),
214
+ path2.join(base, "hdc.cmd"),
215
+ path2.join(base, "hdc")
216
+ ]);
217
+ }
218
+ return path2.join(base, "hdc");
219
+ }
220
+ function getHvigorwPath(config, projectDir) {
221
+ const cmdToolsBin = path2.join(getCmdToolsPath(config), "bin");
222
+ return pickExisting([
223
+ path2.join(projectDir, "hvigorw"),
224
+ path2.join(projectDir, "hvigorw.bat"),
225
+ path2.join(projectDir, "hvigorw.cmd"),
226
+ path2.join(cmdToolsBin, "hvigorw"),
227
+ path2.join(cmdToolsBin, "hvigorw.bat"),
228
+ path2.join(cmdToolsBin, "hvigorw.cmd")
229
+ ]);
230
+ }
231
+
232
+ // src/sdk/detectProjectSdk.ts
233
+ import * as fs2 from "fs";
234
+ import * as path3 from "path";
235
+ import JSON5 from "json5";
236
+ function detectProjectSdkVersion(projectRoot, logger = noopLogger) {
237
+ const buildProfilePath = path3.join(projectRoot, "build-profile.json5");
238
+ if (!fs2.existsSync(buildProfilePath)) {
239
+ logger.warn(`build-profile.json5 not found at ${projectRoot}`);
240
+ return void 0;
241
+ }
242
+ try {
243
+ const content = fs2.readFileSync(buildProfilePath, "utf-8");
244
+ const config = JSON5.parse(content);
245
+ const product = config.app?.products?.[0];
246
+ const v = product?.compileSdkVersion;
247
+ return typeof v === "number" ? v : void 0;
248
+ } catch (err) {
249
+ logger.error(`Error reading build-profile.json5 at ${projectRoot}: ${String(err)}`);
250
+ return void 0;
251
+ }
252
+ }
253
+
254
+ // src/sdk/download.ts
255
+ import * as fs3 from "fs";
256
+ import * as crypto from "crypto";
257
+ import { pipeline } from "stream";
258
+ import { promisify } from "util";
259
+ import followRedirects from "follow-redirects";
260
+ var { http, https } = followRedirects;
261
+ var pipelineAsync = promisify(pipeline);
262
+ async function downloadFile(opts) {
263
+ const { url, dest, progress, abortSignal } = opts;
264
+ const proto = url.startsWith("https") ? https : http;
265
+ return new Promise((resolve2, reject) => {
266
+ const s = Math.max(0, Math.min(100, opts.start ?? 0));
267
+ const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
268
+ let settled = false;
269
+ const done = (err) => {
270
+ if (settled) return;
271
+ settled = true;
272
+ err ? reject(err) : resolve2();
273
+ };
274
+ if (abortSignal?.aborted) {
275
+ done(new CancelledError("Download cancelled before start."));
276
+ return;
277
+ }
278
+ const file = fs3.createWriteStream(dest);
279
+ const req = proto.get(url, (response) => {
280
+ if (response.statusCode !== 200) {
281
+ try {
282
+ response.destroy();
283
+ } catch {
284
+ }
285
+ try {
286
+ file.close();
287
+ } catch {
288
+ }
289
+ fs3.unlink(dest, () => {
290
+ });
291
+ done(new OniroError(`Failed to download '${url}' (HTTP ${response.statusCode})`));
292
+ return;
293
+ }
294
+ const total = parseInt(response.headers["content-length"] || "0", 10);
295
+ let downloaded = 0;
296
+ let lastOverall = Math.round(s);
297
+ response.on("data", (chunk) => {
298
+ downloaded += chunk.length;
299
+ if (progress && total) {
300
+ const localPercent = Math.min(100, Math.round(downloaded / total * 100));
301
+ const overall = Math.min(100, Math.round(s + downloaded / total * r));
302
+ const inc = overall - lastOverall;
303
+ if (inc > 0) {
304
+ progress.report({ message: `Downloading: ${localPercent}%`, increment: inc });
305
+ lastOverall = overall;
306
+ } else {
307
+ progress.report({ message: `Downloading: ${localPercent}%`, increment: 0 });
308
+ }
309
+ }
310
+ });
311
+ response.pipe(file);
312
+ file.on("finish", () => {
313
+ if (progress) {
314
+ const endOverall = Math.min(100, Math.round(s + r));
315
+ const inc = endOverall - lastOverall;
316
+ if (inc > 0) progress.report({ message: "Downloading: 100%", increment: inc });
317
+ }
318
+ file.close((err) => err ? done(err) : done());
319
+ });
320
+ abortSignal?.addEventListener("abort", () => {
321
+ response.destroy();
322
+ file.close();
323
+ fs3.unlink(dest, () => {
324
+ });
325
+ done(new CancelledError("Download cancelled."));
326
+ });
327
+ });
328
+ req.on("error", (err) => {
329
+ try {
330
+ file.close();
331
+ } catch {
332
+ }
333
+ fs3.unlink(dest, () => {
334
+ });
335
+ done(new OniroError(`Error downloading '${url}': ${err.message}`, err));
336
+ });
337
+ abortSignal?.addEventListener("abort", () => {
338
+ req.destroy();
339
+ file.close();
340
+ fs3.unlink(dest, () => {
341
+ });
342
+ done(new CancelledError("Download cancelled."));
343
+ });
344
+ });
345
+ }
346
+ async function verifySha256(filePath, sha256Path) {
347
+ const expected = fs3.readFileSync(sha256Path, "utf8").split(/\s+/)[0];
348
+ const hash = crypto.createHash("sha256");
349
+ await pipelineAsync(fs3.createReadStream(filePath), hash);
350
+ const actual = hash.digest("hex");
351
+ if (actual !== expected) {
352
+ throw new ChecksumMismatchError(expected, actual);
353
+ }
354
+ }
355
+
356
+ // src/sdk/extract.ts
357
+ import * as fs4 from "fs";
358
+ import * as os4 from "os";
359
+ import * as path4 from "path";
360
+ import { pipeline as pipeline2 } from "stream";
361
+ import { promisify as promisify2 } from "util";
362
+ import * as tar from "tar";
363
+ import StreamZip from "node-stream-zip";
364
+ var pipelineAsync2 = promisify2(pipeline2);
365
+ async function extractZipWithProgress(opts) {
366
+ const { zipPath, dest, progress } = opts;
367
+ const logger = opts.logger ?? noopLogger;
368
+ const zip = new StreamZip.async({ file: zipPath });
369
+ const entries = await zip.entries();
370
+ const files = Object.values(entries);
371
+ const total = files.length || 1;
372
+ const s = Math.max(0, Math.min(100, opts.start ?? 0));
373
+ const r = Math.max(0, Math.min(100 - s, opts.range ?? 100));
374
+ let processed = 0;
375
+ let lastOverall = Math.round(s);
376
+ await fs4.promises.mkdir(dest, { recursive: true });
377
+ const destRoot = path4.resolve(dest);
378
+ const safeResolveTarget = (entryName) => {
379
+ const resolved = path4.resolve(destRoot, entryName);
380
+ const rel = path4.relative(destRoot, resolved);
381
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
382
+ throw new OniroError(`Blocked ZIP entry with illegal path: ${entryName}`);
383
+ }
384
+ return resolved;
385
+ };
386
+ try {
387
+ for (const file of files) {
388
+ const entryName = file.name;
389
+ const targetPath = safeResolveTarget(entryName);
390
+ const attr = file.attr ? file.attr >>> 16 : 0;
391
+ const isSymlink = "isSymbolicLink" in file ? Boolean(file.isSymbolicLink) : (attr & 61440) === 40960;
392
+ if (file.isDirectory) {
393
+ await fs4.promises.mkdir(targetPath, { recursive: true });
394
+ } else if (isSymlink) {
395
+ await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
396
+ const linkTargetBuffer = await zip.entryData(file);
397
+ const linkTarget = linkTargetBuffer.toString("utf8");
398
+ try {
399
+ const isWin = os4.platform() === "win32";
400
+ await fs4.promises.symlink(linkTarget, targetPath, isWin ? "file" : void 0);
401
+ } catch (e) {
402
+ logger.warn(`Failed to create symlink at ${targetPath}: ${String(e)}`);
403
+ }
404
+ } else {
405
+ await fs4.promises.mkdir(path4.dirname(targetPath), { recursive: true });
406
+ const readStream = await zip.stream(file);
407
+ const writeStream = fs4.createWriteStream(targetPath);
408
+ await pipelineAsync2(readStream, writeStream);
409
+ if (attr > 0) {
410
+ try {
411
+ await fs4.promises.chmod(targetPath, attr & 511);
412
+ } catch {
413
+ }
414
+ }
415
+ }
416
+ processed++;
417
+ if (progress) {
418
+ const localPercent = Math.min(100, Math.round(processed / total * 100));
419
+ const overall = Math.min(100, Math.round(s + processed / total * r));
420
+ const inc = overall - lastOverall;
421
+ if (inc > 0) {
422
+ progress.report({ message: `Extracting: ${localPercent}%`, increment: inc });
423
+ lastOverall = overall;
424
+ } else {
425
+ progress.report({ message: `Extracting: ${localPercent}%`, increment: 0 });
426
+ }
427
+ }
428
+ }
429
+ } finally {
430
+ await zip.close();
431
+ }
432
+ if (progress) {
433
+ const endOverall = Math.min(100, Math.round(s + r));
434
+ const inc = endOverall - lastOverall;
435
+ if (inc > 0) progress.report({ message: "Extracting: 100%", increment: inc });
436
+ }
437
+ }
438
+ async function extractTarball(tarPath, dest, strip = 0) {
439
+ await tar.x({ file: tarPath, cwd: dest, strip });
440
+ }
441
+
442
+ // src/sdk/list.ts
443
+ import * as fs5 from "fs";
444
+ import * as path5 from "path";
445
+ function getSupportedSdksForUi(config) {
446
+ const base = path5.join(getSdkRootDir(config), getOsFolder());
447
+ return ALL_SDKS.map((sdk) => ({
448
+ version: sdk.version,
449
+ api: sdk.api,
450
+ installed: fs5.existsSync(path5.join(base, sdk.api))
451
+ })).sort((a, b) => Number(b.api) - Number(a.api));
452
+ }
453
+ function getInstalledSdks(config) {
454
+ const sdkRoot = getSdkRootDir(config);
455
+ const versions = /* @__PURE__ */ new Set();
456
+ if (!fs5.existsSync(sdkRoot)) return [];
457
+ for (const osFolder of ["linux", "darwin", "windows"]) {
458
+ const osPath = path5.join(sdkRoot, osFolder);
459
+ if (!fs5.existsSync(osPath) || !fs5.statSync(osPath).isDirectory()) continue;
460
+ for (const api of fs5.readdirSync(osPath)) {
461
+ const apiPath = path5.join(osPath, api);
462
+ if (fs5.statSync(apiPath).isDirectory()) versions.add(api);
463
+ }
464
+ }
465
+ return ALL_SDKS.filter((sdk) => versions.has(sdk.api)).map((sdk) => sdk.version);
466
+ }
467
+
468
+ // src/sdk/remove.ts
469
+ import * as fs6 from "fs";
470
+ import * as path6 from "path";
471
+ function removeSdk(config, api) {
472
+ const sdkRoot = getSdkRootDir(config);
473
+ let removed = false;
474
+ for (const osFolder of ["linux", "darwin", "windows"]) {
475
+ const sdkPath = path6.join(sdkRoot, osFolder, api);
476
+ if (fs6.existsSync(sdkPath)) {
477
+ fs6.rmSync(sdkPath, { recursive: true, force: true });
478
+ removed = true;
479
+ }
480
+ }
481
+ return removed;
482
+ }
483
+
484
+ // src/sdk/install.ts
485
+ import * as fs7 from "fs";
486
+ import * as os5 from "os";
487
+ import * as path7 from "path";
488
+ async function downloadAndInstallSdk(opts) {
489
+ const { config, version, api, progress, abortSignal } = opts;
490
+ const logger = opts.logger ?? noopLogger;
491
+ const { filename, osFolder, strip } = getSdkFilename(version);
492
+ const downloadUrl = `${OHOS_URL_BASE}/${version}-Release/${filename}`;
493
+ const sha256Url = `${downloadUrl}.sha256`;
494
+ const tmpDir = fs7.mkdtempSync(path7.join(os5.tmpdir(), "oniro-sdk-"));
495
+ const tarPath = path7.join(tmpDir, filename);
496
+ const sha256Path = path7.join(tmpDir, `${filename}.sha256`);
497
+ const extractDir = path7.join(tmpDir, "extract");
498
+ fs7.mkdirSync(extractDir);
499
+ const sdkInstallDir = path7.join(getSdkRootDir(config), osFolder, api);
500
+ fs7.mkdirSync(path7.dirname(sdkInstallDir), { recursive: true });
501
+ const checkCancelled = () => {
502
+ if (abortSignal?.aborted) throw new CancelledError();
503
+ };
504
+ try {
505
+ progress?.report({ message: "Downloading SDK archive...", increment: 0 });
506
+ await downloadFile({ url: downloadUrl, dest: tarPath, progress, abortSignal, start: 0, range: 35 });
507
+ progress?.report({ message: "Downloading checksum...", increment: 0 });
508
+ await downloadFile({ url: sha256Url, dest: sha256Path, progress, abortSignal, start: 35, range: 10 });
509
+ progress?.report({ message: "Verifying checksum...", increment: 0 });
510
+ await verifySha256(tarPath, sha256Path);
511
+ progress?.report({ message: "Verifying checksum...", increment: 5 });
512
+ checkCancelled();
513
+ progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 0 });
514
+ await extractTarball(tarPath, extractDir, strip);
515
+ progress?.report({ message: "Extracting SDK (this may take a while)...", increment: 10 });
516
+ checkCancelled();
517
+ const osContentPath = path7.join(extractDir, osFolder);
518
+ if (!fs7.existsSync(osContentPath)) {
519
+ throw new OniroError(
520
+ `Expected folder '${osFolder}' not found in extracted SDK. Tarball layout may have changed.`
521
+ );
522
+ }
523
+ const zipFiles = fs7.readdirSync(osContentPath).filter((n2) => n2.endsWith(".zip"));
524
+ const componentsStart = 60;
525
+ const componentsBudget = 35;
526
+ const n = zipFiles.length;
527
+ if (n === 0) {
528
+ progress?.report({ message: "No SDK component ZIPs found.", increment: componentsBudget });
529
+ } else {
530
+ progress?.report({ message: "Extracting SDK components...", increment: 0 });
531
+ }
532
+ const base = n > 0 ? Math.floor(componentsBudget / n) : 0;
533
+ let rem = n > 0 ? componentsBudget % n : 0;
534
+ let cursor = componentsStart;
535
+ for (const zipFile of zipFiles) {
536
+ checkCancelled();
537
+ logger.info(`Extracting component ${zipFile}`);
538
+ const zipPath = path7.join(osContentPath, zipFile);
539
+ const thisBudget = base + (rem > 0 ? 1 : 0);
540
+ if (rem > 0) rem--;
541
+ await extractZipWithProgress({ zipPath, dest: osContentPath, progress, start: cursor, range: thisBudget, logger });
542
+ cursor += thisBudget;
543
+ fs7.unlinkSync(zipPath);
544
+ }
545
+ progress?.report({ message: "Finalizing installation...", increment: 0 });
546
+ if (fs7.existsSync(sdkInstallDir)) {
547
+ fs7.rmSync(sdkInstallDir, { recursive: true, force: true });
548
+ }
549
+ fs7.renameSync(osContentPath, sdkInstallDir);
550
+ progress?.report({ message: "Finalizing installation...", increment: 3 });
551
+ progress?.report({ message: "Cleaning up...", increment: 0 });
552
+ fs7.rmSync(tmpDir, { recursive: true, force: true });
553
+ progress?.report({ message: "Cleaning up...", increment: 2 });
554
+ } catch (err) {
555
+ logger.error(`SDK install failed: ${err instanceof Error ? err.message : String(err)}`);
556
+ fs7.rmSync(tmpDir, { recursive: true, force: true });
557
+ throw err;
558
+ }
559
+ }
560
+
561
+ // src/cmdTools/install.ts
562
+ import * as fs8 from "fs";
563
+ import * as os6 from "os";
564
+ import * as path8 from "path";
565
+ import { execFileSync } from "child_process";
566
+ function getCmdToolsDownloadUrl(config, platform6 = os6.platform()) {
567
+ if (platform6 === "linux") {
568
+ return config.get("cmdToolsUrlLinux", defaultPaths.cmdToolsUrlLinux);
569
+ }
570
+ if (platform6 === "win32") {
571
+ const url = config.get("cmdToolsUrlWindows", "");
572
+ if (!url) {
573
+ throw new OniroError(
574
+ "No Windows command-line tools URL configured. The Huawei mirror does not host a public Windows build; download the ZIP from the Huawei developer portal and run `oniro-app cmdtools install --from-zip <path>`, or set ONIRO_CMD_TOOLS_URL_WINDOWS to a self-hosted URL."
575
+ );
576
+ }
577
+ return url;
578
+ }
579
+ if (platform6 === "darwin") {
580
+ const url = config.get("cmdToolsUrlMac", "");
581
+ if (!url) {
582
+ throw new OniroError(
583
+ "No macOS command-line tools URL configured. The Huawei mirror does not host a public macOS build; download the ZIP from the Huawei developer portal and run `oniro-app cmdtools install --from-zip <path>`, or set ONIRO_CMD_TOOLS_URL_MAC to a self-hosted URL."
584
+ );
585
+ }
586
+ return url;
587
+ }
588
+ throw new UnsupportedPlatformError(platform6);
589
+ }
590
+ function findCmdToolsSourceDir(extractPath) {
591
+ const known = [
592
+ path8.join(extractPath, "command-line-tools"),
593
+ path8.join(extractPath, "oh-command-line-tools"),
594
+ path8.join(extractPath, "commandline-tools")
595
+ ];
596
+ for (const c of known) {
597
+ if (fs8.existsSync(c)) return c;
598
+ }
599
+ const entries = fs8.readdirSync(extractPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path8.join(extractPath, e.name));
600
+ for (const dir of entries) {
601
+ if (fs8.existsSync(path8.join(dir, "bin"))) return dir;
602
+ }
603
+ throw new OniroError("Could not locate command line tools folder in the extracted archive.");
604
+ }
605
+ async function installCmdTools(opts) {
606
+ const { config, progress, abortSignal } = opts;
607
+ const logger = opts.logger ?? noopLogger;
608
+ const CMD_PATH = getCmdToolsPath(config);
609
+ const tmpDir = fs8.mkdtempSync(path8.join(os6.tmpdir(), "oniro-cmdtools-"));
610
+ const zipPath = opts.localZipPath ?? path8.join(tmpDir, "oh-command-line-tools.zip");
611
+ const extractPath = path8.join(tmpDir, "oh-command-line-tools");
612
+ try {
613
+ if (!opts.localZipPath) {
614
+ const url = getCmdToolsDownloadUrl(config);
615
+ progress?.report({ message: "Downloading command line tools...", increment: 0 });
616
+ await downloadFile({ url, dest: zipPath, progress, abortSignal, start: 0, range: 50 });
617
+ }
618
+ progress?.report({ message: "Extracting tools...", increment: 0 });
619
+ await extractZipWithProgress({ zipPath, dest: extractPath, progress, start: 50, range: 45, logger });
620
+ fs8.mkdirSync(CMD_PATH, { recursive: true });
621
+ const srcDir = findCmdToolsSourceDir(extractPath);
622
+ for (const entry of fs8.readdirSync(srcDir)) {
623
+ const src = path8.join(srcDir, entry);
624
+ const dest = path8.join(CMD_PATH, entry);
625
+ if (fs8.statSync(src).isDirectory()) {
626
+ if (fs8.existsSync(dest)) fs8.rmSync(dest, { recursive: true, force: true });
627
+ fs8.renameSync(src, dest);
628
+ } else {
629
+ fs8.copyFileSync(src, dest);
630
+ }
631
+ }
632
+ const binDir = path8.join(CMD_PATH, "bin");
633
+ if (fs8.existsSync(binDir) && os6.platform() !== "win32") {
634
+ for (const file of fs8.readdirSync(binDir)) {
635
+ fs8.chmodSync(path8.join(binDir, file), 493);
636
+ }
637
+ }
638
+ progress?.report({ message: "Finalizing installation...", increment: 5 });
639
+ progress?.report({ message: "Cleaning up...", increment: 0 });
640
+ } finally {
641
+ fs8.rmSync(tmpDir, { recursive: true, force: true });
642
+ }
643
+ }
644
+ function getCmdToolsStatus(config) {
645
+ const cmdPath = getCmdToolsPath(config);
646
+ const binDir = path8.join(cmdPath, "bin");
647
+ const candidates = os6.platform() === "win32" ? ["ohpm.exe", "ohpm.cmd", "ohpm.bat", "ohpm"] : ["ohpm"];
648
+ const bin = candidates.map((c) => path8.join(binDir, c)).find((p) => fs8.existsSync(p));
649
+ if (!bin) {
650
+ return { installed: false, status: "Not installed" };
651
+ }
652
+ try {
653
+ const versionFile = path8.join(cmdPath, "version.txt");
654
+ if (fs8.existsSync(versionFile)) {
655
+ const content = fs8.readFileSync(versionFile, "utf8");
656
+ for (const line of content.split(/\r?\n/)) {
657
+ const m = line.match(/^\s*#\s*Version:\s*(.+)$/);
658
+ if (m?.[1]) return { installed: true, status: `Installed (${m[1].trim()})` };
659
+ }
660
+ }
661
+ } catch {
662
+ }
663
+ try {
664
+ const version = execFileSync(bin, ["-v"], { encoding: "utf8" }).trim();
665
+ return { installed: true, status: `Installed (${version})` };
666
+ } catch {
667
+ return { installed: true, status: "Installed (version unknown)" };
668
+ }
669
+ }
670
+ function isCmdToolsInstalled(config) {
671
+ return getCmdToolsStatus(config).installed;
672
+ }
673
+ function removeCmdTools(config) {
674
+ const cmdPath = getCmdToolsPath(config);
675
+ if (fs8.existsSync(cmdPath)) {
676
+ fs8.rmSync(cmdPath, { recursive: true, force: true });
677
+ }
678
+ }
679
+
680
+ // src/sign/encryptKey.ts
681
+ import * as fs9 from "fs";
682
+ import * as path9 from "path";
683
+ import * as crypto2 from "crypto";
684
+ var DAEMON_ROOT_KEY_COMPONENT_LENGTH = 16;
685
+ var DAEMON_SALT_KEY_LENGTH = 16;
686
+ var DAEMON_WORK_KEY_LENGTH = 16;
687
+ var KEY_FILE_DIRECTORY_PERMISSIONS = 493;
688
+ var KEY_FILE_PERMISSIONS = 384;
689
+ var COMPONENT = Buffer.from([
690
+ 49,
691
+ 243,
692
+ 9,
693
+ 115,
694
+ 214,
695
+ 175,
696
+ 91,
697
+ 184,
698
+ 211,
699
+ 190,
700
+ 177,
701
+ 88,
702
+ 101,
703
+ 131,
704
+ 192,
705
+ 119
706
+ ]);
707
+ function encrypt(key, data) {
708
+ const iv = crypto2.randomBytes(12);
709
+ const cipher = crypto2.createCipheriv("aes-128-gcm", key, iv);
710
+ const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
711
+ const authTag = cipher.getAuthTag();
712
+ const totalLength = ciphertext.length + authTag.length;
713
+ const out = Buffer.alloc(4 + iv.length + ciphertext.length + authTag.length);
714
+ out.writeUInt32BE(totalLength, 0);
715
+ iv.copy(out, 4);
716
+ ciphertext.copy(out, 16);
717
+ authTag.copy(out, 16 + ciphertext.length);
718
+ return out;
719
+ }
720
+ function decrypt(key, data) {
721
+ const totalLength = data.readUInt32BE(0);
722
+ const iv = data.subarray(4, 16);
723
+ const ciphertextLength = totalLength - 16;
724
+ const ciphertext = data.subarray(16, 16 + ciphertextLength);
725
+ const authTag = data.subarray(16 + ciphertextLength);
726
+ const decipher = crypto2.createDecipheriv("aes-128-gcm", key, iv);
727
+ decipher.setAuthTag(authTag);
728
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
729
+ }
730
+ function xorBuffers(buffers) {
731
+ if (buffers.length === 0) {
732
+ throw new Error("No buffers provided for XOR.");
733
+ }
734
+ const result = Buffer.from(buffers[0]);
735
+ for (let i = 1; i < buffers.length; i++) {
736
+ const buf = buffers[i];
737
+ if (buf.length !== result.length) {
738
+ throw new Error("Buffers have different lengths in XOR.");
739
+ }
740
+ for (let j = 0; j < result.length; j++) {
741
+ result[j] = result[j] ^ buf[j];
742
+ }
743
+ }
744
+ return result;
745
+ }
746
+ function getRootKey(fdComponents, salt) {
747
+ const components = fdComponents.concat([COMPONENT]);
748
+ const xored = xorBuffers(components);
749
+ return crypto2.pbkdf2Sync(xored.toString(), salt, 1e4, 16, "sha256");
750
+ }
751
+ function createAndStoreKey(dir, keyLength) {
752
+ const key = crypto2.randomBytes(keyLength);
753
+ const hash = crypto2.createHash("sha256").update(key).digest("hex");
754
+ const filePath = path9.join(dir, hash);
755
+ fs9.writeFileSync(filePath, key);
756
+ fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
757
+ return key;
758
+ }
759
+ function createAndStoreEnKey(rootKey, ceDir) {
760
+ const workKey = crypto2.randomBytes(DAEMON_WORK_KEY_LENGTH);
761
+ const encrypted = encrypt(rootKey, workKey);
762
+ const hash = crypto2.createHash("sha256").update(encrypted).digest("hex");
763
+ const filePath = path9.join(ceDir, hash);
764
+ fs9.writeFileSync(filePath, encrypted);
765
+ fs9.chmodSync(filePath, KEY_FILE_PERMISSIONS);
766
+ }
767
+ function createMaterial(materialPath) {
768
+ fs9.mkdirSync(materialPath, { recursive: true });
769
+ fs9.chmodSync(materialPath, KEY_FILE_DIRECTORY_PERMISSIONS);
770
+ const fdDir = path9.join(materialPath, "fd");
771
+ const acDir = path9.join(materialPath, "ac");
772
+ const ceDir = path9.join(materialPath, "ce");
773
+ fs9.mkdirSync(fdDir, { recursive: true });
774
+ fs9.mkdirSync(acDir, { recursive: true });
775
+ fs9.mkdirSync(ceDir, { recursive: true });
776
+ const fdSubDirs = ["0", "1", "2"];
777
+ const fdComponents = [];
778
+ for (const sub of fdSubDirs) {
779
+ const subDir = path9.join(fdDir, sub);
780
+ fs9.mkdirSync(subDir, { recursive: true });
781
+ const comp = createAndStoreKey(subDir, DAEMON_ROOT_KEY_COMPONENT_LENGTH);
782
+ fdComponents.push(comp);
783
+ }
784
+ const salt = createAndStoreKey(acDir, DAEMON_SALT_KEY_LENGTH);
785
+ const rootKey = getRootKey(fdComponents, salt);
786
+ createAndStoreEnKey(rootKey, ceDir);
787
+ }
788
+ function readSingleFile(dir) {
789
+ if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
790
+ throw new Error(`Missing signing material directory: ${dir}`);
791
+ }
792
+ const files = fs9.readdirSync(dir).filter((f) => f !== ".DS_Store");
793
+ if (files.length !== 1) {
794
+ throw new Error(`Signing material in ${dir} is illegal (expected exactly one file).`);
795
+ }
796
+ return fs9.readFileSync(path9.join(dir, files[0]));
797
+ }
798
+ function getKey(materialPath) {
799
+ if (!fs9.existsSync(materialPath) || !fs9.statSync(materialPath).isDirectory()) {
800
+ throw new Error("Material directory does not exist.");
801
+ }
802
+ const fdDir = path9.join(materialPath, "fd");
803
+ const fdComponents = ["0", "1", "2"].map((sub) => readSingleFile(path9.join(fdDir, sub)));
804
+ const salt = readSingleFile(path9.join(materialPath, "ac"));
805
+ const rootKey = getRootKey(fdComponents, salt);
806
+ const workMaterial = readSingleFile(path9.join(materialPath, "ce"));
807
+ return decrypt(rootKey, workMaterial);
808
+ }
809
+ function encryptPwd(password, materialPath) {
810
+ const key = getKey(materialPath);
811
+ const pwdBuffer = Buffer.from(password, "utf-8");
812
+ return encrypt(key, pwdBuffer).toString("hex");
813
+ }
814
+ function decryptPwd(encryptedHex, materialPath) {
815
+ const key = getKey(materialPath);
816
+ const encryptedBuffer = Buffer.from(encryptedHex, "hex");
817
+ return decrypt(key, encryptedBuffer).toString("utf-8");
818
+ }
819
+
820
+ // src/sign/generateSigningConfigs.ts
821
+ import * as fs10 from "fs";
822
+ import * as path10 from "path";
823
+ import { execFileSync as execFileSync2 } from "child_process";
824
+ import JSON52 from "json5";
825
+ var APL_VALUES = ["normal", "system_basic", "system_core"];
826
+ var APP_FEATURE_VALUES = ["hos_normal_app", "hos_system_app"];
827
+ function copyFilesToProject(projectDir, paths, logger) {
828
+ logger.info("[sign] Copying signing material into project...");
829
+ const signaturesDir = path10.join(projectDir, "signatures");
830
+ fs10.mkdirSync(signaturesDir, { recursive: true });
831
+ fs10.copyFileSync(paths.keystore, path10.join(signaturesDir, "OpenHarmony.p12"));
832
+ fs10.copyFileSync(paths.profileCert, path10.join(signaturesDir, "OpenHarmonyProfileRelease.pem"));
833
+ fs10.copyFileSync(paths.unsignedProfileTemplate, path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json"));
834
+ }
835
+ function resolveAppFeature(apl, override) {
836
+ if (override) return override;
837
+ return apl === "normal" ? "hos_normal_app" : "hos_system_app";
838
+ }
839
+ function modifyProfileTemplate(projectDir, apl, appFeature, logger) {
840
+ logger.info(`[sign] Modifying profile template (apl=${apl}, app-feature=${appFeature})...`);
841
+ const appJsonPath = path10.join(projectDir, "AppScope/app.json5");
842
+ const profileTemplatePath = path10.join(projectDir, "signatures/UnsgnedReleasedProfileTemplate.json");
843
+ const profileCertFilePath = path10.join(projectDir, "signatures/OpenHarmonyProfileRelease.pem");
844
+ if (!fs10.existsSync(appJsonPath)) {
845
+ throw new OniroError(`${appJsonPath} does not exist.`);
846
+ }
847
+ let appJson;
848
+ try {
849
+ appJson = JSON52.parse(fs10.readFileSync(appJsonPath, "utf-8"));
850
+ } catch (e) {
851
+ throw new OniroError(`Error parsing ${appJsonPath}: ${e.message}`, e);
852
+ }
853
+ let profileTemplate;
854
+ try {
855
+ profileTemplate = JSON.parse(fs10.readFileSync(profileTemplatePath, "utf-8"));
856
+ } catch (e) {
857
+ throw new OniroError(`Error parsing ${profileTemplatePath}: ${e.message}`, e);
858
+ }
859
+ if (!appJson.app?.bundleName) {
860
+ throw new OniroError("app.json5 does not contain app.bundleName.");
861
+ }
862
+ if (!profileTemplate["bundle-info"]) {
863
+ throw new OniroError("UnsgnedReleasedProfileTemplate.json is missing the bundle-info section.");
864
+ }
865
+ profileTemplate["bundle-info"]["bundle-name"] = appJson.app.bundleName;
866
+ profileTemplate["bundle-info"]["apl"] = apl;
867
+ profileTemplate["bundle-info"]["app-feature"] = appFeature;
868
+ const certContent = fs10.readFileSync(profileCertFilePath, "utf-8");
869
+ const certs = certContent.split("-----END CERTIFICATE-----");
870
+ if (certs.length < 3) {
871
+ throw new OniroError(`${profileCertFilePath} does not contain enough certificates.`);
872
+ }
873
+ const thirdCert = certs[2].trim() + "\n-----END CERTIFICATE-----\n";
874
+ profileTemplate["bundle-info"]["distribution-certificate"] = thirdCert;
875
+ fs10.writeFileSync(profileTemplatePath, JSON.stringify(profileTemplate, null, 2));
876
+ }
877
+ function generateP7bFile(projectDir, paths, logger) {
878
+ logger.info("[sign] Generating P7b profile via hap-sign-tool...");
879
+ const signaturesDir = path10.join(projectDir, "signatures");
880
+ const profileTemplatePath = path10.join(signaturesDir, "UnsgnedReleasedProfileTemplate.json");
881
+ const outputProfilePath = path10.join(signaturesDir, "app1-profile.p7b");
882
+ const args = [
883
+ "-jar",
884
+ paths.signTool,
885
+ "sign-profile",
886
+ "-keyAlias",
887
+ "openharmony application profile release",
888
+ "-signAlg",
889
+ "SHA256withECDSA",
890
+ "-mode",
891
+ "localSign",
892
+ "-profileCertFile",
893
+ paths.profileCert,
894
+ "-inFile",
895
+ profileTemplatePath,
896
+ "-keystoreFile",
897
+ paths.keystore,
898
+ "-outFile",
899
+ outputProfilePath,
900
+ "-keyPwd",
901
+ "123456",
902
+ "-keystorePwd",
903
+ "123456"
904
+ ];
905
+ try {
906
+ execFileSync2("java", args, { stdio: "inherit" });
907
+ } catch (e) {
908
+ throw new OniroError(
909
+ `hap-sign-tool failed. Ensure a JDK (with keytool/java) is on PATH. Underlying error: ${e.message}`,
910
+ e
911
+ );
912
+ }
913
+ }
914
+ function updateBuildProfile(projectDir, logger) {
915
+ logger.info("[sign] Writing signing configs into build-profile.json5...");
916
+ const materialDir = path10.join(projectDir, "signatures", "material");
917
+ const buildProfilePath = path10.join(projectDir, "build-profile.json5");
918
+ const encryptedStorePassword = encryptPwd("123456", materialDir);
919
+ const encryptedKeyPassword = encryptPwd("123456", materialDir);
920
+ let buildProfile = { app: {} };
921
+ if (fs10.existsSync(buildProfilePath)) {
922
+ try {
923
+ buildProfile = JSON52.parse(fs10.readFileSync(buildProfilePath, "utf-8"));
924
+ } catch (e) {
925
+ throw new OniroError(`Error parsing ${buildProfilePath}: ${e.message}`, e);
926
+ }
927
+ }
928
+ buildProfile.app = buildProfile.app ?? {};
929
+ buildProfile.app.signingConfigs = [
930
+ {
931
+ name: "default",
932
+ material: {
933
+ certpath: "./signatures/OpenHarmonyProfileRelease.pem",
934
+ storePassword: encryptedStorePassword,
935
+ keyAlias: "openharmony application profile release",
936
+ keyPassword: encryptedKeyPassword,
937
+ profile: "./signatures/app1-profile.p7b",
938
+ signAlg: "SHA256withECDSA",
939
+ storeFile: "./signatures/OpenHarmony.p12"
940
+ }
941
+ }
942
+ ];
943
+ fs10.writeFileSync(buildProfilePath, JSON.stringify(buildProfile, null, 2));
944
+ }
945
+ function prepareMaterialDirectory(projectDir, logger) {
946
+ logger.info("[sign] Generating fresh signing material...");
947
+ const materialDir = path10.join(projectDir, "signatures", "material");
948
+ if (fs10.existsSync(materialDir)) {
949
+ fs10.rmSync(materialDir, { recursive: true, force: true });
950
+ }
951
+ createMaterial(materialDir);
952
+ }
953
+ function generateSigningConfigs(options) {
954
+ const { projectDir, sdkHome } = options;
955
+ const logger = options.logger ?? noopLogger;
956
+ const apl = options.apl ?? "normal";
957
+ if (!APL_VALUES.includes(apl)) {
958
+ throw new OniroError(`Invalid apl '${apl}'. Expected one of: ${APL_VALUES.join(", ")}.`);
959
+ }
960
+ const appFeature = resolveAppFeature(apl, options.appFeature);
961
+ if (!APP_FEATURE_VALUES.includes(appFeature)) {
962
+ throw new OniroError(
963
+ `Invalid appFeature '${appFeature}'. Expected one of: ${APP_FEATURE_VALUES.join(", ")}.`
964
+ );
965
+ }
966
+ const sdkVersion = detectProjectSdkVersion(projectDir, logger);
967
+ if (!sdkVersion) {
968
+ throw new OniroError(
969
+ "Could not detect project SDK version (compileSdkVersion missing in build-profile.json5)."
970
+ );
971
+ }
972
+ const sdkPath = path10.join(sdkHome, String(sdkVersion));
973
+ if (!fs10.existsSync(sdkPath)) {
974
+ throw new OniroError(`SDK path does not exist: ${sdkPath}`);
975
+ }
976
+ const paths = {
977
+ signTool: path10.join(sdkPath, "toolchains/lib/hap-sign-tool.jar"),
978
+ keystore: path10.join(sdkPath, "toolchains/lib/OpenHarmony.p12"),
979
+ profileCert: path10.join(sdkPath, "toolchains/lib/OpenHarmonyProfileRelease.pem"),
980
+ unsignedProfileTemplate: path10.join(sdkPath, "toolchains/lib/UnsgnedReleasedProfileTemplate.json")
981
+ };
982
+ logger.info("[sign] Starting signing configuration generation...");
983
+ copyFilesToProject(projectDir, paths, logger);
984
+ modifyProfileTemplate(projectDir, apl, appFeature, logger);
985
+ generateP7bFile(projectDir, paths, logger);
986
+ prepareMaterialDirectory(projectDir, logger);
987
+ updateBuildProfile(projectDir, logger);
988
+ logger.info("[sign] Signing configuration generated successfully.");
989
+ }
990
+
991
+ // src/emulator/install.ts
992
+ import * as fs11 from "fs";
993
+ import * as os7 from "os";
994
+ import * as path11 from "path";
995
+ async function installEmulator(opts) {
996
+ const { config, progress, abortSignal } = opts;
997
+ const logger = opts.logger ?? noopLogger;
998
+ const url = config.get("emulatorUrl", defaultPaths.emulatorUrl);
999
+ const emulatorDir = getEmulatorDir(config);
1000
+ const tmpDir = fs11.mkdtempSync(path11.join(os7.tmpdir(), "oniro-emulator-"));
1001
+ const tmpZip = path11.join(tmpDir, "oniro_emulator.zip");
1002
+ try {
1003
+ fs11.mkdirSync(emulatorDir, { recursive: true });
1004
+ progress?.report({ message: "Downloading emulator...", increment: 0 });
1005
+ await downloadFile({ url, dest: tmpZip, progress, abortSignal, start: 0, range: 50 });
1006
+ progress?.report({ message: "Extracting emulator...", increment: 0 });
1007
+ await extractZipWithProgress({ zipPath: tmpZip, dest: emulatorDir, progress, start: 50, range: 45, logger });
1008
+ const runSh = path11.join(emulatorDir, "images", "run.sh");
1009
+ if (fs11.existsSync(runSh)) {
1010
+ fs11.chmodSync(runSh, 493);
1011
+ }
1012
+ progress?.report({ message: "Finalizing installation...", increment: 5 });
1013
+ } finally {
1014
+ fs11.rmSync(tmpDir, { recursive: true, force: true });
1015
+ }
1016
+ }
1017
+ function isEmulatorInstalled(config) {
1018
+ return fs11.existsSync(path11.join(getEmulatorDir(config), "images", "run.sh"));
1019
+ }
1020
+ function removeEmulator(config) {
1021
+ const emulatorDir = getEmulatorDir(config);
1022
+ if (fs11.existsSync(emulatorDir)) {
1023
+ fs11.rmSync(emulatorDir, { recursive: true, force: true });
1024
+ }
1025
+ }
1026
+
1027
+ // src/emulator/lifecycle.ts
1028
+ import * as fs12 from "fs";
1029
+ import * as os8 from "os";
1030
+ import * as path12 from "path";
1031
+ import { exec, spawn } from "child_process";
1032
+ var PID_FILE = path12.join(os8.tmpdir(), "oniro_emulator.pid");
1033
+ function execPromise(cmd, cwd, logger) {
1034
+ return new Promise((resolve2, reject) => {
1035
+ exec(cmd, { cwd }, (error, stdout, stderr) => {
1036
+ if (stdout?.trim()) logger.info(`[emulator] ${stdout.trim()}`);
1037
+ if (stderr?.trim()) logger.warn(`[emulator] ${stderr.trim()}`);
1038
+ if (error) {
1039
+ logger.error(`[emulator] ${error.message}`);
1040
+ reject(error);
1041
+ } else {
1042
+ resolve2();
1043
+ }
1044
+ });
1045
+ });
1046
+ }
1047
+ async function attemptHdcConnection(config, address = "127.0.0.1:55555", logger = noopLogger) {
1048
+ const hdc = getHdcPath(config);
1049
+ try {
1050
+ await execPromise(`"${hdc}" start -r`, void 0, logger);
1051
+ await execPromise(`"${hdc}" tconn ${address}`, void 0, logger);
1052
+ return await new Promise((resolve2) => {
1053
+ exec(`"${hdc}" list targets`, (_error, stdout) => {
1054
+ resolve2(stdout?.includes(address) ?? false);
1055
+ });
1056
+ });
1057
+ } catch (err) {
1058
+ logger.warn(`hdc connection attempt failed: ${err.message}`);
1059
+ return false;
1060
+ }
1061
+ }
1062
+ function resolveLauncher(imagesPath, headless, connect) {
1063
+ const runSh = path12.join(imagesPath, "run.sh");
1064
+ const runBat = path12.join(imagesPath, "run.bat");
1065
+ const extra = [];
1066
+ if (headless) extra.push("--headless");
1067
+ if (connect) extra.push("--connect", connect);
1068
+ if (os8.platform() === "win32") {
1069
+ if (fs12.existsSync(runBat)) {
1070
+ return { command: "cmd.exe", args: ["/c", runBat, ...extra], cwd: imagesPath };
1071
+ }
1072
+ if (fs12.existsSync(runSh)) {
1073
+ return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1074
+ }
1075
+ throw new OniroError(`No emulator launcher (run.bat or run.sh) found in ${imagesPath}.`);
1076
+ }
1077
+ if (fs12.existsSync(runSh)) {
1078
+ return { command: "bash", args: [runSh, ...extra], cwd: imagesPath };
1079
+ }
1080
+ if (fs12.existsSync(runBat)) {
1081
+ return { command: "bash", args: [runBat, ...extra], cwd: imagesPath };
1082
+ }
1083
+ throw new OniroError(`No emulator launcher (run.sh or run.bat) found in ${imagesPath}.`);
1084
+ }
1085
+ async function startEmulator(opts) {
1086
+ const { config } = opts;
1087
+ const logger = opts.logger ?? noopLogger;
1088
+ if (os8.platform() !== "win32" && fs12.existsSync(PID_FILE)) {
1089
+ try {
1090
+ const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1091
+ if (Number.isFinite(pid) && pid > 0) {
1092
+ try {
1093
+ process.kill(pid, 0);
1094
+ logger.info(`Emulator already running (PID ${pid}); not starting another.`);
1095
+ if (opts.waitForHdcSeconds && opts.waitForHdcSeconds > 0) {
1096
+ await waitForHdc(config, opts.waitForHdcSeconds, opts.hdcPollIntervalMs ?? 5e3, logger, opts.abortSignal);
1097
+ }
1098
+ return;
1099
+ } catch {
1100
+ }
1101
+ }
1102
+ } catch (err) {
1103
+ logger.warn(`Could not read PID file: ${err.message}`);
1104
+ }
1105
+ }
1106
+ const imagesPath = path12.join(getEmulatorDir(config), "images");
1107
+ if (!fs12.existsSync(imagesPath)) {
1108
+ throw new OniroError(`Emulator images directory not found at ${imagesPath}. Run \`oniro-app emulator install\` first.`);
1109
+ }
1110
+ const launcher = resolveLauncher(imagesPath, opts.headless === true, opts.connect);
1111
+ const logPath = opts.logFile ?? (os8.platform() === "win32" ? "NUL" : "/dev/null");
1112
+ const logFd = fs12.openSync(logPath, "a");
1113
+ let child;
1114
+ try {
1115
+ child = spawn(launcher.command, [...launcher.args], {
1116
+ cwd: launcher.cwd,
1117
+ detached: true,
1118
+ stdio: ["ignore", logFd, logFd],
1119
+ windowsHide: true
1120
+ });
1121
+ } finally {
1122
+ fs12.closeSync(logFd);
1123
+ }
1124
+ if (!child.pid) {
1125
+ throw new OniroError(`Failed to spawn launcher (${launcher.command} ${launcher.args.join(" ")}).`);
1126
+ }
1127
+ fs12.writeFileSync(PID_FILE, String(child.pid));
1128
+ child.unref();
1129
+ logger.info(`Emulator launched (PID ${child.pid}). Logs: ${logPath}`);
1130
+ const earlyExit = new Promise((resolve2) => {
1131
+ child.once("exit", (code) => resolve2(code));
1132
+ child.once("error", () => resolve2(-1));
1133
+ });
1134
+ const liveness = new Promise((resolve2) => setTimeout(() => resolve2("alive"), 2e3));
1135
+ const outcome = await Promise.race([earlyExit, liveness]);
1136
+ if (outcome !== "alive") {
1137
+ const tail = readTail(logPath, 4e3);
1138
+ throw new OniroError(
1139
+ `Emulator launcher exited with code ${outcome} before reaching steady state. Tail of ${logPath}:
1140
+ ${tail}`
1141
+ );
1142
+ }
1143
+ if (opts.waitForHdcSeconds && opts.waitForHdcSeconds > 0) {
1144
+ await waitForHdc(config, opts.waitForHdcSeconds, opts.hdcPollIntervalMs ?? 5e3, logger, opts.abortSignal, logPath);
1145
+ }
1146
+ }
1147
+ async function waitForHdc(config, timeoutSeconds, pollIntervalMs, logger, abortSignal, logPathForDiagnostics) {
1148
+ const deadline = Date.now() + timeoutSeconds * 1e3;
1149
+ let attempt = 0;
1150
+ while (Date.now() < deadline) {
1151
+ if (abortSignal?.aborted) throw new CancelledError("hdc wait cancelled.");
1152
+ attempt++;
1153
+ if (await attemptHdcConnection(config, void 0, noopLogger)) {
1154
+ logger.info(`hdc connected on attempt ${attempt}.`);
1155
+ return;
1156
+ }
1157
+ if (attempt % 6 === 0) {
1158
+ logger.info(`hdc not yet ready (attempt ${attempt}, ${Math.max(0, deadline - Date.now()) / 1e3 | 0}s remaining).`);
1159
+ if (logPathForDiagnostics) {
1160
+ const tail2 = readTail(logPathForDiagnostics, 1200);
1161
+ if (tail2.trim()) logger.debug(`emulator log tail:
1162
+ ${tail2}`);
1163
+ }
1164
+ }
1165
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1166
+ }
1167
+ const tail = logPathForDiagnostics ? readTail(logPathForDiagnostics, 4e3) : "";
1168
+ throw new OniroError(`hdc did not connect within ${timeoutSeconds}s.${tail ? `
1169
+ Tail of emulator log:
1170
+ ${tail}` : ""}`);
1171
+ }
1172
+ function readTail(filePath, bytes) {
1173
+ try {
1174
+ const stat = fs12.statSync(filePath);
1175
+ const start = Math.max(0, stat.size - bytes);
1176
+ const fd = fs12.openSync(filePath, "r");
1177
+ try {
1178
+ const buf = Buffer.alloc(stat.size - start);
1179
+ fs12.readSync(fd, buf, 0, buf.length, start);
1180
+ return buf.toString("utf8");
1181
+ } finally {
1182
+ fs12.closeSync(fd);
1183
+ }
1184
+ } catch {
1185
+ return "";
1186
+ }
1187
+ }
1188
+ async function stopEmulator(logger = noopLogger) {
1189
+ if (fs12.existsSync(PID_FILE)) {
1190
+ try {
1191
+ const pid = Number(fs12.readFileSync(PID_FILE, "utf8").trim());
1192
+ if (Number.isFinite(pid) && pid > 0) {
1193
+ try {
1194
+ process.kill(pid, "SIGTERM");
1195
+ logger.info(`Sent SIGTERM to emulator PID ${pid}.`);
1196
+ } catch (err) {
1197
+ logger.warn(`Could not signal PID ${pid}: ${err.message}`);
1198
+ }
1199
+ }
1200
+ } catch (err) {
1201
+ logger.warn(`Could not read PID file: ${err.message}`);
1202
+ }
1203
+ }
1204
+ const killCmd = os8.platform() === "win32" ? "taskkill /IM qemu-system-x86_64.exe /F" : "pkill -f qemu-system-x86_64";
1205
+ try {
1206
+ await execPromise(killCmd, void 0, logger);
1207
+ } catch {
1208
+ }
1209
+ fs12.rm(PID_FILE, { force: true }, (err) => {
1210
+ if (err) logger.warn(`Could not remove PID file: ${err.message}`);
1211
+ });
1212
+ }
1213
+
1214
+ // src/hdc/project.ts
1215
+ import * as fs13 from "fs";
1216
+ import * as path13 from "path";
1217
+ import JSON53 from "json5";
1218
+ function readJson5(filePath) {
1219
+ const content = fs13.readFileSync(filePath, "utf-8");
1220
+ return JSON53.parse(content);
1221
+ }
1222
+ function getBundleName(projectDir) {
1223
+ const appJsonPath = path13.join(projectDir, "AppScope", "app.json5");
1224
+ if (!fs13.existsSync(appJsonPath)) {
1225
+ throw new OniroError(`Could not find app.json5 at ${appJsonPath}`);
1226
+ }
1227
+ const appJson = readJson5(appJsonPath);
1228
+ if (!appJson.app?.bundleName) {
1229
+ throw new OniroError("bundleName not found in app.json5");
1230
+ }
1231
+ return appJson.app.bundleName;
1232
+ }
1233
+ function getMainAbility(projectDir, moduleName = "entry") {
1234
+ const moduleJsonPath = path13.join(projectDir, moduleName, "src", "main", "module.json5");
1235
+ if (!fs13.existsSync(moduleJsonPath)) {
1236
+ throw new OniroError(`Could not find module.json5 at ${moduleJsonPath}`);
1237
+ }
1238
+ const moduleJson = readJson5(moduleJsonPath);
1239
+ if (!moduleJson.module?.mainElement) {
1240
+ throw new OniroError("mainElement not found in module.json5");
1241
+ }
1242
+ return moduleJson.module.mainElement;
1243
+ }
1244
+
1245
+ // src/hdc/app.ts
1246
+ import * as fs14 from "fs";
1247
+ import * as path14 from "path";
1248
+ import { exec as exec2, spawn as spawn2 } from "child_process";
1249
+ function execPromise2(cmd, logger) {
1250
+ return new Promise((resolve2, reject) => {
1251
+ exec2(cmd, (error, stdout, stderr) => {
1252
+ if (stdout?.trim()) logger.info(`[hdc] ${stdout.trim()}`);
1253
+ if (stderr?.trim()) logger.warn(`[hdc] ${stderr.trim()}`);
1254
+ if (error) {
1255
+ logger.error(`[hdc] ${error.message}`);
1256
+ reject(error);
1257
+ } else {
1258
+ resolve2();
1259
+ }
1260
+ });
1261
+ });
1262
+ }
1263
+ async function installApp(opts) {
1264
+ const logger = opts.logger ?? noopLogger;
1265
+ const relativeHapPath = opts.hapPath ?? opts.config.get("hapPath", defaultPaths.hapPath);
1266
+ const hapPath = path14.isAbsolute(relativeHapPath) ? relativeHapPath : path14.join(opts.projectDir, relativeHapPath);
1267
+ if (!fs14.existsSync(hapPath)) {
1268
+ throw new OniroError(`HAP file not found at: ${hapPath}. Build and sign the app first.`);
1269
+ }
1270
+ const hdc = getHdcPath(opts.config);
1271
+ await execPromise2(`"${hdc}" install "${hapPath}"`, logger);
1272
+ }
1273
+ async function launchApp(opts) {
1274
+ const logger = opts.logger ?? noopLogger;
1275
+ const bundleName = getBundleName(opts.projectDir);
1276
+ const mainAbility = getMainAbility(opts.projectDir, opts.moduleName);
1277
+ const hdc = getHdcPath(opts.config);
1278
+ await execPromise2(`"${hdc}" shell aa start -a ${mainAbility} -b ${bundleName}`, logger);
1279
+ }
1280
+ function listRunningProcesses(config, options = {}) {
1281
+ const logger = options.logger ?? noopLogger;
1282
+ const timeoutMs = options.timeoutMs ?? 1e3;
1283
+ const hdc = getHdcPath(config);
1284
+ return new Promise((resolve2, reject) => {
1285
+ const proc = spawn2(hdc, ["track-jpid"]);
1286
+ const processes = [];
1287
+ let resolved = false;
1288
+ proc.stdout.on("data", (data) => {
1289
+ for (const line of data.toString().split("\n").filter(Boolean)) {
1290
+ const match = line.match(/^(\d+)\s+(.+)$/);
1291
+ if (!match) continue;
1292
+ const pid = match[1];
1293
+ const name = match[2];
1294
+ processes.push({ pid, name });
1295
+ if (options.targetProcessName && name === options.targetProcessName && !resolved) {
1296
+ resolved = true;
1297
+ proc.kill();
1298
+ resolve2(pid);
1299
+ return;
1300
+ }
1301
+ }
1302
+ });
1303
+ proc.stderr.on("data", (data) => {
1304
+ logger.warn(`[hdc track-jpid] ${data.toString()}`);
1305
+ });
1306
+ proc.on("error", (err) => {
1307
+ if (!resolved) {
1308
+ resolved = true;
1309
+ reject(err);
1310
+ }
1311
+ });
1312
+ const timeout = setTimeout(() => {
1313
+ if (resolved) return;
1314
+ resolved = true;
1315
+ proc.kill();
1316
+ if (options.targetProcessName) {
1317
+ reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1318
+ } else {
1319
+ resolve2(processes);
1320
+ }
1321
+ }, timeoutMs);
1322
+ proc.on("close", () => {
1323
+ clearTimeout(timeout);
1324
+ if (resolved) return;
1325
+ resolved = true;
1326
+ if (options.targetProcessName) {
1327
+ reject(new OniroError(`Could not find process for bundle: ${options.targetProcessName}`));
1328
+ } else {
1329
+ resolve2(processes);
1330
+ }
1331
+ });
1332
+ });
1333
+ }
1334
+ async function findAppProcessId(config, projectDir, logger) {
1335
+ const bundleName = getBundleName(projectDir);
1336
+ const result = await listRunningProcesses(config, { targetProcessName: bundleName, logger });
1337
+ return result;
1338
+ }
1339
+
1340
+ // src/hdc/hilog.ts
1341
+ import { spawn as spawn3 } from "child_process";
1342
+ function setHilogLevel(opts) {
1343
+ const logger = opts.logger ?? noopLogger;
1344
+ const hdc = getHdcPath(opts.config);
1345
+ return new Promise((resolve2, reject) => {
1346
+ const child = spawn3(hdc, ["shell", "hilog", "-b", opts.level]);
1347
+ let stderr = "";
1348
+ child.stderr.on("data", (chunk) => {
1349
+ stderr += chunk.toString();
1350
+ });
1351
+ child.once("error", (err) => {
1352
+ logger.error(`[hilog] failed to spawn hdc: ${err.message}`);
1353
+ reject(err);
1354
+ });
1355
+ child.once("close", (code) => {
1356
+ if (code === 0) {
1357
+ resolve2();
1358
+ } else {
1359
+ const msg = stderr.trim() || `hdc shell hilog -b ${opts.level} exited with code ${code}`;
1360
+ logger.error(`[hilog] ${msg}`);
1361
+ reject(new Error(msg));
1362
+ }
1363
+ });
1364
+ });
1365
+ }
1366
+ function streamHilog(opts) {
1367
+ const hdc = getHdcPath(opts.config);
1368
+ const args = ["shell", "hilog"];
1369
+ if (opts.processId && opts.processId.trim() !== "") {
1370
+ args.push("-P", opts.processId.trim());
1371
+ }
1372
+ return spawn3(hdc, args);
1373
+ }
1374
+ var HILOG_LINE_RE = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([DIWEF])\s+(\S+):\s+(.*)$/;
1375
+ function parseHilogLine(line) {
1376
+ const match = HILOG_LINE_RE.exec(line);
1377
+ if (!match) return null;
1378
+ return {
1379
+ time: match[1],
1380
+ pid: match[2],
1381
+ tid: match[3],
1382
+ level: match[4],
1383
+ tag: match[5].trim(),
1384
+ message: match[6]
1385
+ };
1386
+ }
1387
+
1388
+ // src/build/runHvigorw.ts
1389
+ import * as fs15 from "fs";
1390
+ import * as path15 from "path";
1391
+ import { spawn as spawn4 } from "child_process";
1392
+ function runHvigorw(opts) {
1393
+ const { config, projectDir } = opts;
1394
+ const logger = opts.logger ?? noopLogger;
1395
+ if (!fs15.existsSync(path15.join(projectDir, "build-profile.json5"))) {
1396
+ throw new OniroError(`Not an OpenHarmony project: ${projectDir} (build-profile.json5 not found).`);
1397
+ }
1398
+ if (!fs15.existsSync(getCmdToolsPath(config))) {
1399
+ throw new CmdToolsNotInstalledError(getCmdToolsPath(config));
1400
+ }
1401
+ const hvigorw = getHvigorwPath(config, projectDir);
1402
+ const args = [opts.task ?? "assembleHap", "--mode", "module"];
1403
+ args.push("-p", `product=${opts.product ?? "default"}`);
1404
+ if (opts.module) args.push("-p", `module=${opts.module}`);
1405
+ if (opts.buildMode) args.push("-p", `buildMode=${opts.buildMode}`);
1406
+ args.push("--stacktrace", "--no-parallel", "--no-daemon");
1407
+ if (opts.extraArgs?.length) args.push(...opts.extraArgs);
1408
+ if (process.platform !== "win32") {
1409
+ try {
1410
+ fs15.chmodSync(hvigorw, 493);
1411
+ } catch {
1412
+ }
1413
+ }
1414
+ const env = {
1415
+ ...process.env,
1416
+ OHOS_BASE_SDK_HOME: getOhosBaseSdkHome(config)
1417
+ };
1418
+ return new Promise((resolve2, reject) => {
1419
+ logger.info(`[build] ${hvigorw} ${args.join(" ")}`);
1420
+ const child = spawn4(hvigorw, args, { cwd: projectDir, env, shell: false });
1421
+ child.stdout.on("data", (chunk) => {
1422
+ const text = chunk.toString();
1423
+ opts.onOutput?.(text, "stdout");
1424
+ logger.info(text.trimEnd());
1425
+ });
1426
+ child.stderr.on("data", (chunk) => {
1427
+ const text = chunk.toString();
1428
+ opts.onOutput?.(text, "stderr");
1429
+ logger.warn(text.trimEnd());
1430
+ });
1431
+ child.on("error", (err) => reject(new OniroError(`Failed to start hvigorw: ${err.message}`, err)));
1432
+ child.on("close", (code) => {
1433
+ const exitCode = code ?? 1;
1434
+ if (exitCode === 0) resolve2({ exitCode });
1435
+ else reject(new OniroError(`hvigorw exited with code ${exitCode}`));
1436
+ });
1437
+ });
1438
+ }
1439
+
1440
+ // src/project/validators.ts
1441
+ function isValidProjectName(name) {
1442
+ if (!name) return false;
1443
+ if (name.includes("/") || name.includes("\\")) return false;
1444
+ return /^[A-Za-z0-9._-]+$/.test(name);
1445
+ }
1446
+ function isValidBundleName(bundleName) {
1447
+ return /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/.test(bundleName);
1448
+ }
1449
+
1450
+ // src/project/templates.ts
1451
+ import * as fs16 from "fs";
1452
+ import * as path16 from "path";
1453
+ function toHumanTemplateName(folderName) {
1454
+ return folderName.replace(/([a-z])([A-Z])/g, "$1 $2");
1455
+ }
1456
+ function listTemplates(templateRoot) {
1457
+ if (!fs16.existsSync(templateRoot)) return [];
1458
+ const entries = fs16.readdirSync(templateRoot, { withFileTypes: true });
1459
+ return entries.filter((e) => e.isDirectory()).map((e) => {
1460
+ const dir = path16.join(templateRoot, e.name);
1461
+ const metaPath = path16.join(dir, "template.json");
1462
+ let meta = {};
1463
+ if (fs16.existsSync(metaPath)) {
1464
+ try {
1465
+ meta = JSON.parse(fs16.readFileSync(metaPath, "utf8"));
1466
+ } catch {
1467
+ }
1468
+ }
1469
+ return {
1470
+ id: e.name,
1471
+ label: meta.label ?? toHumanTemplateName(e.name),
1472
+ description: meta.description ?? "",
1473
+ defaultModuleName: (meta.defaultModuleName ?? "entry").trim() || "entry"
1474
+ };
1475
+ }).sort((a, b) => a.label.localeCompare(b.label));
1476
+ }
1477
+ function validateTemplateLayout(templateDir, defaultModuleName) {
1478
+ const required = [
1479
+ "build-profile.json5",
1480
+ path16.join("AppScope", "app.json5"),
1481
+ path16.join(defaultModuleName, "src", "main", "module.json5"),
1482
+ path16.join(defaultModuleName, "oh-package.json5"),
1483
+ "hvigorfile.ts"
1484
+ ];
1485
+ return required.filter((rel) => !fs16.existsSync(path16.join(templateDir, rel)));
1486
+ }
1487
+
1488
+ // src/project/createScaffold.ts
1489
+ import * as fs18 from "fs";
1490
+ import * as path17 from "path";
1491
+
1492
+ // src/project/jsonHelpers.ts
1493
+ import * as fs17 from "fs";
1494
+ import JSON54 from "json5";
1495
+ function readJson5File(filePath) {
1496
+ return JSON54.parse(fs17.readFileSync(filePath, "utf8"));
1497
+ }
1498
+ function writeJson5File(filePath, value) {
1499
+ fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1500
+ }
1501
+ function readJsonFile(filePath) {
1502
+ return JSON.parse(fs17.readFileSync(filePath, "utf8"));
1503
+ }
1504
+ function writeJsonFile(filePath, value) {
1505
+ fs17.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
1506
+ }
1507
+
1508
+ // src/project/createScaffold.ts
1509
+ var IGNORED_DIRS = /* @__PURE__ */ new Set(["oh_modules", "node_modules", "build", ".hvigor"]);
1510
+ async function pathExists(p) {
1511
+ try {
1512
+ await fs18.promises.access(p);
1513
+ return true;
1514
+ } catch {
1515
+ return false;
1516
+ }
1517
+ }
1518
+ async function copyDirRecursive(srcDir, destDir) {
1519
+ await fs18.promises.mkdir(destDir, { recursive: true });
1520
+ const entries = await fs18.promises.readdir(srcDir, { withFileTypes: true });
1521
+ for (const entry of entries) {
1522
+ const src = path17.join(srcDir, entry.name);
1523
+ const dest = path17.join(destDir, entry.name);
1524
+ if (entry.isDirectory()) {
1525
+ await copyDirRecursive(src, dest);
1526
+ } else if (entry.isSymbolicLink()) {
1527
+ continue;
1528
+ } else if (entry.isFile()) {
1529
+ if (entry.name === "template.json") continue;
1530
+ await fs18.promises.copyFile(src, dest);
1531
+ }
1532
+ }
1533
+ }
1534
+ async function normalizeJson5ToJson(projectDir, logger) {
1535
+ const stack = [projectDir];
1536
+ while (stack.length > 0) {
1537
+ const currentDir = stack.pop();
1538
+ const entries = await fs18.promises.readdir(currentDir, { withFileTypes: true });
1539
+ for (const entry of entries) {
1540
+ const fullPath = path17.join(currentDir, entry.name);
1541
+ if (entry.isDirectory()) {
1542
+ if (IGNORED_DIRS.has(entry.name)) continue;
1543
+ stack.push(fullPath);
1544
+ continue;
1545
+ }
1546
+ if (!entry.isFile() || !entry.name.endsWith(".json5")) continue;
1547
+ try {
1548
+ const parsed = readJson5File(fullPath);
1549
+ writeJson5File(fullPath, parsed);
1550
+ } catch (err) {
1551
+ logger.warn(`Failed to normalize ${fullPath}: ${String(err)}`);
1552
+ }
1553
+ }
1554
+ }
1555
+ }
1556
+ function toJavaPropertiesPath(p) {
1557
+ return process.platform === "win32" ? p.replace(/\\/g, "\\\\") : p;
1558
+ }
1559
+ function createOrUpdateLocalProperties(projectDir, sdkDir) {
1560
+ const content = `sdk.dir=${toJavaPropertiesPath(sdkDir)}
1561
+ `;
1562
+ fs18.writeFileSync(path17.join(projectDir, "local.properties"), content, "utf8");
1563
+ }
1564
+ function renameIfExists(fromPath, toPath) {
1565
+ if (fs18.existsSync(fromPath) && fromPath !== toPath) {
1566
+ fs18.renameSync(fromPath, toPath);
1567
+ }
1568
+ }
1569
+ function updateTemplateConfigs(projectDir, args, sdkBaseDir) {
1570
+ const appJsonPath = path17.join(projectDir, "AppScope", "app.json5");
1571
+ if (fs18.existsSync(appJsonPath)) {
1572
+ const appJson = readJson5File(appJsonPath);
1573
+ appJson.app = appJson.app ?? {};
1574
+ appJson.app.bundleName = args.bundleName;
1575
+ writeJson5File(appJsonPath, appJson);
1576
+ }
1577
+ const stringsPath = path17.join(projectDir, "AppScope", "resources", "base", "element", "string.json");
1578
+ if (fs18.existsSync(stringsPath)) {
1579
+ const strings = readJsonFile(stringsPath);
1580
+ if (Array.isArray(strings.string)) {
1581
+ const appName = strings.string.find((s) => s.name === "app_name");
1582
+ if (appName) appName.value = args.projectName;
1583
+ }
1584
+ writeJsonFile(stringsPath, strings);
1585
+ }
1586
+ const buildProfilePath = path17.join(projectDir, "build-profile.json5");
1587
+ if (fs18.existsSync(buildProfilePath)) {
1588
+ const buildProfile = readJson5File(buildProfilePath);
1589
+ buildProfile.app = buildProfile.app ?? {};
1590
+ if (Array.isArray(buildProfile.app.products) && buildProfile.app.products.length > 0) {
1591
+ buildProfile.app.products[0].compileSdkVersion = args.sdkApi;
1592
+ buildProfile.app.products[0].compatibleSdkVersion = args.sdkApi;
1593
+ }
1594
+ if (Array.isArray(buildProfile.modules) && buildProfile.modules.length > 0) {
1595
+ buildProfile.modules[0].name = args.moduleName;
1596
+ buildProfile.modules[0].srcPath = `./${args.moduleName}`;
1597
+ }
1598
+ writeJson5File(buildProfilePath, buildProfile);
1599
+ }
1600
+ const moduleJsonPath = path17.join(projectDir, args.moduleName, "src", "main", "module.json5");
1601
+ if (fs18.existsSync(moduleJsonPath)) {
1602
+ const moduleJson = readJson5File(moduleJsonPath);
1603
+ moduleJson.module = moduleJson.module ?? {};
1604
+ moduleJson.module.name = args.moduleName;
1605
+ writeJson5File(moduleJsonPath, moduleJson);
1606
+ }
1607
+ const moduleOhPackagePath = path17.join(projectDir, args.moduleName, "oh-package.json5");
1608
+ if (fs18.existsSync(moduleOhPackagePath)) {
1609
+ const pkg = readJson5File(moduleOhPackagePath);
1610
+ pkg.name = args.moduleName;
1611
+ writeJson5File(moduleOhPackagePath, pkg);
1612
+ }
1613
+ const vscodeDir = path17.join(projectDir, ".vscode");
1614
+ fs18.mkdirSync(vscodeDir, { recursive: true });
1615
+ const hapPath = `${args.moduleName}/build/default/outputs/default/${args.moduleName}-default-signed.hap`;
1616
+ writeJsonFile(path17.join(vscodeDir, "settings.json"), {
1617
+ "oniro.hapPath": hapPath,
1618
+ "files.associations": { "*.json5": "jsonc" }
1619
+ });
1620
+ createOrUpdateLocalProperties(projectDir, sdkBaseDir);
1621
+ }
1622
+ async function createScaffold(opts) {
1623
+ const logger = opts.logger ?? noopLogger;
1624
+ if (!isValidProjectName(opts.projectName)) {
1625
+ throw new OniroError(`Invalid project name '${opts.projectName}'. Use letters/numbers/._- and no slashes.`);
1626
+ }
1627
+ if (!isValidBundleName(opts.bundleName)) {
1628
+ throw new OniroError(`Invalid bundle name '${opts.bundleName}'. Example: com.example.myapplication`);
1629
+ }
1630
+ if (!opts.location || !await pathExists(opts.location)) {
1631
+ throw new OniroError(`Location does not exist: ${opts.location}`);
1632
+ }
1633
+ const templateDir = path17.join(opts.templateRoot, opts.templateId);
1634
+ if (!await pathExists(templateDir)) {
1635
+ throw new OniroError(`Template not found: ${templateDir}`);
1636
+ }
1637
+ const templates = listTemplates(opts.templateRoot);
1638
+ const selected = templates.find((t) => t.id === opts.templateId);
1639
+ const defaultModuleName = selected?.defaultModuleName ?? "entry";
1640
+ const moduleName = (opts.moduleName ?? defaultModuleName).trim() || defaultModuleName;
1641
+ const missing = validateTemplateLayout(templateDir, defaultModuleName);
1642
+ if (missing.length > 0) {
1643
+ throw new OniroError(`Template '${opts.templateId}' is missing required files:
1644
+ - ${missing.join("\n- ")}`);
1645
+ }
1646
+ const projectDir = path17.join(opts.location, opts.projectName);
1647
+ if (await pathExists(projectDir)) {
1648
+ if (!opts.overwrite) {
1649
+ throw new OniroError(`Destination already exists: ${projectDir}. Pass overwrite: true to replace it.`);
1650
+ }
1651
+ await fs18.promises.rm(projectDir, { recursive: true, force: true });
1652
+ }
1653
+ logger.info(`[create] Scaffolding ${opts.templateId} at ${projectDir}`);
1654
+ await copyDirRecursive(templateDir, projectDir);
1655
+ if (moduleName !== defaultModuleName) {
1656
+ renameIfExists(path17.join(projectDir, defaultModuleName), path17.join(projectDir, moduleName));
1657
+ }
1658
+ updateTemplateConfigs(
1659
+ projectDir,
1660
+ {
1661
+ projectName: opts.projectName,
1662
+ bundleName: opts.bundleName,
1663
+ sdkApi: opts.sdkApi,
1664
+ moduleName
1665
+ },
1666
+ getOhosBaseSdkHome(opts.config)
1667
+ );
1668
+ await normalizeJson5ToJson(projectDir, logger);
1669
+ return { projectDir };
1670
+ }
1671
+
1672
+ // src/index.ts
1673
+ var VERSION = "0.6.0";
1674
+ export {
1675
+ ALL_SDKS,
1676
+ APL_VALUES,
1677
+ APP_FEATURE_VALUES,
1678
+ CancelledError,
1679
+ ChecksumMismatchError,
1680
+ CmdToolsNotInstalledError,
1681
+ OHOS_URL_BASE,
1682
+ OniroError,
1683
+ SdkNotInstalledError,
1684
+ UnsupportedPlatformError,
1685
+ VERSION,
1686
+ attemptHdcConnection,
1687
+ consoleLogger,
1688
+ createMaterial,
1689
+ createScaffold,
1690
+ decryptPwd,
1691
+ defaultContext,
1692
+ defaultPaths,
1693
+ detectProjectSdkVersion,
1694
+ downloadAndInstallSdk,
1695
+ downloadFile,
1696
+ encryptPwd,
1697
+ extractTarball,
1698
+ extractZipWithProgress,
1699
+ findAppProcessId,
1700
+ findCmdToolsSourceDir,
1701
+ generateSigningConfigs,
1702
+ getBundleName,
1703
+ getCmdToolsBin,
1704
+ getCmdToolsDownloadUrl,
1705
+ getCmdToolsPath,
1706
+ getCmdToolsStatus,
1707
+ getEmulatorDir,
1708
+ getHdcPath,
1709
+ getHvigorwPath,
1710
+ getInstalledSdks,
1711
+ getKey,
1712
+ getMainAbility,
1713
+ getOhosBaseSdkHome,
1714
+ getOsFolder,
1715
+ getSdkFilename,
1716
+ getSdkRootDir,
1717
+ getSupportedSdksForUi,
1718
+ installApp,
1719
+ installCmdTools,
1720
+ installEmulator,
1721
+ isCmdToolsInstalled,
1722
+ isEmulatorInstalled,
1723
+ isValidBundleName,
1724
+ isValidProjectName,
1725
+ launchApp,
1726
+ listRunningProcesses,
1727
+ listTemplates,
1728
+ nonInteractivePrompter,
1729
+ noopLogger,
1730
+ noopProgress,
1731
+ parseHilogLine,
1732
+ readJson5File,
1733
+ readJsonFile,
1734
+ removeCmdTools,
1735
+ removeEmulator,
1736
+ removeSdk,
1737
+ runHvigorw,
1738
+ setHilogLevel,
1739
+ startEmulator,
1740
+ staticConfig,
1741
+ stopEmulator,
1742
+ streamHilog,
1743
+ validateTemplateLayout,
1744
+ verifySha256,
1745
+ writeJson5File,
1746
+ writeJsonFile
1747
+ };
1748
+ //# sourceMappingURL=index.js.map