@leonxin/meetgames 0.1.11 → 0.1.13

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 (179) hide show
  1. package/README.md +9 -9
  2. package/dist/cache.d.ts +44 -0
  3. package/dist/cache.d.ts.map +1 -0
  4. package/dist/cache.js +101 -0
  5. package/dist/cache.js.map +1 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +43 -37
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config/meetSdkDefaultConfig.d.ts +1 -1
  10. package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
  11. package/dist/config/meetSdkDefaultConfig.js +4 -3
  12. package/dist/config/meetSdkDefaultConfig.js.map +1 -1
  13. package/dist/config/meetSdkIosConfig.d.ts.map +1 -1
  14. package/dist/config/meetSdkIosConfig.js +3 -1
  15. package/dist/config/meetSdkIosConfig.js.map +1 -1
  16. package/dist/config/meetSdkRemoteConfig.d.ts +1 -0
  17. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  18. package/dist/config/meetSdkRemoteConfig.js +4 -1
  19. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  20. package/dist/contracts/types.d.ts +11 -0
  21. package/dist/contracts/types.d.ts.map +1 -1
  22. package/dist/core/doctor.js +7 -7
  23. package/dist/core/doctor.js.map +1 -1
  24. package/dist/core/pipeline.d.ts.map +1 -1
  25. package/dist/core/pipeline.js +3 -0
  26. package/dist/core/pipeline.js.map +1 -1
  27. package/dist/core/previewPatches.d.ts +1 -1
  28. package/dist/core/previewPatches.js +2 -2
  29. package/dist/core/previewPatches.js.map +1 -1
  30. package/dist/core/reporter.d.ts.map +1 -1
  31. package/dist/core/reporter.js +4 -0
  32. package/dist/core/reporter.js.map +1 -1
  33. package/dist/core/workspace.d.ts.map +1 -1
  34. package/dist/core/workspace.js +2 -0
  35. package/dist/core/workspace.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/ios/channelConfig.js +1 -1
  41. package/dist/ios/channelConfig.js.map +1 -1
  42. package/dist/ios/codeUtils.d.ts +1 -0
  43. package/dist/ios/codeUtils.d.ts.map +1 -1
  44. package/dist/ios/codeUtils.js +3 -0
  45. package/dist/ios/codeUtils.js.map +1 -1
  46. package/dist/ios/integrate.d.ts.map +1 -1
  47. package/dist/ios/integrate.js +97 -23
  48. package/dist/ios/integrate.js.map +1 -1
  49. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  50. package/dist/ios/pbxprojEditor.js +123 -6
  51. package/dist/ios/pbxprojEditor.js.map +1 -1
  52. package/dist/ios/sdkBundle.d.ts +1 -6
  53. package/dist/ios/sdkBundle.d.ts.map +1 -1
  54. package/dist/ios/sdkBundle.js +44 -16
  55. package/dist/ios/sdkBundle.js.map +1 -1
  56. package/dist/mcp/server.d.ts.map +1 -1
  57. package/dist/mcp/server.js +12 -6
  58. package/dist/mcp/server.js.map +1 -1
  59. package/dist/mcp/service.d.ts +3 -0
  60. package/dist/mcp/service.d.ts.map +1 -1
  61. package/dist/mcp/service.js +31 -8
  62. package/dist/mcp/service.js.map +1 -1
  63. package/dist/ops/handlers.d.ts.map +1 -1
  64. package/dist/ops/handlers.js +24 -19
  65. package/dist/ops/handlers.js.map +1 -1
  66. package/dist/remote/sdkHomeDownload.d.ts +1 -1
  67. package/dist/remote/sdkHomeDownload.d.ts.map +1 -1
  68. package/dist/remote/sdkHomeDownload.js +27 -6
  69. package/dist/remote/sdkHomeDownload.js.map +1 -1
  70. package/docs/API.md +16 -16
  71. package/docs/CLI.md +21 -22
  72. package/docs/INTEGRATION.md +30 -11
  73. package/docs/MCP.md +1 -1
  74. package/docs/README.md +0 -1
  75. package/docs/archive/api/downloadSDKConfig.md +2 -2
  76. package/docs/archive/api/getChannelConfig-meetgames.md +1 -1
  77. package/docs/archive/product//351/234/200/346/261/202/346/226/207/346/241/243.md +1 -1
  78. package/package.json +2 -5
  79. package/recipes/android-default.yaml +0 -5
  80. package/recipes/integrate-default.yaml +0 -5
  81. package/src/cache.ts +164 -0
  82. package/src/cli.ts +46 -38
  83. package/src/config/meetSdkDefaultConfig.ts +4 -3
  84. package/src/config/meetSdkIosConfig.ts +3 -1
  85. package/src/config/meetSdkRemoteConfig.ts +5 -1
  86. package/src/contracts/types.ts +11 -0
  87. package/src/core/doctor.ts +7 -7
  88. package/src/core/pipeline.ts +3 -0
  89. package/src/core/previewPatches.ts +2 -2
  90. package/src/core/reporter.ts +3 -0
  91. package/src/core/workspace.ts +2 -0
  92. package/src/index.ts +7 -0
  93. package/src/ios/channelConfig.ts +1 -1
  94. package/src/ios/codeUtils.ts +4 -0
  95. package/src/ios/integrate.ts +110 -27
  96. package/src/ios/pbxprojEditor.ts +121 -4
  97. package/src/ios/sdkBundle.ts +41 -18
  98. package/src/mcp/server.ts +12 -6
  99. package/src/mcp/service.ts +39 -8
  100. package/src/ops/handlers.ts +23 -19
  101. package/src/remote/sdkHomeDownload.ts +28 -7
  102. package/tests/doctor.test.ts +4 -2
  103. package/tests/{test-projects-hosts.test.ts → fixtures-hosts.test.ts} +2 -2
  104. package/tests/ios.sdkBundle.test.ts +10 -5
  105. package/tests/mcp.e2e.ts +2 -5
  106. package/tests/meetSdkRemoteConfig.test.ts +25 -0
  107. package/tests/pipeline.android.test.ts +1 -2
  108. package/tests/pipeline.ios.test.ts +133 -20
  109. package/tests/pipeline.preview.patch.test.ts +2 -2
  110. package/tests/sdkVersionConfig.test.ts +3 -2
  111. package/dist/aab-converter/aab-entry.d.ts +0 -3
  112. package/dist/aab-converter/aab-entry.d.ts.map +0 -1
  113. package/dist/aab-converter/aab-entry.js +0 -49
  114. package/dist/aab-converter/aab-entry.js.map +0 -1
  115. package/dist/aab-converter/apksExtractor.d.ts +0 -2
  116. package/dist/aab-converter/apksExtractor.d.ts.map +0 -1
  117. package/dist/aab-converter/apksExtractor.js +0 -108
  118. package/dist/aab-converter/apksExtractor.js.map +0 -1
  119. package/dist/aab-converter/bundletoolRunner.d.ts +0 -15
  120. package/dist/aab-converter/bundletoolRunner.d.ts.map +0 -1
  121. package/dist/aab-converter/bundletoolRunner.js +0 -46
  122. package/dist/aab-converter/bundletoolRunner.js.map +0 -1
  123. package/dist/aab-converter/cliArgs.d.ts +0 -27
  124. package/dist/aab-converter/cliArgs.d.ts.map +0 -1
  125. package/dist/aab-converter/cliArgs.js +0 -170
  126. package/dist/aab-converter/cliArgs.js.map +0 -1
  127. package/dist/aab-converter/convertAabToApk.d.ts +0 -7
  128. package/dist/aab-converter/convertAabToApk.d.ts.map +0 -1
  129. package/dist/aab-converter/convertAabToApk.js +0 -69
  130. package/dist/aab-converter/convertAabToApk.js.map +0 -1
  131. package/dist/aab-converter/resourcePaths.d.ts +0 -4
  132. package/dist/aab-converter/resourcePaths.d.ts.map +0 -1
  133. package/dist/aab-converter/resourcePaths.js +0 -42
  134. package/dist/aab-converter/resourcePaths.js.map +0 -1
  135. package/dist/aab-converter/signingOptions.d.ts +0 -9
  136. package/dist/aab-converter/signingOptions.d.ts.map +0 -1
  137. package/dist/aab-converter/signingOptions.js +0 -21
  138. package/dist/aab-converter/signingOptions.js.map +0 -1
  139. package/dist/aab-converter/types.d.ts +0 -24
  140. package/dist/aab-converter/types.d.ts.map +0 -1
  141. package/dist/aab-converter/types.js +0 -2
  142. package/dist/aab-converter/types.js.map +0 -1
  143. package/dist/shared/fileUtils.d.ts +0 -5
  144. package/dist/shared/fileUtils.d.ts.map +0 -1
  145. package/dist/shared/fileUtils.js +0 -35
  146. package/dist/shared/fileUtils.js.map +0 -1
  147. package/dist/shared/logger.d.ts +0 -10
  148. package/dist/shared/logger.d.ts.map +0 -1
  149. package/dist/shared/logger.js +0 -37
  150. package/dist/shared/logger.js.map +0 -1
  151. package/dist/shared/pathUtils.d.ts +0 -4
  152. package/dist/shared/pathUtils.d.ts.map +0 -1
  153. package/dist/shared/pathUtils.js +0 -22
  154. package/dist/shared/pathUtils.js.map +0 -1
  155. package/dist/shared/processRunner.d.ts +0 -12
  156. package/dist/shared/processRunner.d.ts.map +0 -1
  157. package/dist/shared/processRunner.js +0 -31
  158. package/dist/shared/processRunner.js.map +0 -1
  159. package/docs/AAB_CONVERTER_CLI_PLAN.md +0 -392
  160. package/logs/convert-20260622-155037.log +0 -5
  161. package/logs/convert-20260622-155226.log +0 -6
  162. package/scripts/package-aab-cli-win.mjs +0 -193
  163. package/src/aab-converter/aab-entry.ts +0 -48
  164. package/src/aab-converter/apksExtractor.ts +0 -119
  165. package/src/aab-converter/bundletoolRunner.ts +0 -63
  166. package/src/aab-converter/cliArgs.ts +0 -194
  167. package/src/aab-converter/convertAabToApk.ts +0 -81
  168. package/src/aab-converter/resourcePaths.ts +0 -43
  169. package/src/aab-converter/signingOptions.ts +0 -29
  170. package/src/aab-converter/types.ts +0 -26
  171. package/src/shared/fileUtils.ts +0 -41
  172. package/src/shared/logger.ts +0 -49
  173. package/src/shared/pathUtils.ts +0 -24
  174. package/src/shared/processRunner.ts +0 -43
  175. package/test-projects/README.md +0 -51
  176. package/test-projects/_preview/pipeline.patch +0 -281
  177. package/tests/aab-converter.test.ts +0 -213
  178. /package/{meetsdk-android.json → config/meetsdk-android.json} +0 -0
  179. /package/{meetsdk-ios.json → config/meetsdk-ios.json} +0 -0
