rn-update-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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/README.zh-CN.md +167 -0
  4. package/cli.json +525 -0
  5. package/lib/api.d.ts +56 -0
  6. package/lib/api.js +671 -0
  7. package/lib/app.d.ts +107 -0
  8. package/lib/app.js +329 -0
  9. package/lib/auto-update.d.ts +35 -0
  10. package/lib/auto-update.js +507 -0
  11. package/lib/bin.d.ts +2 -0
  12. package/lib/bin.js +133 -0
  13. package/lib/bundle-pack.d.ts +6 -0
  14. package/lib/bundle-pack.js +91 -0
  15. package/lib/bundle-runner.d.ts +168 -0
  16. package/lib/bundle-runner.js +1305 -0
  17. package/lib/bundle.d.ts +45 -0
  18. package/lib/bundle.js +226 -0
  19. package/lib/cache.d.ts +10 -0
  20. package/lib/cache.js +25 -0
  21. package/lib/commands.d.ts +11 -0
  22. package/lib/commands.js +72 -0
  23. package/lib/diff.d.ts +18 -0
  24. package/lib/diff.js +690 -0
  25. package/lib/exports.d.ts +11 -0
  26. package/lib/exports.js +20 -0
  27. package/lib/install.d.ts +9 -0
  28. package/lib/install.js +51 -0
  29. package/lib/locales/en.d.ts +187 -0
  30. package/lib/locales/en.js +194 -0
  31. package/lib/locales/zh.d.ts +187 -0
  32. package/lib/locales/zh.js +194 -0
  33. package/lib/native-package.d.ts +21 -0
  34. package/lib/native-package.js +298 -0
  35. package/lib/package.d.ts +68 -0
  36. package/lib/package.js +487 -0
  37. package/lib/provider.d.ts +24 -0
  38. package/lib/provider.js +244 -0
  39. package/lib/standalone-publish.d.ts +17 -0
  40. package/lib/standalone-publish.js +433 -0
  41. package/lib/symbolicate.d.ts +35 -0
  42. package/lib/symbolicate.js +130 -0
  43. package/lib/types.d.ts +152 -0
  44. package/lib/types.js +2 -0
  45. package/lib/user.d.ts +8 -0
  46. package/lib/user.js +50 -0
  47. package/lib/utils/add-gitignore.d.ts +6 -0
  48. package/lib/utils/add-gitignore.js +44 -0
  49. package/lib/utils/app-info-parser/aab.d.ts +27 -0
  50. package/lib/utils/app-info-parser/aab.js +256 -0
  51. package/lib/utils/app-info-parser/apk.d.ts +19 -0
  52. package/lib/utils/app-info-parser/apk.js +95 -0
  53. package/lib/utils/app-info-parser/app.d.ts +9 -0
  54. package/lib/utils/app-info-parser/app.js +15 -0
  55. package/lib/utils/app-info-parser/index.d.ts +21 -0
  56. package/lib/utils/app-info-parser/index.js +51 -0
  57. package/lib/utils/app-info-parser/ipa.d.ts +19 -0
  58. package/lib/utils/app-info-parser/ipa.js +101 -0
  59. package/lib/utils/app-info-parser/resource-finder.d.ts +49 -0
  60. package/lib/utils/app-info-parser/resource-finder.js +429 -0
  61. package/lib/utils/app-info-parser/utils.d.ts +36 -0
  62. package/lib/utils/app-info-parser/utils.js +140 -0
  63. package/lib/utils/app-info-parser/xml-parser/binary.d.ts +61 -0
  64. package/lib/utils/app-info-parser/xml-parser/binary.js +577 -0
  65. package/lib/utils/app-info-parser/xml-parser/manifest.d.ts +15 -0
  66. package/lib/utils/app-info-parser/xml-parser/manifest.js +207 -0
  67. package/lib/utils/app-info-parser/zip.d.ts +29 -0
  68. package/lib/utils/app-info-parser/zip.js +97 -0
  69. package/lib/utils/check-lockfile.d.ts +6 -0
  70. package/lib/utils/check-lockfile.js +93 -0
  71. package/lib/utils/check-plugin.d.ts +12 -0
  72. package/lib/utils/check-plugin.js +36 -0
  73. package/lib/utils/command-result.d.ts +8 -0
  74. package/lib/utils/command-result.js +27 -0
  75. package/lib/utils/constants.d.ts +10 -0
  76. package/lib/utils/constants.js +39 -0
  77. package/lib/utils/dep-versions.d.ts +20 -0
  78. package/lib/utils/dep-versions.js +129 -0
  79. package/lib/utils/git.d.ts +14 -0
  80. package/lib/utils/git.js +64 -0
  81. package/lib/utils/hbcTransform.d.ts +111 -0
  82. package/lib/utils/hbcTransform.js +561 -0
  83. package/lib/utils/hermes-base.d.ts +227 -0
  84. package/lib/utils/hermes-base.js +1287 -0
  85. package/lib/utils/hermes-literals.d.ts +64 -0
  86. package/lib/utils/hermes-literals.js +260 -0
  87. package/lib/utils/http-helper.d.ts +10 -0
  88. package/lib/utils/http-helper.js +150 -0
  89. package/lib/utils/i18n.d.ts +22 -0
  90. package/lib/utils/i18n.js +46 -0
  91. package/lib/utils/index.d.ts +67 -0
  92. package/lib/utils/index.js +545 -0
  93. package/lib/utils/latest-version/index.d.ts +154 -0
  94. package/lib/utils/latest-version/index.js +331 -0
  95. package/lib/utils/options.d.ts +10 -0
  96. package/lib/utils/options.js +68 -0
  97. package/lib/utils/plugin-config.d.ts +14 -0
  98. package/lib/utils/plugin-config.js +66 -0
  99. package/lib/utils/runtime.d.ts +54 -0
  100. package/lib/utils/runtime.js +263 -0
  101. package/lib/utils/slim-sourcemap.d.ts +28 -0
  102. package/lib/utils/slim-sourcemap.js +106 -0
  103. package/lib/utils/zip-entries.d.ts +10 -0
  104. package/lib/utils/zip-entries.js +211 -0
  105. package/lib/utils/zip-options.d.ts +17 -0
  106. package/lib/utils/zip-options.js +190 -0
  107. package/lib/utils/zip-range.d.ts +148 -0
  108. package/lib/utils/zip-range.js +581 -0
  109. package/lib/utils/zip-raw-writer.d.ts +52 -0
  110. package/lib/utils/zip-raw-writer.js +209 -0
  111. package/lib/version-operations.d.ts +21 -0
  112. package/lib/version-operations.js +320 -0
  113. package/lib/versions.d.ts +66 -0
  114. package/lib/versions.js +402 -0
  115. package/package.json +113 -0
  116. package/proto/Configuration.proto +183 -0
  117. package/proto/Resources.proto +569 -0
