agc-api-cli 1.0.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/cli.js ADDED
@@ -0,0 +1,981 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_commander = require("commander");
28
+ var fs6 = __toESM(require("fs"));
29
+
30
+ // src/services/auth.ts
31
+ var import_axios = __toESM(require("axios"));
32
+
33
+ // src/config/index.ts
34
+ var dotenv = __toESM(require("dotenv"));
35
+ var import_path = __toESM(require("path"));
36
+ var import_fs = __toESM(require("fs"));
37
+ var envPath = import_path.default.resolve(process.cwd(), ".env");
38
+ if (import_fs.default.existsSync(envPath)) {
39
+ dotenv.config({ path: envPath });
40
+ } else {
41
+ dotenv.config();
42
+ }
43
+ function getConfig() {
44
+ const clientId = process.env.AGC_CLIENT_ID;
45
+ const clientSecret = process.env.AGC_CLIENT_SECRET;
46
+ if (!clientId || !clientSecret) {
47
+ throw new Error("Missing AGC credentials. Please set AGC_CLIENT_ID and AGC_CLIENT_SECRET environment variables.");
48
+ }
49
+ return {
50
+ clientId,
51
+ clientSecret,
52
+ domain: process.env.AGC_DOMAIN || "connect-api.cloud.huawei.com"
53
+ };
54
+ }
55
+
56
+ // src/services/auth.ts
57
+ var AuthService = class {
58
+ /**
59
+ * 获取访问API的Token
60
+ */
61
+ static async getToken() {
62
+ const config2 = getConfig();
63
+ const url = `https://${config2.domain}/api/oauth2/v1/token`;
64
+ const payload = {
65
+ grant_type: "client_credentials",
66
+ client_id: config2.clientId,
67
+ client_secret: config2.clientSecret
68
+ };
69
+ const response = await import_axios.default.post(url, payload, {
70
+ headers: {
71
+ "Content-Type": "application/json"
72
+ }
73
+ });
74
+ return response.data;
75
+ }
76
+ };
77
+
78
+ // src/core/auth-mgr.ts
79
+ var import_fs2 = __toESM(require("fs"));
80
+ var import_path2 = __toESM(require("path"));
81
+ var import_os = __toESM(require("os"));
82
+ var AuthManager = class {
83
+ static cacheFile = import_path2.default.join(import_os.default.homedir(), ".agc-cli-token.json");
84
+ static async getAccessToken() {
85
+ const cache = this.readCache();
86
+ if (cache && cache.expiresAt > Date.now() + 5 * 60 * 1e3) {
87
+ return cache.accessToken;
88
+ }
89
+ const res = await AuthService.getToken();
90
+ if (res.access_token && res.expires_in) {
91
+ const expiresAt = Date.now() + res.expires_in * 1e3;
92
+ this.writeCache({ accessToken: res.access_token, expiresAt });
93
+ return res.access_token;
94
+ }
95
+ throw new Error(`Failed to get token: ${JSON.stringify(res.ret)}`);
96
+ }
97
+ static readCache() {
98
+ try {
99
+ if (import_fs2.default.existsSync(this.cacheFile)) {
100
+ const data = import_fs2.default.readFileSync(this.cacheFile, "utf8");
101
+ return JSON.parse(data);
102
+ }
103
+ } catch (e) {
104
+ }
105
+ return null;
106
+ }
107
+ static writeCache(cache) {
108
+ try {
109
+ import_fs2.default.writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2), "utf8");
110
+ } catch (e) {
111
+ }
112
+ }
113
+ /**
114
+ * 清除缓存的 Token(登出)
115
+ */
116
+ static clearCache() {
117
+ try {
118
+ if (import_fs2.default.existsSync(this.cacheFile)) {
119
+ import_fs2.default.unlinkSync(this.cacheFile);
120
+ return true;
121
+ }
122
+ return false;
123
+ } catch (e) {
124
+ return false;
125
+ }
126
+ }
127
+ };
128
+
129
+ // src/core/http.ts
130
+ var import_axios2 = __toESM(require("axios"));
131
+ var agcClient = import_axios2.default.create();
132
+ agcClient.interceptors.request.use(async (config2) => {
133
+ const cfg = getConfig();
134
+ if (!config2.baseURL) {
135
+ config2.baseURL = `https://${cfg.domain}`;
136
+ }
137
+ const token = await AuthManager.getAccessToken();
138
+ if (!config2.headers) {
139
+ config2.headers = {};
140
+ }
141
+ if (!config2.headers.Authorization) {
142
+ config2.headers.Authorization = `Bearer ${token}`;
143
+ }
144
+ if (!config2.headers.client_id) {
145
+ config2.headers.client_id = cfg.clientId;
146
+ }
147
+ if (config2.data !== void 0 && !config2.headers["Content-Type"]) {
148
+ config2.headers["Content-Type"] = "application/json";
149
+ }
150
+ return config2;
151
+ }, (error) => {
152
+ return Promise.reject(error);
153
+ });
154
+ agcClient.interceptors.response.use(
155
+ (res) => res,
156
+ (err) => {
157
+ if (err.response?.status === 403) {
158
+ const body = err.response?.data;
159
+ const msg = typeof body === "object" ? JSON.stringify(body, null, 2) : body;
160
+ err.message = err.message + (msg ? `
161
+ API \u54CD\u5E94: ${msg}` : "");
162
+ }
163
+ return Promise.reject(err);
164
+ }
165
+ );
166
+
167
+ // src/services/publish.ts
168
+ var PublishService = class {
169
+ /**
170
+ * 查询应用包名对应的appid
171
+ * @param packageName 需要查询的应用包名,多个包名以逗号分隔,最多支持50个
172
+ */
173
+ static async getAppIdList(packageName) {
174
+ const response = await agcClient.get("/api/publish/v2/appid-list", {
175
+ params: { packageName }
176
+ });
177
+ return response.data;
178
+ }
179
+ /**
180
+ * 查询应用信息
181
+ * @param appId 需要查询的应用ID
182
+ * @param lang 需要查询的语言 (例如: 'zh-CN'),不传则查询全部语言
183
+ */
184
+ static async getAppInfo(appId, lang) {
185
+ const params = { appId };
186
+ if (lang) {
187
+ params.lang = lang;
188
+ }
189
+ const response = await agcClient.get("/api/publish/v3/app-info", {
190
+ params
191
+ });
192
+ return response.data;
193
+ }
194
+ };
195
+
196
+ // src/services/provision.ts
197
+ var ProvisionService = class {
198
+ // ================= Certificates =================
199
+ static async createCert(req) {
200
+ const response = await agcClient.post("/api/publish/v3/cert", req);
201
+ return response.data;
202
+ }
203
+ static async getCertList(req) {
204
+ const response = await agcClient.post("/api/publish/v3/cert/list", req);
205
+ return response.data;
206
+ }
207
+ static async deleteCert(req) {
208
+ const response = await agcClient.post("/api/publish/v2/cert/delete", req);
209
+ return response.data;
210
+ }
211
+ // ================= Devices =================
212
+ static async addDevice(req) {
213
+ const response = await agcClient.post("/api/publish/v2/device", req);
214
+ return response.data;
215
+ }
216
+ static async getDeviceList(req) {
217
+ const params = new URLSearchParams();
218
+ if (req.deviceName) params.append("deviceName", req.deviceName);
219
+ if (req.fromRecCount) params.append("fromRecCount", String(req.fromRecCount));
220
+ if (req.maxReqCount) params.append("maxReqCount", String(req.maxReqCount));
221
+ if (req.order) params.append("order", String(req.order));
222
+ const url = `/api/publish/v2/device/list?${params.toString()}`;
223
+ const response = await agcClient.get(url);
224
+ return response.data;
225
+ }
226
+ static async deleteDevice(req) {
227
+ const response = await agcClient.post("/api/publish/v2/device/delete", req);
228
+ return response.data;
229
+ }
230
+ // ================= Profile / Provision =================
231
+ static async createProvision(req) {
232
+ const response = await agcClient.post("/api/publish/v3/provision", req);
233
+ return response.data;
234
+ }
235
+ static async getProvisionList(req) {
236
+ const params = new URLSearchParams();
237
+ if (req.fromRecCount) params.append("fromRecCount", String(req.fromRecCount));
238
+ if (req.maxReqCount) params.append("maxReqCount", String(req.maxReqCount));
239
+ const headers = {
240
+ appId: req.appId
241
+ };
242
+ if (req.provisionId) {
243
+ headers.provisionId = req.provisionId;
244
+ }
245
+ const url = `/api/publish/v3/provision/list?${params.toString()}`;
246
+ const response = await agcClient.get(url, { headers });
247
+ return response.data;
248
+ }
249
+ static async updateProvision(req) {
250
+ const response = await agcClient.put("/api/publish/v3/provision", req);
251
+ return response.data;
252
+ }
253
+ static async deleteProvision(req) {
254
+ const params = new URLSearchParams();
255
+ if (req.id) params.append("id", req.id);
256
+ const url = `/api/publish/v2/provision?${params.toString()}`;
257
+ const response = await agcClient.delete(url);
258
+ return response.data;
259
+ }
260
+ // ================= Fingerprint =================
261
+ static async addFingerprint(req) {
262
+ const { appId, ...body } = req;
263
+ const headers = { appId };
264
+ const response = await agcClient.post("/api/provision/v1/fingerprints", body, { headers });
265
+ return response.data;
266
+ }
267
+ static async getFingerprintList(req) {
268
+ const headers = { appId: req.appId };
269
+ const response = await agcClient.get("/api/provision/v1/fingerprints", { headers });
270
+ return response.data;
271
+ }
272
+ static async deleteFingerprint(req) {
273
+ const { appId, ...body } = req;
274
+ const headers = { appId };
275
+ const response = await agcClient.delete("/api/provision/v1/fingerprints", { headers, data: body });
276
+ return response.data;
277
+ }
278
+ // ================= ACL Permission =================
279
+ static async applyACL(req) {
280
+ const { appId, ...body } = req;
281
+ const headers = { appId };
282
+ const response = await agcClient.post("/api/provision/v1/user/permission/apply", body, { headers });
283
+ return response.data;
284
+ }
285
+ static async getACLStatus(req) {
286
+ const headers = { appId: req.appId };
287
+ const response = await agcClient.get("/api/provision/v1/user/permission/apply/status", { headers });
288
+ return response.data;
289
+ }
290
+ static async getACLQuery(req) {
291
+ const headers = { appId: req.appId };
292
+ const response = await agcClient.get("/api/provision/v1/user/permission", { headers });
293
+ return response.data;
294
+ }
295
+ };
296
+
297
+ // src/services/upload.ts
298
+ var import_axios3 = __toESM(require("axios"));
299
+ var import_fs3 = __toESM(require("fs"));
300
+ var UploadService = class {
301
+ // ================= Raw APIs =================
302
+ static async getUploadUrl(req) {
303
+ const response = await agcClient.get("/api/publish/v2/upload-url/for-obs", {
304
+ params: req
305
+ });
306
+ return response.data;
307
+ }
308
+ static async initMultipart(req) {
309
+ const response = await agcClient.post("/api/publish/v2/upload/multipart/init", null, {
310
+ params: req
311
+ });
312
+ return response.data;
313
+ }
314
+ static async getMultipartParts(req) {
315
+ const { objectId, nspUploadId, parts } = req;
316
+ const response = await agcClient.post("/api/publish/v2/upload/multipart/parts", parts, {
317
+ params: { objectId, nspUploadId }
318
+ });
319
+ return response.data;
320
+ }
321
+ static async composeMultipart(req) {
322
+ const { objectId, nspUploadId, parts } = req;
323
+ const response = await agcClient.post("/api/publish/v2/upload/multipart/compose", parts, {
324
+ params: { objectId, nspUploadId }
325
+ });
326
+ return response.data;
327
+ }
328
+ // ================= High-level Operations =================
329
+ /**
330
+ * 封装好的上传文件核心流程(支持根据文件大小自动单文件或分片上传)
331
+ * 文件大小 < 5MB 时使用单文件上传,否则使用分片上传
332
+ */
333
+ static async uploadFile(options) {
334
+ const stat = import_fs3.default.statSync(options.filePath);
335
+ const fileName = options.filePath.split("/").pop() || "unknown";
336
+ if (stat.size < 5 * 1024 * 1024) {
337
+ await this.uploadSingleFile(options, fileName, stat.size);
338
+ } else {
339
+ await this.uploadMultipartFile(options, fileName, stat.size);
340
+ }
341
+ }
342
+ /**
343
+ * 单文件上传流程
344
+ */
345
+ static async uploadSingleFile(options, fileName, contentLength) {
346
+ const urlRes = await this.getUploadUrl({
347
+ appId: options.appId,
348
+ fileName,
349
+ contentLength,
350
+ releaseType: options.releaseType,
351
+ chineseMainlandFlag: options.chineseMainlandFlag
352
+ });
353
+ if (!urlRes.urlInfo) {
354
+ throw new Error(`Failed to get upload URL: ${JSON.stringify(urlRes.ret)}`);
355
+ }
356
+ const { url, headers } = urlRes.urlInfo;
357
+ const fileStream = import_fs3.default.createReadStream(options.filePath);
358
+ await import_axios3.default.put(url, fileStream, {
359
+ headers: {
360
+ ...headers,
361
+ "Content-Type": "application/octet-stream"
362
+ },
363
+ maxBodyLength: Infinity,
364
+ maxContentLength: Infinity
365
+ });
366
+ }
367
+ /**
368
+ * 分片上传流程
369
+ */
370
+ static async uploadMultipartFile(options, fileName, fileSize) {
371
+ const initRes = await this.initMultipart({
372
+ appId: options.appId,
373
+ fileName,
374
+ releaseType: options.releaseType,
375
+ chineseMainlandFlag: options.chineseMainlandFlag
376
+ });
377
+ const { objectId, nspUploadId, nspPartMinSize = 5242880 } = initRes;
378
+ if (!objectId || !nspUploadId) {
379
+ throw new Error(`Failed to init multipart upload: ${JSON.stringify(initRes.ret)}`);
380
+ }
381
+ const partSize = Number(nspPartMinSize);
382
+ const partsCount = Math.ceil(fileSize / partSize);
383
+ const partsReqBody = {};
384
+ for (let i = 1; i <= partsCount; i++) {
385
+ const start = (i - 1) * partSize;
386
+ const end = Math.min(start + partSize, fileSize);
387
+ partsReqBody[`additionalProp${i}`] = {
388
+ length: end - start
389
+ };
390
+ }
391
+ const partsRes = await this.getMultipartParts({
392
+ objectId,
393
+ nspUploadId,
394
+ parts: partsReqBody
395
+ });
396
+ const uploadInfoMap = partsRes.uploadInfoMap;
397
+ if (!uploadInfoMap) {
398
+ throw new Error(`Failed to get parts upload URL: ${JSON.stringify(partsRes.ret)}`);
399
+ }
400
+ const fd = import_fs3.default.openSync(options.filePath, "r");
401
+ const composeReqBody = {};
402
+ for (let i = 1; i <= partsCount; i++) {
403
+ const propKey = `additionalProp${i}`;
404
+ const uploadInfo = uploadInfoMap[propKey];
405
+ const start = (i - 1) * partSize;
406
+ const end = Math.min(start + partSize, fileSize);
407
+ const length = end - start;
408
+ const buffer = Buffer.alloc(length);
409
+ import_fs3.default.readSync(fd, buffer, 0, length, start);
410
+ const res = await import_axios3.default.put(uploadInfo.url, buffer, {
411
+ headers: {
412
+ ...uploadInfo.headers,
413
+ "Content-Type": "application/octet-stream"
414
+ },
415
+ maxBodyLength: Infinity,
416
+ maxContentLength: Infinity
417
+ });
418
+ const etag = res.headers["etag"];
419
+ composeReqBody[propKey] = {
420
+ partObjectId: uploadInfo.partObjectId || "",
421
+ etag
422
+ };
423
+ }
424
+ import_fs3.default.closeSync(fd);
425
+ await this.composeMultipart({
426
+ objectId,
427
+ nspUploadId,
428
+ parts: composeReqBody
429
+ });
430
+ }
431
+ };
432
+
433
+ // src/services/domain.ts
434
+ var import_fs4 = __toESM(require("fs"));
435
+ var DomainService = class {
436
+ /**
437
+ * 查询元服务的域名配置信息
438
+ */
439
+ static async queryDomain(req) {
440
+ const response = await agcClient.get(
441
+ "/api/dms/domain-manage/v1/app/domain",
442
+ {
443
+ params: { category: req.category },
444
+ headers: { appId: req.appId }
445
+ }
446
+ );
447
+ return response.data;
448
+ }
449
+ /**
450
+ * 新增/更新元服务的域名配置信息
451
+ */
452
+ static async updateDomain(req) {
453
+ const response = await agcClient.post(
454
+ "/api/dms/domain-manage/v1/app/domain",
455
+ { domains: req.domains },
456
+ {
457
+ params: { category: req.category },
458
+ headers: { appId: req.appId }
459
+ }
460
+ );
461
+ return response.data;
462
+ }
463
+ /**
464
+ * 查询域名修改次数/配置上限
465
+ */
466
+ static async queryDomainConfig(req) {
467
+ const response = await agcClient.get(
468
+ "/api/dms/domain-manage/v1/app/domain/config",
469
+ {
470
+ params: { appId: req.appId }
471
+ }
472
+ );
473
+ return response.data;
474
+ }
475
+ /**
476
+ * 下载域名配置文件
477
+ */
478
+ static async verifyFile(req, outputPath) {
479
+ const response = await agcClient.get(
480
+ "/api/dms/domain-manage/v1/app/domain/verify-file",
481
+ {
482
+ headers: { appId: req.appId },
483
+ responseType: "stream"
484
+ }
485
+ );
486
+ return new Promise((resolve, reject) => {
487
+ const writer = import_fs4.default.createWriteStream(outputPath);
488
+ response.data.pipe(writer);
489
+ let error = null;
490
+ writer.on("error", (err) => {
491
+ error = err;
492
+ writer.close();
493
+ reject(err);
494
+ });
495
+ writer.on("close", () => {
496
+ if (!error) {
497
+ resolve();
498
+ }
499
+ });
500
+ });
501
+ }
502
+ /**
503
+ * 预检查业务域名配置
504
+ */
505
+ static async preCheckDomain(req) {
506
+ const response = await agcClient.post(
507
+ "/api/dms/domain-manage/v1/app/domain/pre-check",
508
+ { domain: req.domain },
509
+ {
510
+ headers: { appId: req.appId }
511
+ }
512
+ );
513
+ return response.data;
514
+ }
515
+ };
516
+
517
+ // src/services/sign.ts
518
+ var import_fs5 = __toESM(require("fs"));
519
+ var import_path3 = __toESM(require("path"));
520
+ var import_child_process = require("child_process");
521
+ var import_crypto = __toESM(require("crypto"));
522
+ var SignService = class {
523
+ static async createSignP12AndCsr(options) {
524
+ const outDir = options.outDir ? import_path3.default.resolve(options.outDir) : import_path3.default.join(process.cwd(), "sign_keys");
525
+ if (!import_fs5.default.existsSync(outDir)) {
526
+ import_fs5.default.mkdirSync(outDir, { recursive: true });
527
+ }
528
+ const filename = options.filename || this.createSignDefaultFilename();
529
+ const p12FilePath = import_path3.default.join(outDir, `${filename}.p12`);
530
+ const csrFilePath = import_path3.default.join(outDir, `${filename}.csr`);
531
+ const javaPath = options.javaPath || "java";
532
+ const sdkPath = options.sdkPath || process.env.HARMONYOS_SDK_PATH;
533
+ if (!sdkPath) {
534
+ throw new Error("sdkPath is required. Provide it via --sdk-path or HARMONYOS_SDK_PATH env var.");
535
+ }
536
+ const signToolPath = import_path3.default.join(sdkPath, "default", "openharmony", "toolchains", "lib", "hap-sign-tool.jar");
537
+ if (!import_fs5.default.existsSync(signToolPath)) {
538
+ throw new Error(`hap-sign-tool.jar not found at ${signToolPath}`);
539
+ }
540
+ const alias = options.alias;
541
+ const pwd = options.pwd || "123456";
542
+ this.doCreateSignFileP12(javaPath, signToolPath, alias, pwd, p12FilePath);
543
+ this.doCreateSignFileCsr(javaPath, signToolPath, alias, pwd, p12FilePath, csrFilePath);
544
+ const csrContent = import_fs5.default.readFileSync(csrFilePath, "utf8");
545
+ const cerPath = import_path3.default.join(outDir, `${filename}.cer`);
546
+ const p7bPath = import_path3.default.join(outDir, `${filename}.p7b`);
547
+ return {
548
+ alias,
549
+ p12Path: p12FilePath,
550
+ csrPath: csrFilePath,
551
+ csrContent,
552
+ storePath: outDir,
553
+ cerPath,
554
+ p7bPath
555
+ };
556
+ }
557
+ static createSignDefaultFilename() {
558
+ const timestamp = Date.now().toString();
559
+ const substring = timestamp.substring(timestamp.length - 5);
560
+ const randomString = import_crypto.default.randomInt(100, 1e3);
561
+ return `agc-cli-${substring}-${randomString}`;
562
+ }
563
+ static doCreateSignFileP12(javaPath, signToolPath, alias, pwd, outFilePath) {
564
+ const args = [
565
+ "-jar",
566
+ signToolPath,
567
+ "generate-keypair",
568
+ "-keyAlias",
569
+ alias,
570
+ "-keyPwd",
571
+ pwd,
572
+ "-keyAlg",
573
+ "ECC",
574
+ "-keySize",
575
+ "NIST-P-256",
576
+ "-keystoreFile",
577
+ outFilePath,
578
+ "-keystorePwd",
579
+ pwd
580
+ ];
581
+ console.log(`Executing: ${javaPath} ${args.join(" ")}`);
582
+ (0, import_child_process.execFileSync)(javaPath, args, { stdio: "inherit" });
583
+ }
584
+ static doCreateSignFileCsr(javaPath, signToolPath, alias, pwd, p12Path, outFilePath) {
585
+ const args = [
586
+ "-jar",
587
+ signToolPath,
588
+ "generate-csr",
589
+ "-keyAlias",
590
+ alias,
591
+ "-keyPwd",
592
+ pwd,
593
+ "-subject",
594
+ "C=CN,O=OpenHarmony,OU=OpenHarmony Community,CN=App Release",
595
+ "-signAlg",
596
+ "SHA256withECDSA",
597
+ "-keystoreFile",
598
+ p12Path,
599
+ "-keystorePwd",
600
+ pwd,
601
+ "-outFile",
602
+ outFilePath
603
+ ];
604
+ console.log(`Executing: ${javaPath} ${args.join(" ")}`);
605
+ (0, import_child_process.execFileSync)(javaPath, args, { stdio: "inherit" });
606
+ }
607
+ static COMPONENT = new Int8Array([49, 243, 9, 115, 214, 175, 91, 184, 211, 190, 177, 88, 101, 131, 192, 119]);
608
+ static readDirBytes(dirPath) {
609
+ const entries = import_fs5.default.readdirSync(dirPath).filter((f) => f !== ".DS_Store");
610
+ return new Int8Array(import_fs5.default.readFileSync(import_path3.default.resolve(dirPath, entries[0])));
611
+ }
612
+ static readFd(materialPath) {
613
+ const fdDir = import_path3.default.resolve(materialPath, "fd");
614
+ return import_fs5.default.readdirSync(fdDir).filter((f) => f !== ".DS_Store").map((e) => this.readDirBytes(import_path3.default.resolve(fdDir, e)));
615
+ }
616
+ static xorBuffers(a, b) {
617
+ const result = new Int8Array(a.byteLength);
618
+ for (let i = 0; i < a.byteLength; i++) result[i] = a[i] ^ b[i];
619
+ return result;
620
+ }
621
+ static xorAll(components) {
622
+ let result = this.xorBuffers(components[0], components[1]);
623
+ for (let i = 2; i < components.length; i++) result = this.xorBuffers(result, components[i]);
624
+ return Buffer.from(result);
625
+ }
626
+ static aesDecrypt(key, data) {
627
+ const d = Buffer.from(data);
628
+ const tagLen = (255 & d[0]) << 24 | (255 & d[1]) << 16 | (255 & d[2]) << 8 | 255 & d[3];
629
+ const ivLen = d.length - 4 - tagLen;
630
+ const iv = d.subarray(4, 4 + ivLen);
631
+ const decipher = import_crypto.default.createDecipheriv("aes-128-gcm", key, iv);
632
+ decipher.setAuthTag(d.subarray(d.length - 16));
633
+ return Buffer.concat([decipher.update(d.subarray(4 + ivLen, d.length - 16)), decipher.final()]);
634
+ }
635
+ static aesEncrypt(key, plaintext) {
636
+ const iv = import_crypto.default.randomBytes(12);
637
+ const cipher = import_crypto.default.createCipheriv("aes-128-gcm", key, iv);
638
+ const enc = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
639
+ const tag = cipher.getAuthTag();
640
+ const header = Buffer.alloc(4);
641
+ header.writeUInt32BE(enc.length + 16, 0);
642
+ return Buffer.concat([header, iv, enc, tag]).toString("hex").toUpperCase();
643
+ }
644
+ static deriveEncryptionKey(configDir) {
645
+ const materialDir = import_path3.default.resolve(configDir, "material");
646
+ const fd = this.readFd(materialDir);
647
+ const salt = this.readDirBytes(import_path3.default.resolve(materialDir, "ac"));
648
+ const rawKey = new Int8Array(
649
+ import_crypto.default.pbkdf2Sync(this.xorAll(fd.concat(this.COMPONENT)).toString(), salt, 1e4, 16, "sha256")
650
+ );
651
+ return new Int8Array(this.aesDecrypt(rawKey, this.readDirBytes(import_path3.default.resolve(materialDir, "ce"))));
652
+ }
653
+ /**
654
+ * 使用 DevEco Studio 的加密密钥对密码进行 AES-128-GCM 加密,
655
+ * 生成 build-profile.json5 中 signingConfigs 所需的加密密码。
656
+ */
657
+ static encryptPassword(password, configDir) {
658
+ const dir = configDir || import_path3.default.join(process.env.HOME || "", ".ohos", "config");
659
+ if (!import_fs5.default.existsSync(import_path3.default.resolve(dir, "material"))) {
660
+ throw new Error(`\u672A\u627E\u5230\u52A0\u5BC6\u5BC6\u94A5\u76EE\u5F55: ${import_path3.default.resolve(dir, "material")}\u3002\u8BF7\u786E\u8BA4 DevEco Studio \u5DF2\u5B89\u88C5\u5E76\u751F\u6210\u8FC7\u7B7E\u540D\u914D\u7F6E\u3002`);
661
+ }
662
+ const key = this.deriveEncryptionKey(dir);
663
+ return {
664
+ keyPassword: this.aesEncrypt(key, password),
665
+ storePassword: this.aesEncrypt(key, password)
666
+ };
667
+ }
668
+ };
669
+
670
+ // src/cli/index.ts
671
+ var program = new import_commander.Command();
672
+ program.name("agc-cli").description("AppGallery Connect API CLI Tool").version("1.0.0");
673
+ var authCmd = program.command("auth").description("Authentication management");
674
+ authCmd.command("login").description("Login to AGC API and cache the access token").action(async () => {
675
+ try {
676
+ const token = await AuthManager.getAccessToken();
677
+ console.log("Successfully logged in. Token generated and cached.");
678
+ console.log(`Token snippet: ${token.substring(0, 10)}...`);
679
+ } catch (e) {
680
+ console.error("Login failed:", e.message);
681
+ }
682
+ });
683
+ authCmd.command("logout").description("Logout and clear cached access token").action(() => {
684
+ const cleared = AuthManager.clearCache();
685
+ if (cleared) {
686
+ console.log("Successfully logged out. Token cache cleared.");
687
+ } else {
688
+ console.log("Already logged out. No token cache found.");
689
+ }
690
+ });
691
+ var publishCmd = program.command("publish").description("Publishing API commands");
692
+ publishCmd.command("app-id").description("Get App ID by package name").requiredOption("-p, --package-name <packageName>", "Package name").action(async (options) => {
693
+ try {
694
+ const res = await PublishService.getAppIdList(options.packageName);
695
+ console.log(JSON.stringify(res, null, 2));
696
+ } catch (e) {
697
+ console.error("Request failed:", e.message);
698
+ }
699
+ });
700
+ publishCmd.command("app-info").description("Get App Info by App ID").requiredOption("-a, --app-id <appId>", "App ID").option("-l, --lang <lang>", "Language, e.g. zh-CN").action(async (options) => {
701
+ try {
702
+ const res = await PublishService.getAppInfo(options.appId, options.lang);
703
+ console.log(JSON.stringify(res, null, 2));
704
+ } catch (e) {
705
+ console.error("Request failed:", e.message);
706
+ }
707
+ });
708
+ var uploadCmd = program.command("upload").description("Upload Management API commands");
709
+ uploadCmd.command("file").description("Upload a file (APP, APK, images, etc.)").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("-f, --file-path <filePath>", "Path to the local file to upload").option("-r, --release-type <releaseType>", "Release type: 1 for Full, 6 for HarmonyOS Test").option("-c, --chinese-mainland <flag>", "Chinese mainland flag: 0 or 1").action(async (options) => {
710
+ try {
711
+ console.log(`Uploading file ${options.filePath}...`);
712
+ await UploadService.uploadFile({
713
+ appId: options.appId,
714
+ filePath: options.filePath,
715
+ releaseType: options.releaseType ? Number(options.releaseType) : void 0,
716
+ chineseMainlandFlag: options.chineseMainland ? Number(options.chineseMainland) : void 0
717
+ });
718
+ console.log("Upload successful.");
719
+ } catch (e) {
720
+ console.error("Upload failed:", e.message);
721
+ }
722
+ });
723
+ var provisionCmd = program.command("provision").description("Provisioning API commands");
724
+ provisionCmd.command("csr-generate").description("Generate a new keypair and CSR file").requiredOption("--alias <alias>", "Key alias").option("--pwd <pwd>", "Key password (default: 123456)").option("--out-dir <outDir>", "Output directory (default: ./sign_keys)").option("--filename <filename>", "Base filename for generated files").option("--sdk-path <sdkPath>", "Path to HarmonyOS SDK (or set HARMONYOS_SDK_PATH env var)").option("--java-path <javaPath>", "Path to java executable (default: java)").action(async (options) => {
725
+ try {
726
+ const res = await SignService.createSignP12AndCsr({
727
+ alias: options.alias,
728
+ pwd: options.pwd,
729
+ outDir: options.outDir,
730
+ filename: options.filename,
731
+ sdkPath: options.sdkPath,
732
+ javaPath: options.javaPath
733
+ });
734
+ console.log("CSR generation successful.");
735
+ console.log(JSON.stringify(res, null, 2));
736
+ } catch (e) {
737
+ console.error("CSR generation failed:", e.message);
738
+ }
739
+ });
740
+ provisionCmd.command("encrypt-pwd").description("\u52A0\u5BC6 P12 \u5BC6\u7801\uFF0C\u751F\u6210 build-profile.json5 \u6240\u9700\u7684 keyPassword / storePassword").requiredOption("--pwd <password>", "P12 \u5BC6\u7801\u660E\u6587").option("--config-dir <configDir>", "DevEco Studio \u914D\u7F6E\u76EE\u5F55 (\u9ED8\u8BA4 ~/.ohos/config)").action(async (options) => {
741
+ try {
742
+ const res = SignService.encryptPassword(options.pwd, options.configDir);
743
+ console.log(JSON.stringify(res, null, 2));
744
+ } catch (e) {
745
+ console.error("\u52A0\u5BC6\u5931\u8D25:", e.message);
746
+ }
747
+ });
748
+ provisionCmd.command("cert-create").description("Create a certificate").requiredOption("--csr <csrFile>", "Path to CSR file").requiredOption("--cert-name <certName>", "Certificate name").requiredOption("--cert-type <certType>", "Certificate type (1: debug, 2: release, 3: in-house, 4: binary)").action(async (options) => {
749
+ try {
750
+ const csrContent = fs6.readFileSync(options.csr, "utf8");
751
+ const res = await ProvisionService.createCert({
752
+ csr: csrContent,
753
+ certName: options.certName,
754
+ certType: Number(options.certType)
755
+ });
756
+ console.log(JSON.stringify(res, null, 2));
757
+ } catch (e) {
758
+ console.error("Request failed:", e.message);
759
+ }
760
+ });
761
+ provisionCmd.command("cert-list").description("List certificates").option("-t, --cert-type <certType>", "Certificate type").option("--cert-ids <certIds>", "Comma separated certificate IDs").action(async (options) => {
762
+ try {
763
+ const req = {};
764
+ if (options.certType) req.certType = Number(options.certType);
765
+ if (options.certIds) req.certIds = options.certIds.split(",");
766
+ const res = await ProvisionService.getCertList(req);
767
+ console.log(JSON.stringify(res, null, 2));
768
+ } catch (e) {
769
+ console.error("Request failed:", e.message);
770
+ }
771
+ });
772
+ provisionCmd.command("cert-delete").description("Delete certificates").requiredOption("--cert-ids <certIds>", "Comma separated certificate IDs to delete").action(async (options) => {
773
+ try {
774
+ const res = await ProvisionService.deleteCert({
775
+ certIds: options.certIds.split(",")
776
+ });
777
+ console.log(JSON.stringify(res, null, 2));
778
+ } catch (e) {
779
+ console.error("Request failed:", e.message);
780
+ }
781
+ });
782
+ provisionCmd.command("device-add").description("Add new devices").requiredOption("--devices <devicesFile>", 'Path to JSON file containing devices array [{"deviceName":"name","udid":"udid","deviceType":1}]').action(async (options) => {
783
+ try {
784
+ const fileContent = fs6.readFileSync(options.devices, "utf8");
785
+ const deviceList = JSON.parse(fileContent);
786
+ const res = await ProvisionService.addDevice({ deviceList });
787
+ console.log(JSON.stringify(res, null, 2));
788
+ } catch (e) {
789
+ console.error("Request failed:", e.message);
790
+ }
791
+ });
792
+ provisionCmd.command("device-list").description("List devices").option("--device-name <deviceName>", "Device name to search").option("--from <fromRecCount>", "Starting page").option("--max <maxReqCount>", "Max items per page").option("--order <order>", "1: create time desc, 2: name asc").action(async (options) => {
793
+ try {
794
+ const req = {};
795
+ if (options.deviceName) req.deviceName = options.deviceName;
796
+ if (options.from) req.fromRecCount = Number(options.from);
797
+ if (options.max) req.maxReqCount = Number(options.max);
798
+ if (options.order) req.order = Number(options.order);
799
+ const res = await ProvisionService.getDeviceList(req);
800
+ console.log(JSON.stringify(res, null, 2));
801
+ } catch (e) {
802
+ console.error("Request failed:", e.message);
803
+ }
804
+ });
805
+ provisionCmd.command("device-delete").description("Delete devices").requiredOption("--device-ids <deviceIdList>", "Comma separated device IDs to delete").action(async (options) => {
806
+ try {
807
+ const res = await ProvisionService.deleteDevice({
808
+ deviceIdList: options.deviceIds.split(",")
809
+ });
810
+ console.log(JSON.stringify(res, null, 2));
811
+ } catch (e) {
812
+ console.error("Request failed:", e.message);
813
+ }
814
+ });
815
+ provisionCmd.command("profile-create").description("Create a profile").requiredOption("--name <provisionName>", "Profile name").requiredOption("--type <provisionType>", "Profile type (1:debug, 2:release, 3:in-house, 6:specific device)").requiredOption("--cert <certId>", "Certificate ID").requiredOption("-a, --app-id <appId>", "App ID").option("--device-ids <deviceIdList>", "Comma separated device IDs").option("--acls <aclPermissionList>", "Comma separated ACL permissions").action(async (options) => {
816
+ try {
817
+ const req = {
818
+ provisionName: options.name,
819
+ provisionType: Number(options.type),
820
+ certId: options.cert,
821
+ appId: options.appId
822
+ };
823
+ if (options.deviceIds) req.deviceIdList = options.deviceIds.split(",");
824
+ if (options.acls) req.aclPermissionList = options.acls.split(",");
825
+ const res = await ProvisionService.createProvision(req);
826
+ console.log(JSON.stringify(res, null, 2));
827
+ } catch (e) {
828
+ console.error("Request failed:", e.message);
829
+ }
830
+ });
831
+ provisionCmd.command("profile-list").description("List profiles").requiredOption("-a, --app-id <appId>", "App ID").option("--profile-id <provisionId>", "Profile ID").option("--from <fromRecCount>", "Starting page").option("--max <maxReqCount>", "Max items per page").action(async (options) => {
832
+ try {
833
+ const req = { appId: options.appId };
834
+ if (options.profileId) req.provisionId = options.profileId;
835
+ if (options.from) req.fromRecCount = Number(options.from);
836
+ if (options.max) req.maxReqCount = Number(options.max);
837
+ const res = await ProvisionService.getProvisionList(req);
838
+ console.log(JSON.stringify(res, null, 2));
839
+ } catch (e) {
840
+ console.error("Request failed:", e.message);
841
+ }
842
+ });
843
+ provisionCmd.command("profile-update").description("Update profile devices").requiredOption("--id <provisionId>", "Profile ID").requiredOption("--device-ids <deviceIdList>", "Comma separated device IDs").action(async (options) => {
844
+ try {
845
+ const res = await ProvisionService.updateProvision({
846
+ provisionId: options.id,
847
+ deviceIdList: options.deviceIds.split(",")
848
+ });
849
+ console.log(JSON.stringify(res, null, 2));
850
+ } catch (e) {
851
+ console.error("Request failed:", e.message);
852
+ }
853
+ });
854
+ provisionCmd.command("profile-delete").description("Delete a profile").requiredOption("--id <id>", "Profile ID to delete").action(async (options) => {
855
+ try {
856
+ const res = await ProvisionService.deleteProvision({ id: options.id });
857
+ console.log(JSON.stringify(res, null, 2));
858
+ } catch (e) {
859
+ console.error("Request failed:", e.message);
860
+ }
861
+ });
862
+ provisionCmd.command("fp-add").description("Add certificate fingerprints").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("--fps <fingerprintList>", "Comma separated fingerprints").action(async (options) => {
863
+ try {
864
+ const res = await ProvisionService.addFingerprint({
865
+ appId: options.appId,
866
+ fingerprintList: options.fps.split(",")
867
+ });
868
+ console.log(JSON.stringify(res, null, 2));
869
+ } catch (e) {
870
+ console.error("Request failed:", e.message);
871
+ }
872
+ });
873
+ provisionCmd.command("fp-list").description("List certificate fingerprints").requiredOption("-a, --app-id <appId>", "App ID").action(async (options) => {
874
+ try {
875
+ const res = await ProvisionService.getFingerprintList({ appId: options.appId });
876
+ console.log(JSON.stringify(res, null, 2));
877
+ } catch (e) {
878
+ console.error("Request failed:", e.message);
879
+ }
880
+ });
881
+ provisionCmd.command("fp-delete").description("Delete certificate fingerprints").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("--fps <fingerprintList>", "Comma separated fingerprints to delete").action(async (options) => {
882
+ try {
883
+ const res = await ProvisionService.deleteFingerprint({
884
+ appId: options.appId,
885
+ fingerprintList: options.fps.split(",")
886
+ });
887
+ console.log(JSON.stringify(res, null, 2));
888
+ } catch (e) {
889
+ console.error("Request failed:", e.message);
890
+ }
891
+ });
892
+ provisionCmd.command("acl-apply").description("Apply for restricted ACL permissions").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("--acls <aclsFile>", 'Path to JSON file containing ACL permissions [{"name":"name","reason":"reason"}]').option("--attachment <attachment>", "Attachment object ID").action(async (options) => {
893
+ try {
894
+ const fileContent = fs6.readFileSync(options.acls, "utf8");
895
+ const aclPermissionList = JSON.parse(fileContent);
896
+ const req = { appId: options.appId, aclPermissionList };
897
+ if (options.attachment) req.attachment = options.attachment;
898
+ const res = await ProvisionService.applyACL(req);
899
+ console.log(JSON.stringify(res, null, 2));
900
+ } catch (e) {
901
+ console.error("Request failed:", e.message);
902
+ }
903
+ });
904
+ provisionCmd.command("acl-status").description("Check ACL permission apply status").requiredOption("-a, --app-id <appId>", "App ID").action(async (options) => {
905
+ try {
906
+ const res = await ProvisionService.getACLStatus({ appId: options.appId });
907
+ console.log(JSON.stringify(res, null, 2));
908
+ } catch (e) {
909
+ console.error("Request failed:", e.message);
910
+ }
911
+ });
912
+ provisionCmd.command("acl-query").description("Query granted ACL permissions").requiredOption("-a, --app-id <appId>", "App ID").action(async (options) => {
913
+ try {
914
+ const res = await ProvisionService.getACLQuery({ appId: options.appId });
915
+ console.log(JSON.stringify(res, null, 2));
916
+ } catch (e) {
917
+ console.error("Request failed:", e.message);
918
+ }
919
+ });
920
+ var domainCmd = program.command("domain").description("Domain Management API commands");
921
+ domainCmd.command("query").description("Query domain configuration").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("-c, --category <category>", "Category: server or business").action(async (options) => {
922
+ try {
923
+ const res = await DomainService.queryDomain({
924
+ appId: options.appId,
925
+ category: options.category
926
+ });
927
+ console.log(JSON.stringify(res, null, 2));
928
+ } catch (e) {
929
+ console.error("Request failed:", e.message);
930
+ }
931
+ });
932
+ domainCmd.command("update").description("Add or update domain configuration").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("-c, --category <category>", "Category: server or business").requiredOption("--domains <domainsFile>", 'Path to JSON file containing domains array [{"type":"httpRequest","value":"https://..."}]').action(async (options) => {
933
+ try {
934
+ const fileContent = fs6.readFileSync(options.domains, "utf8");
935
+ const domains = JSON.parse(fileContent);
936
+ const res = await DomainService.updateDomain({
937
+ appId: options.appId,
938
+ category: options.category,
939
+ domains
940
+ });
941
+ console.log(JSON.stringify(res, null, 2));
942
+ } catch (e) {
943
+ console.error("Request failed:", e.message);
944
+ }
945
+ });
946
+ domainCmd.command("config").description("Query domain modification counts and limits").requiredOption("-a, --app-id <appId>", "App ID").action(async (options) => {
947
+ try {
948
+ const res = await DomainService.queryDomainConfig({
949
+ appId: options.appId
950
+ });
951
+ console.log(JSON.stringify(res, null, 2));
952
+ } catch (e) {
953
+ console.error("Request failed:", e.message);
954
+ }
955
+ });
956
+ domainCmd.command("verify-file").description("Download domain verify file").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("-o, --output <outputPath>", "Output file path").action(async (options) => {
957
+ try {
958
+ console.log(`Downloading verify file to ${options.output}...`);
959
+ await DomainService.verifyFile({
960
+ appId: options.appId
961
+ }, options.output);
962
+ console.log("Download successful.");
963
+ } catch (e) {
964
+ console.error("Download failed:", e.message);
965
+ }
966
+ });
967
+ domainCmd.command("pre-check").description("Pre-check business domain configuration").requiredOption("-a, --app-id <appId>", "App ID").requiredOption("--domain <domainFile>", 'Path to JSON file containing domain object {"type":"webView","value":"https://..."}').action(async (options) => {
968
+ try {
969
+ const fileContent = fs6.readFileSync(options.domain, "utf8");
970
+ const domain = JSON.parse(fileContent);
971
+ const res = await DomainService.preCheckDomain({
972
+ appId: options.appId,
973
+ domain
974
+ });
975
+ console.log(JSON.stringify(res, null, 2));
976
+ } catch (e) {
977
+ console.error("Request failed:", e.message);
978
+ }
979
+ });
980
+ program.parse(process.argv);
981
+ //# sourceMappingURL=cli.js.map