fix(android): patch android config identifier and avoid Java reserved words

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-28 08:36:30 +08:00
parent 3edfe7030f
commit e4a0955a8e
5 changed files with 116 additions and 15 deletions

View File

@@ -18,6 +18,12 @@ if (!jobId || !appName || !appIdentifier) {
const zipPath = path.join(root, "builds", jobId, "site.zip"); const zipPath = path.join(root, "builds", jobId, "site.zip");
const distDir = path.join(root, "template", "dist"); const distDir = path.join(root, "template", "dist");
const confPath = path.join(root, "template", "src-tauri", "tauri.conf.json"); const confPath = path.join(root, "template", "src-tauri", "tauri.conf.json");
const androidConfPath = path.join(
root,
"template",
"src-tauri",
"tauri.android.conf.json",
);
if (!fs.existsSync(zipPath)) { if (!fs.existsSync(zipPath)) {
console.error(`Missing upload: ${zipPath}`); console.error(`Missing upload: ${zipPath}`);
@@ -36,14 +42,22 @@ if (!fs.existsSync(indexPath)) {
process.exit(1); process.exit(1);
} }
const conf = JSON.parse(fs.readFileSync(confPath, "utf8")); function patchTauriConfig(filePath) {
conf.productName = appName; const conf = JSON.parse(fs.readFileSync(filePath, "utf8"));
conf.mainBinaryName = appName.replace(/[^\w.-]+/g, "") || "Web2App"; conf.productName = appName;
conf.identifier = appIdentifier; conf.mainBinaryName = appName.replace(/[^\w.-]+/g, "") || "Web2App";
conf.version = "1.0.0"; conf.identifier = appIdentifier;
conf.app.windows = conf.app.windows ?? [{}]; conf.version = "1.0.0";
conf.app.windows[0].title = appName; if (conf.app?.windows?.[0]) {
fs.writeFileSync(confPath, `${JSON.stringify(conf, null, 2)}\n`); conf.app.windows[0].title = appName;
}
fs.writeFileSync(filePath, `${JSON.stringify(conf, null, 2)}\n`);
}
patchTauriConfig(confPath);
if (fs.existsSync(androidConfPath)) {
patchTauriConfig(androidConfPath);
}
const iconCandidates = ["icon.png", "favicon.png", "logo.png"]; const iconCandidates = ["icon.png", "favicon.png", "logo.png"];
for (const candidate of iconCandidates) { for (const candidate of iconCandidates) {

View File

@@ -16,7 +16,12 @@ import {
triggerBuildWorkflow, triggerBuildWorkflow,
uploadSiteZip, uploadSiteZip,
} from "../services/github.js"; } from "../services/github.js";
import { slugifyIdentifier, validateZipBuffer, ZipValidationError } from "../services/zip.js"; import {
normalizeAppIdentifier,
slugifyIdentifier,
validateZipBuffer,
ZipValidationError,
} from "../services/zip.js";
const upload = multer({ const upload = multer({
storage: multer.memoryStorage(), storage: multer.memoryStorage(),
@@ -69,9 +74,10 @@ buildsRouter.post("/", upload.single("file"), async (req, res) => {
const { normalizedBuffer } = validateZipBuffer(req.file.buffer, maxUploadBytes); const { normalizedBuffer } = validateZipBuffer(req.file.buffer, maxUploadBytes);
const jobId = nanoid(10); const jobId = nanoid(10);
const appIdentifier = const slug = slugifyIdentifier(appName) || jobId.toLowerCase();
identifierInput || const appIdentifier = normalizeAppIdentifier(
`com.web2app.${slugifyIdentifier(appName) || jobId.toLowerCase()}`; identifierInput || `com.web2app.${slug}`,
);
insertBuild({ id: jobId, appName, appIdentifier }); insertBuild({ id: jobId, appName, appIdentifier });

View File

@@ -93,10 +93,91 @@ function addDirectoryToZip(zip: AdmZip, dir: string, prefix: string) {
} }
} }
const JAVA_RESERVED_SEGMENTS = new Set([
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extends",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"try",
"void",
"volatile",
"while",
]);
function sanitizeSegment(segment: string): string {
let value = segment.toLowerCase().replace(/[^a-z0-9_]/g, "");
if (!value) return "app";
if (/^\d/.test(value)) value = `app${value}`;
if (JAVA_RESERVED_SEGMENTS.has(value)) value = `${value}app`;
return value.slice(0, 48);
}
export function slugifyIdentifier(input: string): string { export function slugifyIdentifier(input: string): string {
return input return input
.toLowerCase() .toLowerCase()
.replace(/[^a-z0-9]+/g, ".") .replace(/[^a-z0-9]+/g, ".")
.replace(/^\.+|\.+$/g, "") .replace(/^\.+|\.+$/g, "")
.slice(0, 48); .split(".")
.map(sanitizeSegment)
.filter(Boolean)
.join(".")
.slice(0, 120);
}
export function normalizeAppIdentifier(identifier: string): string {
const parts = identifier
.trim()
.split(".")
.map(sanitizeSegment)
.filter(Boolean);
if (parts.length < 2) {
throw new ZipValidationError(
"Bundle ID 至少需要两段,例如 com.example.myapp",
);
}
return parts.join(".");
} }

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Web2App", "productName": "Web2App",
"version": "1.0.0", "version": "1.0.0",
"identifier": "com.web2app.default", "identifier": "com.web2app.app",
"app": { "app": {
"windows": [ "windows": [
{ {

View File

@@ -3,7 +3,7 @@
"productName": "Web2App", "productName": "Web2App",
"mainBinaryName": "Web2App", "mainBinaryName": "Web2App",
"version": "1.0.0", "version": "1.0.0",
"identifier": "com.web2app.default", "identifier": "com.web2app.app",
"build": { "build": {
"beforeDevCommand": "", "beforeDevCommand": "",
"beforeBuildCommand": "", "beforeBuildCommand": "",