@iyexin/mio-android-tools 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mio contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Third-party components (Flutter/Dart, webview_flutter, webview_flutter_windows,
26
+ window_manager, file_selector, url_launcher, path_provider, @electron/asar,
27
+ resedit, apksigner/zipalign, and the Dart/npm dependencies listed in
28
+ packages/*/THIRD_PARTY_NOTICES.md) keep their own licenses. The prebuilt
29
+ Android/Windows templates in packages/cli/templates embed the Flutter engine;
30
+ review those licenses before redistributing an app built with Mio.
package/README.md CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # @iyexin/mio-android-tools
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Android APK 工具链封装。负责:
4
+
5
+ - 发现本机 Android SDK build-tools / Java,或使用 Mio 下载的工具包;
6
+ - `zipalign`(4KB / 16KB page size);
7
+ - `apksigner` 签名(v1/v2/v3);
8
+ - `apksigner verify` 解析;
9
+ - 调试 keystore 生成。
10
+
11
+ ## 为什么需要它
12
+
13
+ Android APK 必须签名;targetSdk 30+ 只带 v1 的 APK 在 Android 11+ 无法安装。
14
+ `apksigner` 是目前最成熟的 v2+ 签名实现,但它需要 JVM。Mio 的策略是:
15
+
16
+ 1. 优先复用系统 Java + Android SDK;
17
+ 2. 没有时下载最小 JRE + `apksigner.jar` + `zipalign` 的工具包;
18
+ 3. CLI 不要求开发者安装 Android Studio。
19
+
20
+ ## 用法
21
+
22
+ ```ts
23
+ import { discoverAndroidTools, signApk } from "@iyexin/mio-android-tools";
24
+
25
+ const tools = await discoverAndroidTools();
26
+
27
+ const result = await signApk({
28
+ tools,
29
+ input: "app-unsigned.apk",
30
+ output: "app-release.apk",
31
+ keystore: "release.p12",
32
+ keystorePassword: process.env.MIO_KS_PASS!,
33
+ keyAlias: "release",
34
+ minSdkVersion: 24,
35
+ pageSize: 16,
36
+ });
37
+
38
+ console.log(result.verify.v2, result.verify.v3);
39
+ ```
40
+
41
+ `signApk` 的固定顺序是:**zipalign → apksigner sign → apksigner verify**。
42
+ `apksigner` 不负责对齐,`zipalign` 不能省。
43
+
44
+ ## 工具包(无 Android SDK 的机器)
45
+
46
+ `installToolBundle` 负责下载、SHA-256 校验、解压和缓存:
47
+
48
+ ```ts
49
+ import { installToolBundle } from "@iyexin/mio-android-tools";
50
+
51
+ const installed = await installToolBundle({
52
+ manifest: "https://example.com/mio-android-tools.json",
53
+ version: "0.1.0",
54
+ // 默认读取 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY
55
+ proxy: "http://127.0.0.1:7897",
56
+ });
57
+
58
+ const result = await signApk({
59
+ tools: installed.tools,
60
+ ...
61
+ });
62
+ ```
63
+
64
+ 默认缓存目录:`~/.mio/tools/android/<version>/<platform>/`。
65
+ 缓存命中条件:版本、平台、SHA-256 都匹配。
66
+
67
+ ### 构建工具包
68
+
69
+ ```bash
70
+ node scripts/build-bundle.mjs \
71
+ --jre <jre-dir> \
72
+ --apksigner <build-tools>/lib/apksigner.jar \
73
+ --zipalign <build-tools>/zipalign.exe \
74
+ --out artifacts/mio-android-tools-win32-x64.tar.gz
75
+ ```
76
+
77
+ 脚本会打印 manifest 里所需的 `sha256` 和 `size`。
78
+
79
+ ## 测试
80
+
81
+ ```bash
82
+ pnpm --filter @iyexin/mio-android-tools test
83
+ ```
84
+
85
+ 测试会使用本机 Android SDK/JAVA_HOME;没有配置时自动跳过签名集成测试。
86
+ `test/fixtures/base.apk` 是只用于签名测试的最小 APK,`test.p12` 是测试密钥,
87
+ 都不是发布材料。
package/dist/apk.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { type ManifestPatch } from "./axml.js";
2
+ export interface InjectApkOptions {
3
+ templateApk: string;
4
+ asar: string;
5
+ output: string;
6
+ manifest: ManifestPatch;
7
+ /** Optional source image used to replace the template's launcher icons. */
8
+ icon?: string;
9
+ }
10
+ export interface InjectApkResult {
11
+ output: string;
12
+ entries: number;
13
+ asarBytes: number;
14
+ }
15
+ declare const SIGNATURE_ENTRY: RegExp;
16
+ /**
17
+ * Inject app.asar into a prebuilt Flutter APK and patch its manifest.
18
+ *
19
+ * This intentionally does not sign. The caller must run zipalign + apksigner
20
+ * afterwards (see signApk in @iyexin/mio-android-tools).
21
+ */
22
+ export declare function injectApk(options: InjectApkOptions): Promise<InjectApkResult>;
23
+ export { SIGNATURE_ENTRY };
package/dist/apk.js ADDED
@@ -0,0 +1,109 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { Buffer } from "node:buffer";
5
+ import yauzl from "yauzl";
6
+ import yazl from "yazl";
7
+ import { patchManifestAxml } from "./axml.js";
8
+ const SIGNATURE_ENTRY = /^META-INF\/(?:MANIFEST\.MF|.*\.(?:SF|RSA|DSA|EC))$/i;
9
+ /**
10
+ * Inject app.asar into a prebuilt Flutter APK and patch its manifest.
11
+ *
12
+ * This intentionally does not sign. The caller must run zipalign + apksigner
13
+ * afterwards (see signApk in @iyexin/mio-android-tools).
14
+ */
15
+ export async function injectApk(options) {
16
+ const entries = await readEntries(options.templateApk);
17
+ const asar = await readFile(options.asar);
18
+ if (asar.length === 0)
19
+ throw new Error("app.asar is empty");
20
+ const manifestEntry = entries.find((entry) => entry.name === "AndroidManifest.xml");
21
+ if (!manifestEntry)
22
+ throw new Error("template APK has no AndroidManifest.xml");
23
+ manifestEntry.data = patchManifestAxml(manifestEntry.data, options.manifest);
24
+ if (options.icon) {
25
+ await replaceLauncherIcons(entries, options.icon);
26
+ }
27
+ const outputEntries = entries.filter((entry) => {
28
+ if (SIGNATURE_ENTRY.test(entry.name))
29
+ return false;
30
+ if (entry.name === "assets/mio/app.asar")
31
+ return false;
32
+ return true;
33
+ });
34
+ const zip = new yazl.ZipFile();
35
+ for (const entry of outputEntries) {
36
+ zip.addBuffer(entry.data, entry.name, {
37
+ compress: entry.compress,
38
+ mtime: new Date(0),
39
+ });
40
+ }
41
+ zip.addBuffer(asar, "assets/mio/app.asar", {
42
+ compress: true,
43
+ mtime: new Date(0),
44
+ });
45
+ zip.end();
46
+ await pipeline(zip.outputStream, createWriteStream(options.output));
47
+ return {
48
+ output: options.output,
49
+ entries: outputEntries.length + 1,
50
+ asarBytes: asar.length,
51
+ };
52
+ }
53
+ function readEntries(path) {
54
+ return new Promise((resolve, reject) => {
55
+ yauzl.open(path, { lazyEntries: true, autoClose: true }, (error, zip) => {
56
+ if (error || !zip) {
57
+ reject(error ?? new Error("cannot open APK"));
58
+ return;
59
+ }
60
+ const entries = [];
61
+ zip.on("error", reject);
62
+ zip.on("end", () => resolve(entries));
63
+ zip.on("entry", (entry) => {
64
+ if (entry.fileName.endsWith("/")) {
65
+ zip.readEntry();
66
+ return;
67
+ }
68
+ if (entry.compressionMethod !== 0 && entry.compressionMethod !== 8) {
69
+ reject(new Error(`unsupported ZIP compression method: ${entry.compressionMethod}`));
70
+ return;
71
+ }
72
+ zip.openReadStream(entry, (streamError, stream) => {
73
+ if (streamError || !stream) {
74
+ reject(streamError ?? new Error(`cannot read ${entry.fileName}`));
75
+ return;
76
+ }
77
+ const chunks = [];
78
+ stream.on("data", (chunk) => chunks.push(chunk));
79
+ stream.on("error", reject);
80
+ stream.on("end", () => {
81
+ entries.push({
82
+ name: entry.fileName.replaceAll("\\", "/"),
83
+ data: Buffer.concat(chunks),
84
+ compress: entry.compressionMethod !== 0,
85
+ });
86
+ zip.readEntry();
87
+ });
88
+ });
89
+ });
90
+ zip.readEntry();
91
+ });
92
+ });
93
+ }
94
+ export { SIGNATURE_ENTRY };
95
+ async function replaceLauncherIcons(entries, iconPath) {
96
+ const { default: sharp } = await import("sharp");
97
+ const icon = await readFile(iconPath);
98
+ const launcher = entries.filter((entry) => entry.name.startsWith("res/") && entry.name.endsWith(".png"));
99
+ for (const entry of launcher) {
100
+ const metadata = await sharp(entry.data).metadata();
101
+ if (!metadata.width || !metadata.height)
102
+ continue;
103
+ entry.data = await sharp(icon)
104
+ .resize(metadata.width, metadata.height, { fit: "cover" })
105
+ .png()
106
+ .toBuffer();
107
+ }
108
+ }
109
+ //# sourceMappingURL=apk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apk.js","sourceRoot":"","sources":["../src/apk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { AndroidTools } from "./discover.js";
2
+ export interface SignApkWithApksignerOptions {
3
+ input: string;
4
+ output: string;
5
+ keystore: string;
6
+ keystorePassword: string;
7
+ keyAlias: string;
8
+ keyPassword?: string;
9
+ minSdkVersion?: number;
10
+ v1?: boolean;
11
+ v2?: boolean;
12
+ v3?: boolean;
13
+ verbose?: boolean;
14
+ }
15
+ export interface SignerCertificate {
16
+ signer: number;
17
+ dn?: string;
18
+ sha256?: string;
19
+ sha1?: string;
20
+ md5?: string;
21
+ keyAlgorithm?: string;
22
+ keySizeBits?: number;
23
+ }
24
+ export interface VerifyResult {
25
+ verified: boolean;
26
+ v1: boolean;
27
+ v2: boolean;
28
+ v3: boolean;
29
+ v4: boolean;
30
+ signers: SignerCertificate[];
31
+ exitCode: number;
32
+ raw: string;
33
+ }
34
+ /**
35
+ * Parse `apksigner verify --verbose --print-certs` output.
36
+ * Exported so the parser can be unit tested without a JVM.
37
+ */
38
+ export declare function parseVerifyOutput(output: string, exitCode?: number): VerifyResult;
39
+ export declare function buildSignArgs(options: SignApkWithApksignerOptions): string[];
40
+ export declare function signApkWithApksigner(tools: AndroidTools, options: SignApkWithApksignerOptions): Promise<{
41
+ stdout: string;
42
+ stderr: string;
43
+ }>;
44
+ export interface VerifyApkOptions {
45
+ minSdkVersion?: number;
46
+ printCerts?: boolean;
47
+ }
48
+ export declare function verifyApkWithApksigner(tools: AndroidTools, apk: string, options?: VerifyApkOptions): Promise<VerifyResult>;
@@ -0,0 +1,158 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { MioToolError } from "./errors.js";
4
+ import { run } from "./exec.js";
5
+ async function exists(path) {
6
+ try {
7
+ await access(path, constants.F_OK);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ function parseBool(value) {
15
+ return value?.toLowerCase() === "true";
16
+ }
17
+ /**
18
+ * Parse `apksigner verify --verbose --print-certs` output.
19
+ * Exported so the parser can be unit tested without a JVM.
20
+ */
21
+ export function parseVerifyOutput(output, exitCode = 0) {
22
+ const normalized = output.replace(/\r\n/g, "\n");
23
+ const scheme = (label) => {
24
+ const expression = new RegExp(`Verified using ${label}[^:]*:\\s*(true|false)`, "i");
25
+ return parseBool(expression.exec(normalized)?.[1]);
26
+ };
27
+ const signers = [];
28
+ let current;
29
+ for (const line of normalized.split("\n")) {
30
+ const signerMatch = /^Signer #(\d+) certificate DN:\s*(.*)$/.exec(line);
31
+ if (signerMatch) {
32
+ current = { signer: Number(signerMatch[1]), dn: signerMatch[2] };
33
+ signers.push(current);
34
+ continue;
35
+ }
36
+ if (!current)
37
+ continue;
38
+ const sha256 = /^Signer #\d+ certificate SHA-256 digest:\s*(\S+)/.exec(line);
39
+ if (sha256) {
40
+ current.sha256 = sha256[1];
41
+ continue;
42
+ }
43
+ const sha1 = /^Signer #\d+ certificate SHA-1 digest:\s*(\S+)/.exec(line);
44
+ if (sha1) {
45
+ current.sha1 = sha1[1];
46
+ continue;
47
+ }
48
+ const md5 = /^Signer #\d+ certificate MD5 digest:\s*(\S+)/.exec(line);
49
+ if (md5) {
50
+ current.md5 = md5[1];
51
+ continue;
52
+ }
53
+ const key = /^Signer #\d+ key algorithm:\s*(.+)$/.exec(line);
54
+ if (key) {
55
+ current.keyAlgorithm = key[1]?.trim();
56
+ continue;
57
+ }
58
+ const bits = /^Signer #\d+ key size \(bits\):\s*(\d+)/.exec(line);
59
+ if (bits) {
60
+ current.keySizeBits = Number(bits[1]);
61
+ }
62
+ }
63
+ const verified = exitCode === 0 &&
64
+ /^Verifies$/m.test(normalized) &&
65
+ !/DOES NOT VERIFY/i.test(normalized);
66
+ return {
67
+ verified,
68
+ v1: scheme("v1 scheme"),
69
+ v2: scheme("v2 scheme"),
70
+ v3: scheme("v3 scheme"),
71
+ v4: scheme("v4 scheme"),
72
+ signers,
73
+ exitCode,
74
+ raw: normalized,
75
+ };
76
+ }
77
+ function resolveV1(options) {
78
+ if (options.v1 !== undefined)
79
+ return options.v1;
80
+ if (options.minSdkVersion !== undefined)
81
+ return options.minSdkVersion < 24;
82
+ return true;
83
+ }
84
+ export function buildSignArgs(options) {
85
+ const v1 = resolveV1(options);
86
+ const v2 = options.v2 ?? true;
87
+ const v3 = options.v3 ?? true;
88
+ const args = [
89
+ "sign",
90
+ "--ks",
91
+ options.keystore,
92
+ "--ks-key-alias",
93
+ options.keyAlias,
94
+ "--ks-pass",
95
+ "env:MIO_KS_PASS",
96
+ "--key-pass",
97
+ "env:MIO_KEY_PASS",
98
+ "--out",
99
+ options.output,
100
+ "--v1-signing-enabled",
101
+ String(v1),
102
+ "--v2-signing-enabled",
103
+ String(v2),
104
+ "--v3-signing-enabled",
105
+ String(v3),
106
+ ];
107
+ if (options.minSdkVersion !== undefined) {
108
+ args.push("--min-sdk-version", String(options.minSdkVersion));
109
+ }
110
+ if (options.verbose) {
111
+ args.push("--verbose");
112
+ }
113
+ args.push(options.input);
114
+ return args;
115
+ }
116
+ export async function signApkWithApksigner(tools, options) {
117
+ if (!(await exists(options.input))) {
118
+ throw new MioToolError("MISSING_FILE", `Input APK does not exist: ${options.input}`);
119
+ }
120
+ if (!(await exists(options.keystore))) {
121
+ throw new MioToolError("KEYSTORE_NOT_FOUND", `Keystore does not exist: ${options.keystore}`);
122
+ }
123
+ const keyPassword = options.keyPassword ?? options.keystorePassword;
124
+ const result = await run(tools.java, ["-jar", tools.apksignerJar, ...buildSignArgs(options)], {
125
+ env: {
126
+ MIO_KS_PASS: options.keystorePassword,
127
+ MIO_KEY_PASS: keyPassword,
128
+ },
129
+ timeoutMs: 300_000,
130
+ });
131
+ if (result.code !== 0) {
132
+ throw new MioToolError("APKSIGNER_SIGN_FAILED", "apksigner sign failed", {
133
+ stdout: result.stdout,
134
+ stderr: result.stderr,
135
+ details: `exit code ${result.code}`,
136
+ });
137
+ }
138
+ return { stdout: result.stdout, stderr: result.stderr };
139
+ }
140
+ export async function verifyApkWithApksigner(tools, apk, options = {}) {
141
+ if (!(await exists(apk))) {
142
+ throw new MioToolError("MISSING_FILE", `APK does not exist: ${apk}`);
143
+ }
144
+ const args = ["verify", "--verbose"];
145
+ if (options.printCerts ?? true) {
146
+ args.push("--print-certs");
147
+ }
148
+ if (options.minSdkVersion !== undefined) {
149
+ args.push("--min-sdk-version", String(options.minSdkVersion));
150
+ }
151
+ args.push(apk);
152
+ const result = await run(tools.java, ["-jar", tools.apksignerJar, ...args], {
153
+ timeoutMs: 120_000,
154
+ });
155
+ const raw = `${result.stdout}${result.stderr}`;
156
+ return parseVerifyOutput(raw, result.code);
157
+ }
158
+ //# sourceMappingURL=apksigner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apksigner.js","sourceRoot":"","sources":["../src/apksigner.ts"],"names":[],"mappings":"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"}
package/dist/axml.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { Buffer } from "node:buffer";
2
+ export interface ManifestPatch {
3
+ packageName: string;
4
+ versionCode?: number;
5
+ versionName?: string;
6
+ appName?: string;
7
+ }
8
+ export interface AxmlAttribute {
9
+ chunkOffset: number;
10
+ attributeOffset: number;
11
+ namespace: number;
12
+ name: number;
13
+ rawValue: number;
14
+ valueType: number;
15
+ data: number;
16
+ }
17
+ export interface AxmlElement {
18
+ chunkOffset: number;
19
+ name: string;
20
+ attributes: AxmlAttribute[];
21
+ }
22
+ export interface AxmlDocument {
23
+ strings: string[];
24
+ elements: AxmlElement[];
25
+ encoding: "utf8" | "utf16";
26
+ }
27
+ export declare function parseAxml(buffer: Buffer): AxmlDocument;
28
+ /**
29
+ * Patch a binary AndroidManifest.xml.
30
+ *
31
+ * The template's MainActivity class is intentionally qualified with the
32
+ * template package (dev.mio.mio_shell.MainActivity). The manifest package and
33
+ * package-derived permission/authority strings are rewritten, but that class
34
+ * name must remain untouched so Android can still resolve it.
35
+ */
36
+ export declare function patchManifestAxml(buffer: Buffer, patch: ManifestPatch): Buffer;