+npm run build-release
```
## • Contributing
@@ -81,14 +82,14 @@ Acode Editor is an open-source project, and we welcome contributions from the co
Please ensure that your code is clean, well-formatted, and follows the project's coding standards. Acode uses [Biomejs](https://biomejs.dev/) for formatting and linting and [typos](https://github.com/crate-ci/typos) for low false positives source code spell checking. You can use following commands to lints/format your code locally:
```shell
-yarn lint # for linting
-yarn format # for formatting
-yarn check # it runs both lint and format
+npm run lint # for linting
+npm run format # for formatting
+npm run check # it runs both lint and format
```
Also, ensure that your code is well-documented and includes comments where necessary.
> [!Note]
-> You can use any package manager like npm or yarn or pnpm or bun.
+> You can use any package manager like npm or npm run or pnpm or bun.
> You can use your editor specific Biomejs plugin for auto-formatting and linting based on Acode's configs.
## • Contributors
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 000000000..072d970bc
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,167 @@
+const os = require("os");
+const { execSync } = require("child_process");
+const path = require("path");
+const fs = require("fs");
+
+const platform = os.platform();
+const isWindows = platform === "win32";
+
+// Check for supported platforms
+if (
+ !["linux", "darwin", "win32"].includes(platform) &&
+ !platform.toLowerCase().includes("bsd")
+) {
+ console.error(`Unsupported platform: ${platform}`);
+ process.exit(1);
+}
+
+
+// CLI args: appType (free/paid), buildMode (d/p or debug/prod)
+const appType = (process.argv[2] || "paid").toLowerCase();
+const buildMode = (process.argv[3] || "d").toLowerCase();
+
+// Normalize app type and mode
+const normalizedType = appType.startsWith("f") ? "Free" : "Paid";
+const normalizedMode =
+ buildMode === "p" || buildMode === "prod" || buildMode === "release"
+ ? "Release"
+ : "Debug";
+
+// Terminal colors
+const colors = {
+ reset: "\x1b[0m",
+ green: "\x1b[32;1m",
+ blue: "\x1b[34;1m",
+ yellow: "\x1b[33;1m",
+ red: "\x1b[31;1m",
+ white: "\x1b[37m",
+};
+
+// Logging helpers
+const log = {
+ success: (msg) =>
+ console.log(`${colors.green}[+] ${colors.white}${msg}${colors.reset}`),
+ info: (msg) =>
+ console.log(`${colors.blue}[*] ${colors.white}${msg}${colors.reset}`),
+ warn: (msg) =>
+ console.log(`${colors.yellow}[~] ${colors.white}${msg}${colors.reset}`),
+ error: (msg) => {
+ console.error(`${colors.red}[!] ${colors.white}${msg}${colors.reset}`);
+ process.exit(1);
+ },
+};
+
+// Check JAVA_HOME
+if (!process.env.JAVA_HOME) {
+ log.warn(
+ "JAVA_HOME is not set. Please set it to your Java installation path."
+ );
+} else {
+ log.info(`JAVA_HOME: ${process.env.JAVA_HOME}`);
+}
+
+// Check ANDROID_HOME
+if (!process.env.ANDROID_HOME) {
+ log.warn("ANDROID_HOME is not set. Please set it to your Android SDK path.");
+} else {
+ log.info(`ANDROID_HOME: ${process.env.ANDROID_HOME}`);
+}
+
+// Verify Java version is 21
+try {
+ const versionOutput = execSync("java -version 2>&1").toString();
+ const match = versionOutput.match(/version\s+"(\d+)(?:\.(\d+))?/);
+ const majorVersion = match ? parseInt(match[1]) : null;
+
+ const versionString = match?.[1]
+ ? `${match[1]}${match[2] ? "." + match[2] : ""}`
+ : "unknown";
+ log.info(`Current Java version is ${versionString}.`);
+
+ if (!majorVersion || majorVersion < 21) {
+ log.error(`Java 21 is required.`);
+ }
+} catch {
+ log.error("Java is not installed or not accessible.");
+}
+
+
+// Display config
+log.info(`App Type: ${normalizedType}`);
+log.info(`Build Mode: ${normalizedMode}`);
+
+// Constants
+//for some reason ad id is not properly added to the android manifest by this cordovalugin so instead of editing this
+//edit the android/app/src/free/AndroidManifest.xml file
+const AD_APP_ID = "ca-app-pub-5911839694379275~4255791238";
+const PROJECT_ROOT = execSync("npm prefix").toString().trim();
+const androidDir = path.join(PROJECT_ROOT, "android");
+
+try {
+ // Plugin logic based on app type
+ if (normalizedType === "Paid") {
+ log.info("Removing Admob plugins for paid build");
+ if (
+ execSync("cordova plugin ls")
+ .toString()
+ .includes("cordova-plugin-consent")
+ ) {
+ execSync("cordova plugin remove cordova-plugin-consent --save", {
+ stdio: "inherit",
+ });
+ }
+ if (
+ execSync("cordova plugin ls").toString().includes("admob-plus-cordova")
+ ) {
+ execSync("cordova plugin remove admob-plus-cordova --save", {
+ stdio: "inherit",
+ });
+ }
+ } else {
+ log.info("Adding Admob plugins for free build");
+ execSync("cordova plugin add cordova-plugin-consent@2.4.0 --save", {
+ stdio: "inherit",
+ });
+ execSync(
+ `cordova plugin add admob-plus-cordova@1.28.0 --save --variable APP_ID_ANDROID="${AD_APP_ID}" --variable PLAY_SERVICES_VERSION="21.5.0"`,
+ { stdio: "inherit" }
+ );
+ }
+
+ // Create build folders
+ fs.mkdirSync(path.join(PROJECT_ROOT, "www", "css", "build"), {
+ recursive: true,
+ });
+ fs.mkdirSync(path.join(PROJECT_ROOT, "www", "js", "build"), {
+ recursive: true,
+ });
+
+ // Webpack
+ const webpackMode =
+ normalizedMode === "Release" ? "production" : "development";
+ execSync(`webpack --progress --mode ${webpackMode}`, { stdio: "inherit" });
+
+ // Preload styles
+ execSync("node ./utils/loadStyles.js", { stdio: "inherit" });
+
+ // Sync
+ execSync("npm run sync", { stdio: "inherit" });
+
+ // Ensure gradlew is executable on Unix systems
+ const gradlewPath = path.join(
+ androidDir,
+ isWindows ? "gradlew.bat" : "gradlew",
+ );
+ if (!isWindows) {
+ fs.chmodSync(gradlewPath, 0o755);
+ }
+
+ // Run gradle task
+ const gradleTask = `assemble${normalizedType}${normalizedMode}`;
+ const gradleCmd = `${isWindows ? "gradlew.bat" : "./gradlew"} ${gradleTask}`;
+ execSync(gradleCmd, { cwd: androidDir, stdio: "inherit" });
+
+ log.success("Build completed successfully.");
+} catch (e) {
+ log.error(`Build failed: ${e.message}`);
+}
diff --git a/scripts/postSync.js b/scripts/postSync.js
new file mode 100644
index 000000000..32c4ca052
--- /dev/null
+++ b/scripts/postSync.js
@@ -0,0 +1,61 @@
+const fs = require('fs');
+const path = require('path');
+
+/**
+ * Recursively find all `build.gradle` files in the `android/` directory
+ */
+function findBuildGradleFiles(dir) {
+ let results = [];
+ const list = fs.readdirSync(dir);
+
+ list.forEach(file => {
+ const filepath = path.join(dir, file);
+ const stat = fs.statSync(filepath);
+
+ if (stat && stat.isDirectory()) {
+ results = results.concat(findBuildGradleFiles(filepath));
+ } else if (file === 'build.gradle') {
+ results.push(filepath);
+ }
+ });
+
+ return results;
+}
+
+/**
+ * Patch build.gradle if needed
+ */
+function patchBuildGradle(filePath) {
+ if(filePath.includes("/app/")){
+ return
+ }
+ let contents = fs.readFileSync(filePath, 'utf8');
+
+ if (!contents.includes('android {')) {
+ console.log(`⏭️ Skipping ${filePath} (no android block)`);
+ return;
+ }
+
+ if (contents.includes('buildConfig true')) {
+ console.log(`✅ ${filePath} already contains buildConfig = true`);
+ return;
+ }
+
+ contents = contents.replace(
+ /android\s*\{/,
+ `android {\n buildFeatures {\n buildConfig true\n }`
+ );
+
+ fs.writeFileSync(filePath, contents);
+ console.log(`✅ Patched ${filePath}`);
+}
+
+// Run it
+const gradleFiles = findBuildGradleFiles(path.resolve('android'));
+
+if (gradleFiles.length === 0) {
+ console.log('❌ No build.gradle files found.');
+} else {
+ gradleFiles.forEach(patchBuildGradle);
+}
+
diff --git a/src/fileSystem/internalFs.js b/src/fileSystem/internalFs.js
index c53654cf7..34fb387c5 100644
--- a/src/fileSystem/internalFs.js
+++ b/src/fileSystem/internalFs.js
@@ -1,24 +1,47 @@
+import { Directory, Encoding, Filesystem } from "@capacitor/filesystem";
import ajax from "@deadlyjack/ajax";
+import { data } from "autoprefixer";
import fsOperation from "fileSystem";
+import path from "path-browserify";
import Url from "utils/Url";
import { decode, encode } from "utils/encodings";
import helpers from "utils/helpers";
const internalFs = {
/**
- *
+ * List files from a Directory (not recursive)
* @param {string} url
* @returns {Promise}
*/
listDir(url) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(url, success, reject);
- function success(fs) {
- const reader = fs.createReader();
- reader.readEntries(resolve, reject);
- }
+ Filesystem.readdir({ path: url })
+ .then((result) => {
+ console.log(
+ `Listed files/directories successfully for url: ${url}, Result: `,
+ result,
+ );
+ resolve(
+ result.files.map((file) => ({
+ name: file.name,
+ url: file.uri,
+ size: file.size,
+ ctime: file.ctime,
+ mtime: file.mtime,
+ isFile: file.type === "file",
+ isDirectory: file.type === "directory",
+ isLink: false,
+ })),
+ );
+ })
+ .catch((error) => {
+ console.log(
+ `Error while listing Directory for url: ${url}, error:`,
+ error,
+ );
+ });
});
},
@@ -32,33 +55,54 @@ const internalFs = {
* @param {boolean} exclusive If true, and the create option is also true,
* the file must not exist prior to issuing the call.
* Instead, it must be possible for it to be created newly at call time. The default is true.
- * @returns {Promise}
+ * @returns {Promise} URL where the file was written into.
*/
- writeFile(filename, data, create = false, exclusive = true) {
+ writeFile(filename, udata, create = false, exclusive = true) {
exclusive = create ? exclusive : false;
const name = filename.split("/").pop();
- const dirname = Url.dirname(filename);
return new Promise((resolve, reject) => {
+ if (udata === undefined || udata == null) {
+ reject("udata is null");
+ }
+
+ let options = {
+ path: filename,
+ recursive: create,
+ };
+
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- dirname,
- (entry) => {
- entry.getFile(
- name,
- { create, exclusive },
- (fileEntry) => {
- fileEntry.createWriter((file) => {
- file.onwriteend = (res) => resolve(filename);
- file.onerror = (err) => reject(err.target.error);
- file.write(data);
- });
- },
- reject,
+
+ if (
+ udata instanceof ArrayBuffer ||
+ Object.prototype.toString.call(udata) === "[object ArrayBuffer]"
+ ) {
+ // Binary data — store as base64
+ options.data = btoa(String.fromCharCode(...new Uint8Array(udata)));
+ options.encoding = Encoding.BASE64;
+ } else if (typeof udata === "string") {
+ // Text data — store as UTF-8
+ options.data = udata;
+ options.encoding = Encoding.UTF8;
+ } else {
+ reject("Unsupported udata type");
+ return;
+ }
+
+ Filesystem.writeFile(options)
+ .then((file) => {
+ console.log(
+ `Successfully written into (name: ${name}) ${filename} file`,
);
- },
- reject,
- );
+ resolve(file.uri);
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to write into (name: ${name}) ${filename} file, error: `,
+ error,
+ );
+ reject(error);
+ });
});
},
@@ -67,20 +111,32 @@ const internalFs = {
* @param {string} filename
* @returns {Promise}
*/
+
delete(filename) {
- return new Promise((resolve, reject) => {
- reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- filename,
- (entry) => {
- if (entry.isFile) {
- entry.remove(resolve, reject);
- } else {
- entry.removeRecursively(resolve, reject);
- }
- },
- reject,
- );
+ return new Promise(async (resolve, reject) => {
+ console.log("Deleting " + filename);
+
+ if (!(await this.exists(filename))) {
+ console.warn(`File ${filename} doesnt exists`);
+ resolve();
+ } else {
+ Filesystem.stat({ path: filename })
+ .then((stats) => {
+ if (stats.type === "directory") {
+ return Filesystem.rmdir({ path: filename, recursive: true });
+ } else {
+ return Filesystem.deleteFile({ path: filename });
+ }
+ })
+ .then(() => {
+ console.log("Deleted successfully!");
+ resolve();
+ })
+ .catch((error) => {
+ console.error("Error while deleting:", error);
+ reject(error);
+ });
+ }
});
},
@@ -90,35 +146,65 @@ const internalFs = {
* @param {string} encoding
* @returns {Promise}
*/
+
readFile(filename) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- filename,
- (fileEntry) => {
- (async () => {
- const url = fileEntry.toInternalURL();
- try {
- const data = await ajax({
- url: url,
- responseType: "arraybuffer",
- });
+ Filesystem.readFile({ path: filename, encoding: Encoding.UTF8 })
+ .then((readFileResult) => {
+ const encoder = new TextEncoder();
+ const buffer = encoder.encode(readFileResult.data).buffer;
- resolve({ data });
- } catch (error) {
- fileEntry.file((file) => {
- const fileReader = new FileReader();
- fileReader.onerror = reject;
- fileReader.readAsArrayBuffer(file);
- fileReader.onloadend = () => {
- resolve({ data: fileReader.result });
- };
- }, reject);
- }
- })();
- },
- reject,
- );
+ resolve({ data: buffer });
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to Read File contents of "${filename}", error: `,
+ error,
+ );
+ reject(error);
+ });
+ });
+ },
+
+ readFileRaw(filename) {
+ return new Promise((resolve, reject) => {
+ reject = setMessage(reject);
+ Filesystem.readFile({ path: filename, encoding: Encoding.BASE64 })
+ .then((readFileResult) => {
+ const base64 = readFileResult.data;
+ const binary = atob(base64);
+ const bytes = new Uint8Array(binary.length);
+ for (let i = 0; i < binary.length; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+
+ resolve({ data: bytes.buffer });
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to read raw file "${filename}", error: `,
+ error,
+ );
+ reject(error);
+ });
+ });
+ },
+
+ readStringFile(filename) {
+ return new Promise((resolve, reject) => {
+ reject = setMessage(reject);
+ Filesystem.readFile({ path: filename, encoding: Encoding.UTF8 })
+ .then((readFileResult) => {
+ resolve({ data: readFileResult.data });
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to Read File contents of "${filename}", error: `,
+ error,
+ );
+ reject(error);
+ });
});
},
@@ -160,21 +246,24 @@ const internalFs = {
createDir(path, dirname) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- path,
- (fs) => {
- fs.getDirectory(
- dirname,
- { create: true },
- async () => {
- const stats = await this.stats(Url.join(path, dirname));
- resolve(stats.url);
- },
- reject,
+ // TODO!: ask about `recursive` option
+ Filesystem.mkdir({
+ path: `${path}/${dirname}`,
+ recursive: true,
+ })
+ .then(() => {
+ console.log(`Created ${path}/${dirname}`);
+ Filesystem.stat({ path: `${path}/${dirname}` })
+ .then((stats) => resolve(stats.url))
+ .catch(reject);
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to create ${dirname} directory in path: ${path}, error:`,
+ error,
);
- },
- reject,
- );
+ reject(error);
+ });
});
},
@@ -210,14 +299,33 @@ const internalFs = {
reject = setMessage(reject);
this.verify(src, dest)
.then((res) => {
- const { src, dest } = res;
-
- src[action](
- dest,
- undefined,
- (entry) => resolve(decodeURIComponent(entry.nativeURL)),
- reject,
- );
+ if (action === "copyTO") {
+ Filesystem.copy({
+ from: src,
+ to: dest,
+ })
+ .then((copyResult) => {
+ console.log(`Successfully copied from "${src}" to "${dest}"`);
+ resolve(copyResult.uri);
+ })
+ .catch((error) => {
+ console.error(`Failed to copy from "${src}" to "${dest}"`);
+ reject(error);
+ });
+ } else if (action === "moveTO") {
+ Filesystem.rename({
+ from: src,
+ to: dest,
+ })
+ .then((moveResult) => {
+ console.log(`Successfully moved from "${src}" to "${dest}"`);
+ resolve(dest);
+ })
+ .catch((error) => {
+ console.error(`Failed to move from "${src}" to "${dest}"`);
+ reject(error);
+ });
+ }
})
.catch(reject);
});
@@ -231,11 +339,14 @@ const internalFs = {
stats(filename) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- filename,
- (entry) => {
+ Filesystem.stat({ path: filename })
+ .then((entry) => {
+ console.log(
+ `Successfully returned stats for "${filename}", Result: `,
+ entry,
+ );
sdcard.stats(
- entry.nativeURL,
+ entry.uri,
(stats) => {
helpers.defineDeprecatedProperty(
stats,
@@ -252,13 +363,19 @@ const internalFs = {
},
reject,
);
- },
- reject,
- );
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to show stats for "${filename}", error:`,
+ error,
+ );
+ reject(error);
+ });
});
},
/**
+ * TODO: check this function with Rohit.
* Verify if a file or directory exists
* @param {string} src
* @param {string} dest
@@ -267,36 +384,48 @@ const internalFs = {
verify(src, dest) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- src,
- (srcEntry) => {
- window.resolveLocalFileSystemURL(
- dest,
- (destEntry) => {
- window.resolveLocalFileSystemURL(
- Url.join(destEntry.nativeURL, srcEntry.name),
- (res) => {
- reject({
- code: 12,
- });
- },
- (err) => {
- if (err.code === 1) {
- resolve({
- src: srcEntry,
- dest: destEntry,
- });
- } else {
- reject(err);
- }
- },
+
+ // check if source exists
+ Filesystem.stat({
+ path: src,
+ })
+ .then((srcStat) => {
+ console.log(
+ `"${src}" source dir/file verified successful, checking if source dir/file already exists in "${dest}" destination file/dir`,
+ );
+ // Check if file/folder already exists at the destination
+ Filesystem.stat({
+ path: `${dest}/${srcStat.name}`,
+ })
+ .then(() => {
+ // File already exists error.
+ reject({
+ code: 12,
+ });
+ })
+ .catch((fileExistsErr) => {
+ console.error(
+ "Failed to verify source in destination, error: ",
+ error,
);
- },
- reject,
+ // if we get a "not found" error (code 1), that's good - we can copy
+ if (fileExistsErr.code === 1) {
+ resolve({
+ src: { path: src },
+ dest: { path: dest },
+ });
+ } else {
+ reject(fileExistsErr);
+ }
+ });
+ })
+ .catch((error) => {
+ console.error(
+ `Failed to verify "${src}" source dir/file, error: `,
+ error,
);
- },
- reject,
- );
+ reject(error);
+ });
});
},
@@ -307,16 +436,21 @@ const internalFs = {
exists(url) {
return new Promise((resolve, reject) => {
reject = setMessage(reject);
- window.resolveLocalFileSystemURL(
- url,
- (entry) => {
+ Filesystem.stat({
+ path: url,
+ })
+ .then((stats) => {
+ if (!stats.uri) return resolve(false);
+ console.log(
+ `Successfully found (name: ${stats.name || "name not found"}) "${url}" existing`,
+ );
resolve(true);
- },
- (err) => {
- if (err.code === 1) resolve(false);
- reject(err);
- },
- );
+ })
+ .catch((err) => {
+ // on-error defaulting to false,
+ // as capacitor doesn't emit error codes, for error types(file not found, etc)
+ resolve(false);
+ });
});
},
@@ -405,6 +539,7 @@ function createFs(url) {
return files;
},
async readFile(encoding) {
+ console.log("fs read " + url);
let { data } = await internalFs.readFile(url, encoding);
if (encoding) {
diff --git a/src/handlers/intent.js b/src/handlers/intent.js
index ae526e79d..58c0edc48 100644
--- a/src/handlers/intent.js
+++ b/src/handlers/intent.js
@@ -1,4 +1,5 @@
import fsOperation from "fileSystem";
+import internalFs from "fileSystem/internalFs";
import openFile from "lib/openFile";
import helpers from "utils/helpers";
@@ -9,9 +10,11 @@ const handlers = [];
* @param {Intent} intent
*/
export default async function HandleIntent(intent = {}) {
- const type = intent.action.split(".").slice(-1)[0];
-
- if (["SEND", "VIEW", "EDIT"].includes(type)) {
+ if (
+ intent !== undefined &&
+ intent.action !== undefined &&
+ ["SEND", "VIEW", "EDIT"].includes(intent.action.split(".").slice(-1)[0])
+ ) {
/**@type {string} */
const url = intent.fileUri || intent.data;
if (!url) return;
@@ -32,7 +35,7 @@ export default async function HandleIntent(intent = {}) {
if (module === "plugin") {
const { default: Plugin } = await import("pages/plugin");
- const installed = await fsOperation(PLUGIN_DIR, value).exists();
+ const installed = await internalFs.exists(PLUGIN_DIR);
Plugin({ id: value, installed, install: action === "install" });
}
diff --git a/src/lib/acode.js b/src/lib/acode.js
index 6ff2dcfc7..734a3ad7a 100644
--- a/src/lib/acode.js
+++ b/src/lib/acode.js
@@ -179,7 +179,7 @@ export default class Acode {
return;
}
- fsOperation(Url.join(PLUGIN_DIR, pluginId))
+ fsOperation(`${PLUGIN_DIR}/${pluginId}`)
.exists()
.then((isPluginExists) => {
if (isPluginExists) {
@@ -189,12 +189,9 @@ export default class Acode {
let purchaseToken;
let product;
- const pluginUrl = Url.join(
- constants.API_BASE,
- `plugin/${pluginId}`,
- );
- fsOperation(pluginUrl)
- .readFile("json")
+ const pluginUrl = `${PLUGIN_DIR}/${pluginId}`;
+ fsOperation(`${pluginUrl}/plugin.json`)
+ .readFile()
.catch(() => {
reject(new Error("Failed to fetch plugin details"));
return null;
diff --git a/src/lib/checkFiles.js b/src/lib/checkFiles.js
index a964e2899..f37f56271 100644
--- a/src/lib/checkFiles.js
+++ b/src/lib/checkFiles.js
@@ -47,7 +47,7 @@ export default async function checkFiles() {
* @returns {Promise}
*/
async function checkFile(file) {
- if (file.isUnsaved || !file.loaded || file.loading) return;
+ if (!file.loaded || file.loading || file.isUnsaved) return;
if (file.uri) {
const fs = fsOperation(file.uri);
diff --git a/src/lib/checkPluginsUpdate.js b/src/lib/checkPluginsUpdate.js
index f58dee687..ecf5be115 100644
--- a/src/lib/checkPluginsUpdate.js
+++ b/src/lib/checkPluginsUpdate.js
@@ -1,25 +1,30 @@
import ajax from "@deadlyjack/ajax";
+import internalFs from "fileSystem/internalFs";
import fsOperation from "../fileSystem";
import Url from "../utils/Url";
export default async function checkPluginsUpdate() {
- const plugins = await fsOperation(PLUGIN_DIR).lsDir();
+ const plugins = await internalFs.listDir(PLUGIN_DIR);
const promises = [];
const updates = [];
plugins.forEach((pluginDir) => {
promises.push(
(async () => {
- const plugin = await fsOperation(
- Url.join(pluginDir.url, "plugin.json"),
- ).readFile("json");
+ try {
+ const plugin = await fsOperation(
+ Url.join(pluginDir.url, "plugin.json"),
+ ).readFile("json");
- const res = await ajax({
- url: `https://acode.app/api/plugin/check-update/${plugin.id}/${plugin.version}`,
- });
+ const res = await ajax({
+ url: `https://acode.app/api/plugin/check-update/${plugin.id}/${plugin.version}`,
+ });
- if (res.update) {
- updates.push(plugin.id);
+ if (res && res.update === true) {
+ updates.push(plugin.id);
+ }
+ } catch (e) {
+ console.warn(e);
}
})(),
);
diff --git a/src/lib/constants.js b/src/lib/constants.js
index 7c1283227..4c44fff2a 100644
--- a/src/lib/constants.js
+++ b/src/lib/constants.js
@@ -23,4 +23,5 @@ export default {
// API_BASE: 'https://192.168.0.102:3001/api', // test api
SKU_LIST: ["basic", "bronze", "silver", "gold", "platinum"],
LOG_FILE_NAME: "Acode.log",
+ CONSOLE_BUILD_PATH: "/js/build/console.build.js",
};
diff --git a/src/lib/installPlugin.js b/src/lib/installPlugin.js
index 84b9b20bd..56ee9462f 100644
--- a/src/lib/installPlugin.js
+++ b/src/lib/installPlugin.js
@@ -3,6 +3,7 @@ import alert from "dialogs/alert";
import confirm from "dialogs/confirm";
import loader from "dialogs/loader";
import fsOperation from "fileSystem";
+import internalFs from "fileSystem/internalFs";
import purchaseListener from "handlers/purchase";
import JSZip from "jszip";
import Url from "utils/Url";
@@ -29,6 +30,7 @@ export default async function installPlugin(
purchaseToken,
isDependency,
) {
+ console.log(`Installing ${name}`);
if (!isDependency) {
loaderDialog = loader.create(name || "Plugin", strings.installing, {
timeout: 6000,
@@ -41,13 +43,14 @@ export default async function installPlugin(
let state;
try {
- if (!(await fsOperation(PLUGIN_DIR).exists())) {
- await fsOperation(DATA_STORAGE).createDirectory("plugins");
+ if (!(await internalFs.exists(PLUGIN_DIR))) {
+ await internalFs.createDir(DATA_STORAGE, "plugins");
}
} catch (error) {
window.log("error", error);
}
+ console.log("installing -------------------------");
if (!/^(https?|file|content):/.test(id)) {
pluginUrl = Url.join(
constants.API_BASE,
@@ -66,6 +69,8 @@ export default async function installPlugin(
try {
if (!isDependency) loaderDialog.show();
+ console.log("installing ...");
+
let plugin;
if (
pluginUrl.includes(constants.API_BASE) ||
@@ -93,9 +98,11 @@ export default async function installPlugin(
(response) => {
resolve(response.data);
loaderDialog.setMessage(`${strings.loading} 100%`);
+ console.log("Download complete");
},
(error) => {
reject(error);
+ console.log("Download failed");
},
);
});
@@ -104,15 +111,19 @@ export default async function installPlugin(
if (plugin) {
const zip = new JSZip();
await zip.loadAsync(plugin);
+ console.log("Plugin zip loaded into memory");
if (!zip.files["plugin.json"]) {
throw new Error(strings["invalid plugin"]);
}
+ console.log("Plugin Json start");
+ const jsonStr = await zip.files["plugin.json"].async("text");
+ console.log(jsonStr);
+ console.log("Plugin json end");
+
/** @type {{ dependencies: string[] }} */
- const pluginJson = JSON.parse(
- await zip.files["plugin.json"].async("text"),
- );
+ const pluginJson = JSON.parse(jsonStr);
/** patch main in manifest */
if (!zip.files[pluginJson.main]) {
@@ -167,20 +178,25 @@ export default async function installPlugin(
pluginDir = Url.join(PLUGIN_DIR, id);
}
+ console.log("Begin Install state");
state = await InstallState.new(id);
+ console.log("Install state end");
- if (!(await fsOperation(pluginDir).exists())) {
- await fsOperation(PLUGIN_DIR).createDirectory(id);
+ if (!(await internalFs.exists(pluginDir))) {
+ await internalFs.createDir(PLUGIN_DIR, id);
}
const promises = Object.keys(zip.files).map(async (file) => {
try {
let correctFile = file;
+
if (/\\/.test(correctFile)) {
correctFile = correctFile.replace(/\\/g, "/");
}
+ console.log(`Correct file ${correctFile}`);
const fileUrl = Url.join(pluginDir, correctFile);
+ console.log("file Url " + fileUrl);
if (!state.exists(correctFile)) {
await createFileRecursive(pluginDir, correctFile);
@@ -196,7 +212,9 @@ export default async function installPlugin(
}
if (!(await state.isUpdated(correctFile, data))) return;
+ console.log("writing file");
await fsOperation(fileUrl).writeFile(data);
+ console.log("file written");
return;
} catch (error) {
console.error(`Error processing file ${file}:`, error);
@@ -206,17 +224,27 @@ export default async function installPlugin(
// Wait for all files to be processed
await Promise.allSettled(promises);
+ console.log("done");
+
if (isDependency) {
depsLoaders.push(async () => {
await loadPlugin(id, true);
});
} else {
+ console.log("loaders");
for (const loader of depsLoaders) {
+ console.log("loading loader");
+ console.log(loader);
await loader();
}
+ console.log("loader loading done");
+ console.log("loading plugin");
await loadPlugin(id, true);
+ console.log("loading plugin done");
}
+ console.log("successfully loaded");
+
await state.save();
deleteRedundantFiles(pluginDir, state);
}
diff --git a/src/lib/installState.js b/src/lib/installState.js
index 4b7859b0f..5fc12b887 100644
--- a/src/lib/installState.js
+++ b/src/lib/installState.js
@@ -25,7 +25,11 @@ export default class InstallState {
}
state.storeUrl = Url.join(INSTALL_STATE_STORAGE, state.id);
- if (await fsOperation(state.storeUrl).exists()) {
+
+ console.log("store url");
+ console.log(state.storeUrl);
+
+ if ((await fsOperation(state.storeUrl).exists()) && false) {
state.store = JSON.parse(
await fsOperation(state.storeUrl).readFile("utf-8"),
);
@@ -41,6 +45,10 @@ export default class InstallState {
await fsOperation(INSTALL_STATE_STORAGE).createFile(state.id);
}
+ await fsOperation(state.storeUrl).delete();
+ console.log("returned state");
+ console.log(state);
+
return state;
} catch (e) {
console.error(e);
diff --git a/src/lib/loadPlugin.js b/src/lib/loadPlugin.js
index cc0b0d13f..00aaeab4c 100644
--- a/src/lib/loadPlugin.js
+++ b/src/lib/loadPlugin.js
@@ -1,11 +1,15 @@
import Page from "components/page";
import fsOperation from "fileSystem";
+import internalFs from "fileSystem/internalFs";
import Url from "utils/Url";
import helpers from "utils/helpers";
import actionStack from "./actionStack";
export default async function loadPlugin(pluginId, justInstalled = false) {
- const baseUrl = await helpers.toInternalUri(Url.join(PLUGIN_DIR, pluginId));
+ const baseUrl = Url.join(PLUGIN_DIR, pluginId);
+
+ console.log("Base url " + baseUrl);
+
const cacheFile = Url.join(CACHE_STORAGE, pluginId);
const pluginJson = await fsOperation(
@@ -21,10 +25,32 @@ export default async function loadPlugin(pluginId, justInstalled = false) {
mainUrl = Url.join(baseUrl, "main.js");
}
- return new Promise((resolve, reject) => {
- const $script = ;
+ console.log(`main url ${mainUrl}`);
+
+ return new Promise(async (resolve, reject) => {
+ if (pluginId === undefined) {
+ console.error("Skipping loading plugin with undefined id");
+ reject("Skipping loading plugin with undefined id");
+ return;
+ }
+
+ const result = await internalFs.readStringFile(mainUrl);
+
+ console.log(`result ${result}`);
+
+ const data = result.data;
+
+ const blob = new Blob([data], { type: "text/javascript" });
+ const url = URL.createObjectURL(blob);
+
+ const $script = document.createElement("script");
+ $script.src = url;
+ $script.type = "text/javascript";
+
+ console.log("script created");
$script.onerror = (error) => {
+ URL.revokeObjectURL(url);
reject(
new Error(
`Failed to load script for plugin ${pluginId}: ${error.message || error}`,
@@ -32,7 +58,10 @@ export default async function loadPlugin(pluginId, justInstalled = false) {
);
};
+ console.log("on error registered");
+
$script.onload = async () => {
+ URL.revokeObjectURL(url);
const $page = Page("Plugin");
$page.show = () => {
actionStack.push({
@@ -48,15 +77,21 @@ export default async function loadPlugin(pluginId, justInstalled = false) {
};
try {
+ console.log("trying");
if (!(await fsOperation(cacheFile).exists())) {
await fsOperation(CACHE_STORAGE).createFile(pluginId);
}
- await acode.initPlugin(pluginId, baseUrl, $page, {
- cacheFileUrl: await helpers.toInternalUri(cacheFile),
- cacheFile: fsOperation(cacheFile),
- firstInit: justInstalled,
- });
+ await acode.initPlugin(
+ pluginId,
+ Capacitor.convertFileSrc(baseUrl),
+ $page,
+ {
+ cacheFileUrl: Capacitor.convertFileSrc(cacheFile),
+ cacheFile: fsOperation(cacheFile),
+ firstInit: justInstalled,
+ },
+ );
resolve();
} catch (error) {
@@ -64,6 +99,8 @@ export default async function loadPlugin(pluginId, justInstalled = false) {
}
};
+ console.log("attaching script");
document.head.append($script);
+ console.log("script attached");
});
}
diff --git a/src/lib/loadPlugins.js b/src/lib/loadPlugins.js
index d3eee52d3..9449543e8 100644
--- a/src/lib/loadPlugins.js
+++ b/src/lib/loadPlugins.js
@@ -1,3 +1,4 @@
+import internalFs from "fileSystem/internalFs";
import fsOperation from "../fileSystem";
import Url from "../utils/Url";
import loadPlugin from "./loadPlugin";
@@ -25,7 +26,7 @@ const THEME_IDENTIFIERS = new Set([
]);
export default async function loadPlugins(loadOnlyTheme = false) {
- const plugins = await fsOperation(PLUGIN_DIR).lsDir();
+ const plugins = await internalFs.listDir(PLUGIN_DIR);
const results = [];
const failedPlugins = [];
const loadedPlugins = new Set();
@@ -53,6 +54,7 @@ export default async function loadPlugins(loadOnlyTheme = false) {
// Load plugins concurrently
const loadPromises = pluginsToLoad.map(async (pluginDir) => {
+ console.log("loading");
const pluginId = Url.basename(pluginDir.url);
if (loadOnlyTheme && currentTheme) {
diff --git a/src/lib/main.js b/src/lib/main.js
index 91017d999..5347ae7ee 100644
--- a/src/lib/main.js
+++ b/src/lib/main.js
@@ -56,6 +56,8 @@ import { getEncoding, initEncodings } from "utils/encodings";
import auth, { loginEvents } from "./auth";
import constants from "./constants";
+import { App as CapacitorApp } from "@capacitor/app";
+
const previousVersionCode = Number.parseInt(localStorage.versionCode, 10);
window.onload = Main;
@@ -431,7 +433,7 @@ async function loadApp() {
editorManager.on("rename-file", onFileUpdate);
editorManager.on("switch-file", onFileUpdate);
editorManager.on("file-loaded", onFileUpdate);
- navigator.app.overrideButton("menubutton", true);
+ //navigator.app.overrideButton("menubutton", true);
system.setIntentHandler(intentHandler, intentHandler.onError);
system.getCordovaIntent(intentHandler, intentHandler.onError);
setTimeout(showTutorials, 1000);
@@ -658,12 +660,21 @@ function showTutorials() {
}
}
-function backButtonHandler() {
+function backButtonHandler(event) {
+ event.preventDefault();
+
if (keydownState.esc) {
keydownState.esc = false;
return;
}
- actionStack.pop();
+
+ const top = actionStack.pop();
+
+ if (top && typeof top.action === "function") {
+ top.action();
+ } else {
+ //CapacitorApp.exitApp();
+ }
}
function menuButtonHandler() {
diff --git a/src/lib/run.js b/src/lib/run.js
index 1934d2e93..02ab65b4d 100644
--- a/src/lib/run.js
+++ b/src/lib/run.js
@@ -1,3 +1,4 @@
+import ajax from "@deadlyjack/ajax";
import tutorial from "components/tutorial";
import alert from "dialogs/alert";
import box from "dialogs/box";
@@ -134,7 +135,7 @@ async function run(
//this extra www is incorrect because asset_directory itself has www
//but keeping it in case something depends on it
- pathName = `${ASSETS_DIRECTORY}www/`;
+ pathName = "/";
port = constants.CONSOLE_PORT;
start();
@@ -192,9 +193,9 @@ async function run(
isConsole ||
appSettings.value.console === appSettings.CONSOLE_LEGACY
) {
- url = `${ASSETS_DIRECTORY}/js/build/console.build.js`;
+ url = constants.CONSOLE_BUILD_PATH;
} else {
- url = `${DATA_STORAGE}/eruda.js`;
+ url = `${DATA_STORAGE}eruda.js`;
}
sendFileContent(url, reqId, "application/javascript");
break;
@@ -479,24 +480,22 @@ async function run(
* @returns
*/
async function sendFileContent(url, id, mime, processText) {
- let fs = fsOperation(url);
-
- if (!(await fs.exists())) {
- const xfs = fsOperation(Url.join(pathName, filename));
+ let text;
+ if (url.includes(constants.CONSOLE_BUILD_PATH)) {
+ text = await ajax.get(url, {
+ responseType: "text",
+ });
+ } else {
+ const fs = fsOperation(url);
- if (await xfs.exists()) {
- fs = xfs;
- isFallback = true;
- console.log(`fallback ${Url.join(pathName, filename)}`);
- } else {
- console.log(`${url} doesnt exists`);
+ if (!(await fs.exists())) {
error(id);
+ return;
}
- return;
+ text = await fs.readFile(appSettings.value.defaultFileEncoding);
}
- let text = await fs.readFile(appSettings.value.defaultFileEncoding);
text = processText ? processText(text) : text;
if (mime === MIMETYPE_HTML) {
sendHTML(text, id);
diff --git a/src/pages/plugin/plugin.js b/src/pages/plugin/plugin.js
index 63bde9d14..ece6d0945 100644
--- a/src/pages/plugin/plugin.js
+++ b/src/pages/plugin/plugin.js
@@ -75,10 +75,19 @@ export default async function PluginInclude(
const installedPlugin = await fsOperation(
Url.join(PLUGIN_DIR, id, "plugin.json"),
).readFile("json");
+
+ console.log(installPlugin);
+
const { author } = installedPlugin;
+
+ console.log(author);
+
const description = await fsOperation(
Url.join(PLUGIN_DIR, id, installedPlugin.readme),
).readFile("utf8");
+
+ console.log(description);
+
let changelogs = "";
if (installedPlugin.changelogs) {
const changelogPath = Url.join(
@@ -92,16 +101,12 @@ export default async function PluginInclude(
}
}
- const iconUrl = await helpers.toInternalUri(
+ const iconUrl = Capacitor.convertFileSrc(
Url.join(PLUGIN_DIR, id, installedPlugin.icon),
);
- const iconData = await fsOperation(iconUrl).readFile();
- const icon = URL.createObjectURL(
- new Blob([iconData], { type: "image/png" }),
- );
plugin = {
id,
- icon,
+ iconUrl,
name: installedPlugin.name,
version: installedPlugin.version,
author: author.name,
diff --git a/src/pages/plugins/item.js b/src/pages/plugins/item.js
index 393b64ee1..d39c28bae 100644
--- a/src/pages/plugins/item.js
+++ b/src/pages/plugins/item.js
@@ -23,6 +23,7 @@ export default function Item({
downloads,
installed,
}) {
+ console.log(`ICON ${icon}`)
const authorName = (() => {
const displayName =
typeof author === "object" ? author.name : author || "Unknown";
diff --git a/src/pages/plugins/plugins.js b/src/pages/plugins/plugins.js
index cd2f2c0e0..c4ebbd82c 100644
--- a/src/pages/plugins/plugins.js
+++ b/src/pages/plugins/plugins.js
@@ -14,6 +14,7 @@ import installPlugin from "lib/installPlugin";
import prompt from "dialogs/prompt";
import actionStack from "lib/actionStack";
import Contextmenu from "components/contextmenu";
+import internalFs from "fileSystem/internalFs";
/**
*
@@ -305,7 +306,7 @@ export default function PluginsInclude(updates) {
hasMore = false;
}
- const installed = await fsOperation(PLUGIN_DIR).lsDir();
+ const installed = await internalFs.listDir(PLUGIN_DIR)
installed.forEach(({ url }) => {
const plugin = newPlugins.find(({ id }) => id === Url.basename(url));
if (plugin) {
@@ -330,15 +331,14 @@ export default function PluginsInclude(updates) {
async function getInstalledPlugins(updates) {
$list.installed.setAttribute("empty-msg", strings["loading..."]);
plugins.installed = [];
- const installed = await fsOperation(PLUGIN_DIR).lsDir();
+ const installed = await internalFs.listDir(PLUGIN_DIR)
await Promise.all(
installed.map(async (item) => {
const id = Url.basename(item.url);
if (!((updates && updates.includes(id)) || !updates)) return;
const url = Url.join(item.url, "plugin.json");
const plugin = await fsOperation(url).readFile("json");
- const iconUrl = getLocalRes(id, plugin.icon);
- plugin.icon = await helpers.toInternalUri(iconUrl);
+ plugin.icon = Capacitor.convertFileSrc(getLocalRes(id, plugin.icon))
plugin.installed = true;
plugins.installed.push(plugin);
if ($list.installed.get(`[data-id="${id}"]`)) return;
diff --git a/src/plugins/browser/android/com/foxdebug/browser/Menu.java b/src/plugins/browser/android/com/foxdebug/browser/Menu.java
index 8b5bbb544..e6ee650bd 100644
--- a/src/plugins/browser/android/com/foxdebug/browser/Menu.java
+++ b/src/plugins/browser/android/com/foxdebug/browser/Menu.java
@@ -15,7 +15,7 @@
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
-import com.foxdebug.acode.R;
+import capacitor.cordova.android.plugins.R;
import com.foxdebug.system.Ui;
public class Menu extends PopupWindow {
diff --git a/src/plugins/browser/index.js b/src/plugins/browser/index.js
index 0670ef9fd..023ea4750 100644
--- a/src/plugins/browser/index.js
+++ b/src/plugins/browser/index.js
@@ -4,6 +4,7 @@ import themes from 'theme/list';
const SERVICE = 'Browser';
function open(url, isConsole = false) {
+ console.log("opening "+url)
const ACTION = 'open';
const success = () => { };
const error = () => { };
diff --git a/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java b/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java
index 05887c4a6..5387c1c67 100644
--- a/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java
+++ b/src/plugins/server/src/android/com/foxdebug/server/NanoHTTPDWebserver.java
@@ -298,14 +298,15 @@ public Response serve(IHTTPSession session) {
responseObject.getString("body")
);
- Iterator> keys = responseObject.getJSONObject("headers").keys();
- while (keys.hasNext()) {
- String key = (String) keys.next();
- response.addHeader(
- key,
- responseObject.getJSONObject("headers").getString(key)
- );
+ if (responseObject.has("headers")) {
+ JSONObject headers = responseObject.getJSONObject("headers");
+ Iterator> keys = headers.keys();
+ while (keys.hasNext()) {
+ String key = (String) keys.next();
+ response.addHeader(key, headers.getString(key));
+ }
}
+
} catch (JSONException e) {
e.printStackTrace();
}
diff --git a/src/plugins/sftp/plugin.xml b/src/plugins/sftp/plugin.xml
index f2c2dfc6d..11ce569b4 100644
--- a/src/plugins/sftp/plugin.xml
+++ b/src/plugins/sftp/plugin.xml
@@ -13,18 +13,21 @@
+
+
+
+
+
-
+
-
-
-
+
diff --git a/src/plugins/system/android/com/foxdebug/system/System.java b/src/plugins/system/android/com/foxdebug/system/System.java
index fb4839dc1..a8bfe116b 100644
--- a/src/plugins/system/android/com/foxdebug/system/System.java
+++ b/src/plugins/system/android/com/foxdebug/system/System.java
@@ -47,6 +47,9 @@
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import android.webkit.WebView;
+import capacitor.cordova.android.plugins.BuildConfig;
+
public class System extends CordovaPlugin {
@@ -66,6 +69,11 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
this.activity = cordova.getActivity();
this.webView = webView;
+ //enable webview debugging when in debug mode
+ if (BuildConfig.DEBUG){
+ ((WebView)webView.getView()).setWebContentsDebuggingEnabled(true);
+ }
+
// Set up global exception handler
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@@ -912,6 +920,7 @@ private void setInputType(String type) {
} else if (type.equals("NO_SUGGESTIONS_AGGRESSIVE")) {
mode = 1;
}
- webView.setInputType(mode);
+ //no such method
+ //webView.setInputType(mode);
}
}
diff --git a/src/plugins/system/plugin.xml b/src/plugins/system/plugin.xml
index e42796afb..4158dcafb 100644
--- a/src/plugins/system/plugin.xml
+++ b/src/plugins/system/plugin.xml
@@ -17,11 +17,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/sidebarApps/extensions/index.js b/src/sidebarApps/extensions/index.js
index feca1639b..d4719bad7 100644
--- a/src/sidebarApps/extensions/index.js
+++ b/src/sidebarApps/extensions/index.js
@@ -6,10 +6,12 @@ import Sidebar from "components/sidebar";
import prompt from "dialogs/prompt";
import select from "dialogs/select";
import fsOperation from "fileSystem";
+import internalFs from "fileSystem/internalFs";
import purchaseListener from "handlers/purchase";
import constants from "lib/constants";
import InstallState from "lib/installState";
import settings from "lib/settings";
+import mimeType from "mime-types";
import FileBrowser from "pages/fileBrowser";
import plugin from "pages/plugin";
import Url from "utils/Url";
@@ -301,18 +303,49 @@ async function loadExplore() {
}
async function listInstalledPlugins() {
+ let dirItems;
+
+ try {
+ dirItems = await internalFs.listDir(PLUGIN_DIR);
+ } catch (err) {
+ return [];
+ }
+
const plugins = await Promise.all(
- (await fsOperation(PLUGIN_DIR).lsDir()).map(async (item) => {
- const id = Url.basename(item.url);
- const url = Url.join(item.url, "plugin.json");
- const plugin = await fsOperation(url).readFile("json");
- const iconUrl = getLocalRes(id, plugin.icon);
- plugin.icon = await helpers.toInternalUri(iconUrl);
+ dirItems.map(async (item, index) => {
+ let id, url;
+ try {
+ id = Url.basename(item.url);
+ url = Url.join(item.url, "plugin.json");
+ } catch (err) {
+ return null;
+ }
+
+ let plugin;
+ try {
+ plugin = await fsOperation(url).readFile("json");
+ } catch (err) {
+ console.error(err);
+ return null;
+ }
+
+ try {
+ const iconUrl = Capacitor.convertFileSrc(getLocalRes(id, plugin.icon));
+ plugin.icon = iconUrl;
+ } catch (err) {
+ plugin.icon = null;
+ }
+
plugin.installed = true;
+ plugin.id = id;
+ plugin.path = item.uri;
+
return plugin;
}),
);
- return plugins;
+
+ const filteredPlugins = plugins.filter(Boolean);
+ return filteredPlugins;
}
async function getFilteredPlugins(filterName) {
diff --git a/utils/scripts/build.sh b/utils/scripts/build.sh
index 066bb3ab3..043adb0a0 100644
--- a/utils/scripts/build.sh
+++ b/utils/scripts/build.sh
@@ -43,4 +43,4 @@ echo \"${RED}$script3${NC}\";
$script3;
echo \"${RED}$script4${NC}\";
$script4;
-"
+"
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index de748a083..4901560dd 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -85,6 +85,12 @@ module.exports = (env, options) => {
filename: '../../css/build/[name].css',
}),
],
+ optimization: {
+ minimize: mode === 'production',
+ // Only set a custom minimizer in production; otherwise, omit it.
+ ...(mode === 'production' ? {} : { minimizer: [] }),
+ },
+
};
return [main];