mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13.git
synced 2026-07-18 16:13:14 +01:00
sync dev server
This commit is contained in:
@@ -6,9 +6,8 @@
|
||||
|
||||
import { exec } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
import { require } from './require.js';
|
||||
|
||||
const axios = require('axios');
|
||||
import axios from 'axios';
|
||||
|
||||
import { createLogger } from './logging.js';
|
||||
|
||||
@@ -119,6 +118,6 @@ export class DreamSeeker {
|
||||
}
|
||||
}
|
||||
|
||||
const plural = (word, n) => {
|
||||
function plural(word, n) {
|
||||
return n !== 1 ? word + 's' : word;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,27 +4,27 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createCompiler } from './webpack.js';
|
||||
import { reloadByondCache } from './reloader.js';
|
||||
import { createCompiler } from './webpack.js';
|
||||
|
||||
const noHot = process.argv.includes('--no-hot');
|
||||
const noTmp = process.argv.includes('--no-tmp');
|
||||
const reloadOnce = process.argv.includes('--reload');
|
||||
|
||||
const setupServer = async () => {
|
||||
async function setupServer() {
|
||||
const compiler = await createCompiler({
|
||||
mode: 'development',
|
||||
hot: !noHot,
|
||||
devServer: true,
|
||||
useTmpFolder: !noTmp,
|
||||
});
|
||||
|
||||
// Reload cache once
|
||||
if (reloadOnce) {
|
||||
await reloadByondCache(compiler.bundleDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run a development server
|
||||
await compiler.watch();
|
||||
};
|
||||
}
|
||||
|
||||
setupServer();
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
let socket;
|
||||
const queue = [];
|
||||
const subscribers = [];
|
||||
|
||||
const ensureConnection = () => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!window.WebSocket) {
|
||||
return;
|
||||
}
|
||||
if (!socket || socket.readyState === WebSocket.CLOSED) {
|
||||
const DEV_SERVER_IP = process.env.DEV_SERVER_IP || '127.0.0.1';
|
||||
socket = new WebSocket(`ws://${DEV_SERVER_IP}:3000`);
|
||||
socket.onopen = () => {
|
||||
// Empty the message queue
|
||||
while (queue.length !== 0) {
|
||||
const msg = queue.shift();
|
||||
socket.send(msg);
|
||||
}
|
||||
};
|
||||
socket.onmessage = event => {
|
||||
const msg = JSON.parse(event.data);
|
||||
for (let subscriber of subscribers) {
|
||||
subscriber(msg);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
window.onunload = () => socket && socket.close();
|
||||
}
|
||||
|
||||
const subscribe = fn => subscribers.push(fn);
|
||||
|
||||
/**
|
||||
* A json serializer which handles circular references and other junk.
|
||||
*/
|
||||
const serializeObject = obj => {
|
||||
let refs = [];
|
||||
const primitiveReviver = value => {
|
||||
if (typeof value === 'number' && !Number.isFinite(value)) {
|
||||
return {
|
||||
__number__: String(value),
|
||||
};
|
||||
}
|
||||
if (typeof value === 'undefined') {
|
||||
return {
|
||||
__undefined__: true,
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const objectReviver = (key, value) => {
|
||||
if (typeof value === 'object') {
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
// Circular reference
|
||||
if (refs.includes(value)) {
|
||||
return '[circular ref]';
|
||||
}
|
||||
refs.push(value);
|
||||
// Error object
|
||||
const isError = value instanceof Error || (
|
||||
value.code && value.message && value.message.includes('Error')
|
||||
);
|
||||
if (isError) {
|
||||
return {
|
||||
__error__: true,
|
||||
string: String(value),
|
||||
stack: value.stack,
|
||||
};
|
||||
}
|
||||
// Array
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(primitiveReviver);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return primitiveReviver(value);
|
||||
};
|
||||
const json = JSON.stringify(obj, objectReviver);
|
||||
refs = null;
|
||||
return json;
|
||||
};
|
||||
|
||||
const sendMessage = msg => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const json = serializeObject(msg);
|
||||
// Send message using WebSocket
|
||||
if (window.WebSocket) {
|
||||
ensureConnection();
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(json);
|
||||
}
|
||||
else {
|
||||
// Keep only 100 latest messages in the queue
|
||||
if (queue.length > 100) {
|
||||
queue.shift();
|
||||
}
|
||||
queue.push(json);
|
||||
}
|
||||
}
|
||||
// Send message using plain HTTP request.
|
||||
else {
|
||||
const DEV_SERVER_IP = process.env.DEV_SERVER_IP || '127.0.0.1';
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('POST', `http://${DEV_SERVER_IP}:3001`, true);
|
||||
req.timeout = 250;
|
||||
req.send(json);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sendLogEntry = (level, ns, ...args) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
try {
|
||||
sendMessage({
|
||||
type: 'log',
|
||||
payload: {
|
||||
level,
|
||||
ns: ns || 'client',
|
||||
args,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {}
|
||||
}
|
||||
};
|
||||
|
||||
const setupHotReloading = () => {
|
||||
if (process.env.NODE_ENV !== 'production'
|
||||
&& process.env.WEBPACK_HMR_ENABLED
|
||||
&& window.WebSocket) {
|
||||
if (module.hot) {
|
||||
ensureConnection();
|
||||
sendLogEntry(0, null, 'setting up hot reloading');
|
||||
subscribe(msg => {
|
||||
const { type } = msg;
|
||||
sendLogEntry(0, null, 'received', type);
|
||||
if (type === 'hotUpdate') {
|
||||
const status = module.hot.status();
|
||||
if (status !== 'idle') {
|
||||
sendLogEntry(0, null, 'hot reload status:', status);
|
||||
return;
|
||||
}
|
||||
module.hot
|
||||
.check({
|
||||
ignoreUnaccepted: true,
|
||||
ignoreDeclined: true,
|
||||
ignoreErrored: true,
|
||||
})
|
||||
.then(modules => {
|
||||
sendLogEntry(0, null, 'outdated modules', modules);
|
||||
})
|
||||
.catch(err => {
|
||||
sendLogEntry(0, null, 'reload error', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
subscribe,
|
||||
sendMessage,
|
||||
sendLogEntry,
|
||||
setupHotReloading,
|
||||
};
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
let socket;
|
||||
const queue = [];
|
||||
const subscribers = [];
|
||||
|
||||
function ensureConnection() {
|
||||
if (process.env.NODE_ENV === 'production') return;
|
||||
|
||||
if (socket && socket.readyState !== WebSocket.CLOSED) return;
|
||||
|
||||
if (!window.WebSocket) return;
|
||||
|
||||
const DEV_SERVER_IP = process.env.DEV_SERVER_IP || '127.0.0.1';
|
||||
|
||||
socket = new WebSocket(`ws://${DEV_SERVER_IP}:3000`);
|
||||
|
||||
socket.onopen = () => {
|
||||
// Empty the message queue
|
||||
while (queue.length !== 0) {
|
||||
const msg = queue.shift();
|
||||
socket.send(msg);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
for (let subscriber of subscribers) {
|
||||
subscriber(msg);
|
||||
}
|
||||
};
|
||||
|
||||
window.onunload = () => socket?.close();
|
||||
}
|
||||
|
||||
export function subscribe(fn) {
|
||||
subscribers.push(fn);
|
||||
}
|
||||
|
||||
function primitiveReviver(value) {
|
||||
if (typeof value === 'number' && !Number.isFinite(value)) {
|
||||
return {
|
||||
__number__: String(value),
|
||||
};
|
||||
}
|
||||
if (typeof value === 'undefined') {
|
||||
return {
|
||||
__undefined__: true,
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A json serializer which handles circular references and other junk.
|
||||
*/
|
||||
function serializeObject(obj) {
|
||||
let refs = [];
|
||||
|
||||
function objectReviver(key, value) {
|
||||
if (typeof value !== 'object') {
|
||||
return primitiveReviver(value);
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
// Circular reference
|
||||
if (refs.includes(value)) {
|
||||
return '[circular ref]';
|
||||
}
|
||||
refs.push(value);
|
||||
// Error object
|
||||
const isError =
|
||||
value instanceof Error ||
|
||||
(value.code && value.message && value.message.includes('Error'));
|
||||
if (isError) {
|
||||
return {
|
||||
__error__: true,
|
||||
string: String(value),
|
||||
stack: value.stack,
|
||||
};
|
||||
}
|
||||
// Array
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(primitiveReviver);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const json = JSON.stringify(obj, objectReviver);
|
||||
refs = null;
|
||||
return json;
|
||||
}
|
||||
|
||||
export function sendMessage(msg) {
|
||||
if (process.env.NODE_ENV === 'production') return;
|
||||
|
||||
const json = serializeObject(msg);
|
||||
// Send message using WebSocket
|
||||
if (!window.WebSocket) return;
|
||||
|
||||
ensureConnection();
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(json);
|
||||
} else {
|
||||
// Keep only 100 latest messages in the queue
|
||||
if (queue.length > 100) {
|
||||
queue.shift();
|
||||
}
|
||||
queue.push(json);
|
||||
}
|
||||
}
|
||||
|
||||
export function sendLogEntry(level, ns, ...args) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
try {
|
||||
sendMessage({
|
||||
type: 'log',
|
||||
payload: {
|
||||
level,
|
||||
ns: ns || 'client',
|
||||
args,
|
||||
},
|
||||
});
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
|
||||
export function setupHotReloading() {
|
||||
if (
|
||||
process.env.NODE_ENV === 'production' ||
|
||||
!process.env.WEBPACK_HMR_ENABLED ||
|
||||
!window.WebSocket
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!import.meta.webpackHot) return;
|
||||
|
||||
ensureConnection();
|
||||
sendLogEntry(0, null, 'setting up hot reloading');
|
||||
subscribe(({ type }) => {
|
||||
sendLogEntry(0, null, 'received', type);
|
||||
if (type !== 'hotUpdate') return;
|
||||
|
||||
const status = import.meta.webpackHot.status();
|
||||
if (status !== 'idle') {
|
||||
sendLogEntry(0, null, 'hot reload status:', status);
|
||||
return;
|
||||
}
|
||||
|
||||
import.meta.webpackHot
|
||||
.check({
|
||||
ignoreUnaccepted: true,
|
||||
ignoreDeclined: true,
|
||||
ignoreErrored: true,
|
||||
})
|
||||
.then((modules) => {
|
||||
sendLogEntry(0, null, 'outdated modules', modules);
|
||||
})
|
||||
.catch((err) => {
|
||||
sendLogEntry(0, null, 'reload error', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -4,21 +4,20 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { basename } from 'path';
|
||||
import { createLogger } from '../logging.js';
|
||||
import { require } from '../require.js';
|
||||
import { resolveGlob } from '../util.js';
|
||||
import fs from 'node:fs';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
const SourceMap = require('source-map');
|
||||
const { parse: parseStackTrace } = require('stacktrace-parser');
|
||||
import { SourceMapConsumer } from 'source-map';
|
||||
import { parse as parseStackTrace } from 'stacktrace-parser';
|
||||
|
||||
import { createLogger } from '../logging.js';
|
||||
import { resolveGlob } from '../util.js';
|
||||
|
||||
const logger = createLogger('retrace');
|
||||
|
||||
const { SourceMapConsumer } = SourceMap;
|
||||
const sourceMaps = [];
|
||||
|
||||
export const loadSourceMaps = async bundleDir => {
|
||||
export async function loadSourceMaps(bundleDir) {
|
||||
// Destroy and garbage collect consumers
|
||||
while (sourceMaps.length !== 0) {
|
||||
const { consumer } = sourceMaps.shift();
|
||||
@@ -30,29 +29,29 @@ export const loadSourceMaps = async bundleDir => {
|
||||
try {
|
||||
const file = basename(path).replace('.map', '');
|
||||
const consumer = await new SourceMapConsumer(
|
||||
JSON.parse(fs.readFileSync(path, 'utf8')));
|
||||
JSON.parse(fs.readFileSync(path, 'utf8')),
|
||||
);
|
||||
sourceMaps.push({ file, consumer });
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
logger.log(`loaded ${sourceMaps.length} source maps`);
|
||||
};
|
||||
}
|
||||
|
||||
export const retrace = stack => {
|
||||
export function retrace(stack) {
|
||||
if (typeof stack !== 'string') {
|
||||
logger.log('ERROR: Stack is not a string!', stack);
|
||||
return stack;
|
||||
}
|
||||
const header = stack.split(/\n\s.*at/)[0];
|
||||
const mappedStack = parseStackTrace(stack)
|
||||
.map(frame => {
|
||||
.map((frame) => {
|
||||
if (!frame.file) {
|
||||
return frame;
|
||||
}
|
||||
// Find the correct source map
|
||||
const sourceMap = sourceMaps.find(sourceMap => {
|
||||
const sourceMap = sourceMaps.find((sourceMap) => {
|
||||
return frame.file.includes(sourceMap.file);
|
||||
});
|
||||
if (!sourceMap) {
|
||||
@@ -72,17 +71,17 @@ export const retrace = stack => {
|
||||
column: mappedFrame.column,
|
||||
};
|
||||
})
|
||||
.map(frame => {
|
||||
.map((frame) => {
|
||||
// Stringify the frame
|
||||
const { file, methodName, lineNumber } = frame;
|
||||
if (!file) {
|
||||
return ` at ${methodName}`;
|
||||
}
|
||||
const compactPath = file
|
||||
.replace(/^webpack:\/\/\/?/, './')
|
||||
.replace(/^rspack:\/\/\/?/, './')
|
||||
.replace(/.*node_modules\//, '');
|
||||
return ` at ${methodName} (${compactPath}:${lineNumber})`;
|
||||
})
|
||||
.join('\n');
|
||||
return header + '\n' + mappedStack;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import { inspect } from 'util';
|
||||
import { createLogger, directLog } from '../logging.js';
|
||||
import { require } from '../require.js';
|
||||
import { loadSourceMaps, retrace } from './retrace.js';
|
||||
import { inspect } from 'node:util';
|
||||
|
||||
const WebSocket = require('ws');
|
||||
import * as WebSocket from 'ws';
|
||||
|
||||
import { createLogger, directLog } from '../logging.js';
|
||||
import { loadSourceMaps, retrace } from './retrace.js';
|
||||
|
||||
const logger = createLogger('link');
|
||||
|
||||
@@ -18,23 +17,26 @@ const DEBUG = process.argv.includes('--debug');
|
||||
|
||||
export { loadSourceMaps };
|
||||
|
||||
export const setupLink = () => new LinkServer();
|
||||
export function setupLink() {
|
||||
return new LinkServer();
|
||||
}
|
||||
|
||||
class LinkServer {
|
||||
constructor() {
|
||||
logger.log('setting up');
|
||||
/** @type {WebSocket.Server | null} */
|
||||
this.wss = null;
|
||||
this.setupWebSocketLink();
|
||||
this.setupHttpLink();
|
||||
}
|
||||
|
||||
// WebSocket-based client link
|
||||
setupWebSocketLink() {
|
||||
const port = 3000;
|
||||
this.wss = new WebSocket.Server({ port });
|
||||
this.wss.on('connection', ws => {
|
||||
this.wss = new WebSocket.WebSocketServer({ port });
|
||||
|
||||
this.wss.on('connection', (ws) => {
|
||||
logger.log('client connected');
|
||||
ws.on('message', json => {
|
||||
ws.on('message', (json) => {
|
||||
const msg = deserializeObject(json);
|
||||
this.handleLinkMessage(ws, msg);
|
||||
});
|
||||
@@ -45,29 +47,10 @@ class LinkServer {
|
||||
logger.log(`listening on port ${port} (WebSocket)`);
|
||||
}
|
||||
|
||||
// One way HTTP-based client link for IE8
|
||||
setupHttpLink() {
|
||||
const port = 3001;
|
||||
this.httpServer = http.createServer((req, res) => {
|
||||
if (req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on('end', () => {
|
||||
const msg = deserializeObject(body);
|
||||
this.handleLinkMessage(null, msg);
|
||||
res.end();
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.write('Hello');
|
||||
res.end();
|
||||
});
|
||||
this.httpServer.listen(port);
|
||||
logger.log(`listening on port ${port} (HTTP)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {WebSocket.Client} ws
|
||||
* @param {WebSocket.MessageEvent} msg
|
||||
*/
|
||||
handleLinkMessage(ws, msg) {
|
||||
const { type, payload } = msg;
|
||||
if (type === 'log') {
|
||||
@@ -76,19 +59,26 @@ class LinkServer {
|
||||
if (level <= 0 && !DEBUG) {
|
||||
return;
|
||||
}
|
||||
directLog(ns, ...args.map(arg => {
|
||||
if (typeof arg === 'object') {
|
||||
return inspect(arg, {
|
||||
depth: Infinity,
|
||||
colors: true,
|
||||
compact: 8,
|
||||
});
|
||||
}
|
||||
return arg;
|
||||
}));
|
||||
|
||||
directLog(
|
||||
ns,
|
||||
...args.map((arg) => {
|
||||
if (typeof arg === 'object') {
|
||||
return inspect(arg, {
|
||||
depth: Infinity,
|
||||
colors: true,
|
||||
compact: 8,
|
||||
});
|
||||
}
|
||||
return arg;
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === 'relay') {
|
||||
if (!this.wss) {
|
||||
return;
|
||||
}
|
||||
for (let client of this.wss.clients) {
|
||||
if (client === ws) {
|
||||
continue;
|
||||
@@ -105,6 +95,9 @@ class LinkServer {
|
||||
}
|
||||
|
||||
broadcastMessage(msg) {
|
||||
if (!this.wss) {
|
||||
return;
|
||||
}
|
||||
const clients = [...this.wss.clients];
|
||||
if (clients.length === 0) {
|
||||
return;
|
||||
@@ -117,7 +110,7 @@ class LinkServer {
|
||||
}
|
||||
}
|
||||
|
||||
const deserializeObject = str => {
|
||||
function deserializeObject(str) {
|
||||
return JSON.parse(str, (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value.__undefined__) {
|
||||
@@ -140,4 +133,4 @@ const deserializeObject = str => {
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,22 +7,22 @@
|
||||
const inception = Date.now();
|
||||
|
||||
// Runtime detection
|
||||
const isNode = process && process.release && process.release.name === 'node';
|
||||
const isNode = process?.release?.name === 'node';
|
||||
let isChrome = false;
|
||||
try {
|
||||
isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
|
||||
}
|
||||
catch {}
|
||||
} catch {}
|
||||
|
||||
// Timestamping function
|
||||
const getTimestamp = () => {
|
||||
function getTimestamp() {
|
||||
const timestamp = String(Date.now() - inception)
|
||||
.padStart(4, '0')
|
||||
.padStart(7, ' ');
|
||||
const seconds = timestamp.substr(0, timestamp.length - 3);
|
||||
const millis = timestamp.substr(-3);
|
||||
|
||||
return `${seconds}.${millis}`;
|
||||
};
|
||||
}
|
||||
|
||||
const getPrefix = (() => {
|
||||
if (isNode) {
|
||||
@@ -32,7 +32,7 @@ const getPrefix = (() => {
|
||||
bright: '\x1b[37;1m',
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
return ns => [
|
||||
return (ns) => [
|
||||
`${ESC.dimmed}${getTimestamp()} ${ESC.bright}${ns}${ESC.reset}`,
|
||||
];
|
||||
}
|
||||
@@ -42,31 +42,33 @@ const getPrefix = (() => {
|
||||
dimmed: 'color: #888',
|
||||
bright: 'font-weight: bold',
|
||||
};
|
||||
return ns => [
|
||||
return (ns) => [
|
||||
`%c${getTimestamp()}%c ${ns}`,
|
||||
styles.dimmed,
|
||||
styles.bright,
|
||||
];
|
||||
}
|
||||
return ns => [
|
||||
`${getTimestamp()} ${ns}`,
|
||||
];
|
||||
|
||||
return (ns) => [`${getTimestamp()} ${ns}`];
|
||||
})();
|
||||
|
||||
/**
|
||||
* Creates a logger object.
|
||||
*/
|
||||
export const createLogger = ns => ({
|
||||
log: (...args) => console.log(...getPrefix(ns), ...args),
|
||||
trace: (...args) => console.trace(...getPrefix(ns), ...args),
|
||||
debug: (...args) => console.debug(...getPrefix(ns), ...args),
|
||||
info: (...args) => console.info(...getPrefix(ns), ...args),
|
||||
warn: (...args) => console.warn(...getPrefix(ns), ...args),
|
||||
error: (...args) => console.error(...getPrefix(ns), ...args),
|
||||
});
|
||||
export function createLogger(ns) {
|
||||
return {
|
||||
log: (...args) => console.log(...getPrefix(ns), ...args),
|
||||
trace: (...args) => console.trace(...getPrefix(ns), ...args),
|
||||
debug: (...args) => console.debug(...getPrefix(ns), ...args),
|
||||
info: (...args) => console.info(...getPrefix(ns), ...args),
|
||||
warn: (...args) => console.warn(...getPrefix(ns), ...args),
|
||||
error: (...args) => console.error(...getPrefix(ns), ...args),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly log with chosen namespace.
|
||||
*/
|
||||
export const directLog = (ns, ...args) =>
|
||||
export function directLog(ns, ...args) {
|
||||
console.log(...getPrefix(ns), ...args);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "tgui-dev-server",
|
||||
"version": "4.3.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^1.8.4",
|
||||
"glob": "^13.0.0",
|
||||
"source-map": "^0.7.6",
|
||||
"stacktrace-parser": "^0.1.11",
|
||||
"ws": "^8.18.3"
|
||||
}
|
||||
"private": true,
|
||||
"name": "tgui-dev-server",
|
||||
"version": "6.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@rspack/core": "^1.3.7",
|
||||
"axios": "^1.8.4",
|
||||
"source-map": "^0.7.4",
|
||||
"stacktrace-parser": "^0.1.11",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.18.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { basename } from 'path';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import { basename } from 'node:path';
|
||||
|
||||
import { DreamSeeker } from './dreamseeker.js';
|
||||
import { createLogger } from './logging.js';
|
||||
import { resolveGlob, resolvePath } from './util.js';
|
||||
@@ -30,7 +31,7 @@ const SEARCH_LOCATIONS = [
|
||||
|
||||
let cacheRoot;
|
||||
|
||||
export const findCacheRoot = async () => {
|
||||
export async function findCacheRoot() {
|
||||
if (cacheRoot) {
|
||||
return cacheRoot;
|
||||
}
|
||||
@@ -50,49 +51,53 @@ export const findCacheRoot = async () => {
|
||||
// Query the Windows Registry
|
||||
if (process.platform === 'win32') {
|
||||
logger.log('querying windows registry');
|
||||
let userpath = await regQuery(
|
||||
'HKCU\\Software\\Dantom\\BYOND',
|
||||
'userpath');
|
||||
let userpath = await regQuery('HKCU\\Software\\Dantom\\BYOND', 'userpath');
|
||||
if (userpath) {
|
||||
cacheRoot = userpath
|
||||
.replace(/\\$/, '')
|
||||
.replace(/\\/g, '/')
|
||||
+ '/cache';
|
||||
cacheRoot = userpath.replace(/\\$/, '').replace(/\\/g, '/') + '/cache';
|
||||
onCacheRootFound(cacheRoot);
|
||||
return cacheRoot;
|
||||
}
|
||||
}
|
||||
logger.log('found no cache directories');
|
||||
};
|
||||
}
|
||||
|
||||
const onCacheRootFound = cacheRoot => {
|
||||
function onCacheRootFound(cacheRoot) {
|
||||
logger.log(`found cache at '${cacheRoot}'`);
|
||||
};
|
||||
// Plant a dummy browser window file, we'll be using this to avoid world topic. For byond 514.
|
||||
fs.closeSync(fs.openSync(cacheRoot + '/dummy.htm', 'w'));
|
||||
}
|
||||
|
||||
export const reloadByondCache = async bundleDir => {
|
||||
export async function reloadByondCache(bundleDir) {
|
||||
const cacheRoot = await findCacheRoot();
|
||||
if (!cacheRoot) {
|
||||
return;
|
||||
}
|
||||
// Find tmp folders in cache
|
||||
const cacheDirs = await resolveGlob(cacheRoot, './tmp*');
|
||||
const cacheDirs = resolveGlob(cacheRoot, './tmp*');
|
||||
if (cacheDirs.length === 0) {
|
||||
logger.log('found no tmp folder in cache');
|
||||
return;
|
||||
}
|
||||
// Get dreamseeker instances
|
||||
const pids = cacheDirs.map(cacheDir => (
|
||||
parseInt(cacheDir.split('/cache/tmp').pop(), 10)
|
||||
));
|
||||
|
||||
const pids = cacheDirs.map((cacheDir) => {
|
||||
return parseInt(cacheDir.split('\\cache\\tmp')[1], 10);
|
||||
});
|
||||
|
||||
const dssPromise = DreamSeeker.getInstancesByPids(pids);
|
||||
// Copy assets
|
||||
const assets = await resolveGlob(bundleDir, './*.+(bundle|chunk|hot-update).*');
|
||||
const assets = await resolveGlob(
|
||||
bundleDir,
|
||||
'./*.+(bundle|chunk|hot-update).*',
|
||||
);
|
||||
for (let cacheDir of cacheDirs) {
|
||||
// Clear garbage
|
||||
const garbage = await resolveGlob(cacheDir, './*.+(bundle|chunk|hot-update).*');
|
||||
const garbage = await resolveGlob(
|
||||
cacheDir,
|
||||
'./*.+(bundle|chunk|hot-update).*',
|
||||
);
|
||||
try {
|
||||
// Plant a dummy browser window file, we'll be using this to avoid world topic
|
||||
fs.closeSync(fs.openSync(cacheDir + '/dummy', 'w'));
|
||||
// Plant a dummy browser window file, we'll be using this to avoid world topic. For byond 515-516.
|
||||
fs.closeSync(fs.openSync(cacheDir + '/dummy.htm', 'w'));
|
||||
|
||||
for (let file of garbage) {
|
||||
fs.unlinkSync(file);
|
||||
@@ -103,8 +108,7 @@ export const reloadByondCache = async bundleDir => {
|
||||
fs.writeFileSync(destination, fs.readFileSync(asset));
|
||||
}
|
||||
logger.log(`copied ${assets.length} files to '${cacheDir}'`);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(`failed copying to '${cacheDir}'`);
|
||||
logger.error(err);
|
||||
}
|
||||
@@ -120,4 +124,4 @@ export const reloadByondCache = async bundleDir => {
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createRequire } from 'module';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
export const require = createRequire(import.meta.url);
|
||||
|
||||
@@ -4,30 +4,25 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { require } from './require.js';
|
||||
|
||||
const globPkg = require('glob');
|
||||
import fs, { globSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export const resolvePath = path.resolve;
|
||||
|
||||
/**
|
||||
* Combines path.resolve with glob patterns.
|
||||
*/
|
||||
export const resolveGlob = (...sections) => {
|
||||
const unsafePaths = globPkg.sync(path.resolve(...sections), {
|
||||
strict: false,
|
||||
silent: true,
|
||||
windowsPathsNoEscape: true,
|
||||
});
|
||||
/** Combines path.resolve with glob patterns. */
|
||||
export function resolveGlob(...sections) {
|
||||
/** @type {string[]} */
|
||||
const unsafePaths = globSync(path.resolve(...sections));
|
||||
|
||||
/** @type {string[]} */
|
||||
const safePaths = [];
|
||||
|
||||
for (let path of unsafePaths) {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
safePaths.push(path);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return safePaths;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,40 +4,49 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from 'path';
|
||||
import fs from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
import { loadSourceMaps, setupLink } from './link/server.js';
|
||||
import { createLogger } from './logging.js';
|
||||
import { reloadByondCache } from './reloader.js';
|
||||
import { resolveGlob } from './util.js';
|
||||
|
||||
const logger = createLogger('webpack');
|
||||
const logger = createLogger('rspack');
|
||||
|
||||
/**
|
||||
* @param {any} config
|
||||
* @return {WebpackCompiler}
|
||||
* @return {RspackCompiler}
|
||||
*/
|
||||
export const createCompiler = async options => {
|
||||
const compiler = new WebpackCompiler();
|
||||
export async function createCompiler(options) {
|
||||
const compiler = new RspackCompiler();
|
||||
await compiler.setup(options);
|
||||
return compiler;
|
||||
};
|
||||
|
||||
class WebpackCompiler {
|
||||
return compiler;
|
||||
}
|
||||
|
||||
class RspackCompiler {
|
||||
async setup(options) {
|
||||
// Create a require context that is relative to project root
|
||||
// and retrieve all necessary dependencies.
|
||||
const requireFromRoot = createRequire(dirname(import.meta.url) + '/../..');
|
||||
const webpack = await requireFromRoot('webpack');
|
||||
const createConfig = await requireFromRoot('./webpack.config.js');
|
||||
const requireFromRoot = createRequire(import.meta.dirname + '/../../..');
|
||||
/** @type {typeof import('@rspack/core')} */
|
||||
const rspack = await requireFromRoot('@rspack/core');
|
||||
|
||||
const createConfig = await requireFromRoot('./rspack.config.cjs');
|
||||
const createDevConfig = await requireFromRoot('./rspack.config-dev.cjs');
|
||||
|
||||
const config = createConfig({}, options);
|
||||
const devConfig = createDevConfig({}, options);
|
||||
|
||||
const mergedConfig = { ...config, ...devConfig };
|
||||
|
||||
// Inject the HMR plugin into the config if we're using it
|
||||
if (options.hot) {
|
||||
config.plugins.push(new webpack.HotModuleReplacementPlugin());
|
||||
mergedConfig.plugins.push(new rspack.HotModuleReplacementPlugin());
|
||||
}
|
||||
this.webpack = webpack;
|
||||
this.config = config;
|
||||
this.rspack = rspack;
|
||||
this.config = mergedConfig;
|
||||
this.bundleDir = config.output.path;
|
||||
}
|
||||
|
||||
@@ -46,7 +55,7 @@ class WebpackCompiler {
|
||||
// Setup link
|
||||
const link = setupLink();
|
||||
// Instantiate the compiler
|
||||
const compiler = this.webpack.webpack(this.config);
|
||||
const compiler = this.rspack.rspack(this.config);
|
||||
// Clear garbage before compiling
|
||||
compiler.hooks.watchRun.tapPromise('tgui-dev-server', async () => {
|
||||
const files = await resolveGlob(this.bundleDir, './*.hot-update.*');
|
||||
@@ -57,7 +66,7 @@ class WebpackCompiler {
|
||||
logger.log('compiling');
|
||||
});
|
||||
// Start reloading when it's finished
|
||||
compiler.hooks.done.tap('tgui-dev-server', async stats => {
|
||||
compiler.hooks.done.tap('tgui-dev-server', async (stats) => {
|
||||
// Load source maps
|
||||
await loadSourceMaps(this.bundleDir);
|
||||
// Reload cache
|
||||
@@ -75,9 +84,9 @@ class WebpackCompiler {
|
||||
return;
|
||||
}
|
||||
stats
|
||||
.toString(this.config.devServer.stats)
|
||||
?.toString(this.config.devServer.stats)
|
||||
.split('\n')
|
||||
.forEach(line => logger.log(line));
|
||||
.forEach((line) => logger.log(line));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,42 +6,49 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { exec } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import { createLogger } from './logging.js';
|
||||
|
||||
const logger = createLogger('winreg');
|
||||
|
||||
export const regQuery = async (path, key) => {
|
||||
/**
|
||||
* Query a registry key.
|
||||
* @param {string} path
|
||||
* @param {string} key
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
export async function regQuery(path, key) {
|
||||
if (process.platform !== 'win32') {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const command = `reg query "${path}" /v ${key}`;
|
||||
const { stdout } = await promisify(exec)(command);
|
||||
const keyPattern = ` ${key} `;
|
||||
const indexOfKey = stdout.indexOf(keyPattern);
|
||||
|
||||
if (indexOfKey === -1) {
|
||||
logger.error('could not find the registry key');
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const indexOfEol = stdout.indexOf('\r\n', indexOfKey);
|
||||
if (indexOfEol === -1) {
|
||||
logger.error('could not find the end of the line');
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
const indexOfValue = stdout.indexOf(
|
||||
' ',
|
||||
indexOfKey + keyPattern.length);
|
||||
|
||||
const indexOfValue = stdout.indexOf(' ', indexOfKey + keyPattern.length);
|
||||
if (indexOfValue === -1) {
|
||||
logger.error('could not find the start of the key value');
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
const value = stdout.substring(indexOfValue + 4, indexOfEol);
|
||||
return value;
|
||||
}
|
||||
catch (err) {
|
||||
|
||||
return stdout.substring(indexOfValue + 4, indexOfEol);
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user