@leonxin/meetgames 0.1.7 → 0.1.11
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/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
- package/.cursor/mcp.example.json +16 -0
- package/.cursor/mcp.json +11 -0
- package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
- package/.vite/vitest/results.json +1 -0
- package/README.md +31 -8
- package/dist/aab-converter/aab-entry.d.ts +3 -0
- package/dist/aab-converter/aab-entry.d.ts.map +1 -0
- package/dist/aab-converter/aab-entry.js +49 -0
- package/dist/aab-converter/aab-entry.js.map +1 -0
- package/dist/aab-converter/apksExtractor.d.ts +2 -0
- package/dist/aab-converter/apksExtractor.d.ts.map +1 -0
- package/dist/aab-converter/apksExtractor.js +108 -0
- package/dist/aab-converter/apksExtractor.js.map +1 -0
- package/dist/aab-converter/bundletoolRunner.d.ts +15 -0
- package/dist/aab-converter/bundletoolRunner.d.ts.map +1 -0
- package/dist/aab-converter/bundletoolRunner.js +46 -0
- package/dist/aab-converter/bundletoolRunner.js.map +1 -0
- package/dist/aab-converter/cliArgs.d.ts +27 -0
- package/dist/aab-converter/cliArgs.d.ts.map +1 -0
- package/dist/aab-converter/cliArgs.js +170 -0
- package/dist/aab-converter/cliArgs.js.map +1 -0
- package/dist/aab-converter/convertAabToApk.d.ts +7 -0
- package/dist/aab-converter/convertAabToApk.d.ts.map +1 -0
- package/dist/aab-converter/convertAabToApk.js +69 -0
- package/dist/aab-converter/convertAabToApk.js.map +1 -0
- package/dist/aab-converter/resourcePaths.d.ts +4 -0
- package/dist/aab-converter/resourcePaths.d.ts.map +1 -0
- package/dist/aab-converter/resourcePaths.js +42 -0
- package/dist/aab-converter/resourcePaths.js.map +1 -0
- package/dist/aab-converter/signingOptions.d.ts +9 -0
- package/dist/aab-converter/signingOptions.d.ts.map +1 -0
- package/dist/aab-converter/signingOptions.js +21 -0
- package/dist/aab-converter/signingOptions.js.map +1 -0
- package/dist/aab-converter/types.d.ts +24 -0
- package/dist/aab-converter/types.d.ts.map +1 -0
- package/dist/aab-converter/types.js +2 -0
- package/dist/aab-converter/types.js.map +1 -0
- package/dist/android/adapter.d.ts.map +1 -1
- package/dist/android/adapter.js +2 -2
- package/dist/android/adapter.js.map +1 -1
- package/dist/android/detect.d.ts +2 -2
- package/dist/android/detect.d.ts.map +1 -1
- package/dist/android/detect.js +36 -8
- package/dist/android/detect.js.map +1 -1
- package/dist/android/meetSdkRemoteGradle.d.ts +0 -3
- package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
- package/dist/android/meetSdkRemoteGradle.js +13 -20
- package/dist/android/meetSdkRemoteGradle.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +157 -31
- package/dist/cli.js.map +1 -1
- package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
- package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
- package/dist/config/meetSdkDefaultConfig.js +67 -5
- package/dist/config/meetSdkDefaultConfig.js.map +1 -1
- package/dist/config/meetSdkIosConfig.d.ts +21 -0
- package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
- package/dist/config/meetSdkIosConfig.js +66 -0
- package/dist/config/meetSdkIosConfig.js.map +1 -0
- package/dist/config/meetSdkRemoteConfig.d.ts +19 -11
- package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
- package/dist/config/meetSdkRemoteConfig.js +89 -69
- package/dist/config/meetSdkRemoteConfig.js.map +1 -1
- package/dist/config/topsdkFeatureModules.d.ts +5 -0
- package/dist/config/topsdkFeatureModules.d.ts.map +1 -1
- package/dist/config/topsdkFeatureModules.js +26 -0
- package/dist/config/topsdkFeatureModules.js.map +1 -1
- package/dist/contracts/types.d.ts +19 -6
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/core/doctor.d.ts +17 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +444 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +0 -15
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/platform.d.ts +12 -0
- package/dist/core/platform.d.ts.map +1 -0
- package/dist/core/platform.js +40 -0
- package/dist/core/platform.js.map +1 -0
- package/dist/core/reporter.js +1 -1
- package/dist/core/reporter.js.map +1 -1
- package/dist/core/workspace.d.ts +2 -2
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +4 -5
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ios/channelConfig.d.ts +1 -0
- package/dist/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +82 -0
- package/dist/ios/channelConfig.js.map +1 -1
- package/dist/ios/codeUtils.d.ts +1 -0
- package/dist/ios/codeUtils.d.ts.map +1 -1
- package/dist/ios/codeUtils.js +11 -2
- package/dist/ios/codeUtils.js.map +1 -1
- package/dist/ios/detect.d.ts +2 -2
- package/dist/ios/detect.d.ts.map +1 -1
- package/dist/ios/detect.js +49 -10
- package/dist/ios/detect.js.map +1 -1
- package/dist/ios/entitlements.d.ts +4 -0
- package/dist/ios/entitlements.d.ts.map +1 -0
- package/dist/ios/entitlements.js +53 -0
- package/dist/ios/entitlements.js.map +1 -0
- package/dist/ios/fileManager.d.ts.map +1 -1
- package/dist/ios/fileManager.js +3 -2
- package/dist/ios/fileManager.js.map +1 -1
- package/dist/ios/infoPlist.d.ts +1 -1
- package/dist/ios/infoPlist.d.ts.map +1 -1
- package/dist/ios/infoPlist.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +211 -36
- package/dist/ios/integrate.js.map +1 -1
- package/dist/ios/pbxprojEditor.d.ts +2 -0
- package/dist/ios/pbxprojEditor.d.ts.map +1 -1
- package/dist/ios/pbxprojEditor.js +179 -1
- package/dist/ios/pbxprojEditor.js.map +1 -1
- package/dist/ios/pluginConfig.d.ts +1 -0
- package/dist/ios/pluginConfig.d.ts.map +1 -1
- package/dist/ios/pluginConfig.js +36 -4
- package/dist/ios/pluginConfig.js.map +1 -1
- package/dist/ios/sdkBundle.d.ts +1 -1
- package/dist/ios/sdkBundle.d.ts.map +1 -1
- package/dist/ios/sdkBundle.js +7 -5
- package/dist/ios/sdkBundle.js.map +1 -1
- package/dist/ios/template.d.ts +1 -0
- package/dist/ios/template.d.ts.map +1 -1
- package/dist/ios/template.js +14 -1
- package/dist/ios/template.js.map +1 -1
- package/dist/ios/types.d.ts +2 -2
- package/dist/ios/types.d.ts.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service.d.ts +8 -6
- package/dist/mcp/service.d.ts.map +1 -1
- package/dist/mcp/service.js +34 -14
- package/dist/mcp/service.js.map +1 -1
- package/dist/ops/handlers.d.ts.map +1 -1
- package/dist/ops/handlers.js +10 -4
- package/dist/ops/handlers.js.map +1 -1
- package/dist/remote/sdkHomeDownload.d.ts +65 -0
- package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
- package/dist/remote/sdkHomeDownload.js +208 -0
- package/dist/remote/sdkHomeDownload.js.map +1 -0
- package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
- package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
- package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +16 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/fileUtils.d.ts +5 -0
- package/dist/shared/fileUtils.d.ts.map +1 -0
- package/dist/shared/fileUtils.js +35 -0
- package/dist/shared/fileUtils.js.map +1 -0
- package/dist/shared/logger.d.ts +10 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +37 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/pathUtils.d.ts +4 -0
- package/dist/shared/pathUtils.d.ts.map +1 -0
- package/dist/shared/pathUtils.js +22 -0
- package/dist/shared/pathUtils.js.map +1 -0
- package/dist/shared/processRunner.d.ts +12 -0
- package/dist/shared/processRunner.d.ts.map +1 -0
- package/dist/shared/processRunner.js +31 -0
- package/dist/shared/processRunner.js.map +1 -0
- package/docs/AAB_CONVERTER_CLI_PLAN.md +392 -0
- package/docs/API.md +246 -32
- package/docs/CLI.md +292 -0
- package/docs/INTEGRATION.md +116 -0
- package/docs/MCP.md +86 -0
- package/docs/README.md +19 -10
- package/docs/{api → archive/api}/downloadSDKConfig.md +8 -6
- package/docs/{api → archive/api}/getChannelConfig-meetgames.md +1 -1
- package/docs/archive/ios-migration.md +2139 -0
- package/docs/{ → archive/product/}/346/212/200/346/234/257/346/226/271/346/241/210/350/260/203/347/240/224.md +7 -7
- package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +15 -14
- package/logs/convert-20260622-155037.log +5 -0
- package/logs/convert-20260622-155226.log +6 -0
- package/meetsdk-android.json +2 -1
- package/meetsdk-ios.json +15 -0
- package/package.json +10 -35
- package/scripts/package-aab-cli-win.mjs +193 -0
- package/src/aab-converter/aab-entry.ts +48 -0
- package/src/aab-converter/apksExtractor.ts +119 -0
- package/src/aab-converter/bundletoolRunner.ts +63 -0
- package/src/aab-converter/cliArgs.ts +194 -0
- package/src/aab-converter/convertAabToApk.ts +81 -0
- package/src/aab-converter/resourcePaths.ts +43 -0
- package/src/aab-converter/signingOptions.ts +29 -0
- package/src/aab-converter/types.ts +26 -0
- package/src/android/adapter.ts +9 -0
- package/src/android/assembleIntegrationJson.ts +33 -0
- package/src/android/detect.ts +132 -0
- package/src/android/downloadGoogleServicesJson.ts +56 -0
- package/src/android/gradle.ts +116 -0
- package/src/android/manifest.ts +50 -0
- package/src/android/meetSdkRemoteGradle.ts +837 -0
- package/src/cli.ts +488 -0
- package/src/config/fetchConfigWrite.ts +30 -0
- package/src/config/loadAndroidIntegration.ts +41 -0
- package/src/config/loadManifest.ts +40 -0
- package/src/config/meetSdkDefaultConfig.ts +99 -0
- package/src/config/meetSdkIosConfig.ts +87 -0
- package/src/config/meetSdkRemoteConfig.ts +1211 -0
- package/src/config/topsdkFeatureModules.ts +92 -0
- package/src/contracts/types.ts +121 -0
- package/src/core/doctor.ts +485 -0
- package/src/core/patch.ts +64 -0
- package/src/core/pipeline.ts +107 -0
- package/src/core/platform.ts +47 -0
- package/src/core/previewPatches.ts +23 -0
- package/src/core/reporter.ts +24 -0
- package/src/core/workspace.ts +29 -0
- package/src/entry.ts +7 -0
- package/src/index.ts +133 -0
- package/src/ios/channelConfig.ts +128 -0
- package/src/ios/codeUtils.ts +160 -0
- package/src/ios/detect.ts +105 -0
- package/src/ios/entitlements.ts +61 -0
- package/src/ios/fileManager.ts +48 -0
- package/src/ios/infoPlist.ts +55 -0
- package/src/ios/integrate.ts +516 -0
- package/src/ios/pbxprojEditor.ts +383 -0
- package/src/ios/pluginConfig.ts +97 -0
- package/src/ios/reserved.ts +8 -0
- package/src/ios/sdkBundle.ts +36 -0
- package/src/ios/template.ts +36 -0
- package/src/ios/types.ts +65 -0
- package/src/mcp/server.ts +170 -0
- package/src/mcp/service.ts +222 -0
- package/src/mcp-entry.ts +7 -0
- package/src/ops/fileStore.ts +56 -0
- package/src/ops/handlers.ts +304 -0
- package/src/remote/fetchJson.ts +22 -0
- package/src/remote/sdkHomeDownload.ts +274 -0
- package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
- package/src/remote/topsdkGetSdkConfig.ts +122 -0
- package/src/remote/topsdkSign.ts +10 -0
- package/src/shared/errors.ts +16 -0
- package/src/shared/fileUtils.ts +41 -0
- package/src/shared/logger.ts +49 -0
- package/src/shared/pathUtils.ts +24 -0
- package/src/shared/processRunner.ts +43 -0
- package/test-projects/README.md +51 -0
- package/test-projects/_preview/pipeline.patch +281 -0
- package/tests/aab-converter.test.ts +213 -0
- package/tests/assemble.test.ts +12 -0
- package/tests/doctor.test.ts +89 -0
- package/tests/downloadGoogleServicesJson.test.ts +47 -0
- package/tests/fetch-remote.test.ts +23 -0
- package/tests/fetchConfigOverrides.test.ts +28 -0
- package/tests/fetchConfigWrite.test.ts +54 -0
- package/tests/gradle.test.ts +33 -0
- package/tests/integration-json.test.ts +29 -0
- package/tests/ios.codeUtils.test.ts +23 -0
- package/tests/ios.sdkBundle.test.ts +16 -0
- package/tests/loadManifest.test.ts +15 -0
- package/tests/manifest-xml.test.ts +30 -0
- package/tests/mcp.e2e.ts +217 -0
- package/tests/mcp.service.test.ts +53 -0
- package/tests/meetSdkRemoteConfig.test.ts +456 -0
- package/tests/meetSdkRemoteGradle.test.ts +414 -0
- package/tests/pipeline.android.test.ts +96 -0
- package/tests/pipeline.integration-json.test.ts +58 -0
- package/tests/pipeline.ios.test.ts +385 -0
- package/tests/pipeline.preview.patch.test.ts +85 -0
- package/tests/platformSelection.test.ts +77 -0
- package/tests/sdkHomeDownload.test.ts +124 -0
- package/tests/sdkVersionConfig.test.ts +130 -0
- package/tests/test-projects-hosts.test.ts +78 -0
- package/tests/topsdk.test.ts +53 -0
- package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
- package/tests/topsdkFeatureModules.test.ts +116 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +9 -0
- package/vitest.mcp.config.ts +11 -0
- package/bundled/android/sample.txt +0 -1
- package/docs/ANDROID.md +0 -133
- package/docs/CURSOR-MCP-SETUP.md +0 -72
- package/docs/MCP-SKILL.md +0 -63
- package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
- package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
- package/fixtures/meetsdk-remote-config.mock.json +0 -69
- package/fixtures/recipes/android-default.fixture.yaml +0 -15
- package/fixtures/recipes/android-integration.fixture.json +0 -29
- package/fixtures/topsdk-config-reference.json +0 -39
- /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AndroidDetectOptions, AndroidDetectResult, AndroidPlatformAdapter } from "../contracts/types.js";
|
|
2
|
+
import { detectAndroid } from "./detect.js";
|
|
3
|
+
|
|
4
|
+
export const androidPlatformAdapter: AndroidPlatformAdapter = {
|
|
5
|
+
id: "android",
|
|
6
|
+
detect(projectRoot: string, options?: AndroidDetectOptions): AndroidDetectResult {
|
|
7
|
+
return detectAndroid(projectRoot, options);
|
|
8
|
+
},
|
|
9
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AndroidIntegrationDocument } from "../contracts/types.js";
|
|
2
|
+
|
|
3
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
4
|
+
return typeof v === "object" && v !== null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Normalizes common API envelopes into the integration document shape.
|
|
9
|
+
* Supported roots: top-level `{ version, steps }`, `{ data: ... }`, `{ config: ... }`, `{ android: ... }` (when it contains steps).
|
|
10
|
+
*/
|
|
11
|
+
export function unwrapRemoteIntegrationPayload(body: unknown): unknown {
|
|
12
|
+
if (!isRecord(body)) return body;
|
|
13
|
+
if (Array.isArray(body.steps)) return body;
|
|
14
|
+
if (isRecord(body.data) && Array.isArray(body.data.steps)) return body.data;
|
|
15
|
+
if (isRecord(body.config) && Array.isArray(body.config.steps)) return body.config;
|
|
16
|
+
if (isRecord(body.android) && Array.isArray(body.android.steps)) return body.android;
|
|
17
|
+
return body;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function attachIntegrationMeta(
|
|
21
|
+
doc: AndroidIntegrationDocument,
|
|
22
|
+
sourceUrl: string,
|
|
23
|
+
fetchedAt: string
|
|
24
|
+
): AndroidIntegrationDocument {
|
|
25
|
+
return {
|
|
26
|
+
...doc,
|
|
27
|
+
meta: {
|
|
28
|
+
...(doc.meta ?? {}),
|
|
29
|
+
sourceUrl,
|
|
30
|
+
fetchedAt,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { AndroidDetectOptions, AndroidDetectResult } from "../contracts/types.js";
|
|
4
|
+
|
|
5
|
+
function parseIncludes(settingsContent: string): string[] {
|
|
6
|
+
const includes: string[] = [];
|
|
7
|
+
const includeRegex = /include\s+([^\n]+)/g;
|
|
8
|
+
let match = includeRegex.exec(settingsContent);
|
|
9
|
+
while (match) {
|
|
10
|
+
const raw = match[1];
|
|
11
|
+
const parts = raw
|
|
12
|
+
.split(",")
|
|
13
|
+
.map((x) => x.trim())
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.map((x) => x.replace(/^['"]|['"]$/g, ""));
|
|
16
|
+
includes.push(...parts);
|
|
17
|
+
match = includeRegex.exec(settingsContent);
|
|
18
|
+
}
|
|
19
|
+
return includes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isApplicationModule(buildGradleContent: string): boolean {
|
|
23
|
+
const applyPlugin = /apply\s+plugin:\s*['"]com\.android\.application['"]/m.test(buildGradleContent);
|
|
24
|
+
const pluginsBlock = /id\s*\(?\s*['"]com\.android\.application['"]\s*\)?/m.test(buildGradleContent);
|
|
25
|
+
const pluginAlias = /\balias\s*\(\s*libs\.plugins\.[A-Za-z0-9_.-]*application[A-Za-z0-9_.-]*\s*\)/m.test(buildGradleContent);
|
|
26
|
+
const hasAndroidBlock = /\bandroid\s*\{/m.test(buildGradleContent);
|
|
27
|
+
const hasApplicationId = /\bapplicationId\s+(?:=+\s*)?["'][^"']+["']/m.test(buildGradleContent);
|
|
28
|
+
return applyPlugin || pluginsBlock || pluginAlias || (hasAndroidBlock && hasApplicationId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function moduleNameToDir(projectRoot: string, moduleName: string): string {
|
|
32
|
+
const normalized = moduleName.startsWith(":") ? moduleName.slice(1) : moduleName;
|
|
33
|
+
const relative = normalized.replace(/:/g, "/");
|
|
34
|
+
return path.join(projectRoot, relative);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function defaultManifestPath(moduleDir: string): string {
|
|
38
|
+
return path.join(moduleDir, "src", "main", "AndroidManifest.xml");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeModuleSelector(value: string): string {
|
|
42
|
+
const trimmed = value.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
43
|
+
if (!trimmed) return "";
|
|
44
|
+
const asGradlePath = trimmed.replace(/\//g, ":");
|
|
45
|
+
return asGradlePath.startsWith(":") ? asGradlePath : `:${asGradlePath}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function applicationModuleList(modules: { moduleName: string }[]): string {
|
|
49
|
+
return modules.map((x) => x.moduleName).join(", ");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function successResult(projectRoot: string, moduleName: string, moduleDir: string): AndroidDetectResult {
|
|
53
|
+
const manifestPath = defaultManifestPath(moduleDir);
|
|
54
|
+
if (!fs.existsSync(manifestPath)) {
|
|
55
|
+
return { ok: false, error: `AndroidManifest.xml not found at ${manifestPath}` };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok: true, moduleName, moduleDir, manifestPath };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function detectAndroid(projectRoot: string, options: AndroidDetectOptions = {}): AndroidDetectResult {
|
|
62
|
+
const settingsGradle = path.join(projectRoot, "settings.gradle");
|
|
63
|
+
const settingsGradleKts = path.join(projectRoot, "settings.gradle.kts");
|
|
64
|
+
let settingsContent = "";
|
|
65
|
+
if (fs.existsSync(settingsGradle)) {
|
|
66
|
+
settingsContent = fs.readFileSync(settingsGradle, "utf8");
|
|
67
|
+
} else if (fs.existsSync(settingsGradleKts)) {
|
|
68
|
+
settingsContent = fs.readFileSync(settingsGradleKts, "utf8");
|
|
69
|
+
} else {
|
|
70
|
+
return { ok: false, error: "settings.gradle / settings.gradle.kts not found in project root" };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const modules = parseIncludes(settingsContent);
|
|
74
|
+
if (!modules.length) {
|
|
75
|
+
return { ok: false, error: "no modules found in settings.gradle" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const applicationModules: { moduleName: string; moduleDir: string }[] = [];
|
|
79
|
+
for (const moduleName of modules) {
|
|
80
|
+
const moduleDir = moduleNameToDir(projectRoot, moduleName);
|
|
81
|
+
const candidates = [path.join(moduleDir, "build.gradle"), path.join(moduleDir, "build.gradle.kts")];
|
|
82
|
+
const moduleGradle = candidates.find((p) => fs.existsSync(p));
|
|
83
|
+
if (!moduleGradle) continue;
|
|
84
|
+
const content = fs.readFileSync(moduleGradle, "utf8");
|
|
85
|
+
if (isApplicationModule(content)) {
|
|
86
|
+
applicationModules.push({ moduleName, moduleDir });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const requestedModule = options.appTarget ? normalizeModuleSelector(options.appTarget) : "";
|
|
91
|
+
if (applicationModules.length === 0) {
|
|
92
|
+
return { ok: false, error: "NO_APPLICATION_MODULE_FOUND" };
|
|
93
|
+
}
|
|
94
|
+
if (requestedModule) {
|
|
95
|
+
const selected = applicationModules.find((x) => normalizeModuleSelector(x.moduleName) === requestedModule);
|
|
96
|
+
if (!selected) {
|
|
97
|
+
const list = applicationModuleList(applicationModules);
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: `ANDROID_APPLICATION_MODULE_NOT_FOUND: ${requestedModule}; available application modules: ${list}`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return successResult(projectRoot, selected.moduleName, selected.moduleDir);
|
|
104
|
+
}
|
|
105
|
+
if (applicationModules.length > 1) {
|
|
106
|
+
const list = applicationModuleList(applicationModules);
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
error: `MULTIPLE_APPLICATION_MODULES_FOUND: ${list}; pass --app-target <module> to select one`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { moduleName, moduleDir } = applicationModules[0];
|
|
114
|
+
return successResult(projectRoot, moduleName, moduleDir);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Relative path from project root to the detected application module directory (e.g. `app`, `launcher`). */
|
|
118
|
+
export function applicationModuleRelPath(
|
|
119
|
+
projectRoot: string,
|
|
120
|
+
android: Extract<AndroidDetectResult, { ok: true }>
|
|
121
|
+
): string {
|
|
122
|
+
return path.relative(projectRoot, android.moduleDir).split(path.sep).join("/");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Default `google-services.json` location: `{applicationModule}/google-services.json`. */
|
|
126
|
+
export function defaultGoogleServicesJsonRelPath(
|
|
127
|
+
projectRoot: string,
|
|
128
|
+
android: Extract<AndroidDetectResult, { ok: true }>
|
|
129
|
+
): string {
|
|
130
|
+
const moduleRel = applicationModuleRelPath(projectRoot, android);
|
|
131
|
+
return moduleRel ? `${moduleRel}/google-services.json` : "google-services.json";
|
|
132
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { MeetSdkAnalyticsFirebaseCredentials } from "../config/meetSdkRemoteConfig.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve target file under the detected Android application module:
|
|
7
|
+
* `{projectRoot}/{applicationModuleRel}/{fileName}`.
|
|
8
|
+
* `fileName` comes from remote `firebase_file_name` (DB `firebase_name`, upload original name + suffix).
|
|
9
|
+
* Relative path is not configured in MG; meet-sdk-tool detects the app module from `projectRoot`.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveGoogleServicesJsonTarget(
|
|
12
|
+
projectRoot: string,
|
|
13
|
+
firebase: MeetSdkAnalyticsFirebaseCredentials,
|
|
14
|
+
applicationModuleRelPath?: string
|
|
15
|
+
): string {
|
|
16
|
+
const fileName = firebase.firebase_file_name?.trim() || "google-services.json";
|
|
17
|
+
const moduleRel = applicationModuleRelPath?.trim().replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18
|
+
if (moduleRel) {
|
|
19
|
+
return path.join(projectRoot, moduleRel, fileName);
|
|
20
|
+
}
|
|
21
|
+
return path.join(projectRoot, fileName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function downloadGoogleServicesJson(
|
|
25
|
+
projectRoot: string,
|
|
26
|
+
firebase: MeetSdkAnalyticsFirebaseCredentials,
|
|
27
|
+
options?: { applicationModuleRelPath?: string; dryRun?: boolean }
|
|
28
|
+
): Promise<{ ok: true; relPath: string; absPath: string } | { ok: false; error: string }> {
|
|
29
|
+
const url = firebase.firebase_file_url?.trim();
|
|
30
|
+
if (!url) {
|
|
31
|
+
return { ok: false, error: "firebase_file_url is empty" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const absPath = resolveGoogleServicesJsonTarget(projectRoot, firebase, options?.applicationModuleRelPath);
|
|
35
|
+
const relPath = path.relative(projectRoot, absPath).split(path.sep).join("/");
|
|
36
|
+
|
|
37
|
+
if (options?.dryRun) {
|
|
38
|
+
return { ok: true, relPath, absPath };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let res: Response;
|
|
42
|
+
try {
|
|
43
|
+
res = await fetch(url);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
46
|
+
return { ok: false, error: `fetch failed: ${msg}` };
|
|
47
|
+
}
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
53
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
54
|
+
fs.writeFileSync(absPath, buf);
|
|
55
|
+
return { ok: true, relPath, absPath };
|
|
56
|
+
}
|