From e4a0955a8e848e0970f440aec41b117be06d1b4b Mon Sep 17 00:00:00 2001 From: shumengya Date: Thu, 28 May 2026 08:36:30 +0800 Subject: [PATCH] fix(android): patch android config identifier and avoid Java reserved words Co-authored-by: Cursor --- .github/scripts/prepare-template.mjs | 30 +++++--- server/routes/builds.ts | 14 ++-- server/services/zip.ts | 83 +++++++++++++++++++++- template/src-tauri/tauri.android.conf.json | 2 +- template/src-tauri/tauri.conf.json | 2 +- 5 files changed, 116 insertions(+), 15 deletions(-) diff --git a/.github/scripts/prepare-template.mjs b/.github/scripts/prepare-template.mjs index b73fbf2..3efd76a 100644 --- a/.github/scripts/prepare-template.mjs +++ b/.github/scripts/prepare-template.mjs @@ -18,6 +18,12 @@ if (!jobId || !appName || !appIdentifier) { const zipPath = path.join(root, "builds", jobId, "site.zip"); const distDir = path.join(root, "template", "dist"); 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)) { console.error(`Missing upload: ${zipPath}`); @@ -36,14 +42,22 @@ if (!fs.existsSync(indexPath)) { process.exit(1); } -const conf = JSON.parse(fs.readFileSync(confPath, "utf8")); -conf.productName = appName; -conf.mainBinaryName = appName.replace(/[^\w.-]+/g, "") || "Web2App"; -conf.identifier = appIdentifier; -conf.version = "1.0.0"; -conf.app.windows = conf.app.windows ?? [{}]; -conf.app.windows[0].title = appName; -fs.writeFileSync(confPath, `${JSON.stringify(conf, null, 2)}\n`); +function patchTauriConfig(filePath) { + const conf = JSON.parse(fs.readFileSync(filePath, "utf8")); + conf.productName = appName; + conf.mainBinaryName = appName.replace(/[^\w.-]+/g, "") || "Web2App"; + conf.identifier = appIdentifier; + conf.version = "1.0.0"; + if (conf.app?.windows?.[0]) { + 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"]; for (const candidate of iconCandidates) { diff --git a/server/routes/builds.ts b/server/routes/builds.ts index f027dc6..c2e6688 100644 --- a/server/routes/builds.ts +++ b/server/routes/builds.ts @@ -16,7 +16,12 @@ import { triggerBuildWorkflow, uploadSiteZip, } from "../services/github.js"; -import { slugifyIdentifier, validateZipBuffer, ZipValidationError } from "../services/zip.js"; +import { + normalizeAppIdentifier, + slugifyIdentifier, + validateZipBuffer, + ZipValidationError, +} from "../services/zip.js"; const upload = multer({ storage: multer.memoryStorage(), @@ -69,9 +74,10 @@ buildsRouter.post("/", upload.single("file"), async (req, res) => { const { normalizedBuffer } = validateZipBuffer(req.file.buffer, maxUploadBytes); const jobId = nanoid(10); - const appIdentifier = - identifierInput || - `com.web2app.${slugifyIdentifier(appName) || jobId.toLowerCase()}`; + const slug = slugifyIdentifier(appName) || jobId.toLowerCase(); + const appIdentifier = normalizeAppIdentifier( + identifierInput || `com.web2app.${slug}`, + ); insertBuild({ id: jobId, appName, appIdentifier }); diff --git a/server/services/zip.ts b/server/services/zip.ts index 8493366..be85f60 100644 --- a/server/services/zip.ts +++ b/server/services/zip.ts @@ -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 { return input .toLowerCase() .replace(/[^a-z0-9]+/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("."); } diff --git a/template/src-tauri/tauri.android.conf.json b/template/src-tauri/tauri.android.conf.json index 37f30f6..330375c 100644 --- a/template/src-tauri/tauri.android.conf.json +++ b/template/src-tauri/tauri.android.conf.json @@ -2,7 +2,7 @@ "$schema": "https://schema.tauri.app/config/2", "productName": "Web2App", "version": "1.0.0", - "identifier": "com.web2app.default", + "identifier": "com.web2app.app", "app": { "windows": [ { diff --git a/template/src-tauri/tauri.conf.json b/template/src-tauri/tauri.conf.json index d55944c..c830932 100644 --- a/template/src-tauri/tauri.conf.json +++ b/template/src-tauri/tauri.conf.json @@ -3,7 +3,7 @@ "productName": "Web2App", "mainBinaryName": "Web2App", "version": "1.0.0", - "identifier": "com.web2app.default", + "identifier": "com.web2app.app", "build": { "beforeDevCommand": "", "beforeBuildCommand": "",