fix(android): patch android config identifier and avoid Java reserved words
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
30
.github/scripts/prepare-template.mjs
vendored
30
.github/scripts/prepare-template.mjs
vendored
@@ -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) {
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|
||||||
|
|||||||
@@ -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(".");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user