package/lib/types.d.ts ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * [INPUT]: 依赖 CLI 命令参数与服务端原生构建/发布字段
3
+ * [OUTPUT]: 对外提供平台、含 channelId/bundleHash 的原生包体、发布与 Provider 共享契约
4
+ * [POS]: CLI 公共模型层,兼容旧数字 ID 与独立服务 UUID,并为上传前版本组检查保留内容身份
5
+ */
6
+ declare global {
7
+ var NO_INTERACTIVE: boolean | undefined;
8
+ var USE_ACC_OSS: boolean | undefined;
9
+ }
10
+ export interface Session {
11
+ token: string;
12
+ }
13
+ export type Platform = 'ios' | 'android' | 'harmony';
14
+ export interface Package {
15
+ id: string | number;
16
+ name: string;
17
+ version?: string;
18
+ status?: string;
19
+ appId?: string;
20
+ appKey?: string;
21
+ versionName?: string | number | null;
22
+ channelId?: string;
23
+ bundleHash?: string;
24
+ hash?: string;
25
+ /** Opaque UUID generated by the native build; never parsed as a number. */
26
+ buildTime?: string | null;
27
+ channel?: string;
28
+ deps?: Record<string, string> | string | null;
29
+ }
30
+ export interface Version {
31
+ id: string | number;
32
+ hash: string;
33
+ name: string;
34
+ packages?: Package[];
35
+ deps?: Record<string, string> | string | null;
36
+ /** storage key of the archived source map (absent when published without one) */
37
+ sourceMapKey?: string;
38
+ }
39
+ export interface CommandContext<TOptions extends Record<string, unknown> = Record<string, unknown>> {
40
+ args: string[];
41
+ options: TOptions;
42
+ platform?: Platform;
43
+ appId?: string;
44
+ session?: Session;
45
+ }
46
+ export interface CommandResult {
47
+ success: boolean;
48
+ data?: unknown;
49
+ error?: string;
50
+ }
51
+ export interface BundleOptions {
52
+ dev?: boolean;
53
+ platform?: Platform;
54
+ bundleName?: string;
55
+ entryFile?: string;
56
+ output?: string;
57
+ sourcemap?: boolean;
58
+ taro?: boolean;
59
+ expo?: boolean;
60
+ rncli?: boolean;
61
+ hermes?: boolean;
62
+ /** 'auto' (default) | 'none' | path to a .hbc/.ppk/.apk/.ipa used as hermesc -base-bytecode */
63
+ hermesBase?: string;
64
+ /** run the disassembly equivalence check after a base compile (default true) */
65
+ verifyHermesBase?: boolean;
66
+ /** pass --reset-cache to Metro (default true); false reuses its transform cache */
67
+ resetCache?: boolean;
68
+ sentryRelease?: string;
69
+ sentryDist?: string;
70
+ /** publish to this app instead of the one selected in the config file */
71
+ appId?: string;
72
+ /** selected-app config file (default: update.json) */
73
+ config?: string;
74
+ }
75
+ export interface PublishOptions {
76
+ filePath?: string;
77
+ /** path of the final source map to archive with the version */
78
+ sourcemap?: string;
79
+ platform?: Platform;
80
+ name?: string;
81
+ description?: string;
82
+ metaInfo?: string;
83
+ diffFromHash?: string;
84
+ packageId?: string;
85
+ packageVersion?: string;
86
+ /** Default channel code; targets.json can override it for each deployment target. */
87
+ channel?: string;
88
+ /** JSON file containing independent channel × native-version targets. */
89
+ targets?: string;
90
+ /** Exact deployment ids returned by standalone publish for a safe retry. */
91
+ deploymentIds?: string;
92
+ /** Legacy alias for deploymentIds. */
93
+ releaseIds?: string;
94
+ minPackageVersion?: string;
95
+ maxPackageVersion?: string;
96
+ packageVersionRange?: string;
97
+ /** Global rollout fallback; target.rollout wins, then this value, then 100%. */
98
+ rollout?: number | string;
99
+ dryRun?: boolean;
100
+ appId?: string;
101
+ config?: string;
102
+ }
103
+ export interface UploadOptions {
104
+ platform?: Platform;
105
+ filePath: string;
106
+ appId?: string;
107
+ appKey?: string;
108
+ version?: string;
109
+ /** Assertion only; never overrides the channel embedded in the native package. */
110
+ channel?: string;
111
+ config?: string;
112
+ }
113
+ export interface UpdateVersionOptions {
114
+ platform?: Platform;
115
+ packageId?: string;
116
+ packageVersion?: string;
117
+ channel?: string;
118
+ minPackageVersion?: string;
119
+ maxPackageVersion?: string;
120
+ packageVersionRange?: string;
121
+ rollout?: number | string;
122
+ dryRun?: boolean;
123
+ }
124
+ export interface SymbolicateOptions {
125
+ /** stack trace file ('-' for stdin) */
126
+ stackFile?: string;
127
+ /** update hash of the running version (from getUpdateMetadata().currentVersion) */
128
+ hash?: string;
129
+ versionId?: string;
130
+ platform?: Platform;
131
+ appId?: string;
132
+ config?: string;
133
+ /** write the symbolicated stack here instead of stdout */
134
+ output?: string;
135
+ }
136
+ export interface CLIProvider {
137
+ bundle: (options: BundleOptions) => Promise<CommandResult>;
138
+ publish: (options: PublishOptions) => Promise<CommandResult>;
139
+ symbolicate: (options: SymbolicateOptions) => Promise<CommandResult>;
140
+ upload: (options: UploadOptions) => Promise<CommandResult>;
141
+ createApp: (name: string, platform: Platform) => Promise<CommandResult>;
142
+ listApps: (platform?: Platform) => Promise<CommandResult>;
143
+ getSelectedApp: (platform?: Platform, config?: string) => Promise<{
144
+ appId: string;
145
+ platform: Platform;
146
+ }>;
147
+ listVersions: (appId: string) => Promise<CommandResult>;
148
+ updateVersion: (appId: string, versionId: string, updates: UpdateVersionOptions) => Promise<CommandResult>;
149
+ listPackages: (appId: string) => Promise<CommandResult>;
150
+ getPlatform: (platform?: Platform) => Promise<Platform>;
151
+ loadSession: () => Promise<Session>;
152
+ }
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/lib/user.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { CommandContext } from './types';
2
+ export declare const userCommands: {
3
+ login: ({ args }: {
4
+ args: string[];
5
+ }) => Promise<void>;
6
+ logout: (_context: CommandContext) => Promise<void>;
7
+ me: () => Promise<void>;
8
+ };
package/lib/user.js ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.userCommands = void 0;
4
+ /**
5
+ * [INPUT]: 依赖 CLI API 会话存储、凭据隔离和登录提示
6
+ * [OUTPUT]: 对外提供 login/logout/me 用户命令
7
+ * [POS]: CLI 身份层,所有服务地址统一走邮箱账号管理 API,并复用现有会话与令牌传输协议
8
+ */
9
+ const api_1 = require("./api");
10
+ const utils_1 = require("./utils");
11
+ const add_gitignore_1 = require("./utils/add-gitignore");
12
+ const constants_1 = require("./utils/constants");
13
+ const i18n_1 = require("./utils/i18n");
14
+ exports.userCommands = {
15
+ login: async ({ args }) => {
16
+ const email = args[0] || (await (0, utils_1.question)('email:'));
17
+ const pwd = args[1] || (await (0, utils_1.question)('password:', true));
18
+ if (!email || !pwd) {
19
+ // without a terminal `question` answers '': fail here instead of
20
+ // sending empty credentials to the server
21
+ throw new Error((0, i18n_1.t)('loginCredentialsRequired', { scriptName: constants_1.scriptName }));
22
+ }
23
+ const response = await (0, api_1.post)((0, api_1.servicePath)('/auth/login'), { email, password: pwd });
24
+ const data = response?.data ?? response;
25
+ const token = data?.accessToken ?? data?.token;
26
+ if (typeof token !== 'string' || token.length === 0) {
27
+ throw new Error('登录响应缺少 accessToken');
28
+ }
29
+ (0, api_1.replaceSession)({ token });
30
+ await (0, api_1.saveSession)();
31
+ // make sure the token file is ignored before the user's next commit,
32
+ // not only when they first run `bundle`
33
+ (0, add_gitignore_1.addGitIgnore)();
34
+ console.log((0, i18n_1.t)('welcomeMessage', { name: data?.user?.displayName || data?.user?.email || data?.info?.name || email }));
35
+ },
36
+ logout: async (_context) => {
37
+ await (0, api_1.post)((0, api_1.servicePath)('/auth/logout')).catch(() => undefined);
38
+ await (0, api_1.closeSession)();
39
+ console.log((0, i18n_1.t)('loggedOut'));
40
+ },
41
+ me: async () => {
42
+ const me = await (0, api_1.get)((0, api_1.servicePath)('/auth/me'));
43
+ const value = me?.data ?? me;
44
+ for (const k in value) {
45
+ if (k !== 'ok') {
46
+ console.log(`${k}: ${value[k]}`);
47
+ }
48
+ }
49
+ },
50
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * [INPUT]: 依赖文件系统、CLI 凭据/临时目录常量与本地化文案
3
+ * [OUTPUT]: 对外提供 addGitIgnore,幂等写入 Pakta CLI 工作目录的忽略项
4
+ * [POS]: CLI 项目卫生辅助层,只修改当前项目 .gitignore,不参与命令业务流程
5
+ */
6
+ export declare function addGitIgnore(): void;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * [INPUT]: 依赖文件系统、CLI 凭据/临时目录常量与本地化文案
4
+ * [OUTPUT]: 对外提供 addGitIgnore,幂等写入 Pakta CLI 工作目录的忽略项
5
+ * [POS]: CLI 项目卫生辅助层,只修改当前项目 .gitignore,不参与命令业务流程
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.addGitIgnore = addGitIgnore;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ // import path from 'path';
14
+ const constants_1 = require("./constants");
15
+ const i18n_1 = require("./i18n");
16
+ function addGitIgnore() {
17
+ const shouldIgnore = [constants_1.credentialFile, constants_1.tempDir];
18
+ const gitignorePath = '.gitignore';
19
+ if (!fs_1.default.existsSync(gitignorePath)) {
20
+ return;
21
+ }
22
+ const gitignoreContent = fs_1.default.readFileSync(gitignorePath, 'utf-8');
23
+ const gitignoreLines = gitignoreContent.split('\n');
24
+ // `.pakta`, `/.pakta` and `.pakta/` all ignore the same directory; a
25
+ // trailing slash only matches directories, so it counts for tempDir alone
26
+ const covers = (entry, line) => {
27
+ const pattern = line.trim().replace(/^\//, '');
28
+ return pattern === entry || (entry === constants_1.tempDir && pattern === `${entry}/`);
29
+ };
30
+ for (const line of gitignoreLines) {
31
+ const index = shouldIgnore.findIndex((entry) => covers(entry, line));
32
+ if (index !== -1) {
33
+ shouldIgnore.splice(index, 1);
34
+ }
35
+ }
36
+ if (shouldIgnore.length > 0) {
37
+ gitignoreLines.push('# rn-update');
38
+ for (const line of shouldIgnore) {
39
+ gitignoreLines.push(line);
40
+ console.log((0, i18n_1.t)('addedToGitignore', { line }));
41
+ }
42
+ fs_1.default.writeFileSync(gitignorePath, gitignoreLines.join('\n'));
43
+ }
44
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * [INPUT]: 依赖 AAB ZIP 读取、Android manifest/resource 解析、文件系统与外部包工具
3
+ * [OUTPUT]: 对外提供 AabParser,提取 Android App Bundle 的版本和资源元数据
4
+ * [POS]: 应用包解析子域的 AAB 实现,与 ApkParser 共享基础 ZIP/资源解析协议
5
+ */
6
+ import { Zip } from './zip';
7
+ export declare class AabParser extends Zip {
8
+ extractApk(outputPath: string, { includeAllSplits, splits, }: {
9
+ includeAllSplits?: boolean;
10
+ splits?: string[] | null;
11
+ }): Promise<void>;
12
+ /**
13
+ * 解析 AAB 文件信息(类似 APK parser 的 parse 方法)
14
+ * 注意:AAB 中的 AndroidManifest.xml 在 base/manifest/AndroidManifest.xml
15
+ */
16
+ parse(): Promise<Record<string, any>>;
17
+ /**
18
+ * Parse manifest
19
+ * @param {Buffer} buffer // manifest file's buffer
20
+ */
21
+ private _parseManifest;
22
+ /**
23
+ * Parse resourceMap
24
+ * @param {Buffer} buffer // resourceMap file's buffer
25
+ */
26
+ private _parseResourceMap;
27
+ }
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ /**
3
+ * [INPUT]: 依赖 AAB ZIP 读取、Android manifest/resource 解析、文件系统与外部包工具
4
+ * [OUTPUT]: 对外提供 AabParser,提取 Android App Bundle 的版本和资源元数据
5
+ * [POS]: 应用包解析子域的 AAB 实现,与 ApkParser 共享基础 ZIP/资源解析协议
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.AabParser = void 0;
45
+ const child_process_1 = require("child_process");
46
+ const fs = __importStar(require("fs-extra"));
47
+ const os_1 = __importDefault(require("os"));
48
+ const path_1 = __importDefault(require("path"));
49
+ const yauzl_1 = require("yauzl");
50
+ const i18n_1 = require("../i18n");
51
+ const resource_finder_1 = require("./resource-finder");
52
+ const utils_1 = require("./utils");
53
+ const manifest_1 = require("./xml-parser/manifest");
54
+ const zip_1 = require("./zip");
55
+ class AabParser extends zip_1.Zip {
56
+ async extractApk(outputPath, { includeAllSplits, splits, }) {
57
+ const normalizedSplits = Array.isArray(splits)
58
+ ? splits.map((item) => item.trim()).filter(Boolean)
59
+ : [];
60
+ const modules = includeAllSplits
61
+ ? null
62
+ : Array.from(new Set(['base', ...normalizedSplits]));
63
+ const modulesArgs = modules ? [`--modules=${modules.join(',')}`] : [];
64
+ const runCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
65
+ const inheritStdio = options.stdio === 'inherit';
66
+ const child = (0, child_process_1.spawn)(command, args, {
67
+ stdio: inheritStdio ? 'inherit' : ['ignore', 'pipe', 'pipe'],
68
+ env: options.env,
69
+ });
70
+ let stderr = '';
71
+ if (!inheritStdio) {
72
+ child.stderr?.on('data', (chunk) => {
73
+ stderr += chunk.toString();
74
+ });
75
+ }
76
+ child.on('error', reject);
77
+ child.on('close', (code) => {
78
+ if (code === 0) {
79
+ resolve();
80
+ return;
81
+ }
82
+ reject(new Error(stderr.trim() || `Command failed: ${command} (code ${code})`));
83
+ });
84
+ });
85
+ // Create a temp file for the .apks output
86
+ const tempDir = await fs.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'bundletool-'));
87
+ const tempApksPath = path_1.default.join(tempDir, 'output.apks');
88
+ const needsNpxDownload = async () => {
89
+ try {
90
+ await runCommand('npx', [
91
+ '--no-install',
92
+ 'node-bundletool',
93
+ '--version',
94
+ ]);
95
+ return false;
96
+ }
97
+ catch {
98
+ return true;
99
+ }
100
+ };
101
+ try {
102
+ // 1. Build APKS (universal mode)
103
+ // We assume bundletool is in the path.
104
+ // User might need keystore to sign it properly but for simple extraction we stick to default debug key if possible or unsigned?
105
+ // actually bundletool build-apks signs with debug key by default if no keystore provided.
106
+ try {
107
+ await runCommand('bundletool', [
108
+ 'build-apks',
109
+ '--mode=universal',
110
+ `--bundle=${this.file}`,
111
+ `--output=${tempApksPath}`,
112
+ '--overwrite',
113
+ ...modulesArgs,
114
+ ]);
115
+ }
116
+ catch (e) {
117
+ // Only fall back when bundletool itself is missing from PATH (spawn
118
+ // ENOENT). Any other failure (corrupt AAB, Java issues, bad args,
119
+ // non-zero exit) must surface as-is instead of being masked by an
120
+ // online npx install.
121
+ if (e?.code !== 'ENOENT') {
122
+ throw e;
123
+ }
124
+ // Fallback to npx node-bundletool if bundletool is not in PATH
125
+ // We use -y to avoid interactive prompt for installation
126
+ if (await needsNpxDownload()) {
127
+ console.log((0, i18n_1.t)('aabBundletoolDownloadHint'));
128
+ }
129
+ await runCommand('npx', [
130
+ '-y',
131
+ 'node-bundletool',
132
+ 'build-apks',
133
+ '--mode=universal',
134
+ `--bundle=${this.file}`,
135
+ `--output=${tempApksPath}`,
136
+ '--overwrite',
137
+ ...modulesArgs,
138
+ ], {
139
+ stdio: 'inherit',
140
+ env: {
141
+ ...process.env,
142
+ npm_config_progress: 'true',
143
+ },
144
+ });
145
+ }
146
+ // 2. Extract universal.apk from the .apks (zip) file
147
+ await new Promise((resolve, reject) => {
148
+ (0, yauzl_1.open)(tempApksPath, { lazyEntries: true }, (err, zipfile) => {
149
+ if (err || !zipfile) {
150
+ reject(err || new Error((0, i18n_1.t)('aabOpenApksFailed')));
151
+ return;
152
+ }
153
+ let found = false;
154
+ zipfile.readEntry();
155
+ zipfile.on('entry', (entry) => {
156
+ if (entry.fileName === 'universal.apk') {
157
+ found = true;
158
+ zipfile.openReadStream(entry, (err, readStream) => {
159
+ if (err || !readStream) {
160
+ reject(err || new Error((0, i18n_1.t)('aabReadUniversalApkFailed')));
161
+ return;
162
+ }
163
+ const writeStream = fs.createWriteStream(outputPath);
164
+ readStream.pipe(writeStream);
165
+ writeStream.on('close', () => {
166
+ zipfile.close();
167
+ resolve();
168
+ });
169
+ readStream.on('error', reject);
170
+ writeStream.on('error', reject);
171
+ });
172
+ }
173
+ else {
174
+ zipfile.readEntry();
175
+ }
176
+ });
177
+ zipfile.on('end', () => {
178
+ if (!found)
179
+ reject(new Error((0, i18n_1.t)('aabUniversalApkNotFound')));
180
+ });
181
+ zipfile.on('error', reject);
182
+ });
183
+ });
184
+ }
185
+ finally {
186
+ // Cleanup
187
+ if (await fs.pathExists(tempDir)) {
188
+ await fs.remove(tempDir);
189
+ }
190
+ }
191
+ }
192
+ /**
193
+ * 解析 AAB 文件信息(类似 APK parser 的 parse 方法)
194
+ * 注意:AAB 中的 AndroidManifest.xml 在 base/manifest/AndroidManifest.xml
195
+ */
196
+ async parse() {
197
+ const manifestPath = 'base/manifest/AndroidManifest.xml';
198
+ const ResourceName = /^base\/resources\.arsc$/;
199
+ try {
200
+ const manifestBuffer = await this.getEntry(new RegExp(`^${escapeRegExp(manifestPath)}$`));
201
+ if (!manifestBuffer) {
202
+ throw new Error((0, i18n_1.t)('aabManifestNotFound'));
203
+ }
204
+ let apkInfo = this._parseManifest(manifestBuffer);
205
+ try {
206
+ const resourceBuffer = await this.getEntry(ResourceName);
207
+ if (resourceBuffer) {
208
+ const resourceMap = this._parseResourceMap(resourceBuffer);
209
+ apkInfo = (0, utils_1.mapInfoResource)(apkInfo, resourceMap);
210
+ }
211
+ }
212
+ catch (e) {
213
+ console.warn((0, i18n_1.t)('aabParseResourcesWarning', { error: e?.message ?? e }));
214
+ }
215
+ return apkInfo;
216
+ }
217
+ catch (error) {
218
+ throw new Error((0, i18n_1.t)('aabParseFailed', { error: error.message ?? error }));
219
+ }
220
+ }
221
+ /**
222
+ * Parse manifest
223
+ * @param {Buffer} buffer // manifest file's buffer
224
+ */
225
+ _parseManifest(buffer) {
226
+ try {
227
+ const parser = new manifest_1.ManifestParser(buffer, {
228
+ ignore: [
229
+ 'application.activity',
230
+ 'application.service',
231
+ 'application.receiver',
232
+ 'application.provider',
233
+ 'permission-group',
234
+ ],
235
+ });
236
+ return parser.parse();
237
+ }
238
+ catch (e) {
239
+ throw new Error((0, i18n_1.t)('aabParseManifestError', { error: e?.message ?? e }));
240
+ }
241
+ }
242
+ /**
243
+ * Parse resourceMap
244
+ * @param {Buffer} buffer // resourceMap file's buffer
245
+ */
246
+ _parseResourceMap(buffer) {
247
+ try {
248
+ return new resource_finder_1.ResourceFinder().processResourceTable(buffer);
249
+ }
250
+ catch (e) {
251
+ throw new Error((0, i18n_1.t)('aabParseResourcesError', { error: e?.message ?? e }));
252
+ }
253
+ }
254
+ }
255
+ exports.AabParser = AabParser;
256
+ const escapeRegExp = (value) => value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
@@ -0,0 +1,19 @@
1
+ /**
2
+ * [INPUT]: 依赖 APK ZIP、Android manifest 二进制 XML、资源表与图标资源解析器
3
+ * [OUTPUT]: 对外提供 ApkParser,提取 Android APK 的应用元数据
4
+ * [POS]: 应用包解析子域的 APK 实现,复用 Zip、ManifestParser 与 ResourceFinder
5
+ */
6
+ import { Zip } from './zip';
7
+ export declare class ApkParser extends Zip {
8
+ parse(): Promise<any>;
9
+ /**
10
+ * Parse manifest
11
+ * @param {Buffer} buffer // manifest file's buffer
12
+ */
13
+ private _parseManifest;
14
+ /**
15
+ * Parse resourceMap
16
+ * @param {Buffer} buffer // resourceMap file's buffer
17
+ */
18
+ private _parseResourceMap;
19
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * [INPUT]: 依赖 APK ZIP、Android manifest 二进制 XML、资源表与图标资源解析器
4
+ * [OUTPUT]: 对外提供 ApkParser,提取 Android APK 的应用元数据
5
+ * [POS]: 应用包解析子域的 APK 实现,复用 Zip、ManifestParser 与 ResourceFinder
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ApkParser = void 0;
9
+ const resource_finder_1 = require("./resource-finder");
10
+ const utils_1 = require("./utils");
11
+ const manifest_1 = require("./xml-parser/manifest");
12
+ const zip_1 = require("./zip");
13
+ const ManifestName = /^androidmanifest\.xml$/;
14
+ const ResourceName = /^resources\.arsc$/;
15
+ class ApkParser extends zip_1.Zip {
16
+ parse() {
17
+ const manifestKey = ManifestName.toString();
18
+ const resourceKey = ResourceName.toString();
19
+ return new Promise((resolve, reject) => {
20
+ this.getEntries([ManifestName, ResourceName])
21
+ .then((buffers) => {
22
+ const manifestBuffer = buffers[manifestKey];
23
+ if (!manifestBuffer) {
24
+ throw new Error("AndroidManifest.xml can't be found.");
25
+ }
26
+ let apkInfo;
27
+ let resourceMap;
28
+ apkInfo = this._parseManifest(manifestBuffer);
29
+ if (!buffers[resourceKey]) {
30
+ resolve(apkInfo);
31
+ }
32
+ else {
33
+ resourceMap = this._parseResourceMap(buffers[resourceKey]);
34
+ apkInfo = (0, utils_1.mapInfoResource)(apkInfo, resourceMap);
35
+ const iconPath = (0, utils_1.findApkIconPath)(apkInfo);
36
+ if (iconPath) {
37
+ this.getEntry(iconPath)
38
+ .then((iconBuffer) => {
39
+ apkInfo.icon = Buffer.isBuffer(iconBuffer)
40
+ ? (0, utils_1.getBase64FromBuffer)(iconBuffer)
41
+ : null;
42
+ resolve(apkInfo);
43
+ })
44
+ .catch((e) => {
45
+ apkInfo.icon = null;
46
+ resolve(apkInfo);
47
+ console.warn('[Warning] failed to parse icon: ', e);
48
+ });
49
+ }
50
+ else {
51
+ apkInfo.icon = null;
52
+ resolve(apkInfo);
53
+ }
54
+ }
55
+ })
56
+ .catch((e) => {
57
+ reject(e);
58
+ });
59
+ });
60
+ }
61
+ /**
62
+ * Parse manifest
63
+ * @param {Buffer} buffer // manifest file's buffer
64
+ */
65
+ _parseManifest(buffer) {
66
+ try {
67
+ const parser = new manifest_1.ManifestParser(buffer, {
68
+ ignore: [
69
+ 'application.activity',
70
+ 'application.service',
71
+ 'application.receiver',
72
+ 'application.provider',
73
+ 'permission-group',
74
+ ],
75
+ });
76
+ return parser.parse();
77
+ }
78
+ catch (e) {
79
+ throw new Error(`Parse AndroidManifest.xml error: ${e.message || e}`);
80
+ }
81
+ }
82
+ /**
83
+ * Parse resourceMap
84
+ * @param {Buffer} buffer // resourceMap file's buffer
85
+ */
86
+ _parseResourceMap(buffer) {
87
+ try {
88
+ return new resource_finder_1.ResourceFinder().processResourceTable(buffer);
89
+ }
90
+ catch (e) {
91
+ throw new Error(`Parser resources.arsc error: ${e}`);
92
+ }
93
+ }
94
+ }
95
+ exports.ApkParser = ApkParser;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * [INPUT]: 依赖通用 Zip 容器读取能力
3
+ * [OUTPUT]: 对外提供 AppParser,处理 macOS .app 包的通用归档边界
4
+ * [POS]: 应用包解析子域的轻量 fallback,实现与平台特定解析器一致的 parse 接口
5
+ */
6
+ import { Zip } from './zip';
7
+ export declare class AppParser extends Zip {
8
+ parse(): Promise<Record<string, never>>;
9
+ }