feat: Notify build events to browser
- Update assets_json directly from node - Show error overlay or success message - Open file in editor from error overlay
This commit is contained in:
parent
86c0f7f9d6
commit
adc236e35d
12 changed files with 319 additions and 26 deletions
|
|
@ -20,7 +20,9 @@ let {
|
||||||
log,
|
log,
|
||||||
log_warn,
|
log_warn,
|
||||||
log_error,
|
log_error,
|
||||||
|
bench_path
|
||||||
} = require("./utils");
|
} = require("./utils");
|
||||||
|
let { get_redis_subscriber } = require("../node_utils");
|
||||||
|
|
||||||
let argv = yargs
|
let argv = yargs
|
||||||
.usage("Usage: node esbuild [options]")
|
.usage("Usage: node esbuild [options]")
|
||||||
|
|
@ -173,11 +175,19 @@ function build_files({ files, outdir }) {
|
||||||
watch: WATCH_MODE
|
watch: WATCH_MODE
|
||||||
? {
|
? {
|
||||||
onRebuild(error, result) {
|
onRebuild(error, result) {
|
||||||
if (error) console.error("watch build failed:", error);
|
if (error) {
|
||||||
else {
|
log_error(
|
||||||
|
"There was an error during rebuilding changes."
|
||||||
|
);
|
||||||
|
log();
|
||||||
|
log(chalk.dim(error.stack));
|
||||||
|
notify_redis({ error });
|
||||||
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
`${new Date().toLocaleTimeString()}: Compiled changes...`
|
`${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(
|
let assets_json = JSON.stringify(out, null, 4);
|
||||||
path.resolve(assets_path, "frappe", "dist", "assets.json"),
|
return fs.promises
|
||||||
JSON.stringify(out, null, 4)
|
.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();
|
||||||
|
|
|
||||||
111
frappe/public/js/frappe/build_events/BuildError.vue
Normal file
111
frappe/public/js/frappe/build_events/BuildError.vue
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
<template>
|
||||||
|
<div class="build-error-overlay" @click.self="data = null" v-show="data">
|
||||||
|
<div class="window" v-if="data">
|
||||||
|
<div v-for="(error, i) in data.formatted" :key="i">
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<pre class="frame"><component :is="error_component(error, i)" /></pre>
|
||||||
|
</div>
|
||||||
|
<pre class="stack">{{ data.stack }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BuildError",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
data: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show(data) {
|
||||||
|
this.data = data;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.data = null;
|
||||||
|
},
|
||||||
|
open_in_editor(location) {
|
||||||
|
frappe.socketio.socket.emit("open_in_editor", location);
|
||||||
|
},
|
||||||
|
error_component(error, i) {
|
||||||
|
let location = this.data.error.errors[i].location;
|
||||||
|
let location_string = `${location.file}:${location.line}:${
|
||||||
|
location.column
|
||||||
|
}`;
|
||||||
|
let template = error.replace(
|
||||||
|
" > " + location_string,
|
||||||
|
` > <a class="file-link" @click="open">${location_string}</a>`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
template: `<div>${template}</div>`,
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
frappe.socketio.socket.emit("open_in_editor", location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.build-error-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
margin: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.66);
|
||||||
|
--monospace: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier,
|
||||||
|
monospace;
|
||||||
|
--dim: var(--gray-400);
|
||||||
|
}
|
||||||
|
.window {
|
||||||
|
font-family: var(--monospace);
|
||||||
|
line-height: 1.5;
|
||||||
|
width: 800px;
|
||||||
|
color: #d8d8d8;
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 25px 40px;
|
||||||
|
position: relative;
|
||||||
|
background: #181818;
|
||||||
|
border-radius: 6px 6px 8px 8px;
|
||||||
|
box-shadow: 0 19px 38px rgba(0, 0, 0, 0.3), 0 15px 12px rgba(0, 0, 0, 0.22);
|
||||||
|
overflow: hidden;
|
||||||
|
border-top: 8px solid var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: var(--monospace);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: var(--monospace);
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
line-height: 1.3;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.frame {
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
.stack {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--dim);
|
||||||
|
}
|
||||||
|
.file-link {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
52
frappe/public/js/frappe/build_events/BuildSuccess.vue
Normal file
52
frappe/public/js/frappe/build_events/BuildSuccess.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="is_shown"
|
||||||
|
class="flex justify-between build-success-message align-center"
|
||||||
|
>
|
||||||
|
<div class="mr-4">Compiled successfully</div>
|
||||||
|
<a class="text-white underline" href="/" @click.prevent="reload">
|
||||||
|
Refresh
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "BuildSuccess",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
is_shown: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show() {
|
||||||
|
this.is_shown = true;
|
||||||
|
if (this.timeout) {
|
||||||
|
clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.hide();
|
||||||
|
}, 10000);
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.is_shown = false;
|
||||||
|
},
|
||||||
|
reload() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
.build-success-message {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
frappe/public/js/frappe/build_events/build_events.bundle.js
Normal file
48
frappe/public/js/frappe/build_events/build_events.bundle.js
Normal file
|
|
@ -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 = $('<div class="build-success-container">')
|
||||||
|
.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 = $('<div class="build-error-container">')
|
||||||
|
.appendTo($container)
|
||||||
|
.get(0);
|
||||||
|
let vm = new Vue({
|
||||||
|
el: target,
|
||||||
|
render: h => h(BuildError)
|
||||||
|
});
|
||||||
|
error = vm.$children[0];
|
||||||
|
}
|
||||||
|
error.show(data);
|
||||||
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ frappe.Application = class Application {
|
||||||
this.setup_user_group_listeners();
|
this.setup_user_group_listeners();
|
||||||
|
|
||||||
// listen to build errors
|
// listen to build errors
|
||||||
this.setup_build_error_listener();
|
this.setup_build_events();
|
||||||
|
|
||||||
if (frappe.sys_defaults.email_user_password) {
|
if (frappe.sys_defaults.email_user_password) {
|
||||||
var email_list = frappe.sys_defaults.email_user_password.split(',');
|
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) {
|
if (frappe.boot.developer_mode) {
|
||||||
frappe.realtime.on('build_error', (data) => {
|
frappe.require("build_events.bundle.js");
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import "./jquery-bootstrap";
|
||||||
import Vue from "vue/dist/vue.esm.js";
|
import Vue from "vue/dist/vue.esm.js";
|
||||||
import moment from "moment/min/moment-with-locales.js";
|
import moment from "moment/min/moment-with-locales.js";
|
||||||
import momentTimezone from "moment-timezone/builds/moment-timezone-with-data.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";
|
import Sortable from "./lib/Sortable.min.js";
|
||||||
// TODO: esbuild
|
// TODO: esbuild
|
||||||
// Don't think jquery.hotkeys is being used anywhere. Will remove this after being sure.
|
// 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.moment = momentTimezone;
|
||||||
window.Vue = Vue;
|
window.Vue = Vue;
|
||||||
window.Sortable = Sortable;
|
window.Sortable = Sortable;
|
||||||
|
window.io = io
|
||||||
|
|
|
||||||
|
|
@ -762,18 +762,15 @@ def get_build_version():
|
||||||
|
|
||||||
def get_assets_json():
|
def get_assets_json():
|
||||||
if not hasattr(frappe.local, "assets_json"):
|
if not hasattr(frappe.local, "assets_json"):
|
||||||
|
cache = frappe.cache()
|
||||||
assets_json = frappe.cache().get_value("assets_json", shared=True)
|
# using .get instead of .get_value to avoid pickle.loads
|
||||||
|
assets_json = cache.get("assets_json")
|
||||||
if not assets_json:
|
if not assets_json:
|
||||||
import json
|
assets_json = frappe.read_file("assets/frappe/dist/assets.json")
|
||||||
assets_json = json.loads(
|
cache.set_value("assets_json", assets_json, shared=True)
|
||||||
frappe.read_file("assets/frappe/dist/assets.json")
|
frappe.local.assets_json = frappe.safe_decode(assets_json)
|
||||||
)
|
|
||||||
frappe.cache().set_value("assets_json", assets_json, shared=True)
|
|
||||||
|
|
||||||
frappe.local.assets_json = assets_json
|
return frappe.parse_json(frappe.local.assets_json)
|
||||||
|
|
||||||
return frappe.local.assets_json
|
|
||||||
|
|
||||||
|
|
||||||
def get_bench_relative_path(file_path):
|
def get_bench_relative_path(file_path):
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
<div id="body"></div>
|
<div id="body"></div>
|
||||||
<footer></footer>
|
<footer></footer>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="build-events-overlay"></div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window._version_number = "{{ build_version }}";
|
window._version_number = "{{ build_version }}";
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,9 @@ function get_conf() {
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_redis_subscriber() {
|
function get_redis_subscriber(kind="redis_socketio") {
|
||||||
const conf = get_conf();
|
const conf = get_conf();
|
||||||
const host = conf.redis_socketio || conf.redis_async_broker_port;
|
const host = conf[kind] || conf.redis_async_broker_port;
|
||||||
return redis.createClient(host);
|
return redis.createClient(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "frappe-framework",
|
"name": "frappe-framework",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node rollup/build.js",
|
"build": "node esbuild",
|
||||||
"production": "FRAPPE_ENV=production node rollup/build.js",
|
"production": "node esbuild --production",
|
||||||
"watch": "node rollup/watch.js",
|
"watch": "node esbuild --watch",
|
||||||
"snyk-protect": "snyk protect"
|
"snyk-protect": "snyk protect"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
"fast-glob": "^3.2.5",
|
"fast-glob": "^3.2.5",
|
||||||
"graphlib": "^2.1.8",
|
"graphlib": "^2.1.8",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
|
"launch-editor": "^2.2.1",
|
||||||
"less": "^3.11.1",
|
"less": "^3.11.1",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"rollup": "^1.2.2",
|
"rollup": "^1.2.2",
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,11 @@ io.on('connection', function (socket) {
|
||||||
'type'
|
'type'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on('open_in_editor', (data) => {
|
||||||
|
let s = get_redis_subscriber('redis_socketio');
|
||||||
|
s.publish('open_in_editor', JSON.stringify(data));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
subscriber.on("message", function (_channel, message) {
|
subscriber.on("message", function (_channel, message) {
|
||||||
|
|
|
||||||
13
yarn.lock
13
yarn.lock
|
|
@ -4520,6 +4520,14 @@ latest-version@^5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
package-json "^6.3.0"
|
package-json "^6.3.0"
|
||||||
|
|
||||||
|
launch-editor@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.2.1.tgz#871b5a3ee39d6680fcc26d37930b6eeda89db0ca"
|
||||||
|
integrity sha512-On+V7K2uZK6wK7x691ycSUbLD/FyKKelArkbaAMSSJU8JmqmhwN2+mnJDNINuJWSrh2L0kDk+ZQtbC/gOWUwLw==
|
||||||
|
dependencies:
|
||||||
|
chalk "^2.3.0"
|
||||||
|
shell-quote "^1.6.1"
|
||||||
|
|
||||||
lazy-cache@^1.0.3:
|
lazy-cache@^1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
|
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
|
||||||
|
|
@ -7711,6 +7719,11 @@ shebang-regex@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
||||||
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
|
integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
|
||||||
|
|
||||||
|
shell-quote@^1.6.1:
|
||||||
|
version "1.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
|
||||||
|
integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
|
||||||
|
|
||||||
should-equal@^2.0.0:
|
should-equal@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue