diff --git a/esbuild/index.js b/esbuild/index.js index ba0df5a00c..62c3712f7a 100644 --- a/esbuild/index.js +++ b/esbuild/index.js @@ -20,7 +20,9 @@ let { log, log_warn, log_error, + bench_path } = require("./utils"); +let { get_redis_subscriber } = require("../node_utils"); let argv = yargs .usage("Usage: node esbuild [options]") @@ -173,11 +175,19 @@ function build_files({ files, outdir }) { watch: WATCH_MODE ? { onRebuild(error, result) { - if (error) console.error("watch build failed:", error); - else { + if (error) { + log_error( + "There was an error during rebuilding changes." + ); + log(); + log(chalk.dim(error.stack)); + notify_redis({ error }); + } else { console.log( `${new Date().toLocaleTimeString()}: Compiled changes...` ); + write_meta_file(result.metafile); + notify_redis({ success: true }); } } } @@ -268,8 +278,64 @@ function write_meta_file(metafile) { } } - return fs.promises.writeFile( - path.resolve(assets_path, "frappe", "dist", "assets.json"), - JSON.stringify(out, null, 4) + let assets_json = JSON.stringify(out, null, 4); + return fs.promises + .writeFile( + path.resolve(assets_path, "frappe", "dist", "assets.json"), + assets_json + ) + .then(() => { + let client = get_redis_subscriber("redis_cache"); + // update assets_json cache in redis, so that it can be read directly by python + return client.set("assets_json", assets_json); + }); +} + +async function notify_redis({ error, success }) { + let subscriber = get_redis_subscriber("redis_socketio"); + // notify redis which in turns tells socketio to publish this to browser + + let payload = null; + + if (error) { + let formatted = await esbuild.formatMessages(error.errors, { + kind: "error", + terminalWidth: 100 + }); + let stack = error.stack.replace(new RegExp(bench_path, "g"), ""); + payload = { + error, + formatted, + stack + }; + } + if (success) { + payload = { + success: true + }; + } + + subscriber.publish( + "events", + JSON.stringify({ + event: "build_event", + message: payload + }) ); } + +function open_in_editor() { + let subscriber = get_redis_subscriber("redis_socketio"); + subscriber.on("message", (event, file) => { + if (event === "open_in_editor") { + file = JSON.parse(file); + let file_path = path.resolve(file.file); + console.log("Opening file in editor:", file_path); + let launch = require("launch-editor"); + launch(`${file_path}:${file.line}:${file.column}`); + } + }); + subscriber.subscribe("open_in_editor"); +} + +open_in_editor(); diff --git a/frappe/public/js/frappe/build_events/BuildError.vue b/frappe/public/js/frappe/build_events/BuildError.vue new file mode 100644 index 0000000000..6e10852719 --- /dev/null +++ b/frappe/public/js/frappe/build_events/BuildError.vue @@ -0,0 +1,111 @@ + + + diff --git a/frappe/public/js/frappe/build_events/BuildSuccess.vue b/frappe/public/js/frappe/build_events/BuildSuccess.vue new file mode 100644 index 0000000000..75a365fdc2 --- /dev/null +++ b/frappe/public/js/frappe/build_events/BuildSuccess.vue @@ -0,0 +1,52 @@ + + + diff --git a/frappe/public/js/frappe/build_events/build_events.bundle.js b/frappe/public/js/frappe/build_events/build_events.bundle.js new file mode 100644 index 0000000000..6c8986af3f --- /dev/null +++ b/frappe/public/js/frappe/build_events/build_events.bundle.js @@ -0,0 +1,48 @@ +import BuildError from "./BuildError.vue"; +import BuildSuccess from "./BuildSuccess.vue"; + +let $container = $("#build-events-overlay"); +let success = null; +let error = null; + +frappe.realtime.on("build_event", data => { + if (data.success) { + show_build_success(data); + } else if (data.error) { + show_build_error(data); + } +}); + +function show_build_success() { + if (error) { + error.hide(); + } + if (!success) { + let target = $('
') + .appendTo($container) + .get(0); + let vm = new Vue({ + el: target, + render: h => h(BuildSuccess) + }); + success = vm.$children[0]; + } + success.show(); +} + +function show_build_error(data) { + if (success) { + success.hide(); + } + if (!error) { + let target = $('
') + .appendTo($container) + .get(0); + let vm = new Vue({ + el: target, + render: h => h(BuildError) + }); + error = vm.$children[0]; + } + error.show(data); +} diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 950663b973..4bd2d53083 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -117,7 +117,7 @@ frappe.Application = class Application { this.setup_user_group_listeners(); // listen to build errors - this.setup_build_error_listener(); + this.setup_build_events(); if (frappe.sys_defaults.email_user_password) { var email_list = frappe.sys_defaults.email_user_password.split(','); @@ -585,11 +585,9 @@ frappe.Application = class Application { } } - setup_build_error_listener() { + setup_build_events() { if (frappe.boot.developer_mode) { - frappe.realtime.on('build_error', (data) => { - console.log(data); - }); + frappe.require("build_events.bundle.js"); } } diff --git a/frappe/public/js/libs.bundle.js b/frappe/public/js/libs.bundle.js index a857b69f1d..605673a14e 100644 --- a/frappe/public/js/libs.bundle.js +++ b/frappe/public/js/libs.bundle.js @@ -2,7 +2,7 @@ import "./jquery-bootstrap"; import Vue from "vue/dist/vue.esm.js"; import moment from "moment/min/moment-with-locales.js"; import momentTimezone from "moment-timezone/builds/moment-timezone-with-data.js"; -import "socket.io-client/dist/socket.io.slim.js"; +import io from "socket.io-client/dist/socket.io.slim.js"; import Sortable from "./lib/Sortable.min.js"; // TODO: esbuild // Don't think jquery.hotkeys is being used anywhere. Will remove this after being sure. @@ -12,3 +12,4 @@ import Sortable from "./lib/Sortable.min.js"; window.moment = momentTimezone; window.Vue = Vue; window.Sortable = Sortable; +window.io = io diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 724d2a2878..0073770b3e 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -762,18 +762,15 @@ def get_build_version(): def get_assets_json(): if not hasattr(frappe.local, "assets_json"): - - assets_json = frappe.cache().get_value("assets_json", shared=True) + cache = frappe.cache() + # using .get instead of .get_value to avoid pickle.loads + assets_json = cache.get("assets_json") if not assets_json: - import json - assets_json = json.loads( - frappe.read_file("assets/frappe/dist/assets.json") - ) - frappe.cache().set_value("assets_json", assets_json, shared=True) - - frappe.local.assets_json = assets_json + assets_json = frappe.read_file("assets/frappe/dist/assets.json") + cache.set_value("assets_json", assets_json, shared=True) + frappe.local.assets_json = frappe.safe_decode(assets_json) - return frappe.local.assets_json + return frappe.parse_json(frappe.local.assets_json) def get_bench_relative_path(file_path): diff --git a/frappe/www/app.html b/frappe/www/app.html index 164be40bdc..c8172693b9 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -35,6 +35,7 @@
+