27 changed files with 4101 additions and 2790 deletions
			
			
		| @ -0,0 +1,9 @@ | |||
| { | |||
|   "presets": [ | |||
|     "@babel/preset-typescript", | |||
|     ["@babel/env"] | |||
|   ], | |||
|   "plugins": [ | |||
|     "lodash" | |||
|   ] | |||
| } | |||
| @ -1,16 +1,32 @@ | |||
| module.exports = { | |||
|   parser: 'eslint-plugin-typescript/parser', | |||
|   plugins: ['typescript', 'prettier'], | |||
|   env: { | |||
|     es6: true, | |||
|     node: true, | |||
|     browser: true | |||
|     browser: true, | |||
|   }, | |||
|   root: true, | |||
|   extends: ["airbnb-base", "plugin:prettier/recommended"], | |||
|   extends: [ | |||
|     'airbnb-base', | |||
|     'plugin:typescript/recommended', | |||
|     'plugin:prettier/recommended', | |||
|   ], | |||
|   rules: { | |||
|     "linebreak-style": ["error", "unix"], | |||
|     "arrow-parens": ["error", "as-needed"], | |||
|     "no-param-reassign": ["error", { props: false }], | |||
|     "func-style": ["error", "declaration", { allowArrowFunctions: true }], | |||
|     "no-use-before-define": ["error", { functions: false }] | |||
|   } | |||
|     'typescript/indent': 'off', | |||
|     'linebreak-style': ['error', 'unix'], | |||
|     'arrow-parens': ['error', 'as-needed'], | |||
|     'no-param-reassign': ['error', { props: false }], | |||
|     'func-style': ['error', 'declaration', { allowArrowFunctions: true }], | |||
|     'no-use-before-define': ['error', { functions: false }], | |||
|     'typescript/no-use-before-define': ['error', { functions: false }], | |||
|   }, | |||
|   settings: { | |||
|     'import/resolver': { | |||
|       'typescript-eslint-parser': ['.ts', '.tsx'], | |||
|       node: { | |||
|         extensions: ['.ts', '.js'], | |||
|       }, | |||
|     }, | |||
|   }, | |||
| }; | |||
|  | |||
| @ -1,12 +1,72 @@ | |||
| /* eslint-disable */ | |||
| require = require('@std/esm')(module, { | |||
|   cjs: 'true', | |||
|   esm: 'js', | |||
| }); | |||
| const wetty = require('./lib/index.mjs').default; | |||
| /* eslint-disable typescript/no-var-requires */ | |||
| 
 | |||
| const yargs = require('yargs'); | |||
| const wetty = require('./dist').default; | |||
| 
 | |||
| module.exports = wetty.wetty; | |||
| 
 | |||
