html2apk 0.1.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.
Files changed (36) hide show
  1. package/README.md +472 -0
  2. package/bin/html2apk-desktop.js +23 -0
  3. package/bin/html2apk.js +19 -0
  4. package/examples/minimal/app.json +27 -0
  5. package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
  6. package/examples/minimal/index.html +41 -0
  7. package/html2apk.png +0 -0
  8. package/index.js +3 -0
  9. package/package.json +76 -0
  10. package/src/android/README.md +7 -0
  11. package/src/bridge/install-bridge.js +16 -0
  12. package/src/cli/index.js +163 -0
  13. package/src/cordova/apk-finder.js +45 -0
  14. package/src/cordova/config-xml.js +110 -0
  15. package/src/cordova/project.js +56 -0
  16. package/src/core/build-apk.js +189 -0
  17. package/src/core/config.js +99 -0
  18. package/src/core/defaults.js +37 -0
  19. package/src/core/validation.js +58 -0
  20. package/src/desktop/main.js +522 -0
  21. package/src/desktop/preload.js +30 -0
  22. package/src/desktop/renderer/index.html +323 -0
  23. package/src/desktop/renderer/renderer.js +1074 -0
  24. package/src/desktop/renderer/styles.css +1208 -0
  25. package/src/index.js +12 -0
  26. package/src/runtime-manager/doctor.js +164 -0
  27. package/src/runtime-manager/index.js +190 -0
  28. package/src/templates/cordova-plugin-html2apk-bridge/package.json +16 -0
  29. package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +39 -0
  30. package/src/templates/cordova-plugin-html2apk-bridge/src/android/BootReceiver.java +20 -0
  31. package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +375 -0
  32. package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +112 -0
  33. package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationStore.java +91 -0
  34. package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +129 -0
  35. package/src/utils/command-runner.js +124 -0
  36. package/src/utils/fs-extra.js +111 -0
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+
3
+ const { spawn } = require("child_process");
4
+ const path = require("path");
5
+
6
+ function pathDelimiter() {
7
+ return process.platform === "win32" ? ";" : ":";
8
+ }
9
+
10
+ function packageBinPath() {
11
+ return path.resolve(__dirname, "..", "..", "node_modules", ".bin");
12
+ }
13
+
14
+ function withLocalBin(env) {
15
+ const nextEnv = { ...env };
16
+ const key = Object.prototype.hasOwnProperty.call(nextEnv, "Path") ? "Path" : "PATH";
17
+ const current = nextEnv[key] || "";
18
+ const localBin = packageBinPath();
19
+ const parts = current.split(pathDelimiter()).filter(Boolean);
20
+
21
+ if (!parts.includes(localBin)) {
22
+ nextEnv[key] = [localBin, ...parts].join(pathDelimiter());
23
+ }
24
+
25
+ return nextEnv;
26
+ }
27
+
28
+ function quoteForCmd(value) {
29
+ const text = String(value);
30
+ if (text.length === 0) {
31
+ return "\"\"";
32
+ }
33
+
34
+ if (/^[a-zA-Z0-9_@./:\\=+\-]+$/.test(text)) {
35
+ return text;
36
+ }
37
+
38
+ return `"${text.replace(/(["^&|<>])/g, "^$1")}"`;
39
+ }
40
+
41
+ function createSpawnSpec(command, args) {
42
+ const needsCmdShim = process.platform === "win32" && new Set(["cordova", "gradle"]).has(command);
43
+ if (!needsCmdShim) {
44
+ return { command, args };
45
+ }
46
+
47
+ return {
48
+ command: "cmd.exe",
49
+ args: ["/d", "/s", "/c", [command, ...args.map(quoteForCmd)].join(" ")]
50
+ };
51
+ }
52
+
53
+ function createCommandRunner({ logs = [], env = process.env, onLog } = {}) {
54
+ const baseEnv = withLocalBin(env);
55
+
56
+ function log(line) {
57
+ logs.push(line);
58
+ if (typeof onLog === "function") {
59
+ onLog(line);
60
+ }
61
+ }
62
+
63
+ async function run(command, args = [], options = {}) {
64
+ const pretty = `${command} ${args.join(" ")}`.trim();
65
+ log(`$ ${pretty}`);
66
+ const spawnSpec = createSpawnSpec(command, args);
67
+
68
+ return new Promise((resolve, reject) => {
69
+ const child = spawn(spawnSpec.command, spawnSpec.args, {
70
+ cwd: options.cwd,
71
+ env: { ...baseEnv, ...(options.env || {}) },
72
+ stdio: ["ignore", "pipe", "pipe"],
73
+ shell: false
74
+ });
75
+
76
+ let stdout = "";
77
+ let stderr = "";
78
+
79
+ child.stdout.on("data", (chunk) => {
80
+ const text = chunk.toString();
81
+ stdout += text;
82
+ if (options.pipeOutput) {
83
+ process.stdout.write(text);
84
+ }
85
+ });
86
+
87
+ child.stderr.on("data", (chunk) => {
88
+ const text = chunk.toString();
89
+ stderr += text;
90
+ if (options.pipeOutput) {
91
+ process.stderr.write(text);
92
+ }
93
+ });
94
+
95
+ child.on("error", (error) => {
96
+ reject(new Error(`Failed to run "${pretty}": ${error.message}`));
97
+ });
98
+
99
+ child.on("close", (code) => {
100
+ const summary = [stdout, stderr].join("\n").trim();
101
+ if (summary) {
102
+ log(summary.slice(-4000));
103
+ }
104
+
105
+ if (code === 0) {
106
+ resolve({ code, stdout, stderr });
107
+ return;
108
+ }
109
+
110
+ const error = new Error(`Command failed (${code}): ${pretty}`);
111
+ error.code = code;
112
+ error.stdout = stdout;
113
+ error.stderr = stderr;
114
+ reject(error);
115
+ });
116
+ });
117
+ }
118
+
119
+ return { run, logs };
120
+ }
121
+
122
+ module.exports = {
123
+ createCommandRunner
124
+ };
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs/promises");
4
+ const path = require("path");
5
+
6
+ const DEFAULT_IGNORES = new Set([
7
+ ".git",
8
+ ".svn",
9
+ ".hg",
10
+ "node_modules",
11
+ "platforms",
12
+ "plugins",
13
+ "hooks",
14
+ "dist",
15
+ "build",
16
+ ".gradle",
17
+ "app.json",
18
+ "config.json"
19
+ ]);
20
+
21
+ async function ensureDir(dirPath) {
22
+ await fs.mkdir(dirPath, { recursive: true });
23
+ }
24
+
25
+ async function removePath(targetPath) {
26
+ await fs.rm(targetPath, { recursive: true, force: true });
27
+ }
28
+
29
+ async function copyFile(source, destination) {
30
+ await ensureDir(path.dirname(destination));
31
+ await fs.copyFile(source, destination);
32
+ }
33
+
34
+ async function copyDirectory(source, destination, shouldIgnore) {
35
+ await ensureDir(destination);
36
+ const entries = await fs.readdir(source, { withFileTypes: true });
37
+
38
+ for (const entry of entries) {
39
+ const sourcePath = path.join(source, entry.name);
40
+ const destinationPath = path.join(destination, entry.name);
41
+ if (shouldIgnore(sourcePath, entry)) {
42
+ continue;
43
+ }
44
+
45
+ if (entry.isDirectory()) {
46
+ await copyDirectory(sourcePath, destinationPath, shouldIgnore);
47
+ } else if (entry.isFile()) {
48
+ await copyFile(sourcePath, destinationPath);
49
+ }
50
+ }
51
+ }
52
+
53
+ function normalizeFileMapping(item) {
54
+ if (typeof item === "string") {
55
+ return { from: item, to: item };
56
+ }
57
+
58
+ if (item && typeof item === "object" && item.from) {
59
+ return {
60
+ from: item.from,
61
+ to: item.to || item.from
62
+ };
63
+ }
64
+
65
+ throw new Error(`Invalid files entry: ${JSON.stringify(item)}`);
66
+ }
67
+
68
+ function isInside(parent, child) {
69
+ const relative = path.relative(parent, child);
70
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
71
+ }
72
+
73
+ async function copyWebAssets(webRoot, destination, options, projectRoot) {
74
+ await removePath(destination);
75
+ await ensureDir(destination);
76
+
77
+ if (Array.isArray(options.files) && options.files.length > 0) {
78
+ for (const item of options.files) {
79
+ const mapping = normalizeFileMapping(item);
80
+ const source = path.resolve(webRoot, mapping.from);
81
+ if (!isInside(projectRoot, source)) {
82
+ throw new Error(`File mapping leaves project root: ${mapping.from}`);
83
+ }
84
+
85
+ const target = path.resolve(destination, mapping.to);
86
+ if (!isInside(destination, target)) {
87
+ throw new Error(`File mapping leaves Cordova www folder: ${mapping.to}`);
88
+ }
89
+
90
+ const stat = await fs.stat(source);
91
+ if (stat.isDirectory()) {
92
+ await copyDirectory(source, target, () => false);
93
+ } else {
94
+ await copyFile(source, target);
95
+ }
96
+ }
97
+ return;
98
+ }
99
+
100
+ await copyDirectory(webRoot, destination, (_sourcePath, entry) => {
101
+ return DEFAULT_IGNORES.has(entry.name);
102
+ });
103
+ }
104
+
105
+ module.exports = {
106
+ ensureDir,
107
+ removePath,
108
+ copyFile,
109
+ copyDirectory,
110
+ copyWebAssets
111
+ };