@@ -1,119 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import zlib from "node:zlib";
4
- import { UserFacingError } from "../shared/errors.js";
5
- import { assertCanWriteFile } from "../shared/fileUtils.js";
6
-
7
- interface ZipEntry {
8
- name: string;
9
- method: number;
10
- compressedSize: number;
11
- localHeaderOffset: number;
12
- }
13
-
14
- function readAt(fd: number, position: number, length: number): Buffer {
15
- const buf = Buffer.alloc(length);
16
- const bytesRead = fs.readSync(fd, buf, 0, length, position);
17
- return bytesRead === length ? buf : buf.subarray(0, bytesRead);
18
- }
19
-
20
- function findEndOfCentralDirectory(buf: Buffer): number {
21
- const signature = 0x06054b50;
22
- for (let i = buf.length - 22; i >= 0; i -= 1) {
23
- if (buf.readUInt32LE(i) === signature) return i;
24
- }
25
- throw new UserFacingError("APKS_INVALID", "Invalid .apks file: end of central directory not found");
26
- }
27
-
28
- function readZipEntries(apksPath: string): ZipEntry[] {
29
- const fd = fs.openSync(apksPath, "r");
30
- try {
31
- const fileSize = fs.fstatSync(fd).size;
32
- const tailSize = Math.min(fileSize, 0xffff + 22);
33
- const tailStart = fileSize - tailSize;
34
- const tail = readAt(fd, tailStart, tailSize);
35
- const eocd = findEndOfCentralDirectory(tail);
36
- const totalEntries = tail.readUInt16LE(eocd + 10);
37
- const centralDirSize = tail.readUInt32LE(eocd + 12);
38
- const centralDirOffset = tail.readUInt32LE(eocd + 16);
39
- if (centralDirSize === 0xffffffff || centralDirOffset === 0xffffffff || totalEntries === 0xffff) {
40
- throw new UserFacingError("APKS_ZIP64_UNSUPPORTED", "ZIP64 .apks files are not supported yet");
41
- }
42
- const buf = readAt(fd, centralDirOffset, centralDirSize);
43
- return parseCentralDirectory(buf, totalEntries);
44
- } finally {
45
- fs.closeSync(fd);
46
- }
47
- }
48
-
49
- function parseCentralDirectory(buf: Buffer, totalEntries: number): ZipEntry[] {
50
- const entries: ZipEntry[] = [];
51
- let pos = 0;
52
- for (let i = 0; i < totalEntries; i += 1) {
53
- if (buf.readUInt32LE(pos) !== 0x02014b50) {
54
- throw new UserFacingError("APKS_INVALID", "Invalid .apks file: central directory is corrupt");
55
- }
56
- const method = buf.readUInt16LE(pos + 10);
57
- const compressedSize = buf.readUInt32LE(pos + 20);
58
- const nameLength = buf.readUInt16LE(pos + 28);
59
- const extraLength = buf.readUInt16LE(pos + 30);
60
- const commentLength = buf.readUInt16LE(pos + 32);
61
- const localHeaderOffset = buf.readUInt32LE(pos + 42);
62
- const name = buf.subarray(pos + 46, pos + 46 + nameLength).toString("utf8");
63
- entries.push({ name, method, compressedSize, localHeaderOffset });
64
- pos += 46 + nameLength + extraLength + commentLength;
65
- }
66
- return entries;
67
- }
68
-
69
- async function extractEntryToFile(apksPath: string, entry: ZipEntry, outputApkPath: string): Promise<void> {
70
- const fd = fs.openSync(apksPath, "r");
71
- let dataStart: number;
72
- try {
73
- const localHeader = readAt(fd, entry.localHeaderOffset, 30);
74
- if (localHeader.readUInt32LE(0) !== 0x04034b50) {
75
- throw new UserFacingError("APKS_INVALID", `Invalid .apks file: local header not found for ${entry.name}`);
76
- }
77
- const nameLength = localHeader.readUInt16LE(26);
78
- const extraLength = localHeader.readUInt16LE(28);
79
- dataStart = entry.localHeaderOffset + 30 + nameLength + extraLength;
80
- } finally {
81
- fs.closeSync(fd);
82
- }
83
-
84
- await new Promise<void>((resolve, reject) => {
85
- const input = fs.createReadStream(apksPath, {
86
- start: dataStart,
87
- end: dataStart + entry.compressedSize - 1,
88
- });
89
- const output = fs.createWriteStream(outputApkPath);
90
- const source =
91
- entry.method === 0
92
- ? input
93
- : entry.method === 8
94
- ? input.pipe(zlib.createInflateRaw())
95
- : null;
96
- if (!source) {
97
- input.destroy();
98
- output.destroy();
99
- reject(new UserFacingError("APKS_UNSUPPORTED_ZIP_METHOD", `Unsupported zip compression method ${entry.method}`));
100
- return;
101
- }
102
- input.on("error", reject);
103
- source.on("error", reject);
104
- output.on("error", reject);
105
- output.on("finish", resolve);
106
- source.pipe(output);
107
- });
108
- }
109
-
110
- export async function extractUniversalApk(apksPath: string, outputApkPath: string, overwrite: boolean): Promise<void> {
111
- assertCanWriteFile(outputApkPath, overwrite, "Output APK");
112
- const entries = readZipEntries(apksPath);
113
- const universal = entries.find((entry) => entry.name === "universal.apk" || entry.name.endsWith("/universal.apk"));
114
- if (!universal) {
115
- throw new UserFacingError("UNIVERSAL_APK_NOT_FOUND", "universal.apk not found in .apks output");
116
- }
117
- fs.mkdirSync(path.dirname(outputApkPath), { recursive: true });
118
- await extractEntryToFile(apksPath, universal, outputApkPath);
119
- }
@@ -1,63 +0,0 @@
1
- import path from "node:path";
2
- import { UserFacingError } from "../shared/errors.js";
3
- import { runProcess } from "../shared/processRunner.js";
4
- import type { Logger } from "../shared/logger.js";
5
- import type { SigningOptions } from "./types.js";
6
-
7
- export interface BuildApksOptions {
8
- javaPath: string;
9
- bundletoolPath: string;
10
- aabPath: string;
11
- apksPath: string;
12
- signing: SigningOptions;
13
- overwrite: boolean;
14
- logger?: Logger;
15
- }
16
-
17
- export function buildBundletoolArgs(options: Omit<BuildApksOptions, "javaPath" | "logger">): string[] {
18
- const args = [
19
- "-jar",
20
- options.bundletoolPath,
21
- "build-apks",
22
- `--bundle=${options.aabPath}`,
23
- `--output=${options.apksPath}`,
24
- "--mode=universal",
25
- `--ks=${options.signing.keyStorePath}`,
26
- `--ks-key-alias=${options.signing.keyAlias}`,
27
- `--ks-pass=pass:${options.signing.keyStorePassword}`,
28
- ];
29
- if (options.signing.keyPassword) {
30
- args.push(`--key-pass=pass:${options.signing.keyPassword}`);
31
- }
32
- if (options.overwrite) {
33
- args.push("--overwrite");
34
- }
35
- return args;
36
- }
37
-
38
- function redactArg(arg: string): string {
39
- if (arg.startsWith("--ks-pass=pass:")) return "--ks-pass=pass:******";
40
- if (arg.startsWith("--key-pass=pass:")) return "--key-pass=pass:******";
41
- return arg;
42
- }
43
-
44
- export function redactedBundletoolCommand(javaPath: string, args: string[]): string {
45
- return [javaPath, ...args.map(redactArg)].map((part) => (part.includes(" ") ? `"${part}"` : part)).join(" ");
46
- }
47
-
48
- export async function buildUniversalApks(options: BuildApksOptions): Promise<void> {
49
- const args = buildBundletoolArgs(options);
50
- options.logger?.info(`Running bundletool: ${redactedBundletoolCommand(options.javaPath, args)}`);
51
- const result = await runProcess(options.javaPath, args, {
52
- cwd: path.dirname(options.apksPath),
53
- onStdout: (chunk) => options.logger?.info(chunk.trimEnd()),
54
- onStderr: (chunk) => options.logger?.warn(chunk.trimEnd()),
55
- });
56
- if (result.exitCode !== 0) {
57
- throw new UserFacingError(
58
- "BUNDLETOOL_FAILED",
59
- "bundletool failed to build APK set",
60
- (result.stderr || result.stdout).trim()
61
- );
62
- }
63
- }
@@ -1,194 +0,0 @@
1
- import path from "node:path";
2
- import { UserFacingError } from "../shared/errors.js";
3
- import { toAbsolutePath } from "../shared/pathUtils.js";
4
- import { resolveSigningOptions } from "./signingOptions.js";
5
- import type { SigningOptions } from "./types.js";
6
-
7
- export interface ParsedAabCli {
8
- help: boolean;
9
- command: string;
10
- aabPath: string;
11
- outDir: string;
12
- keyStorePath: string;
13
- keyAlias: string;
14
- keyStorePassword?: string;
15
- keyPassword?: string;
16
- apkName?: string;
17
- keepApks: boolean;
18
- overwrite: boolean;
19
- verbose: boolean;
20
- }
21
-
22
- function valueAfter(args: string[], index: number, flag: string): string {
23
- const value = args[index + 1] ?? "";
24
- if (!value || value.startsWith("--")) {
25
- throw new UserFacingError("INVALID_ARGS", `${flag} requires a value`);
26
- }
27
- return value;
28
- }
29
-
30
- function passwordFromEnv(envName: string, flag: string): string {
31
- const value = process.env[envName];
32
- if (!value) {
33
- throw new UserFacingError("ENV_PASSWORD_MISSING", `${flag} points to an empty or missing environment variable: ${envName}`);
34
- }
35
- return value;
36
- }
37
-
38
- export function printAabHelp(): void {
39
- console.log(`
40
- aab2apk — Convert Android App Bundle (.aab) to signed universal APK
41
-
42
- Usage:
43
- aab2apk.cmd convert --aab <file.aab> --out-dir <dir> --ks <file.jks> --ks-key-alias <alias> [password options]
44
-
45
- Required:
46
- --aab AAB file path
47
- --out-dir Output directory
48
- --ks Keystore/JKS file path
49
- --ks-key-alias Key alias in keystore
50
-
51
- Password options:
52
- --ks-pass-env Read keystore password from environment variable (recommended)
53
- --key-pass-env Read key password from environment variable (recommended)
54
- --ks-pass Keystore password as command argument (not recommended)
55
- --key-pass Key password as command argument (not recommended)
56
-
57
- Other options:
58
- --apk-name Output APK file name
59
- --keep-apks Keep intermediate .apks file
60
- --no-overwrite Fail if output already exists (default is overwrite)
61
- --verbose Print detailed logs
62
- --help, -h Show help
63
- `);
64
- }
65
-
66
- export function parseAabCliArgv(argv: string[]): ParsedAabCli {
67
- const rest = argv.slice(2);
68
- if (!rest.length || rest[0] === "--help" || rest[0] === "-h") {
69
- return {
70
- help: true,
71
- command: "",
72
- aabPath: "",
73
- outDir: "",
74
- keyStorePath: "",
75
- keyAlias: "",
76
- keepApks: false,
77
- overwrite: true,
78
- verbose: false,
79
- };
80
- }
81
- const parsed: ParsedAabCli = {
82
- help: false,
83
- command: rest[0] ?? "",
84
- aabPath: "",
85
- outDir: "",
86
- keyStorePath: "",
87
- keyAlias: "",
88
- keepApks: false,
89
- overwrite: true,
90
- verbose: false,
91
- };
92
- if (parsed.command !== "convert") {
93
- throw new UserFacingError("INVALID_COMMAND", `Unknown command: ${parsed.command}`);
94
- }
95
- for (let i = 1; i < rest.length; i += 1) {
96
- const flag = rest[i];
97
- if (flag === "--help" || flag === "-h") {
98
- parsed.help = true;
99
- return parsed;
100
- }
101
- if (flag === "--keep-apks") {
102
- parsed.keepApks = true;
103
- continue;
104
- }
105
- if (flag === "--verbose") {
106
- parsed.verbose = true;
107
- continue;
108
- }
109
- if (flag === "--no-overwrite") {
110
- parsed.overwrite = false;
111
- continue;
112
- }
113
- if (flag === "--overwrite") {
114
- parsed.overwrite = true;
115
- continue;
116
- }
117
- if (flag === "--aab") {
118
- parsed.aabPath = valueAfter(rest, i, flag);
119
- i += 1;
120
- continue;
121
- }
122
- if (flag === "--out-dir") {
123
- parsed.outDir = valueAfter(rest, i, flag);
124
- i += 1;
125
- continue;
126
- }
127
- if (flag === "--ks") {
128
- parsed.keyStorePath = valueAfter(rest, i, flag);
129
- i += 1;
130
- continue;
131
- }
132
- if (flag === "--ks-key-alias") {
133
- parsed.keyAlias = valueAfter(rest, i, flag);
134
- i += 1;
135
- continue;
136
- }
137
- if (flag === "--ks-pass-env") {
138
- parsed.keyStorePassword = passwordFromEnv(valueAfter(rest, i, flag), flag);
139
- i += 1;
140
- continue;
141
- }
142
- if (flag === "--key-pass-env") {
143
- parsed.keyPassword = passwordFromEnv(valueAfter(rest, i, flag), flag);
144
- i += 1;
145
- continue;
146
- }
147
- if (flag === "--ks-pass") {
148
- parsed.keyStorePassword = valueAfter(rest, i, flag);
149
- i += 1;
150
- continue;
151
- }
152
- if (flag === "--key-pass") {
153
- parsed.keyPassword = valueAfter(rest, i, flag);
154
- i += 1;
155
- continue;
156
- }
157
- if (flag === "--apk-name") {
158
- parsed.apkName = valueAfter(rest, i, flag);
159
- i += 1;
160
- continue;
161
- }
162
- throw new UserFacingError("INVALID_ARGS", `Unsupported argument: ${flag}`);
163
- }
164
- return parsed;
165
- }
166
-
167
- export function validateConvertArgs(parsed: ParsedAabCli): {
168
- aabPath: string;
169
- outDir: string;
170
- signing: SigningOptions;
171
- apkName?: string;
172
- keepApks: boolean;
173
- overwrite: boolean;
174
- verbose: boolean;
175
- } {
176
- if (!parsed.aabPath) throw new UserFacingError("INVALID_ARGS", "Missing --aab");
177
- if (!parsed.outDir) throw new UserFacingError("INVALID_ARGS", "Missing --out-dir");
178
- if (!parsed.keyStorePath) throw new UserFacingError("INVALID_ARGS", "Missing --ks");
179
- if (!parsed.keyAlias) throw new UserFacingError("INVALID_ARGS", "Missing --ks-key-alias");
180
- return {
181
- aabPath: toAbsolutePath(parsed.aabPath),
182
- outDir: path.resolve(parsed.outDir),
183
- signing: resolveSigningOptions({
184
- keyStorePath: parsed.keyStorePath,
185
- keyAlias: parsed.keyAlias,
186
- keyStorePassword: parsed.keyStorePassword,
187
- keyPassword: parsed.keyPassword,
188
- }),
189
- apkName: parsed.apkName,
190
- keepApks: parsed.keepApks,
191
- overwrite: parsed.overwrite,
192
- verbose: parsed.verbose,
193
- };
194
- }
@@ -1,81 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { UserFacingError } from "../shared/errors.js";
4
- import { assertCanWriteFile, assertFileExists, assertWritableDirectory } from "../shared/fileUtils.js";
5
- import { baseNameWithoutExt, toAbsolutePath, validateOutputFileName } from "../shared/pathUtils.js";
6
- import { buildUniversalApks } from "./bundletoolRunner.js";
7
- import { extractUniversalApk } from "./apksExtractor.js";
8
- import type { ConvertAabToApkOptions, ConvertAabToApkResult } from "./types.js";
9
-
10
- export function resolveOutputPaths(options: Pick<ConvertAabToApkOptions, "aabPath" | "outDir" | "apkName">): {
11
- apksPath: string;
12
- apkPath: string;
13
- } {
14
- const aabBase = baseNameWithoutExt(options.aabPath, ".aab");
15
- const apkName = options.apkName ? validateOutputFileName(options.apkName) : `${aabBase}_universal.apk`;
16
- return {
17
- apksPath: path.join(options.outDir, `${aabBase}.apks`),
18
- apkPath: path.join(options.outDir, apkName),
19
- };
20
- }
21
-
22
- export async function convertAabToApk(options: ConvertAabToApkOptions): Promise<ConvertAabToApkResult> {
23
- const aabPath = toAbsolutePath(options.aabPath);
24
- const outDir = toAbsolutePath(options.outDir);
25
- const javaPath = options.javaPath;
26
- const bundletoolPath = toAbsolutePath(options.bundletoolPath);
27
- const signing = {
28
- ...options.signing,
29
- keyStorePath: toAbsolutePath(options.signing.keyStorePath),
30
- };
31
-
32
- assertFileExists(aabPath, "AAB_NOT_FOUND", "AAB file");
33
- baseNameWithoutExt(aabPath, ".aab");
34
- assertFileExists(signing.keyStorePath, "KEYSTORE_NOT_FOUND", "Keystore file");
35
- assertFileExists(bundletoolPath, "BUNDLETOOL_NOT_FOUND", "bundletool.jar");
36
- if (javaPath.includes("/") || javaPath.includes("\\")) {
37
- assertFileExists(javaPath, "JAVA_NOT_FOUND", "Java executable");
38
- }
39
- assertWritableDirectory(outDir);
40
-
41
- const { apksPath, apkPath } = resolveOutputPaths({ aabPath, outDir, apkName: options.apkName });
42
- assertCanWriteFile(apksPath, options.overwrite, "APK set");
43
- assertCanWriteFile(apkPath, options.overwrite, "Output APK");
44
-
45
- options.logger?.info(`AAB: ${aabPath}`);
46
- options.logger?.info(`Output directory: ${outDir}`);
47
- options.logger?.info(`APK set: ${apksPath}`);
48
- options.logger?.info(`Output APK: ${apkPath}`);
49
-
50
- await buildUniversalApks({
51
- javaPath,
52
- bundletoolPath,
53
- aabPath,
54
- apksPath,
55
- signing,
56
- overwrite: options.overwrite,
57
- logger: options.logger,
58
- });
59
-
60
- await extractUniversalApk(apksPath, apkPath, options.overwrite);
61
- options.logger?.info(`Extracted universal APK: ${apkPath}`);
62
-
63
- if (!options.keepApks) {
64
- try {
65
- fs.unlinkSync(apksPath);
66
- options.logger?.info(`Removed intermediate APK set: ${apksPath}`);
67
- } catch (e) {
68
- options.logger?.warn(`Failed to remove intermediate APK set: ${e instanceof Error ? e.message : String(e)}`);
69
- }
70
- }
71
-
72
- if (!fs.existsSync(apkPath)) {
73
- throw new UserFacingError("APK_OUTPUT_MISSING", `Output APK was not created: ${apkPath}`);
74
- }
75
-
76
- return {
77
- apkPath,
78
- apksPath: options.keepApks ? apksPath : undefined,
79
- logPath: options.logger?.logPath,
80
- };
81
- }
@@ -1,43 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { UserFacingError } from "../shared/errors.js";
5
-
6
- export function resolveToolRoot(metaUrl: string): string {
7
- const here = path.dirname(fileURLToPath(metaUrl));
8
- return path.resolve(here, "..", "..");
9
- }
10
-
11
- function firstExisting(candidates: string[]): string | null {
12
- for (const candidate of candidates) {
13
- if (fs.existsSync(candidate)) return candidate;
14
- }
15
- return null;
16
- }
17
-
18
- export function resolveBundletoolPath(toolRoot: string): string {
19
- const envPath = process.env.AAB2APK_BUNDLETOOL_PATH?.trim();
20
- if (envPath) return path.resolve(envPath);
21
- const found = firstExisting([
22
- path.join(toolRoot, "tools", "bundletool.jar"),
23
- path.join(toolRoot, "bundled", "android", "bundletool.jar"),
24
- ]);
25
- if (found) return found;
26
- throw new UserFacingError(
27
- "BUNDLETOOL_NOT_FOUND",
28
- `bundletool.jar not found. Expected ${path.join(toolRoot, "tools", "bundletool.jar")}`
29
- );
30
- }
31
-
32
- export function resolveJavaPath(toolRoot: string): string {
33
- const envPath = process.env.AAB2APK_JAVA_PATH?.trim();
34
- if (envPath) return path.resolve(envPath);
35
- const exe = process.platform === "win32" ? "java.exe" : "java";
36
- const candidates = [path.join(toolRoot, "runtime", "jre", "bin", exe)];
37
- if (process.platform === "win32") {
38
- candidates.push(path.join(toolRoot, "runtime", "win-x64", "jre", "bin", "java.exe"));
39
- }
40
- const found = firstExisting(candidates);
41
- if (found) return found;
42
- return exe;
43
- }
@@ -1,29 +0,0 @@
1
- import path from "node:path";
2
- import { UserFacingError } from "../shared/errors.js";
3
- import { assertFileExists } from "../shared/fileUtils.js";
4
- import { toAbsolutePath } from "../shared/pathUtils.js";
5
- import type { SigningOptions } from "./types.js";
6
-
7
- export interface SigningInput {
8
- keyStorePath: string;
9
- keyAlias: string;
10
- keyStorePassword?: string;
11
- keyPassword?: string;
12
- }
13
-
14
- export function resolveSigningOptions(input: SigningInput): SigningOptions {
15
- const keyStorePath = toAbsolutePath(input.keyStorePath);
16
- assertFileExists(keyStorePath, "KEYSTORE_NOT_FOUND", "Keystore file");
17
- if (!input.keyAlias.trim()) {
18
- throw new UserFacingError("SIGNING_ALIAS_MISSING", "Missing --ks-key-alias");
19
- }
20
- if (!input.keyStorePassword) {
21
- throw new UserFacingError("SIGNING_PASSWORD_MISSING", "Missing keystore password");
22
- }
23
- return {
24
- keyStorePath: path.resolve(keyStorePath),
25
- keyAlias: input.keyAlias,
26
- keyStorePassword: input.keyStorePassword,
27
- keyPassword: input.keyPassword || undefined,
28
- };
29
- }
@@ -1,26 +0,0 @@
1
- import type { Logger } from "../shared/logger.js";
2
-
3
- export interface SigningOptions {
4
- keyStorePath: string;
5
- keyAlias: string;
6
- keyStorePassword: string;
7
- keyPassword?: string;
8
- }
9
-
10
- export interface ConvertAabToApkOptions {
11
- aabPath: string;
12
- outDir: string;
13
- signing: SigningOptions;
14
- javaPath: string;
15
- bundletoolPath: string;
16
- apkName?: string;
17
- keepApks: boolean;
18
- overwrite: boolean;
19
- logger?: Logger;
20
- }
21
-
22
- export interface ConvertAabToApkResult {
23
- apkPath: string;
24
- apksPath?: string;
25
- logPath?: string;
26
- }
@@ -1,41 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { UserFacingError } from "./errors.js";
4
-
5
- export function assertFileExists(filePath: string, code: string, label: string): void {
6
- if (!fs.existsSync(filePath)) {
7
- throw new UserFacingError(code, `${label} does not exist: ${filePath}`);
8
- }
9
- if (!fs.statSync(filePath).isFile()) {
10
- throw new UserFacingError(code, `${label} is not a file: ${filePath}`);
11
- }
12
- }
13
-
14
- export function ensureDirectory(dirPath: string): void {
15
- fs.mkdirSync(dirPath, { recursive: true });
16
- if (!fs.statSync(dirPath).isDirectory()) {
17
- throw new UserFacingError("OUTPUT_DIR_INVALID", `Output path is not a directory: ${dirPath}`);
18
- }
19
- }
20
-
21
- export function assertWritableDirectory(dirPath: string): void {
22
- ensureDirectory(dirPath);
23
- try {
24
- const probe = path.join(dirPath, `.write-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
25
- fs.writeFileSync(probe, "");
26
- fs.unlinkSync(probe);
27
- } catch (e) {
28
- throw new UserFacingError(
29
- "OUTPUT_DIR_NOT_WRITABLE",
30
- `Output directory is not writable: ${dirPath}`,
31
- e instanceof Error ? e.message : String(e)
32
- );
33
- }
34
- }
35
-
36
- export function assertCanWriteFile(filePath: string, overwrite: boolean, label: string): void {
37
- if (!overwrite && fs.existsSync(filePath)) {
38
- throw new UserFacingError("OUTPUT_EXISTS", `${label} already exists: ${filePath}`);
39
- }
40
- ensureDirectory(path.dirname(filePath));
41
- }
@@ -1,49 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- export interface Logger {
5
- readonly logPath?: string;
6
- info(message: string): void;
7
- warn(message: string): void;
8
- error(message: string): void;
9
- close(): void;
10
- }
11
-
12
- function timestamp(): string {
13
- return new Date().toISOString();
14
- }
15
-
16
- function logFileName(): string {
17
- const d = new Date();
18
- const pad = (n: number) => String(n).padStart(2, "0");
19
- return `convert-${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(
20
- d.getMinutes()
21
- )}${pad(d.getSeconds())}.log`;
22
- }
23
-
24
- export function createFileLogger(logDir: string): Logger {
25
- fs.mkdirSync(logDir, { recursive: true });
26
- const logPath = path.join(logDir, logFileName());
27
- const stream = fs.createWriteStream(logPath, { flags: "a", encoding: "utf8" });
28
- const write = (level: string, message: string) => {
29
- stream.write(`[${timestamp()}] [${level}] ${message}\n`);
30
- };
31
- return {
32
- logPath,
33
- info: (message) => write("INFO", message),
34
- warn: (message) => write("WARN", message),
35
- error: (message) => write("ERROR", message),
36
- close: () => stream.end(),
37
- };
38
- }
39
-
40
- export function createConsoleLogger(verbose: boolean): Logger {
41
- return {
42
- info: (message) => {
43
- if (verbose) console.log(message);
44
- },
45
- warn: (message) => console.warn(message),
46
- error: (message) => console.error(message),
47
- close: () => undefined,
48
- };
49
- }
@@ -1,24 +0,0 @@
1
- import path from "node:path";
2
- import { UserFacingError } from "./errors.js";
3
-
4
- export function toAbsolutePath(value: string, baseDir = process.cwd()): string {
5
- return path.resolve(baseDir, value);
6
- }
7
-
8
- export function baseNameWithoutExt(filePath: string, expectedExt: string): string {
9
- const parsed = path.parse(filePath);
10
- if (parsed.ext.toLowerCase() !== expectedExt.toLowerCase()) {
11
- throw new UserFacingError("INVALID_EXTENSION", `Expected ${expectedExt} file: ${filePath}`);
12
- }
13
- return parsed.name;
14
- }
15
-
16
- export function validateOutputFileName(fileName: string): string {
17
- if (!fileName.trim()) {
18
- throw new UserFacingError("INVALID_APK_NAME", "--apk-name cannot be empty");
19
- }
20
- if (fileName.includes("/") || fileName.includes("\\") || path.basename(fileName) !== fileName) {
21
- throw new UserFacingError("INVALID_APK_NAME", "--apk-name must be a file name, not a path");
22
- }
23
- return fileName.toLowerCase().endsWith(".apk") ? fileName : `${fileName}.apk`;
24
- }