| /** | |||
|  * Check if being run by cli or require | |||
|  */ | |||
| if (require.main === module) wetty.init(); | |||
| if (require.main === module) | |||
|   wetty.init( | |||
|     yargs | |||
|       .options({ | |||
|         sslkey: { | |||
|           demand: false, | |||
|           type: 'string', | |||
|           description: 'path to SSL key', | |||
|         }, | |||
|         sslcert: { | |||
|           demand: false, | |||
|           type: 'string', | |||
|           description: 'path to SSL certificate', | |||
|         }, | |||
|         sshhost: { | |||
|           demand: false, | |||
|           description: 'ssh server host', | |||
|           type: 'string', | |||
|           default: process.env.SSHHOST || 'localhost', | |||
|         }, | |||
|         sshport: { | |||
|           demand: false, | |||
|           description: 'ssh server port', | |||
|           type: 'number', | |||
|           default: parseInt(process.env.SSHPOST, 10) || 22, | |||
|         }, | |||
|         sshuser: { | |||
|           demand: false, | |||
|           description: 'ssh user', | |||
|           type: 'string', | |||
|           default: process.env.SSHUSER || '', | |||
|         }, | |||
|         sshauth: { | |||
|           demand: false, | |||
|           description: | |||
|             'defaults to "password", you can use "publickey,password" instead', | |||
|           type: 'string', | |||
|           default: process.env.SSHAUTH || 'password', | |||
|         }, | |||
|         base: { | |||
|           demand: false, | |||
|           alias: 'b', | |||
|           description: 'base path to wetty', | |||
|           type: 'string', | |||
|           default: process.env.BASE || '/wetty/', | |||
|         }, | |||
|         port: { | |||
|           demand: false, | |||
|           alias: 'p', | |||
|           description: 'wetty listen port', | |||
|           type: 'number', | |||
|           default: parseInt(process.env.PORT, 10) || 3000, | |||
|         }, | |||
|         help: { | |||
|           demand: false, | |||
|           alias: 'h', | |||
|           type: 'boolean', | |||
|           description: 'Print help message', | |||
|         }, | |||
|       }) | |||
|       .boolean('allow_discovery').argv | |||
|   ); | |||
|  | |||
| @ -1,30 +0,0 @@ | |||
| const localhost = host => | |||
|   process.getuid() === 0 && | |||
|   (host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'); | |||
| 
 | |||
| export default ( | |||
|   { request: { headers }, client: { conn } }, | |||
|   { user, host, port, auth } | |||
| ) => ({ | |||
|   args: localhost(host) | |||
|     ? ['login', '-h', conn.remoteAddress.split(':')[3]] | |||
|     : [ | |||
|         'ssh', | |||
|         address(headers, user, host), | |||
|         '-p', | |||
|         port, | |||
|         '-o', | |||
|         `PreferredAuthentications=${auth}`, | |||
|       ], | |||
|   user: | |||
|     localhost(host) || | |||
|     user !== '' || | |||
|     user.includes('@') || | |||
|     address(headers, user, host).includes('@'), | |||
| }); | |||
| 
 | |||
| function address(headers, user, host) { | |||
|   const match = headers.referer.match('.+/ssh/([^/]+)$'); | |||
|   const fallback = user ? `${user}@${host}` : host; | |||
|   return match ? `${match[1]}@${host}` : fallback; | |||
| } | |||
| @ -1,100 +0,0 @@ | |||
| import optimist from 'optimist'; | |||
| import logger from './logger.mjs'; | |||
| import wetty from './emitter.mjs'; | |||
| 
 | |||
| const opts = optimist | |||
|   .options({ | |||
|     sslkey: { | |||
|       demand: false, | |||
|       description: 'path to SSL key', | |||
|     }, | |||
|     sslcert: { | |||
|       demand: false, | |||
|       description: 'path to SSL certificate', | |||
|     }, | |||
|     sshhost: { | |||
|       demand: false, | |||
|       description: 'ssh server host', | |||
|     }, | |||
|     sshport: { | |||
|       demand: false, | |||
|       description: 'ssh server port', | |||
|     }, | |||
|     sshuser: { | |||
|       demand: false, | |||
|       description: 'ssh user', | |||
|     }, | |||
|     sshauth: { | |||
|       demand: false, | |||
|       description: | |||
|         'defaults to "password", you can use "publickey,password" instead', | |||
|     }, | |||
|     base: { | |||
|       demand: false, | |||
|       alias: 'b', | |||
|       description: 'base path to wetty', | |||
|     }, | |||
|     port: { | |||
|       demand: false, | |||
|       alias: 'p', | |||
|       description: 'wetty listen port', | |||
|     }, | |||
|     help: { | |||
|       demand: false, | |||
|       alias: 'h', | |||
|       description: 'Print help message', | |||
|     }, | |||
|   }) | |||
|   .boolean('allow_discovery').argv; | |||
| 
 | |||
| export default class { | |||
|   static start({ | |||
|     sshuser = process.env.SSHUSER || '', | |||
|     sshhost = process.env.SSHHOST || 'localhost', | |||
|     sshauth = process.env.SSHAUTH || 'password', | |||
|     sshport = process.env.SSHPOST || 22, | |||
|     base = process.env.BASE || '/wetty/', | |||
|     port = process.env.PORT || 3000, | |||
|     sslkey, | |||
|     sslcert, | |||
|   }) { | |||
|     wetty | |||
|       .on('exit', ({ code, msg }) => { | |||
|         logger.info(`Exit with code: ${code} ${msg}`); | |||
|       }) | |||
|       .on('disconnect', () => { | |||
|         logger.info('disconnect'); | |||
|       }) | |||
|       .on('spawn', ({ msg }) => logger.info(msg)) | |||
|       .on('connection', ({ msg, date }) => logger.info(`${date} ${msg}`)) | |||
|       .on('server', ({ msg }) => logger.info(msg)) | |||
|       .on('debug', msg => logger.debug(msg)); | |||
|     return wetty.start( | |||
|       { | |||
|         user: sshuser, | |||
|         host: sshhost, | |||
|         auth: sshauth, | |||
|         port: sshport, | |||
|       }, | |||
|       base, | |||
|       port, | |||
|       { key: sslkey, cert: sslcert } | |||
|     ); | |||
|   } | |||
| 
 | |||
|   static get wetty() { | |||
|     return wetty; | |||
|   } | |||
| 
 | |||
|   static init() { | |||
|     if (!opts.help) { | |||
|       this.start(opts).catch(err => { | |||
|         logger.error(err); | |||
|         process.exitCode = 1; | |||
|       }); | |||
|     } else { | |||
|       optimist.showHelp(); | |||
|       process.exitCode = 0; | |||
|     } | |||
|   } | |||
| } | |||
| @ -1,46 +0,0 @@ | |||
| import compression from 'compression'; | |||
| import express from 'express'; | |||
| import favicon from 'serve-favicon'; | |||
| import helmet from 'helmet'; | |||
| import http from 'http'; | |||
| import https from 'https'; | |||
| import path from 'path'; | |||
| import socket from 'socket.io'; | |||
| import { isUndefined } from 'lodash'; | |||
| import morgan from 'morgan'; | |||
| import logger from './logger.mjs'; | |||
| import events from './emitter.mjs'; | |||
| 
 | |||
| const pubDir = path.join(__dirname, '..', 'public'); | |||
| const distDir = path.join(__dirname, '..', 'dist'); | |||
| 
 | |||
| export default function createServer(base, port, { key, cert }) { | |||
|   base = base.replace(/\/*$/, ""); | |||
|   events.emit('debug', `key: ${key}, cert: ${cert}, port: ${port}, base: ${base}`); | |||
|   const app = express(); | |||
|   const html = (req, res) => res.sendFile(path.join(pubDir, 'index.html')); | |||
|   const css = (req, res) => res.sendFile(path.join(distDir, 'main.css')); | |||
|   const js = (req, res) => res.sendFile(path.join(distDir, 'main.js')); | |||
|   app | |||
|     .use(morgan('combined', { stream: logger.stream })) | |||
|     .use(helmet()) | |||
|     .use(compression()) | |||
|     .use(favicon(path.join(pubDir, 'favicon.ico'))) | |||
|     .get(`${base}/`, html) | |||
|     .get(`${base}/main.css`, css) | |||
|     .get(`${base}/main.js`, js) | |||
|     .get(`${base}/ssh/main.css`, css) | |||
|     .get(`${base}/ssh/main.js`, js) | |||
|     .get(`${base}/ssh/:user`, html) | |||
| 
 | |||
|   return socket( | |||
|     !isUndefined(key) && !isUndefined(cert) | |||
|       ? https.createServer({ key, cert }, app).listen(port, () => { | |||
|           events.server(port, 'https'); | |||
|         }) | |||
|       : http.createServer(app).listen(port, () => { | |||
|           events.server(port, 'http'); | |||
|         }), | |||
|     { path: `${base}/socket.io` } | |||
|   ); | |||
| } | |||
| @ -1,11 +0,0 @@ | |||
| import fs from 'fs-extra'; | |||
| import path from 'path'; | |||
| import { isUndefined } from 'lodash'; | |||
| 
 | |||
| export default (sslkey, sslcert) => | |||
|   isUndefined(sslkey) || isUndefined(sslcert) | |||
|     ? Promise.resolve({}) | |||
|     : Promise.all([ | |||
|         fs.readFile(path.resolve(sslkey)), | |||
|         fs.readFile(path.resolve(sslcert)), | |||
|       ]).then(([key, cert]) => ({ key, cert })); | |||
| @ -1,20 +0,0 @@ | |||
| <!doctype html> | |||
| <html lang="en"> | |||
|   <head> | |||
|     <meta charset="UTF-8"> | |||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |||
|     <title>WeTTy - The Web Terminal Emulator</title> | |||
|     <link rel="stylesheet" href="main.css" /> | |||
|   </head> | |||
|   <body> | |||
|     <div id="overlay"> | |||
|       <div class="error"> | |||
|         <div id="msg"></div> | |||
|         <input type="button" onclick="location.reload();" value="reconnect" /> | |||
|       </div> | |||
|     </div> | |||
|     <div id="terminal"></div> | |||
|     <script src="main.js"></script> | |||
|   </body> | |||
| </html> | |||
| Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB | 
| @ -1,39 +0,0 @@ | |||
| import { isUndefined } from 'lodash'; | |||
| 
 | |||
| export function proposeGeometry({ element, renderer }) { | |||
|   if (!element.parentElement) return null; | |||
| 
 | |||
|   const parentElementStyle = window.getComputedStyle(element.parentElement); | |||
| 
 | |||
|   return { | |||
|     cols: Math.floor( | |||
|       Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'), 10)) / | |||
|         renderer.dimensions.actualCellWidth, | |||
|     ), | |||
|     rows: Math.floor( | |||
|       parseInt(parentElementStyle.getPropertyValue('height'), 10) / | |||
|         renderer.dimensions.actualCellHeight, | |||
|     ), | |||
|   }; | |||
| } | |||
| 
 | |||
| export function fit(term) { | |||
|   const { rows, cols } = proposeGeometry(term); | |||
|   if (!isUndefined(rows) && !isUndefined(cols)) { | |||
|     // Force a full render
 | |||
|     if (term.rows !== rows || term.cols !== cols) { | |||
|       term.renderer.clear(); | |||
|       term.resize(cols, rows); | |||
|     } | |||
|   } | |||
| } | |||
| 
 | |||
| export function apply({ prototype }) { | |||
|   prototype.proposeGeometry = function proProposeGeometry() { | |||
|     return proposeGeometry(this); | |||
|   }; | |||
| 
 | |||
|   prototype.fit = function proFit() { | |||
|     return fit(this); | |||
|   }; | |||
| } | |||
| @ -1,9 +1,9 @@ | |||
| import rl from 'readline'; | |||
| import { createInterface } from 'readline'; | |||
| 
 | |||
| ask('Enter your username'); | |||
| 
 | |||
| export default function ask(question) { | |||
|   const r = rl.createInterface({ | |||
| export default function ask(question: string): Promise<string> { | |||
|   const r = createInterface({ | |||
|     input: process.stdin, | |||
|     output: process.stdout, | |||
|   }); | |||
| @ -0,0 +1,33 @@ | |||
| import { Socket } from 'socket.io'; | |||
| import { SSH } from './interfaces'; | |||
| 
 | |||
| const localhost = (host: string): boolean => | |||
|   process.getuid() === 0 && | |||
|   (host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'); | |||
| 
 | |||
| export default ( | |||
|   { request: { headers }, client: { conn } }: Socket, | |||
|   { user, host, port, auth }: SSH | |||
| ): { args: string[]; user: boolean } => ({ | |||
|   args: localhost(host) | |||
|     ? ['login', '-h', conn.remoteAddress.split(':')[3]] | |||
|     : [ | |||
|         'ssh', | |||
|         address(headers.referer, user, host), | |||
|         '-p', | |||
|         `${port}`, | |||
|         '-o', | |||
|         `PreferredAuthentications=${auth}`, | |||
|       ], | |||
|   user: | |||
|     localhost(host) || | |||
|     user !== '' || | |||
|     user.includes('@') || | |||
|     address(headers.referer, user, host).includes('@'), | |||
| }); | |||
| 
 | |||
| function address(referer: string, user: string, host: string): string { | |||
|   const match = referer.match('.+/ssh/([^/]+)$'); | |||
|   const fallback = user ? `${user}@${host}` : host; | |||
|   return match ? `${match[1]}@${host}` : fallback; | |||
| } | |||
| @ -0,0 +1,3 @@ | |||
| import WeTTy from './wetty'; | |||
| 
 | |||
| export default new WeTTy(); | |||
| @ -0,0 +1,71 @@ | |||
| import * as yargs from 'yargs'; | |||
| import logger from './logger'; | |||
| import wetty from './emitter'; | |||
| import WeTTy from './wetty'; | |||
| 
 | |||
| export interface Options { | |||
|   sshhost: string; | |||
|   sshport: number; | |||
|   sshuser: string; | |||
|   sshauth: string; | |||
|   sslkey?: string; | |||
|   sslcert?: string; | |||
|   base: string; | |||
|   port: number; | |||
| } | |||
| 
 | |||
| interface CLI extends Options { | |||
|   help: boolean; | |||
| } | |||
| 
 | |||
| export default class Server { | |||
|   public static start({ | |||
|     sshuser, | |||
|     sshhost, | |||
|     sshauth, | |||
|     sshport, | |||
|     base, | |||
|     port, | |||
|     sslkey, | |||
|     sslcert, | |||
|   }: Options): Promise<void> { | |||
|     wetty | |||
|       .on('exit', ({ code, msg }: { code: number; msg: string }) => { | |||
|         logger.info(`Exit with code: ${code} ${msg}`); | |||
|       }) | |||
|       .on('disconnect', () => { | |||
|         logger.info('disconnect'); | |||
|       }) | |||
|       .on('spawn', ({ msg }) => logger.info(msg)) | |||
|       .on('connection', ({ msg, date }) => logger.info(`${date} ${msg}`)) | |||
|       .on('server', ({ msg }) => logger.info(msg)) | |||
|       .on('debug', (msg: string) => logger.debug(msg)); | |||
|     return wetty.start( | |||
|       { | |||
|         user: sshuser, | |||
|         host: sshhost, | |||
|         auth: sshauth, | |||
|         port: sshport, | |||
|       }, | |||
|       base, | |||
|       port, | |||
|       { key: sslkey, cert: sslcert } | |||
|     ); | |||
|   } | |||
| 
 | |||
|   public static get wetty(): WeTTy { | |||
|     return wetty; | |||
|   } | |||
| 
 | |||
|   public static init(opts: CLI): void { | |||
|     if (!opts.help) { | |||
|       this.start(opts).catch(err => { | |||
|         logger.error(err); | |||
|         process.exitCode = 1; | |||
|       }); | |||
|     } else { | |||
|       yargs.showHelp(); | |||
|       process.exitCode = 0; | |||
|     } | |||
|   } | |||
| } | |||
| @ -0,0 +1,16 @@ | |||
| export interface SSH { | |||
|   user: string; | |||
|   host: string; | |||
|   auth: string; | |||
|   port: number; | |||
| } | |||
| 
 | |||
| export interface SSL { | |||
|   key?: string; | |||
|   cert?: string; | |||
| } | |||
| 
 | |||
| export interface SSLBuffer { | |||
|   key?: Buffer; | |||
|   cert?: Buffer; | |||
| } | |||
| @ -0,0 +1,84 @@ | |||
| import * as compression from 'compression'; | |||
| import * as express from 'express'; | |||
| import * as favicon from 'serve-favicon'; | |||
| import * as helmet from 'helmet'; | |||
| import * as http from 'http'; | |||
| import * as https from 'https'; | |||
| import * as path from 'path'; | |||
| import * as socket from 'socket.io'; | |||
| import { isUndefined } from 'lodash'; | |||
| import * as morgan from 'morgan'; | |||
| import logger from './logger'; | |||
| import events from './emitter'; | |||
| import { SSLBuffer } from './interfaces'; | |||
| 
 | |||
| const distDir = path.join('./', 'dist', 'client'); | |||
| 
 | |||
| const trim = (str: string): string => str.replace(/\/*$/, ''); | |||
| 
 | |||
| export default function createServer( | |||
|   base: string, | |||
|   port: number, | |||
|   { key, cert }: SSLBuffer | |||
| ): SocketIO.Server { | |||
|   const basePath = trim(base); | |||
|   events.emit( | |||
|     'debug', | |||
|     `key: ${key}, cert: ${cert}, port: ${port}, base: ${base}` | |||
|   ); | |||
| 
 | |||
|   const html = ( | |||
|     req: express.Request, | |||
|     res: express.Response | |||
|   ): express.Response => | |||
|     res.send(`<!doctype html>
 | |||
| <html lang="en"> | |||
|   <head> | |||
|     <meta charset="UTF-8"> | |||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |||
|     <title>WeTTy - The Web Terminal Emulator</title> | |||
|     <link rel="stylesheet" href="${basePath}/public/index.css" /> | |||
|   </head> | |||
|   <body> | |||
|     <div id="overlay"> | |||
|       <div class="error"> | |||
|         <div id="msg"></div> | |||
|         <input type="button" onclick="location.reload();" value="reconnect" /> | |||
|       </div> | |||
|     </div> | |||
|     <div id="terminal"></div> | |||
|     <script src="${basePath}/public/index.js"></script> | |||
|   </body> | |||
| </html>`);
 | |||
| 
 | |||
|   const app = express(); | |||
|   app | |||
|     .use(morgan('combined', { stream: logger.stream })) | |||
|     .use(helmet()) | |||
|     .use(compression()) | |||
|     .use(favicon(path.join(distDir, 'favicon.ico'))) | |||
|     .use(`${basePath}/public`, express.static(distDir)) | |||
|     .use((req, res, next) => { | |||
|       if ( | |||
|         req.url.substr(-1) === '/' && | |||
|         req.url.length > 1 && | |||
|         !/\?[^]*\//.test(req.url) | |||
|       ) | |||
|         res.redirect(301, req.url.slice(0, -1)); | |||
|       else next(); | |||
|     }) | |||
|     .get(basePath, html) | |||
|     .get(`${basePath}/ssh/:user`, html); | |||
| 
 | |||
|   return socket( | |||
|     !isUndefined(key) && !isUndefined(cert) | |||
|       ? https.createServer({ key, cert }, app).listen(port, () => { | |||
|           events.server(port, 'https'); | |||
|         }) | |||
|       : http.createServer(app).listen(port, () => { | |||
|           events.server(port, 'http'); | |||
|         }), | |||
|     { path: `${basePath}/socket.io` } | |||
|   ); | |||
| } | |||
| @ -0,0 +1,11 @@ | |||
| import { readFile } from 'fs-extra'; | |||
| import { resolve } from 'path'; | |||
| import { isUndefined } from 'lodash'; | |||
| import { SSL, SSLBuffer } from './interfaces'; | |||
| 
 | |||
| export default async function loadSSL(ssl: SSL): Promise<SSLBuffer> { | |||
|   if (isUndefined(ssl.key) || isUndefined(ssl.cert)) return {}; | |||
|   const files = [readFile(resolve(ssl.key)), readFile(resolve(ssl.cert))]; | |||
|   const [key, cert]: Buffer[] = await Promise.all(files); | |||
|   return { key, cert }; | |||
| } | |||
| @ -0,0 +1,22 @@ | |||
| { | |||
|   "compilerOptions": { | |||
|     "module": "commonjs", | |||
|     "outDir": "./dist", | |||
|     "allowJs": true, | |||
|     "esModuleInterop": false, | |||
|     "target": "es6", | |||
|     "noImplicitAny": true, | |||
|     "moduleResolution": "node", | |||
|     "sourceMap": true, | |||
|     "baseUrl": ".", | |||
|     "paths": { | |||
|       "*": [ | |||
|         "node_modules/", | |||
|         "src/types/*" | |||
|       ] | |||
|     } | |||
|   }, | |||
|   "include": [ | |||
|     "./src/**/*.ts" | |||
|   ] | |||
| } | |||
| @ -0,0 +1,134 @@ | |||
| import path from 'path'; | |||
| import webpack from 'webpack'; | |||
| import MiniCssExtractPlugin from 'mini-css-extract-plugin'; | |||
| import nodeExternals from 'webpack-node-externals'; | |||
| 
 | |||
| const template = override => | |||
|   Object.assign( | |||
|     { | |||
|       mode: process.env.NODE_ENV || 'development', | |||
|       resolve: { | |||
|         modules: [path.resolve(__dirname, 'src'), 'node_modules'], | |||
|         extensions: ['.ts', '.json', '.js', '.node'], | |||
|       }, | |||
| 
 | |||
|       stats: { | |||
|         colors: true, | |||
|       }, | |||
|     }, | |||
|     override | |||
|   ); | |||
| 
 | |||
| const entry = (folder, file) => | |||
|   path.join(__dirname, 'src', folder, `${file}.ts`); | |||
| 
 | |||
| const entries = (folder, files) => | |||
|   Object.assign(...files.map(file => ({ [file]: entry(folder, file) }))); | |||
| 
 | |||
| export default [ | |||
|   template({ | |||
|     entry: entries('server', ['index', 'buffer']), | |||
|     target: 'node', | |||
|     devtool: 'source-map', | |||
|     output: { | |||
|       path: path.resolve(__dirname, 'dist'), | |||
|       libraryTarget: 'commonjs2', | |||
|       filename: '[name].js', | |||
|     }, | |||
|     externals: [nodeExternals()], | |||
|     module: { | |||
|       rules: [ | |||
|         { | |||
|           test: /\.ts$/, | |||
|           use: { | |||
|             loader: 'babel-loader', | |||
|             options: { | |||
|               presets: [ | |||
|                 '@babel/preset-typescript', | |||
|                 [ | |||
|                   '@babel/preset-env', | |||
|                   { | |||
|                     targets: { | |||
|                       node: 'current', | |||
|                     }, | |||
|                   }, | |||
|                 ], | |||
|               ], | |||
|               plugins: ['lodash'], | |||
|             }, | |||
|           }, | |||
|         }, | |||
|         { | |||
|           test: /\.js$/, | |||
|           use: ['source-map-loader'], | |||
|           enforce: 'pre', | |||
|         }, | |||
|       ], | |||
|     }, | |||
|     plugins: [new webpack.IgnorePlugin(/uws/)], | |||
|   }), | |||
|   template({ | |||
|     entry: entries('client', ['index']), | |||
|     output: { | |||
|       path: path.resolve(__dirname, 'dist', 'client'), | |||
|       filename: '[name].js', | |||
|     }, | |||
|     module: { | |||
|       rules: [ | |||
|         { | |||
|           test: /\.ts$/, | |||
|           use: { | |||
|             loader: 'babel-loader', | |||
|             options: { | |||
|               presets: [ | |||
|                 '@babel/preset-typescript', | |||
|                 [ | |||
|                   '@babel/preset-env', | |||
|                   { | |||
|                     targets: { | |||
|                       browsers: ['last 2 versions', 'safari >= 7'], | |||
|                     }, | |||
|                   }, | |||
|                 ], | |||
|               ], | |||
|               plugins: ['lodash'], | |||
|             }, | |||
|           }, | |||
|         }, | |||
|         { | |||
|           test: /\.js$/, | |||
|           use: ['source-map-loader'], | |||
|           enforce: 'pre', | |||
|         }, | |||
|         { | |||
|           test: /\.scss$/, | |||
|           use: [ | |||
|             { | |||
|               loader: MiniCssExtractPlugin.loader, | |||
|             }, | |||
|             { | |||
|               loader: 'css-loader', | |||
|             }, | |||
|             { | |||
|               loader: 'sass-loader', | |||
|             }, | |||
|           ], | |||
|         }, | |||
|         { | |||
|           test: /\.(jpg|jpeg|png|gif|mp3|svg|ico)$/, | |||
|           loader: 'file-loader', | |||
|           options: { | |||
|             name: '[name].[ext]', | |||
|           }, | |||
|         }, | |||
|       ], | |||
|     }, | |||
|     plugins: [ | |||
|       new MiniCssExtractPlugin({ | |||
|         filename: '[name].css', | |||
|         chunkFilename: '[id].css', | |||
|       }), | |||
|     ], | |||
|     devtool: 'source-map', | |||
|   }), | |||
| ]; | |||
| @ -1,74 +0,0 @@ | |||
| const webpack = require('webpack'); | |||
| const path = require('path'); | |||
| const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); | |||
| const ExtractTextPlugin = require('extract-text-webpack-plugin'); | |||
| 
 | |||
| const extractSass = new ExtractTextPlugin({ | |||
|   filename: '[name].css', | |||
|   disable: process.env.NODE_ENV === 'development', | |||
| }); | |||
| 
 | |||
| const loader = new webpack.ProvidePlugin({ | |||
|   fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch', | |||
| }); | |||
| 
 | |||
| module.exports = { | |||
|   entry: './src/index.js', | |||
|   output: { | |||
|     filename: '[name].js', | |||
|     path: path.resolve(__dirname, 'dist'), | |||
|   }, | |||
|   module: { | |||
|     loaders: [ | |||
|       { | |||
|         test: /\.js$/, | |||
|         loader: 'babel-loader', | |||
|         exclude: /node_modules/, | |||
|         query: { | |||
|           plugins: ['lodash'], | |||
|           presets: [ | |||
|             [ | |||
|               'env', | |||
|               { | |||
|                 targets: { | |||
|                   browsers: ['last 2 versions', 'safari >= 7'], | |||
|                 }, | |||
|               }, | |||
|             ], | |||
|           ], | |||
|         }, | |||
|       }, | |||
|       { | |||
|         test: /\.scss$/, | |||
|         use: extractSass.extract({ | |||
|           use: [ | |||
|             { | |||
|               loader: 'css-loader', | |||
|               options: { minimize: true }, | |||
|             }, | |||
|             { | |||
|               loader: 'sass-loader', | |||
|             }, | |||
|           ], | |||
|           fallback: 'style-loader', | |||
|         }), | |||
|       }, | |||
|     ], | |||
|   }, | |||
|   plugins: | |||
|     process.env.NODE_ENV !== 'development' | |||
|       ? [ | |||
|           loader, | |||
|           extractSass, | |||
|           new UglifyJSPlugin({ | |||
|             parallel: true, | |||
|             uglifyOptions: { | |||
|               ecma: 8, | |||
|             }, | |||
|           }), | |||
|         ] | |||
|       : [loader, extractSass], | |||
|   stats: { | |||
|     colors: true, | |||
|   }, | |||
| }; | |||
								
									
										File diff suppressed because it is too large
									
								
							
						
					
					Loading…
					
					
				
		Reference in new issue