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 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": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2apk",
3
- "version": "0.5.0",
3
+ "version": "0.8.0",
4
4
  "description": "Node CLI and library to turn an HTML/CSS/JS folder into an Android APK through Cordova.",
5
5
  "main": "index.js",
6
6
  "bin": {
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(".apk")) {
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
- async function findApk(buildDir, options) {
28
- const outputRoot = path.join(buildDir, "platforms", "android", "app", "build", "outputs", "apk");
29
- const apks = await walk(outputRoot);
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 (apks.length === 0) {
32
- throw new Error("Cordova build finished, but no APK was found.");
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 = apks
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 || apks.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]).path;
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
  };
@@ -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
@@ -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 injectCordovaRuntimeIntoHtml(htmlPath, scriptPath = "cordova.js", earlyBridgePath = EARLY_BRIDGE_SCRIPT_NAME) {
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
- if (hasCordova && hasEarlyBridge) {
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 earlyBridgePath = toCordovaPath(path.relative(path.dirname(entryHtmlPath), earlyBridgeTarget)) || EARLY_BRIDGE_SCRIPT_NAME;
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
- return injectCordovaRuntimeIntoHtml(entryHtmlPath, scriptPath, earlyBridgePath);
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 outputApkName(options) {
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
- return `${safeName}-${options.version}-${flavor}.apk`;
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
- if (await installCordovaRuntimeScript(buildDir, options)) {
354
- log("Cordova runtime: injected cordova.js into the entry HTML.");
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 copyBuiltApk(projectRoot, buildDir, options) {
378
- const apkPathInBuild = await findApk(buildDir, options);
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 apkPath = path.join(outputDir, outputApkName(options));
383
- await copyFile(apkPathInBuild, apkPath);
384
- return apkPath;
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 apkPath = await copyBuiltApk(projectRoot, buildDir, options);
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 copyBuiltApk(projectRoot, buildDir, options);
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
  };
@@ -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
  };
@@ -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: [],