457 lines
12 KiB
JavaScript
457 lines
12 KiB
JavaScript
// Sshwifty - A Web SSH client
|
|
//
|
|
// Copyright (C) 2019-2022 Ni Rui <ranqus@gmail.com>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
const webpack = require("webpack"),
|
|
{ spawn } = require("child_process"),
|
|
path = require("path"),
|
|
os = require("os"),
|
|
HtmlWebpackPlugin = require("html-webpack-plugin"),
|
|
MiniCssExtractPlugin = require("mini-css-extract-plugin"),
|
|
CssMinimizerPlugin = require("css-minimizer-webpack-plugin"),
|
|
ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"),
|
|
{ VueLoaderPlugin } = require("vue-loader"),
|
|
WebpackFavicons = require("webpack-favicons"),
|
|
CopyPlugin = require("copy-webpack-plugin"),
|
|
TerserPlugin = require("terser-webpack-plugin"),
|
|
{ CleanWebpackPlugin } = require("clean-webpack-plugin"),
|
|
ESLintPlugin = require("eslint-webpack-plugin");
|
|
|
|
const inDevMode = process.env.NODE_ENV === "development";
|
|
|
|
process.traceDeprecation = true;
|
|
|
|
let appSpawnProc = null,
|
|
appBuildProc = null;
|
|
|
|
const killSpawnProc = (proc, then) => {
|
|
if (proc === null) {
|
|
then();
|
|
|
|
return;
|
|
}
|
|
|
|
process.stdout.write("Shutdown application ...\n");
|
|
|
|
process.kill(-proc.proc.pid, "SIGINT");
|
|
|
|
let forceKill = setTimeout(() => {
|
|
process.kill(-proc.proc.pid);
|
|
}, 3000);
|
|
|
|
proc.waiter.then(() => {
|
|
clearTimeout(forceKill);
|
|
|
|
then();
|
|
});
|
|
};
|
|
|
|
const startAppSpawnProc = (onExit) => {
|
|
killSpawnProc(appSpawnProc, () => {
|
|
let mEnv = {};
|
|
|
|
for (let i in process.env) {
|
|
mEnv[i] = process.env[i];
|
|
}
|
|
|
|
mEnv["SSHWIFTY_CONFIG"] = path.join(
|
|
__dirname,
|
|
"sshwifty.conf.example.json"
|
|
);
|
|
|
|
mEnv["SSHWIFTY_DEBUG"] = "_";
|
|
|
|
process.stdout.write("Starting application ...\n");
|
|
|
|
let proc = spawn("go", ["run", "sshwifty.go"], {
|
|
env: mEnv,
|
|
detached: true,
|
|
}),
|
|
waiter = new Promise((resolve) => {
|
|
let closed = false;
|
|
|
|
proc.stdout.on("data", (msg) => {
|
|
process.stdout.write(msg.toString());
|
|
});
|
|
|
|
proc.stderr.on("data", (msg) => {
|
|
process.stderr.write(msg.toString());
|
|
});
|
|
|
|
proc.on("exit", (n) => {
|
|
process.stdout.write("Application process is exited.\n");
|
|
|
|
if (closed) {
|
|
return;
|
|
}
|
|
|
|
closed = true;
|
|
|
|
appSpawnProc = null;
|
|
resolve(n);
|
|
|
|
onExit();
|
|
});
|
|
});
|
|
|
|
appSpawnProc = {
|
|
proc,
|
|
waiter,
|
|
};
|
|
});
|
|
};
|
|
|
|
const startBuildSpawnProc = (onExit) => {
|
|
killSpawnProc(appBuildProc, () => {
|
|
let mEnv = {};
|
|
|
|
for (let i in process.env) {
|
|
mEnv[i] = process.env[i];
|
|
}
|
|
|
|
mEnv["NODE_ENV"] = process.env.NODE_ENV;
|
|
|
|
process.stdout.write("Generating source code ...\n");
|
|
|
|
let proc = spawn("go", ["generate", "./..."], {
|
|
env: mEnv,
|
|
detached: true,
|
|
}),
|
|
waiter = new Promise((resolve) => {
|
|
let closed = false;
|
|
|
|
proc.stdout.on("data", (msg) => {
|
|
process.stdout.write(msg.toString());
|
|
});
|
|
|
|
proc.stderr.on("data", (msg) => {
|
|
process.stderr.write(msg.toString());
|
|
});
|
|
|
|
proc.on("exit", (n) => {
|
|
process.stdout.write("Code generation process is exited.\n");
|
|
|
|
if (closed) {
|
|
return;
|
|
}
|
|
|
|
closed = true;
|
|
|
|
appBuildProc = null;
|
|
resolve(n);
|
|
|
|
onExit();
|
|
});
|
|
});
|
|
|
|
appBuildProc = {
|
|
proc,
|
|
waiter,
|
|
};
|
|
});
|
|
};
|
|
|
|
const killAllProc = () => {
|
|
if (appBuildProc !== null) {
|
|
killSpawnProc(appBuildProc, () => {
|
|
killSpawnProc(appSpawnProc, () => {
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
killSpawnProc(appSpawnProc, () => {
|
|
process.exit(0);
|
|
});
|
|
};
|
|
|
|
process.on("SIGTERM", killAllProc);
|
|
process.on("SIGINT", killAllProc);
|
|
|
|
module.exports = {
|
|
entry: {
|
|
app: path.join(__dirname, "ui", "app.js"),
|
|
},
|
|
devtool: inDevMode ? "inline-source-map" : "source-map",
|
|
output: {
|
|
publicPath: "/sshwifty/assets/",
|
|
path: path.join(__dirname, ".tmp", "dist"),
|
|
filename: "[name]-[contenthash:8].js",
|
|
chunkFormat: "array-push",
|
|
chunkFilename: "chunk[contenthash:8].js",
|
|
assetModuleFilename: "asset[contenthash:8][ext]",
|
|
clean: true,
|
|
charset: true,
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
vue$: "vue/dist/vue.esm.js",
|
|
},
|
|
},
|
|
optimization: {
|
|
nodeEnv: process.env.NODE_ENV,
|
|
concatenateModules: true,
|
|
runtimeChunk: "single",
|
|
mergeDuplicateChunks: true,
|
|
flagIncludedChunks: true,
|
|
providedExports: true,
|
|
usedExports: true,
|
|
realContentHash: false,
|
|
innerGraph: true,
|
|
splitChunks: inDevMode
|
|
? false
|
|
: {
|
|
chunks: "all",
|
|
minSize: 20000,
|
|
maxSize: 90000,
|
|
minRemainingSize: 0,
|
|
minChunks: 1,
|
|
enforceSizeThreshold: 50000,
|
|
name(module, chunks, cacheGroupKey) {
|
|
const moduleFileName = module
|
|
.identifier()
|
|
.split("/")
|
|
.reduceRight((item) => item);
|
|
const allChunksNames = chunks.map((item) => item.name).join("~");
|
|
return `${cacheGroupKey}~${allChunksNames}~${moduleFileName}`;
|
|
},
|
|
cacheGroups: {
|
|
vendors: {
|
|
test: /[\\/]node_modules[\\/]/,
|
|
priority: -10,
|
|
reuseExistingChunk: true,
|
|
},
|
|
default: {
|
|
priority: -20,
|
|
reuseExistingChunk: true,
|
|
},
|
|
},
|
|
},
|
|
minimize: !inDevMode,
|
|
minimizer: inDevMode
|
|
? []
|
|
: [
|
|
new CssMinimizerPlugin(),
|
|
new TerserPlugin({
|
|
test: /\.js(\?.*)?$/i,
|
|
terserOptions: {
|
|
ecma: undefined,
|
|
parse: {},
|
|
compress: {},
|
|
mangle: true,
|
|
module: false,
|
|
},
|
|
extractComments: /^\**!|@preserve|@license|@cc_on/i,
|
|
}),
|
|
],
|
|
},
|
|
module: {
|
|
rules: [
|
|
{
|
|
test: /\.vue$/,
|
|
use: "vue-loader",
|
|
},
|
|
{
|
|
test: /\.css$/,
|
|
use: [
|
|
inDevMode ? "vue-style-loader" : MiniCssExtractPlugin.loader,
|
|
"css-loader",
|
|
],
|
|
},
|
|
{
|
|
test: /\.html$/,
|
|
use: "html-loader",
|
|
},
|
|
{
|
|
test: /\.(ico|jpe?g|png|gif|svg|woff2?)$/i,
|
|
type: "asset",
|
|
},
|
|
{
|
|
test: /\.js$/,
|
|
exclude: /(node_modules)/,
|
|
use: "babel-loader",
|
|
},
|
|
],
|
|
},
|
|
plugins: (function () {
|
|
var plugins = [
|
|
new webpack.optimize.LimitChunkCountPlugin({
|
|
maxChunks: 7,
|
|
}),
|
|
new webpack.optimize.MinChunkSizePlugin({
|
|
minChunkSize: 56000,
|
|
}),
|
|
new webpack.DefinePlugin(
|
|
!inDevMode
|
|
? {
|
|
"process.env": {
|
|
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
|
},
|
|
}
|
|
: {}
|
|
),
|
|
new webpack.LoaderOptionsPlugin({
|
|
options: {
|
|
handlebarsLoader: {},
|
|
},
|
|
}),
|
|
new webpack.BannerPlugin({
|
|
banner:
|
|
"This file is a part of Sshwifty. Automatically " +
|
|
"generated at " +
|
|
new Date().toTimeString() +
|
|
", DO NOT MODIFIY",
|
|
}),
|
|
new ESLintPlugin({}),
|
|
new CopyPlugin({
|
|
patterns: [
|
|
{
|
|
from: path.join(__dirname, "ui", "robots.txt"),
|
|
to: path.join(__dirname, ".tmp", "dist"),
|
|
},
|
|
{
|
|
from: path.join(__dirname, "README.md"),
|
|
to: path.join(__dirname, ".tmp", "dist"),
|
|
},
|
|
{
|
|
from: path.join(__dirname, "DEPENDENCIES.md"),
|
|
to: path.join(__dirname, ".tmp", "dist"),
|
|
},
|
|
{
|
|
from: path.join(__dirname, "LICENSE.md"),
|
|
to: path.join(__dirname, ".tmp", "dist"),
|
|
},
|
|
],
|
|
}),
|
|
new VueLoaderPlugin(),
|
|
{
|
|
apply(compiler) {
|
|
compiler.hooks.afterEmit.tapAsync(
|
|
"AfterEmittedPlugin",
|
|
(_params, callback) => {
|
|
killSpawnProc(appBuildProc, () => {
|
|
startBuildSpawnProc(() => {
|
|
callback();
|
|
|
|
if (!inDevMode) {
|
|
return;
|
|
}
|
|
|
|
startAppSpawnProc(() => {
|
|
process.stdout.write("Application is closed\n");
|
|
});
|
|
});
|
|
});
|
|
}
|
|
);
|
|
},
|
|
},
|
|
new WebpackFavicons({
|
|
src: path.join(__dirname, "ui", "sshwifty.svg"),
|
|
appName: "Sshwifty SSH Client",
|
|
appShortName: "Sshwifty",
|
|
appDescription: "Web SSH Client",
|
|
developerName: "Ni Rui",
|
|
developerURL: "https://nirui.org",
|
|
background: "#333",
|
|
theme_color: "#333",
|
|
appleStatusBarStyle: "black",
|
|
display: "standalone",
|
|
icons: {
|
|
android: { offset: 0, overlayGlow: false, overlayShadow: true },
|
|
appleIcon: { offset: 5, overlayGlow: false },
|
|
appleStartup: { offset: 5, overlayGlow: false },
|
|
coast: false,
|
|
favicons: { overlayGlow: false },
|
|
windows: { offset: 5, overlayGlow: false },
|
|
yandex: false,
|
|
},
|
|
}),
|
|
new HtmlWebpackPlugin({
|
|
inject: true,
|
|
template: path.join(__dirname, "ui", "index.html"),
|
|
meta: [
|
|
{
|
|
name: "description",
|
|
content: "Connect to a SSH Server from your web browser",
|
|
},
|
|
],
|
|
mobile: true,
|
|
lang: "en-US",
|
|
inlineManifestWebpackName: "webpackManifest",
|
|
title: "Sshwifty Web SSH Client",
|
|
minify: {
|
|
html5: true,
|
|
collapseWhitespace: !inDevMode,
|
|
caseSensitive: true,
|
|
removeComments: true,
|
|
removeEmptyElements: false,
|
|
},
|
|
}),
|
|
new HtmlWebpackPlugin({
|
|
filename: "error.html",
|
|
inject: true,
|
|
template: path.join(__dirname, "ui", "error.html"),
|
|
meta: [
|
|
{
|
|
name: "description",
|
|
content: "Connect to a SSH Server from your web browser",
|
|
},
|
|
],
|
|
mobile: true,
|
|
lang: "en-US",
|
|
minify: {
|
|
html5: true,
|
|
collapseWhitespace: !inDevMode,
|
|
caseSensitive: true,
|
|
removeComments: true,
|
|
removeEmptyElements: false,
|
|
},
|
|
}),
|
|
new MiniCssExtractPlugin({
|
|
filename: inDevMode ? "[name].css" : "[name]-[contenthash:8].css",
|
|
chunkFilename: inDevMode
|
|
? "[name].css"
|
|
: "[name]-chunk[contenthash:8].css",
|
|
}),
|
|
];
|
|
|
|
if (!inDevMode) {
|
|
plugins.push(
|
|
new ImageMinimizerPlugin({
|
|
concurrency: os.cpus().length,
|
|
minimizer: {
|
|
implementation: ImageMinimizerPlugin.imageminMinify,
|
|
options: {
|
|
plugins: [
|
|
["imagemin-gifsicle", { interlaced: true }],
|
|
["imagemin-mozjpeg", { progressive: true }],
|
|
["imagemin-pngquant", { quality: [0.02, 0.2] }],
|
|
["imagemin-svgo", { plugins: ["preset-default"] }],
|
|
],
|
|
},
|
|
},
|
|
})
|
|
);
|
|
plugins.push(new CleanWebpackPlugin());
|
|
}
|
|
|
|
return plugins;
|
|
})(),
|
|
};
|