html2apk 0.3.0 → 0.5.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.
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
 
3
+ const nodeFs = require("fs");
3
4
  const fs = require("fs/promises");
4
5
  const path = require("path");
5
6
  const { spawn } = require("child_process");
6
7
  const { app, BrowserWindow, dialog, ipcMain, screen, shell } = require("electron");
7
- const { buildApk } = require("../core/build-apk");
8
+ const { buildApk, runDebugUsb } = require("../core/build-apk");
8
9
  const {
9
10
  REQUIRED_ANDROID_BUILD_TOOLS,
10
11
  REQUIRED_ANDROID_PLATFORM,
@@ -13,6 +14,9 @@ const {
13
14
  const { runDoctor, formatDoctorReport } = require("../runtime-manager/doctor");
14
15
 
15
16
  let mainWindow = null;
17
+ let projectWatcher = null;
18
+ let projectWatchTimer = null;
19
+ let watchedProjectRoot = null;
16
20
  const smokeTest = process.env.HTML2APK_DESKTOP_SMOKE === "1";
17
21
  const APP_ID = "dev.caiomultiversando.html2apk";
18
22
  const APP_NAME = "html2apk";
@@ -135,6 +139,90 @@ async function inspectProject(projectRoot) {
135
139
  };
136
140
  }
137
141
 
142
+ function shouldIgnoreProjectWatchPath(fileName) {
143
+ const normalized = String(fileName || "").replace(/\\/g, "/");
144
+ return normalized
145
+ && (normalized.includes(".html2apk-doctor-")
146
+ || normalized.startsWith("dist/")
147
+ || normalized === "dist"
148
+ || normalized.startsWith("node_modules/")
149
+ || normalized === "node_modules"
150
+ || normalized.startsWith(".git/")
151
+ || normalized === ".git");
152
+ }
153
+
154
+ function stopProjectWatcher() {
155
+ if (projectWatchTimer) {
156
+ clearTimeout(projectWatchTimer);
157
+ projectWatchTimer = null;
158
+ }
159
+
160
+ if (projectWatcher) {
161
+ projectWatcher.close();
162
+ projectWatcher = null;
163
+ }
164
+
165
+ watchedProjectRoot = null;
166
+ }
167
+
168
+ function startProjectWatcher(projectRoot, sender) {
169
+ stopProjectWatcher();
170
+ watchedProjectRoot = projectRoot;
171
+
172
+ try {
173
+ projectWatcher = nodeFs.watch(projectRoot, { recursive: true }, (eventType, fileName) => {
174
+ if (shouldIgnoreProjectWatchPath(fileName)) {
175
+ return;
176
+ }
177
+
178
+ const changedPath = path.join(projectRoot, String(fileName || ""));
179
+ if (projectWatchTimer) {
180
+ clearTimeout(projectWatchTimer);
181
+ }
182
+
183
+ projectWatchTimer = setTimeout(async () => {
184
+ projectWatchTimer = null;
185
+ try {
186
+ if (watchedProjectRoot !== projectRoot) {
187
+ return;
188
+ }
189
+
190
+ const project = await inspectProject(projectRoot);
191
+ sender.send("project:changed", {
192
+ eventType,
193
+ changedPath,
194
+ project,
195
+ time: new Date().toISOString()
196
+ });
197
+ } catch (error) {
198
+ sender.send("project:watch-error", {
199
+ message: error.message,
200
+ time: new Date().toISOString()
201
+ });
202
+ }
203
+ }, 350);
204
+ });
205
+
206
+ projectWatcher.on("error", (error) => {
207
+ sender.send("project:watch-error", {
208
+ message: error.message,
209
+ time: new Date().toISOString()
210
+ });
211
+ });
212
+
213
+ return {
214
+ ok: true,
215
+ projectRoot
216
+ };
217
+ } catch (error) {
218
+ stopProjectWatcher();
219
+ return {
220
+ ok: false,
221
+ message: error.message
222
+ };
223
+ }
224
+ }
225
+
138
226
  function cleanBuildOptions(options = {}) {
139
227
  const output = {
140
228
  projectRoot: options.projectRoot,
@@ -365,6 +453,7 @@ app.whenReady().then(() => {
365
453
  });
366
454
 
367
455
  app.on("window-all-closed", () => {
456
+ stopProjectWatcher();
368
457
  if (process.platform !== "darwin") {
369
458
  app.quit();
370
459
  }
@@ -440,6 +529,15 @@ ipcMain.handle("project:inspect", async (_event, projectRoot) => {
440
529
  return inspectProject(projectRoot);
441
530
  });
442
531
 
532
+ ipcMain.handle("project:watch", async (event, projectRoot) => {
533
+ return startProjectWatcher(projectRoot, event.sender);
534
+ });
535
+
536
+ ipcMain.handle("project:unwatch", async () => {
537
+ stopProjectWatcher();
538
+ return { ok: true };
539
+ });
540
+
443
541
  ipcMain.handle("doctor:run", async (_event, projectRoot) => {
444
542
  const report = await runDoctor({ projectRoot });
445
543
  return {
@@ -517,6 +615,39 @@ ipcMain.handle("build:run", async (event, options) => {
517
615
  }
518
616
  });
519
617
 
618
+ ipcMain.handle("build:run-usb-debug", async (event, options) => {
619
+ const buildOptions = cleanBuildOptions(options);
620
+ const sendLog = (line, kind = "raw") => {
621
+ event.sender.send("build:log", {
622
+ line,
623
+ kind,
624
+ time: new Date().toISOString()
625
+ });
626
+ };
627
+
628
+ try {
629
+ sendLog("Starting html2apk USB debug build.", "system");
630
+ const result = await runDebugUsb({
631
+ ...buildOptions,
632
+ release: false,
633
+ onLog: (line) => sendLog(line)
634
+ });
635
+ sendLog(`USB debug installed on device: ${result.device?.id || "Android device"}`, "success");
636
+ return {
637
+ ok: true,
638
+ result
639
+ };
640
+ } catch (error) {
641
+ sendLog(error.message, "error");
642
+ return {
643
+ ok: false,
644
+ message: error.message,
645
+ logs: error.logs || [],
646
+ buildDir: error.buildDir || null
647
+ };
648
+ }
649
+ });
650
+
520
651
  ipcMain.handle("shell:open-path", async (_event, targetPath) => {
521
652
  if (!targetPath) {
522
653
  return false;
@@ -7,9 +7,12 @@ contextBridge.exposeInMainWorld("html2apkDesktop", {
7
7
  selectFolder: () => ipcRenderer.invoke("dialog:select-folder"),
8
8
  selectIcon: () => ipcRenderer.invoke("dialog:select-icon"),
9
9
  inspectProject: (projectRoot) => ipcRenderer.invoke("project:inspect", projectRoot),
10
+ watchProject: (projectRoot) => ipcRenderer.invoke("project:watch", projectRoot),
11
+ unwatchProject: () => ipcRenderer.invoke("project:unwatch"),
10
12
  runDoctor: (projectRoot) => ipcRenderer.invoke("doctor:run", projectRoot),
11
13
  installAndroidRequirements: () => ipcRenderer.invoke("install:android-requirements"),
12
14
  runBuild: (options) => ipcRenderer.invoke("build:run", options),
15
+ runUsbDebugBuild: (options) => ipcRenderer.invoke("build:run-usb-debug", options),
13
16
  openPath: (targetPath) => ipcRenderer.invoke("shell:open-path", targetPath),
14
17
  showItem: (targetPath) => ipcRenderer.invoke("shell:show-item", targetPath),
15
18
  openExternalUrl: (targetUrl) => ipcRenderer.invoke("shell:open-external", targetUrl),
@@ -27,5 +30,15 @@ contextBridge.exposeInMainWorld("html2apkDesktop", {
27
30
  const handler = (_event, payload) => listener(payload);
28
31
  ipcRenderer.on("install:log", handler);
29
32
  return () => ipcRenderer.removeListener("install:log", handler);
33
+ },
34
+ onProjectChanged: (listener) => {
35
+ const handler = (_event, payload) => listener(payload);
36
+ ipcRenderer.on("project:changed", handler);
37
+ return () => ipcRenderer.removeListener("project:changed", handler);
38
+ },
39
+ onProjectWatchError: (listener) => {
40
+ const handler = (_event, payload) => listener(payload);
41
+ ipcRenderer.on("project:watch-error", handler);
42
+ return () => ipcRenderer.removeListener("project:watch-error", handler);
30
43
  }
31
44
  });
@@ -287,6 +287,7 @@
287
287
  </div>
288
288
  <section class="action-strip">
289
289
  <button id="buildButton" class="primary-action" type="button" disabled data-i18n="startBuild">Gerar APK</button>
290
+ <button id="usbDebugButton" class="secondary-action" type="button" disabled data-i18n="startUsbDebug">Testar no USB</button>
290
291
  </section>
291
292
  <div id="resultPanel" class="result-panel hidden">
292
293
  <div>
@@ -304,8 +305,8 @@
304
305
  <div class="success-panel">
305
306
  <img src="../../../html2apk.png" alt="" class="success-icon">
306
307
  <p class="eyebrow" data-i18n="successEyebrow">Concluido</p>
307
- <h1 data-i18n="successTitle">APK gerado com sucesso</h1>
308
- <p data-i18n="successText">Seu arquivo Android esta pronto na pasta dist.</p>
308
+ <h1 id="successTitle" data-i18n="successTitle">APK gerado com sucesso</h1>
309
+ <p id="successText" data-i18n="successText">Seu arquivo Android esta pronto na pasta dist.</p>
309
310
  <code id="successApkPath">-</code>
310
311
  <div class="result-actions">
311
312
  <button id="successOpenDistButton" class="secondary-action" type="button" data-i18n="openDist">Abrir dist</button>