html2apk 0.5.0 → 0.8.0
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/README.md +28 -2
- package/examples/minimal/app.json +2 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/examples/minimal/dist/MeuApp-1.0.0-release.aab +0 -0
- package/package.json +1 -1
- package/src/cli/index.js +12 -1
- package/src/cordova/apk-finder.js +22 -10
- package/src/cordova/project.js +5 -0
- package/src/core/build-apk.js +88 -19
- package/src/core/config.js +16 -0
- package/src/core/defaults.js +2 -0
- package/src/desktop/main.js +191 -2
- package/src/desktop/preload.js +5 -0
- package/src/desktop/renderer/index.html +72 -1
- package/src/desktop/renderer/renderer.js +381 -5
- package/src/desktop/renderer/styles.css +189 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +55 -0
- package/src/templates/html2apk-runtime-console.js +805 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# html2apk
|
|
2
2
|
|
|
3
|
-
`html2apk` transforma uma pasta com HTML, CSS, JS e assets em um APK Android usando Cordova.
|
|
3
|
+
`html2apk` transforma uma pasta com HTML, CSS, JS e assets em um APK ou AAB Android usando Cordova.
|
|
4
4
|
|
|
5
|
-
Use ele quando voce ja tem um app web, por exemplo uma pasta com `index.html`, `style.css`, `app.js` e imagens, e quer gerar um `.apk` instalavel no Android.
|
|
5
|
+
Use ele quando voce ja tem um app web, por exemplo uma pasta com `index.html`, `style.css`, `app.js` e imagens, e quer gerar um `.apk` instalavel no Android ou um `.aab` para loja.
|
|
6
6
|
|
|
7
7
|
## O Mais Importante
|
|
8
8
|
|
|
@@ -83,6 +83,21 @@ No Windows, o `html2apk doctor` tenta encontrar automaticamente o SDK em:
|
|
|
83
83
|
C:\Users\SEU_USUARIO\AppData\Local\Android\Sdk
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
## Interface Grafica
|
|
87
|
+
|
|
88
|
+
A interface desktop permite arrastar a pasta do projeto, preencher as configuracoes obrigatorias em etapas e gerar o arquivo Android sem mexer no terminal.
|
|
89
|
+
|
|
90
|
+
Recursos principais:
|
|
91
|
+
|
|
92
|
+
- Escolher idioma, tema claro/escuro e ver logs do processo.
|
|
93
|
+
- Gerar APK normal, APK release ou AAB.
|
|
94
|
+
- Escolher icone PNG, cor de tema, OneSignal App ID e permissoes.
|
|
95
|
+
- Preencher keystore pela interface: arquivo, alias, senha da store e senha da key.
|
|
96
|
+
- Marcar `Console no APK` para o app gerado mostrar um modal de debug no celular.
|
|
97
|
+
- Usar a aba `Arquivos` para abrir arquivos HTML/CSS/JS/JSON do projeto, salvar edicoes, criar novos arquivos e ver uma previa com sintaxe.
|
|
98
|
+
|
|
99
|
+
Para AAB, a interface pede keystore completo antes de liberar o build.
|
|
100
|
+
|
|
86
101
|
## Passo A Passo
|
|
87
102
|
|
|
88
103
|
Entre na pasta do seu app web:
|
|
@@ -177,6 +192,9 @@ Opcoes comuns:
|
|
|
177
192
|
```bash
|
|
178
193
|
html2apk build --debug
|
|
179
194
|
html2apk build --release
|
|
195
|
+
html2apk build --apk
|
|
196
|
+
html2apk build --aab
|
|
197
|
+
html2apk build --show-runtime-logs
|
|
180
198
|
html2apk build --mode fullscreen
|
|
181
199
|
html2apk build --mode standalone
|
|
182
200
|
html2apk build --mode floating
|
|
@@ -194,6 +212,10 @@ Com `--debug`, a pasta Cordova temporaria fica salva para inspecao caso voce que
|
|
|
194
212
|
|
|
195
213
|
Sem `--debug`, a pasta temporaria e limpa no fim do build.
|
|
196
214
|
|
|
215
|
+
Com `--aab`, o build usa formato AAB e release automaticamente. Para publicar, preencha `keystore`.
|
|
216
|
+
|
|
217
|
+
Com `--show-runtime-logs`, o APK/AAB gerado recebe um botao `Console` dentro do app. Esse console intercepta erros JavaScript, promises rejeitadas, logs, chamadas das funcoes interpretadas e requisicoes de rede (`fetch`/`XMLHttpRequest`), ajudando o dev a debugar no proprio celular. Se essa opcao ficar desligada, esse console nao aparece no app gerado.
|
|
218
|
+
|
|
197
219
|
## Configuracao Com `app.json`
|
|
198
220
|
|
|
199
221
|
O `app.json` fica na raiz do seu app. Se ele nao existir, `config.json` e usado como fallback.
|
|
@@ -206,6 +228,7 @@ Exemplo completo:
|
|
|
206
228
|
"appName": "MeuApp",
|
|
207
229
|
"packageId": "com.seuapp.meuapp",
|
|
208
230
|
"version": "1.0.0",
|
|
231
|
+
"buildFormat": "apk",
|
|
209
232
|
"mode": "fullscreen",
|
|
210
233
|
"orientation": "default",
|
|
211
234
|
"minSdkVersion": 24,
|
|
@@ -227,6 +250,7 @@ Exemplo completo:
|
|
|
227
250
|
"permissions": ["INTERNET", "CAMERA", "RECORD_AUDIO", "POST_NOTIFICATIONS", "VIBRATE"],
|
|
228
251
|
"plugins": ["cordova-plugin-camera"],
|
|
229
252
|
"release": false,
|
|
253
|
+
"showRuntimeLogs": false,
|
|
230
254
|
"androidPlatform": "android@15.0.0",
|
|
231
255
|
"keystore": {
|
|
232
256
|
"path": "",
|
|
@@ -248,6 +272,7 @@ Campos principais:
|
|
|
248
272
|
| `appName` | Nome visivel do app. |
|
|
249
273
|
| `packageId` | Identificador Android. Precisa ter formato como `com.empresa.app`. |
|
|
250
274
|
| `version` | Versao do app. |
|
|
275
|
+
| `buildFormat` | `apk` para instalar direto ou `aab` para loja. |
|
|
251
276
|
| `mode` | `fullscreen` para tela cheia, `standalone` para modo normal ou `floating` para icone flutuante. |
|
|
252
277
|
| `orientation` | `default`, `vertical`, `horizontal`, `portrait` ou `landscape`. |
|
|
253
278
|
| `minSdkVersion` | Versao minima do Android em API level. Padrao: `24`. |
|
|
@@ -263,6 +288,7 @@ Campos principais:
|
|
|
263
288
|
| `androidPlatform` | Versao da plataforma Cordova Android. Padrao: `android@15.0.0`. |
|
|
264
289
|
| `debug` | Se `true`, preserva a pasta temporaria de build. |
|
|
265
290
|
| `release` | Se `true`, gera build release. |
|
|
291
|
+
| `showRuntimeLogs` | Se `true`, mostra um modal `Console` no app gerado para depurar erros e funcoes interpretadas. |
|
|
266
292
|
| `keystore` | Dados de assinatura para build release. |
|
|
267
293
|
|
|
268
294
|
Prioridade de configuracao:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"appName": "MeuApp",
|
|
4
4
|
"packageId": "com.seuapp.meuapp",
|
|
5
5
|
"version": "1.0.0",
|
|
6
|
+
"buildFormat": "apk",
|
|
6
7
|
"mode": "fullscreen",
|
|
7
8
|
"orientation": "default",
|
|
8
9
|
"minSdkVersion": 24,
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
],
|
|
19
20
|
"plugins": [],
|
|
20
21
|
"release": false,
|
|
22
|
+
"showRuntimeLogs": false,
|
|
21
23
|
"androidPlatform": "android@15.0.0",
|
|
22
24
|
"keystore": {
|
|
23
25
|
"path": "",
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function printHelp() {
|
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
12
|
html2apk init
|
|
13
|
-
html2apk build [--release] [--debug] [--mode fullscreen|standalone|floating] [--theme fixed|auto] [--orientation vertical|horizontal] [--min-sdk 24] [--android-platform android@15.0.0]
|
|
13
|
+
html2apk build [--release] [--debug] [--apk|--aab] [--show-runtime-logs] [--mode fullscreen|standalone|floating] [--theme fixed|auto] [--orientation vertical|horizontal] [--min-sdk 24] [--android-platform android@15.0.0]
|
|
14
14
|
html2apk doctor
|
|
15
15
|
|
|
16
16
|
The current working directory is always treated as the user app root.`);
|
|
@@ -25,6 +25,15 @@ function parseBuildArgs(args) {
|
|
|
25
25
|
options.release = true;
|
|
26
26
|
} else if (arg === "--debug") {
|
|
27
27
|
options.debug = true;
|
|
28
|
+
} else if (arg === "--aab") {
|
|
29
|
+
options.buildFormat = "aab";
|
|
30
|
+
} else if (arg === "--apk") {
|
|
31
|
+
options.buildFormat = "apk";
|
|
32
|
+
} else if (arg === "--show-runtime-logs" || arg === "--runtime-logs" || arg === "--mostrar-logs") {
|
|
33
|
+
options.showRuntimeLogs = true;
|
|
34
|
+
} else if (arg === "--build-format" || arg === "--output-format" || arg === "--format") {
|
|
35
|
+
options.buildFormat = args[index + 1];
|
|
36
|
+
index += 1;
|
|
28
37
|
} else if (arg === "--mode") {
|
|
29
38
|
options.mode = args[index + 1];
|
|
30
39
|
index += 1;
|
|
@@ -94,6 +103,8 @@ function createPlaceholderConfig(projectName = "MeuApp") {
|
|
|
94
103
|
],
|
|
95
104
|
plugins: [],
|
|
96
105
|
release: false,
|
|
106
|
+
buildFormat: "apk",
|
|
107
|
+
showRuntimeLogs: false,
|
|
97
108
|
androidPlatform: "android@15.0.0",
|
|
98
109
|
keystore: {
|
|
99
110
|
path: "",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
|
-
async function walk(dirPath, results = []) {
|
|
6
|
+
async function walk(dirPath, extension, results = []) {
|
|
7
7
|
let entries = [];
|
|
8
8
|
try {
|
|
9
9
|
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
@@ -14,8 +14,8 @@ async function walk(dirPath, results = []) {
|
|
|
14
14
|
for (const entry of entries) {
|
|
15
15
|
const fullPath = path.join(dirPath, entry.name);
|
|
16
16
|
if (entry.isDirectory()) {
|
|
17
|
-
await walk(fullPath, results);
|
|
18
|
-
} else if (entry.isFile() && entry.name.endsWith(
|
|
17
|
+
await walk(fullPath, extension, results);
|
|
18
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(extension)) {
|
|
19
19
|
const stat = await fs.stat(fullPath);
|
|
20
20
|
results.push({ path: fullPath, mtimeMs: stat.mtimeMs });
|
|
21
21
|
}
|
|
@@ -24,22 +24,34 @@ async function walk(dirPath, results = []) {
|
|
|
24
24
|
return results;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
function artifactFormat(options = {}) {
|
|
28
|
+
return String(options.buildFormat || options.outputFormat || options.packageType || "").toLowerCase() === "aab" ? "aab" : "apk";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function findAndroidArtifact(buildDir, options) {
|
|
32
|
+
const format = artifactFormat(options);
|
|
33
|
+
const outputRoot = path.join(buildDir, "platforms", "android", "app", "build", "outputs", format === "aab" ? "bundle" : "apk");
|
|
34
|
+
const extension = `.${format}`;
|
|
35
|
+
const artifacts = await walk(outputRoot, extension);
|
|
30
36
|
|
|
31
|
-
if (
|
|
32
|
-
throw new Error(
|
|
37
|
+
if (artifacts.length === 0) {
|
|
38
|
+
throw new Error(`Cordova build finished, but no ${format.toUpperCase()} was found.`);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const expectedFlavor = options.release ? "release" : "debug";
|
|
36
|
-
const preferred =
|
|
42
|
+
const preferred = artifacts
|
|
37
43
|
.filter((item) => item.path.toLowerCase().includes(expectedFlavor))
|
|
38
44
|
.sort((a, b) => b.mtimeMs - a.mtimeMs)[0];
|
|
39
45
|
|
|
40
|
-
return (preferred ||
|
|
46
|
+
return (preferred || artifacts.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]).path;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function findApk(buildDir, options) {
|
|
50
|
+
return findAndroidArtifact(buildDir, { ...options, buildFormat: "apk" });
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
module.exports = {
|
|
54
|
+
artifactFormat,
|
|
55
|
+
findAndroidArtifact,
|
|
44
56
|
findApk
|
|
45
57
|
};
|
package/src/cordova/project.js
CHANGED
|
@@ -30,6 +30,7 @@ async function addAndroidPlatform(buildDir, options, runner) {
|
|
|
30
30
|
|
|
31
31
|
async function buildAndroid(buildDir, options, buildJsonPath, runner) {
|
|
32
32
|
const args = ["build", "android", "--no-telemetry"];
|
|
33
|
+
const wantsBundle = String(options.buildFormat || options.outputFormat || options.packageType || "").toLowerCase() === "aab";
|
|
33
34
|
|
|
34
35
|
if (options.release) {
|
|
35
36
|
args.push("--release");
|
|
@@ -41,6 +42,10 @@ async function buildAndroid(buildDir, options, buildJsonPath, runner) {
|
|
|
41
42
|
args.push("--buildConfig", buildJsonPath);
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
if (wantsBundle) {
|
|
46
|
+
args.push("--", "--packageType=bundle");
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
await runner.run("cordova", args, {
|
|
45
50
|
cwd: buildDir,
|
|
46
51
|
pipeOutput: options.debug
|
package/src/core/build-apk.js
CHANGED
|
@@ -7,7 +7,7 @@ const { resolveBuildOptions } = require("./config");
|
|
|
7
7
|
const { validateEntryFile, validateRequiredOptions } = require("./validation");
|
|
8
8
|
const { createCordovaProject, addAndroidPlatform, buildAndroid, runAndroidDevice, addCordovaPlugin } = require("../cordova/project");
|
|
9
9
|
const { writeConfigXml } = require("../cordova/config-xml");
|
|
10
|
-
const { findApk } = require("../cordova/apk-finder");
|
|
10
|
+
const { artifactFormat, findAndroidArtifact, findApk } = require("../cordova/apk-finder");
|
|
11
11
|
const { copyWebAssets, ensureDir, removePath, copyFile, copyDirectory } = require("../utils/fs-extra");
|
|
12
12
|
const { createCommandRunner } = require("../utils/command-runner");
|
|
13
13
|
const { installBridgePlugin } = require("../bridge/install-bridge");
|
|
@@ -15,9 +15,12 @@ const { getRuntimeEnvironment } = require("../runtime-manager");
|
|
|
15
15
|
|
|
16
16
|
const AUTO_THEME_SCRIPT_NAME = "html2apk-auto-theme.js";
|
|
17
17
|
const EARLY_BRIDGE_SCRIPT_NAME = "html2apk-early-bridge.js";
|
|
18
|
+
const RUNTIME_CONSOLE_SCRIPT_NAME = "html2apk-runtime-console.js";
|
|
19
|
+
const RUNTIME_CONSOLE_ICON_NAME = "html2apk-console.png";
|
|
18
20
|
const ONESIGNAL_SCRIPT_NAME = "html2apk-onesignal.js";
|
|
19
21
|
const ONESIGNAL_PLUGIN_PACKAGE = "onesignal-cordova-plugin";
|
|
20
22
|
const DEFAULT_APP_ICON_NAME = "html2apk.png";
|
|
23
|
+
const DEFAULT_CONSOLE_ICON_NAME = "console.png";
|
|
21
24
|
|
|
22
25
|
function defaultAppIconPath() {
|
|
23
26
|
return path.resolve(__dirname, "..", "..", DEFAULT_APP_ICON_NAME);
|
|
@@ -60,23 +63,58 @@ function hasOneSignal(options) {
|
|
|
60
63
|
return oneSignalAppId(options).length > 0;
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
async function pathExists(filePath) {
|
|
67
|
+
try {
|
|
68
|
+
await fs.access(filePath);
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
function scriptTag(scriptPath) {
|
|
64
76
|
return `<script src="${scriptPath}"></script>`;
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
async function
|
|
79
|
+
async function findHtmlFiles(dirPath, results = []) {
|
|
80
|
+
let entries = [];
|
|
81
|
+
try {
|
|
82
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
83
|
+
} catch {
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const entry of entries) {
|
|
88
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
await findHtmlFiles(fullPath, results);
|
|
91
|
+
} else if (entry.isFile() && /\.html?$/i.test(entry.name)) {
|
|
92
|
+
results.push(fullPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function injectCordovaRuntimeIntoHtml(htmlPath, scriptPath = "cordova.js", earlyBridgePath = EARLY_BRIDGE_SCRIPT_NAME, runtimeConsolePath = null) {
|
|
68
100
|
let html = await fs.readFile(htmlPath, "utf8");
|
|
69
101
|
const hasCordova = /<script\b[^>]*\bsrc=["'][^"']*cordova\.js["'][^>]*>/i.test(html);
|
|
70
102
|
const hasEarlyBridge = /<script\b[^>]*\bsrc=["'][^"']*html2apk-early-bridge\.js["'][^>]*>/i.test(html);
|
|
71
|
-
|
|
103
|
+
const hasRuntimeConsole = /<script\b[^>]*\bsrc=["'][^"']*html2apk-runtime-console\.js["'][^>]*>/i.test(html);
|
|
104
|
+
if (hasCordova && hasEarlyBridge && (!runtimeConsolePath || hasRuntimeConsole)) {
|
|
72
105
|
return false;
|
|
73
106
|
}
|
|
74
107
|
|
|
75
108
|
const tags = [
|
|
76
109
|
hasEarlyBridge ? null : scriptTag(earlyBridgePath),
|
|
110
|
+
runtimeConsolePath && !hasRuntimeConsole ? scriptTag(runtimeConsolePath) : null,
|
|
77
111
|
hasCordova ? null : scriptTag(scriptPath)
|
|
78
112
|
].filter(Boolean).join("\n ");
|
|
79
113
|
|
|
114
|
+
if (!tags) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
80
118
|
if (/<head\b[^>]*>/i.test(html)) {
|
|
81
119
|
html = html.replace(/<head\b[^>]*>/i, (match) => `${match}\n ${tags}`);
|
|
82
120
|
} else if (/<html\b[^>]*>/i.test(html)) {
|
|
@@ -92,17 +130,39 @@ async function injectCordovaRuntimeIntoHtml(htmlPath, scriptPath = "cordova.js",
|
|
|
92
130
|
async function installCordovaRuntimeScript(buildDir, options) {
|
|
93
131
|
const wwwDir = path.join(buildDir, "www");
|
|
94
132
|
const entryHtmlPath = path.resolve(wwwDir, options.entryFile || "index.html");
|
|
95
|
-
const scriptPath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), path.join(wwwDir, "cordova.js"))) || "cordova.js";
|
|
96
133
|
const earlyBridgeSource = path.resolve(__dirname, "..", "templates", EARLY_BRIDGE_SCRIPT_NAME);
|
|
97
134
|
const earlyBridgeTarget = path.join(wwwDir, EARLY_BRIDGE_SCRIPT_NAME);
|
|
98
|
-
const
|
|
135
|
+
const runtimeConsoleSource = path.resolve(__dirname, "..", "templates", RUNTIME_CONSOLE_SCRIPT_NAME);
|
|
136
|
+
const runtimeConsoleTarget = path.join(wwwDir, RUNTIME_CONSOLE_SCRIPT_NAME);
|
|
137
|
+
const runtimeConsoleIconSource = path.resolve(__dirname, "..", "..", DEFAULT_CONSOLE_ICON_NAME);
|
|
138
|
+
const runtimeConsoleIconTarget = path.join(wwwDir, RUNTIME_CONSOLE_ICON_NAME);
|
|
139
|
+
const htmlFiles = await findHtmlFiles(wwwDir);
|
|
140
|
+
let injectedCount = 0;
|
|
99
141
|
|
|
100
142
|
if (!isInside(wwwDir, entryHtmlPath)) {
|
|
101
143
|
throw new Error(`Entry file must stay inside the Cordova www folder: ${options.entryFile}`);
|
|
102
144
|
}
|
|
103
145
|
|
|
104
146
|
await copyFile(earlyBridgeSource, earlyBridgeTarget);
|
|
105
|
-
|
|
147
|
+
if (options.showRuntimeLogs) {
|
|
148
|
+
await copyFile(runtimeConsoleSource, runtimeConsoleTarget);
|
|
149
|
+
if (await pathExists(runtimeConsoleIconSource)) {
|
|
150
|
+
await copyFile(runtimeConsoleIconSource, runtimeConsoleIconTarget);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const htmlPath of htmlFiles) {
|
|
155
|
+
const scriptPath = toCordovaPath(path.relative(path.dirname(htmlPath), path.join(wwwDir, "cordova.js"))) || "cordova.js";
|
|
156
|
+
const earlyBridgePath = toCordovaPath(path.relative(path.dirname(htmlPath), earlyBridgeTarget)) || EARLY_BRIDGE_SCRIPT_NAME;
|
|
157
|
+
const runtimeConsolePath = options.showRuntimeLogs
|
|
158
|
+
? (toCordovaPath(path.relative(path.dirname(htmlPath), runtimeConsoleTarget)) || RUNTIME_CONSOLE_SCRIPT_NAME)
|
|
159
|
+
: null;
|
|
160
|
+
if (await injectCordovaRuntimeIntoHtml(htmlPath, scriptPath, earlyBridgePath, runtimeConsolePath)) {
|
|
161
|
+
injectedCount += 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return injectedCount;
|
|
106
166
|
}
|
|
107
167
|
|
|
108
168
|
async function injectScriptIntoHtml(htmlPath, scriptPath) {
|
|
@@ -265,7 +325,7 @@ async function createBuildJson(buildDir, options, projectRoot) {
|
|
|
265
325
|
const storeFile = path.resolve(projectRoot, options.keystore.path);
|
|
266
326
|
const buildJsonPath = path.join(buildDir, "build.json");
|
|
267
327
|
const release = {
|
|
268
|
-
packageType: "apk",
|
|
328
|
+
packageType: artifactFormat(options) === "aab" ? "bundle" : "apk",
|
|
269
329
|
keystore: storeFile,
|
|
270
330
|
storePassword: options.keystore.storePassword || options.keystore.password,
|
|
271
331
|
alias: options.keystore.alias,
|
|
@@ -283,10 +343,11 @@ async function createBuildJson(buildDir, options, projectRoot) {
|
|
|
283
343
|
return buildJsonPath;
|
|
284
344
|
}
|
|
285
345
|
|
|
286
|
-
function
|
|
346
|
+
function outputAndroidName(options) {
|
|
287
347
|
const safeName = String(options.appName || "app").replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
288
348
|
const flavor = options.release ? "release" : "debug";
|
|
289
|
-
|
|
349
|
+
const extension = artifactFormat(options);
|
|
350
|
+
return `${safeName}-${options.version}-${flavor}.${extension}`;
|
|
290
351
|
}
|
|
291
352
|
|
|
292
353
|
function parseAdbDevices(output) {
|
|
@@ -350,8 +411,12 @@ async function prepareCordovaProject(projectRoot, buildDir, options, runner, log
|
|
|
350
411
|
cordovaOptions.androidSplashScreenAnimatedIcon = toBuildAssetPath(buildDir, cordovaOptions.splash);
|
|
351
412
|
await writeConfigXml(path.join(buildDir, "config.xml"), cordovaOptions);
|
|
352
413
|
await copyWebAssets(path.resolve(projectRoot, options.webRoot || "."), path.join(buildDir, "www"), options, projectRoot);
|
|
353
|
-
|
|
354
|
-
|
|
414
|
+
const injectedRuntimePages = await installCordovaRuntimeScript(buildDir, options);
|
|
415
|
+
if (injectedRuntimePages) {
|
|
416
|
+
log(`Cordova runtime: injected scripts into ${injectedRuntimePages} HTML page(s).`);
|
|
417
|
+
}
|
|
418
|
+
if (options.showRuntimeLogs) {
|
|
419
|
+
log("Runtime console: enabled inside the generated APK.");
|
|
355
420
|
}
|
|
356
421
|
if (await installAutoThemeScript(buildDir, options)) {
|
|
357
422
|
log("Theme mode: auto (system bars follow the visible screen color).");
|
|
@@ -374,14 +439,14 @@ async function prepareCordovaProject(projectRoot, buildDir, options, runner, log
|
|
|
374
439
|
await addAndroidPlatform(buildDir, options, runner);
|
|
375
440
|
}
|
|
376
441
|
|
|
377
|
-
async function
|
|
378
|
-
const
|
|
442
|
+
async function copyBuiltArtifact(projectRoot, buildDir, options) {
|
|
443
|
+
const artifactPathInBuild = await findAndroidArtifact(buildDir, options);
|
|
379
444
|
const outputDir = path.resolve(projectRoot, options.outputDir || "dist");
|
|
380
445
|
await ensureDir(outputDir);
|
|
381
446
|
|
|
382
|
-
const
|
|
383
|
-
await copyFile(
|
|
384
|
-
return
|
|
447
|
+
const artifactPath = path.join(outputDir, outputAndroidName(options));
|
|
448
|
+
await copyFile(artifactPathInBuild, artifactPath);
|
|
449
|
+
return artifactPath;
|
|
385
450
|
}
|
|
386
451
|
|
|
387
452
|
async function ensureBuiltDebugApk(buildDir, options, runner, log) {
|
|
@@ -459,7 +524,8 @@ async function buildApk(overrides = {}) {
|
|
|
459
524
|
const buildJsonPath = await createBuildJson(buildDir, options, projectRoot);
|
|
460
525
|
await buildAndroid(buildDir, options, buildJsonPath, runner);
|
|
461
526
|
|
|
462
|
-
const
|
|
527
|
+
const artifactPath = await copyBuiltArtifact(projectRoot, buildDir, options);
|
|
528
|
+
const artifactType = artifactFormat(options);
|
|
463
529
|
|
|
464
530
|
if (!options.debug) {
|
|
465
531
|
await removePath(tempRoot);
|
|
@@ -467,7 +533,9 @@ async function buildApk(overrides = {}) {
|
|
|
467
533
|
}
|
|
468
534
|
|
|
469
535
|
return {
|
|
470
|
-
apkPath,
|
|
536
|
+
apkPath: artifactPath,
|
|
537
|
+
artifactPath,
|
|
538
|
+
artifactType,
|
|
471
539
|
buildDir: options.debug ? buildDir : null,
|
|
472
540
|
logs,
|
|
473
541
|
status: "success",
|
|
@@ -534,7 +602,7 @@ async function runDebugUsb(overrides = {}) {
|
|
|
534
602
|
await installDebugApkWithAdb(buildDir, options, runner, device, log);
|
|
535
603
|
}
|
|
536
604
|
|
|
537
|
-
const apkPath = await
|
|
605
|
+
const apkPath = await copyBuiltArtifact(projectRoot, buildDir, { ...options, buildFormat: "apk" });
|
|
538
606
|
|
|
539
607
|
if (!options.debug) {
|
|
540
608
|
await removePath(tempRoot);
|
|
@@ -571,6 +639,7 @@ module.exports = {
|
|
|
571
639
|
buildApk,
|
|
572
640
|
defaultAppIconPath,
|
|
573
641
|
injectCordovaRuntimeIntoHtml,
|
|
642
|
+
installCordovaRuntimeScript,
|
|
574
643
|
parseAdbDevices,
|
|
575
644
|
runDebugUsb
|
|
576
645
|
};
|
package/src/core/config.js
CHANGED
|
@@ -94,12 +94,23 @@ function normalizeOptions(options) {
|
|
|
94
94
|
: "default";
|
|
95
95
|
normalized.debug = Boolean(normalized.debug);
|
|
96
96
|
normalized.release = Boolean(normalized.release);
|
|
97
|
+
normalized.buildFormat = normalizeBuildFormat(normalized.buildFormat || normalized.outputFormat || normalized.artifactType || normalized.packageType);
|
|
98
|
+
if (normalized.buildFormat === "aab") {
|
|
99
|
+
normalized.release = true;
|
|
100
|
+
}
|
|
97
101
|
const themeColorText = String(normalized.themeColor || "").trim().toLowerCase();
|
|
98
102
|
const themeModeText = String(normalized.themeMode || "").trim().toLowerCase();
|
|
99
103
|
const themeText = String(normalized.theme || "").trim().toLowerCase();
|
|
100
104
|
normalized.themeMode = themeModeText === "auto" || themeText === "auto" || themeColorText === "auto" ? "auto" : "fixed";
|
|
101
105
|
normalized.theme = normalized.themeMode;
|
|
102
106
|
normalized.themeColor = normalizeThemeColor(themeColorText === "auto" ? normalized.backgroundColor : normalized.themeColor);
|
|
107
|
+
normalized.showRuntimeLogs = Boolean(
|
|
108
|
+
normalized.showRuntimeLogs ||
|
|
109
|
+
normalized.mostrarLogs ||
|
|
110
|
+
normalized.runtimeLogs ||
|
|
111
|
+
normalized.debugConsole ||
|
|
112
|
+
normalized.console
|
|
113
|
+
);
|
|
103
114
|
normalized.oneSignalAppId = normalizeOneSignalAppId(normalized.oneSignalAppId || normalized.onesignalAppId || normalized.oneSignal?.appId || normalized.onesignal?.appId);
|
|
104
115
|
normalized.minSdkVersion = normalizeMinSdkVersion(normalized.minSdkVersion || normalized.androidMinSdkVersion);
|
|
105
116
|
normalized.permissions = Array.isArray(normalized.permissions)
|
|
@@ -119,6 +130,10 @@ function normalizeOneSignalAppId(value) {
|
|
|
119
130
|
return String(value || "").trim();
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
function normalizeBuildFormat(value) {
|
|
134
|
+
return String(value || "").trim().toLowerCase() === "aab" ? "aab" : "apk";
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
function normalizeDeepLinks(value) {
|
|
123
138
|
const input = value && typeof value === "object" ? value : {};
|
|
124
139
|
const schemes = Array.isArray(input.schemes)
|
|
@@ -164,5 +179,6 @@ module.exports = {
|
|
|
164
179
|
normalizeMinSdkVersion,
|
|
165
180
|
normalizeThemeMode,
|
|
166
181
|
normalizeOneSignalAppId,
|
|
182
|
+
normalizeBuildFormat,
|
|
167
183
|
normalizeDeepLinks
|
|
168
184
|
};
|
package/src/core/defaults.js
CHANGED
|
@@ -27,11 +27,13 @@ function createDefaultOptions(projectRoot) {
|
|
|
27
27
|
permissions: ["INTERNET", "POST_NOTIFICATIONS", "VIBRATE"],
|
|
28
28
|
plugins: [],
|
|
29
29
|
release: false,
|
|
30
|
+
buildFormat: "apk",
|
|
30
31
|
keystore: null,
|
|
31
32
|
androidPlatform: "android@15.0.0",
|
|
32
33
|
minSdkVersion: DEFAULT_ANDROID_MIN_SDK_VERSION,
|
|
33
34
|
themeColor: "#126fff",
|
|
34
35
|
themeMode: "fixed",
|
|
36
|
+
showRuntimeLogs: false,
|
|
35
37
|
oneSignalAppId: "",
|
|
36
38
|
deepLinks: {
|
|
37
39
|
schemes: [],
|