@yeow/create-yeow 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -2,23 +2,85 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const AdmZip = require('adm-zip');
|
|
5
|
+
|
|
5
6
|
const configPath = path.resolve('yeow.config.js');
|
|
6
|
-
|
|
7
|
-
function
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error('\u274C yeow.config.js not found');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return require(configPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function compileUserCode() {
|
|
17
|
+
console.log('\uD83D\uDCE6 Compiling user code with Rspack...');
|
|
18
|
+
try {
|
|
19
|
+
execSync('npx rspack build', { stdio: 'inherit' });
|
|
20
|
+
console.log('\u2705 User code compiled');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('\u274C Compilation failed:', error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
function buildPlugin() {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const { plugin, build } = config;
|
|
30
|
+
|
|
31
|
+
console.log('\n\uD83D\uDE80 Building Yeow plugin...');
|
|
32
|
+
console.log(` Plugin: ${plugin.name} v${plugin.version}`);
|
|
33
|
+
|
|
11
34
|
compileUserCode();
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
|
|
36
|
+
const templateJarPath = path.resolve(build.templateJar || '.yeow/assets/yeow-template-1.0.0.jar');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(templateJarPath)) {
|
|
39
|
+
console.error('\u274C Template JAR not found:', templateJarPath);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n\uD83D\uDCE6 Packaging plugin JAR...');
|
|
44
|
+
|
|
14
45
|
const templateZip = new AdmZip(templateJarPath);
|
|
15
|
-
|
|
46
|
+
|
|
47
|
+
const pluginYml = `name: ${plugin.name}
|
|
48
|
+
version: ${plugin.version}
|
|
49
|
+
main: yeow.template.YeowPluginBootstrap
|
|
50
|
+
api-version: '1.21'
|
|
51
|
+
|
|
52
|
+
depend:
|
|
53
|
+
- Yeow-Runtime
|
|
54
|
+
|
|
55
|
+
author: ${plugin.author || 'Unknown'}
|
|
56
|
+
description: ${plugin.description || 'A Yeow plugin'}`;
|
|
57
|
+
|
|
58
|
+
templateZip.updateFile('plugin.yml', Buffer.from(pluginYml));
|
|
59
|
+
|
|
16
60
|
const userCodePath = path.resolve('dist/.yeow/main.js');
|
|
17
|
-
if (fs.existsSync(userCodePath)) {
|
|
18
|
-
|
|
61
|
+
if (fs.existsSync(userCodePath)) {
|
|
62
|
+
const userCode = fs.readFileSync(userCodePath);
|
|
63
|
+
templateZip.addFile('.yeow/main.js', userCode);
|
|
64
|
+
} else {
|
|
65
|
+
console.error('\u274C Compiled user code not found:', userCodePath);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputPath = build.output || `dist/${plugin.name}-${plugin.version}.jar`;
|
|
19
70
|
const outputDir = path.dirname(outputPath);
|
|
20
|
-
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(outputDir)) {
|
|
73
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
21
76
|
templateZip.writeZip(outputPath);
|
|
22
|
-
|
|
77
|
+
|
|
78
|
+
const stats = fs.statSync(outputPath);
|
|
79
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
80
|
+
|
|
81
|
+
console.log(`\n\u2705 Plugin built successfully!`);
|
|
82
|
+
console.log(` Output: ${outputPath}`);
|
|
83
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
23
84
|
}
|
|
24
|
-
|
|
85
|
+
|
|
86
|
+
buildPlugin();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { spawn, execSync } = require('child_process');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
@@ -8,128 +9,321 @@ const CACHE_DIR = path.join(DEV_DIR, 'cache');
|
|
|
8
9
|
const SERVER_DIR = path.join(DEV_DIR, 'server');
|
|
9
10
|
const PAPER_VERSION = '1.21.4';
|
|
10
11
|
const PAPER_BUILD = 232;
|
|
11
|
-
const PAPER_FILENAME =
|
|
12
|
-
const PAPER_URL =
|
|
12
|
+
const PAPER_FILENAME = `paper-${PAPER_VERSION}-${PAPER_BUILD}.jar`;
|
|
13
|
+
const PAPER_URL = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${PAPER_BUILD}/downloads/${PAPER_FILENAME}`;
|
|
13
14
|
const PAPER_CACHE_PATH = path.join(CACHE_DIR, PAPER_FILENAME);
|
|
14
15
|
const DOUBLE_CTRL_C_WINDOW = 3000;
|
|
16
|
+
|
|
15
17
|
const YEOW_CONFIG_PATH = path.resolve('yeow.config.js');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
22
|
+
ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let serverProcess = null;
|
|
26
|
+
let ctrlCPressCount = 0;
|
|
27
|
+
let ctrlCTimer = null;
|
|
28
|
+
let srcWatcher = null;
|
|
29
|
+
let isCompiling = false;
|
|
30
|
+
|
|
31
|
+
function log(msg, color = '') {
|
|
32
|
+
console.log(`${c.dim}[${new Date().toLocaleTimeString()}]${c.reset} ${color}${msg}${c.reset}`);
|
|
33
|
+
}
|
|
34
|
+
function logOk(msg) { log(`${c.ok} ${msg}`, c.green); }
|
|
35
|
+
function logFail(msg) { log(`${c.fail} ${msg}`, c.red); }
|
|
36
|
+
function logWarn(msg) { log(`${c.warn} ${msg}`, c.yellow); }
|
|
37
|
+
function logInfo(msg) { log(`${c.info} ${msg}`, c.cyan); }
|
|
23
38
|
function logBright(msg) { log(msg, c.bold); }
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
41
|
+
|
|
42
|
+
async function promptProxy(devConfig) {
|
|
43
|
+
const currentProxy = devConfig.proxy || '';
|
|
27
44
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
const answer = await new Promise((resolve) => {
|
|
46
|
+
const hint = currentProxy ? ` (current: ${c.cyan}${currentProxy}${c.reset})` : '';
|
|
47
|
+
log(` ${c.yellow}Configure proxy to download PaperMC server JAR`);
|
|
48
|
+
rl.question(` ${c.yellow}Proxy address${c.reset}${hint} [blank to skip]: `, (val) => {
|
|
49
|
+
rl.close();
|
|
50
|
+
resolve(val);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const val = answer.trim();
|
|
54
|
+
if (val) {
|
|
55
|
+
devConfig.proxy = val;
|
|
56
|
+
delete require.cache[YEOW_CONFIG_PATH];
|
|
57
|
+
const cfg = require(YEOW_CONFIG_PATH);
|
|
58
|
+
cfg.dev = cfg.dev || {};
|
|
59
|
+
cfg.dev.proxy = val;
|
|
60
|
+
fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n');
|
|
61
|
+
logInfo(`Proxy set to: ${val}`);
|
|
62
|
+
} else if (!currentProxy) {
|
|
63
|
+
log('No proxy, connecting directly');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function createAgent(proxyUrl) {
|
|
68
|
+
if (!proxyUrl) return null;
|
|
35
69
|
try {
|
|
36
|
-
await
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
const { HttpsProxyAgent } = await import('https-proxy-agent');
|
|
71
|
+
return new HttpsProxyAgent(proxyUrl);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
logWarn(`Invalid proxy URL "${proxyUrl}", connecting directly`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function downloadFile(url, dest, agent) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const file = fs.createWriteStream(dest);
|
|
81
|
+
const options = {};
|
|
82
|
+
if (agent) {
|
|
83
|
+
options.agent = agent;
|
|
84
|
+
log(` Using proxy: ${c.cyan}${agent.proxy.href}${c.reset}`);
|
|
85
|
+
}
|
|
86
|
+
const request = https.get(url, options, (response) => {
|
|
87
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
88
|
+
file.close();
|
|
89
|
+
fs.unlinkSync(dest);
|
|
90
|
+
return downloadFile(response.headers.location, dest, agent).then(resolve).catch(reject);
|
|
91
|
+
}
|
|
92
|
+
if (response.statusCode !== 200) {
|
|
93
|
+
file.close();
|
|
94
|
+
fs.unlinkSync(dest);
|
|
95
|
+
return reject(new Error(`HTTP ${response.statusCode}`));
|
|
96
|
+
}
|
|
97
|
+
const total = parseInt(response.headers['content-length'], 10);
|
|
98
|
+
let downloaded = 0;
|
|
99
|
+
response.on('data', (chunk) => {
|
|
100
|
+
downloaded += chunk.length;
|
|
101
|
+
const pct = total ? (downloaded / total * 100).toFixed(1) : '?';
|
|
102
|
+
readline.cursorTo(process.stdout, 0);
|
|
103
|
+
process.stdout.write(` ${c.blue}Downloading...${c.reset} ${c.bold}${(downloaded / 1024 / 1024).toFixed(1)}MB${c.reset} / ${total ? (total / 1024 / 1024).toFixed(1) + 'MB' : '?'} (${c.yellow}${pct}%${c.reset})`);
|
|
44
104
|
});
|
|
45
|
-
|
|
105
|
+
response.pipe(file);
|
|
106
|
+
file.on('finish', () => { file.close(); process.stdout.write('\n'); resolve(); });
|
|
46
107
|
});
|
|
108
|
+
request.on('error', (err) => { file.close(); if (fs.existsSync(dest)) fs.unlinkSync(dest); reject(err); });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function ensurePaperJar(devConfig) {
|
|
113
|
+
if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) {
|
|
114
|
+
logOk('Paper jar found in cache');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await promptProxy(devConfig);
|
|
118
|
+
const agent = await createAgent(devConfig.proxy);
|
|
119
|
+
logInfo(agent ? `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD} via proxy...` : `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD}...`);
|
|
120
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
121
|
+
try {
|
|
122
|
+
await downloadFile(PAPER_URL, PAPER_CACHE_PATH, agent);
|
|
47
123
|
logOk('Paper jar downloaded');
|
|
48
|
-
} catch(err) {
|
|
49
|
-
logFail(
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logFail(`Download failed: ${err.message}`);
|
|
126
|
+
log(' You can manually download the jar and place it at:');
|
|
127
|
+
log(` ${c.cyan}${PAPER_CACHE_PATH}${c.reset}`);
|
|
50
128
|
process.exit(1);
|
|
51
129
|
}
|
|
52
130
|
}
|
|
53
131
|
|
|
54
132
|
async function ensureRuntimeJar(devConfig) {
|
|
55
|
-
|
|
56
|
-
if (fs.existsSync(
|
|
57
|
-
logFail(
|
|
133
|
+
const cfgPath = path.resolve(devConfig.runtimeJar);
|
|
134
|
+
if (fs.existsSync(cfgPath) && fs.statSync(cfgPath).size > 1000) return cfgPath;
|
|
135
|
+
logFail(`Yeow-Runtime.jar not found at ${cfgPath}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function runInitProcess(devConfig) {
|
|
140
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
141
|
+
logInfo('Initializing server for the first time...');
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
const initProc = spawn(devConfig.javaPath, [
|
|
144
|
+
`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`,
|
|
145
|
+
'-jar', paperJar, '--nogui'
|
|
146
|
+
], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
|
|
147
|
+
const timer = setTimeout(() => { if (!initProc.killed) initProc.kill('SIGTERM'); resolve(); }, 15000);
|
|
148
|
+
initProc.on('exit', () => { clearTimeout(timer); resolve(); });
|
|
149
|
+
initProc.on('error', () => { clearTimeout(timer); resolve(); });
|
|
150
|
+
});
|
|
58
151
|
}
|
|
59
152
|
|
|
60
|
-
function initServer(devConfig) {
|
|
61
|
-
|
|
153
|
+
async function initServer(devConfig) {
|
|
154
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
62
155
|
if (!fs.existsSync(paperJar)) fs.copyFileSync(PAPER_CACHE_PATH, paperJar);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
var p = spawn(devConfig.javaPath, ['-Xmx' + devConfig.memory, '-Xms' + devConfig.memory, '-jar', paperJar, '--nogui'], { cwd: SERVER_DIR, stdio: 'pipe' });
|
|
69
|
-
var t = setTimeout(function() { if (!p.killed) p.kill(); }, 15000);
|
|
70
|
-
p.on('exit', function() { clearTimeout(t); });
|
|
71
|
-
p.on('error', function() { clearTimeout(t); });
|
|
156
|
+
|
|
157
|
+
const eulaPath = path.join(SERVER_DIR, 'eula.txt');
|
|
158
|
+
if (fs.existsSync(eulaPath)) {
|
|
159
|
+
const eulaContent = fs.readFileSync(eulaPath, 'utf-8');
|
|
160
|
+
if (eulaContent.includes('eula=true')) return;
|
|
72
161
|
}
|
|
73
|
-
|
|
74
|
-
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(path.join(SERVER_DIR, 'server.properties'))) {
|
|
164
|
+
await runInitProcess(devConfig);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
168
|
+
await new Promise((resolve) => {
|
|
169
|
+
rl.question(` ${c.yellow}Press Enter to accept Mojang EULA and start the server...${c.reset} `, () => {
|
|
170
|
+
rl.close();
|
|
171
|
+
fs.writeFileSync(eulaPath, '#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).\neula=true\n');
|
|
172
|
+
logOk('EULA accepted');
|
|
173
|
+
resolve();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
75
176
|
}
|
|
76
177
|
|
|
77
178
|
function getServerProperties(port) {
|
|
78
|
-
|
|
79
|
-
|
|
179
|
+
const propsPath = path.join(SERVER_DIR, 'server.properties');
|
|
180
|
+
const merged = {};
|
|
80
181
|
if (fs.existsSync(propsPath)) {
|
|
81
|
-
fs.readFileSync(propsPath, 'utf-8')
|
|
82
|
-
|
|
83
|
-
|
|
182
|
+
const content = fs.readFileSync(propsPath, 'utf-8');
|
|
183
|
+
for (const line of content.split('\n')) {
|
|
184
|
+
const trimmed = line.trim();
|
|
185
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
186
|
+
const eqIdx = trimmed.indexOf('=');
|
|
187
|
+
if (eqIdx > 0) merged[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
84
190
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
191
|
+
Object.assign(merged, {
|
|
192
|
+
'difficulty': 'easy', 'enable-command-block': 'true', 'max-players': '10',
|
|
193
|
+
'motd': 'A Yeow Dev Server', 'online-mode': 'false', 'server-port': String(port), 'spawn-protection': '0'
|
|
194
|
+
});
|
|
195
|
+
let output = '#Minecraft server properties\n#Generated by Yeow Dev Server\n';
|
|
196
|
+
for (const [key, val] of Object.entries(merged)) output += `${key}=${val}\n`;
|
|
197
|
+
fs.writeFileSync(propsPath, output);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function copyToPlugins(sourcePath, label) {
|
|
201
|
+
const pluginsDir = path.join(SERVER_DIR, 'plugins');
|
|
202
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
203
|
+
const destPath = path.join(pluginsDir, path.basename(sourcePath));
|
|
204
|
+
if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
|
|
205
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
206
|
+
logOk(`${label} copied to plugins`);
|
|
88
207
|
}
|
|
89
208
|
|
|
90
209
|
function buildDevPlugin() {
|
|
91
210
|
logInfo('Building plugin...');
|
|
92
|
-
try {
|
|
93
|
-
|
|
211
|
+
try {
|
|
212
|
+
execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') });
|
|
213
|
+
logOk('Plugin built');
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logFail('Build failed: ' + err.message);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function handleCtrlC() {
|
|
221
|
+
ctrlCPressCount++;
|
|
222
|
+
if (ctrlCPressCount >= 2) {
|
|
223
|
+
logWarn('Force shutdown...');
|
|
224
|
+
if (srcWatcher) srcWatcher.close();
|
|
225
|
+
if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
logWarn('Sending stop command... (press Ctrl+C again to force kill)');
|
|
229
|
+
if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
|
|
230
|
+
if (ctrlCTimer) clearTimeout(ctrlCTimer);
|
|
231
|
+
ctrlCTimer = setTimeout(() => { ctrlCPressCount = 0; ctrlCTimer = null; }, DOUBLE_CTRL_C_WINDOW);
|
|
94
232
|
}
|
|
95
233
|
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
|
|
234
|
+
function startHotReloadWatcher() {
|
|
235
|
+
const srcDir = path.resolve(__dirname, '../src');
|
|
236
|
+
if (!fs.existsSync(srcDir)) return;
|
|
237
|
+
|
|
238
|
+
let debounceTimer = null;
|
|
239
|
+
srcWatcher = fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
240
|
+
if (!filename || !filename.endsWith('.js')) return;
|
|
241
|
+
if (isCompiling) return;
|
|
242
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
243
|
+
debounceTimer = setTimeout(async () => {
|
|
244
|
+
isCompiling = true;
|
|
245
|
+
logInfo('Source file changed, rebuilding...');
|
|
246
|
+
try {
|
|
247
|
+
execSync('node .yeow/build.js', { stdio: 'pipe', cwd: path.resolve(__dirname, '..') });
|
|
248
|
+
logOk('Hot reload file updated');
|
|
249
|
+
} catch (e) {
|
|
250
|
+
logFail('Build failed: ' + e.message);
|
|
251
|
+
}
|
|
252
|
+
isCompiling = false;
|
|
253
|
+
}, 1000);
|
|
254
|
+
});
|
|
255
|
+
logInfo('Watching src/ for changes (hot reload)...');
|
|
99
256
|
}
|
|
100
257
|
|
|
101
258
|
function startServer(devConfig) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
259
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
260
|
+
const jvmArgs = [`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`, `-Dyeow.dev=true`];
|
|
261
|
+
|
|
262
|
+
logBright('\nStarting server (type commands to send to console)...\n');
|
|
263
|
+
|
|
264
|
+
serverProcess = spawn(devConfig.javaPath, [...jvmArgs, '-jar', paperJar, '--nogui'], {
|
|
265
|
+
cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit']
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
serverProcess.on('exit', (code) => {
|
|
269
|
+
logWarn(`Server exited with code ${code}`);
|
|
270
|
+
if (srcWatcher) srcWatcher.close();
|
|
271
|
+
process.exit(0);
|
|
272
|
+
});
|
|
273
|
+
serverProcess.on('error', (err) => { logFail('Failed to start server: ' + err.message); process.exit(1); });
|
|
274
|
+
|
|
275
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
276
|
+
rl.on('line', (line) => {
|
|
277
|
+
if (serverProcess && !serverProcess.killed && line.trim()) {
|
|
278
|
+
serverProcess.stdin.write(line.trim() + '\n');
|
|
279
|
+
}
|
|
280
|
+
});
|
|
109
281
|
}
|
|
110
282
|
|
|
111
|
-
process.on('SIGINT',
|
|
112
|
-
ctrlCPressCount++;
|
|
113
|
-
if (ctrlCPressCount >= 2) { if (serverProcess && !serverProcess.killed) serverProcess.kill(); process.exit(0); }
|
|
114
|
-
if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
|
|
115
|
-
if (ctrlCTimer) clearTimeout(ctrlCTimer);
|
|
116
|
-
ctrlCTimer = setTimeout(function() { ctrlCPressCount = 0; }, DOUBLE_CTRL_C_WINDOW);
|
|
117
|
-
});
|
|
283
|
+
process.on('SIGINT', () => handleCtrlC());
|
|
118
284
|
|
|
119
285
|
async function main() {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
286
|
+
if (!fs.existsSync(YEOW_CONFIG_PATH)) { logFail('yeow.config.js not found'); process.exit(1); }
|
|
287
|
+
|
|
288
|
+
const yeowConfig = require(YEOW_CONFIG_PATH);
|
|
289
|
+
const devConfig = yeowConfig.dev || {};
|
|
290
|
+
devConfig.port = devConfig.port || 17367;
|
|
291
|
+
devConfig.memory = devConfig.memory || '8G';
|
|
292
|
+
devConfig.javaPath = devConfig.javaPath || 'java';
|
|
293
|
+
devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
|
|
294
|
+
|
|
295
|
+
console.log(`\n${c.bold}${c.blue} Yeow Dev Server${c.reset}`);
|
|
296
|
+
console.log(` ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
297
|
+
console.log(` Port: ${c.bold}${devConfig.port}${c.reset}`);
|
|
298
|
+
console.log(` Memory: ${c.bold}${devConfig.memory}${c.reset}`);
|
|
299
|
+
if (devConfig.proxy) console.log(` Proxy: ${c.cyan}${devConfig.proxy}${c.reset}`);
|
|
300
|
+
console.log();
|
|
301
|
+
|
|
302
|
+
const runtimeJarPath = await ensureRuntimeJar(devConfig);
|
|
125
303
|
await ensurePaperJar(devConfig);
|
|
304
|
+
|
|
126
305
|
fs.mkdirSync(SERVER_DIR, { recursive: true });
|
|
127
|
-
|
|
306
|
+
|
|
307
|
+
await initServer(devConfig);
|
|
128
308
|
getServerProperties(devConfig.port);
|
|
309
|
+
|
|
310
|
+
const pluginOutputPath = path.resolve(yeowConfig.build.output || `dist/${yeowConfig.plugin.name}-${yeowConfig.plugin.version}.jar`);
|
|
311
|
+
|
|
129
312
|
buildDevPlugin();
|
|
130
|
-
copyToPlugins(
|
|
313
|
+
copyToPlugins(pluginOutputPath, 'Plugin');
|
|
131
314
|
copyToPlugins(runtimeJarPath, 'Yeow-Runtime');
|
|
315
|
+
|
|
316
|
+
console.log(`\n${c.bold}${c.green} Starting Paper ${PAPER_VERSION} server...${c.reset}`);
|
|
317
|
+
console.log(` ${c.info} Local address: ${c.bold}${c.cyan}127.0.0.1:${devConfig.port}${c.reset}`);
|
|
318
|
+
console.log(` ${c.dim} Server directory: ${path.relative(process.cwd(), SERVER_DIR)}${c.reset}`);
|
|
319
|
+
|
|
320
|
+
startHotReloadWatcher();
|
|
132
321
|
startServer(devConfig);
|
|
133
322
|
}
|
|
134
323
|
|
|
135
|
-
|
|
324
|
+
process.on('exit', () => {
|
|
325
|
+
if (srcWatcher) srcWatcher.close();
|
|
326
|
+
if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
main().catch((err) => { logFail('Error: ' + err.message); process.exit(1); });
|
|
@@ -2,23 +2,85 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const AdmZip = require('adm-zip');
|
|
5
|
+
|
|
5
6
|
const configPath = path.resolve('yeow.config.js');
|
|
6
|
-
|
|
7
|
-
function
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error('\u274C yeow.config.js not found');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return require(configPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function compileUserCode() {
|
|
17
|
+
console.log('\uD83D\uDCE6 Compiling user code with Rspack...');
|
|
18
|
+
try {
|
|
19
|
+
execSync('npx rspack build', { stdio: 'inherit' });
|
|
20
|
+
console.log('\u2705 User code compiled');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('\u274C Compilation failed:', error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
function buildPlugin() {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const { plugin, build } = config;
|
|
30
|
+
|
|
31
|
+
console.log('\n\uD83D\uDE80 Building Yeow plugin...');
|
|
32
|
+
console.log(` Plugin: ${plugin.name} v${plugin.version}`);
|
|
33
|
+
|
|
11
34
|
compileUserCode();
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
|
|
36
|
+
const templateJarPath = path.resolve(build.templateJar || '.yeow/assets/yeow-template-1.0.0.jar');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(templateJarPath)) {
|
|
39
|
+
console.error('\u274C Template JAR not found:', templateJarPath);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n\uD83D\uDCE6 Packaging plugin JAR...');
|
|
44
|
+
|
|
14
45
|
const templateZip = new AdmZip(templateJarPath);
|
|
15
|
-
|
|
46
|
+
|
|
47
|
+
const pluginYml = `name: ${plugin.name}
|
|
48
|
+
version: ${plugin.version}
|
|
49
|
+
main: yeow.template.YeowPluginBootstrap
|
|
50
|
+
api-version: '1.21'
|
|
51
|
+
|
|
52
|
+
depend:
|
|
53
|
+
- Yeow-Runtime
|
|
54
|
+
|
|
55
|
+
author: ${plugin.author || 'Unknown'}
|
|
56
|
+
description: ${plugin.description || 'A Yeow plugin'}`;
|
|
57
|
+
|
|
58
|
+
templateZip.updateFile('plugin.yml', Buffer.from(pluginYml));
|
|
59
|
+
|
|
16
60
|
const userCodePath = path.resolve('dist/.yeow/main.js');
|
|
17
|
-
if (fs.existsSync(userCodePath)) {
|
|
18
|
-
|
|
61
|
+
if (fs.existsSync(userCodePath)) {
|
|
62
|
+
const userCode = fs.readFileSync(userCodePath);
|
|
63
|
+
templateZip.addFile('.yeow/main.js', userCode);
|
|
64
|
+
} else {
|
|
65
|
+
console.error('\u274C Compiled user code not found:', userCodePath);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputPath = build.output || `dist/${plugin.name}-${plugin.version}.jar`;
|
|
19
70
|
const outputDir = path.dirname(outputPath);
|
|
20
|
-
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(outputDir)) {
|
|
73
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
21
76
|
templateZip.writeZip(outputPath);
|
|
22
|
-
|
|
77
|
+
|
|
78
|
+
const stats = fs.statSync(outputPath);
|
|
79
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
80
|
+
|
|
81
|
+
console.log(`\n\u2705 Plugin built successfully!`);
|
|
82
|
+
console.log(` Output: ${outputPath}`);
|
|
83
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
23
84
|
}
|
|
85
|
+
|
|
24
86
|
buildPlugin();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { spawn, execSync } = require('child_process');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
@@ -8,107 +9,246 @@ const CACHE_DIR = path.join(DEV_DIR, 'cache');
|
|
|
8
9
|
const SERVER_DIR = path.join(DEV_DIR, 'server');
|
|
9
10
|
const PAPER_VERSION = '1.21.4';
|
|
10
11
|
const PAPER_BUILD = 232;
|
|
11
|
-
const PAPER_FILENAME =
|
|
12
|
-
const PAPER_URL =
|
|
12
|
+
const PAPER_FILENAME = `paper-${PAPER_VERSION}-${PAPER_BUILD}.jar`;
|
|
13
|
+
const PAPER_URL = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${PAPER_BUILD}/downloads/${PAPER_FILENAME}`;
|
|
13
14
|
const PAPER_CACHE_PATH = path.join(CACHE_DIR, PAPER_FILENAME);
|
|
14
15
|
const DOUBLE_CTRL_C_WINDOW = 3000;
|
|
16
|
+
|
|
15
17
|
const YEOW_CONFIG_PATH = path.resolve('yeow.config.js');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
22
|
+
ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let serverProcess = null;
|
|
26
|
+
let ctrlCPressCount = 0;
|
|
27
|
+
let ctrlCTimer = null;
|
|
28
|
+
let srcWatcher = null;
|
|
29
|
+
let isCompiling = false;
|
|
30
|
+
|
|
31
|
+
function log(msg, color = '') {
|
|
32
|
+
console.log(`${c.dim}[${new Date().toLocaleTimeString()}]${c.reset} ${color}${msg}${c.reset}`);
|
|
33
|
+
}
|
|
34
|
+
function logOk(msg) { log(`${c.ok} ${msg}`, c.green); }
|
|
35
|
+
function logFail(msg) { log(`${c.fail} ${msg}`, c.red); }
|
|
36
|
+
function logWarn(msg) { log(`${c.warn} ${msg}`, c.yellow); }
|
|
37
|
+
function logInfo(msg) { log(`${c.info} ${msg}`, c.cyan); }
|
|
23
38
|
function logBright(msg) { log(msg, c.bold); }
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
41
|
+
|
|
42
|
+
async function promptProxy(devConfig) {
|
|
43
|
+
const currentProxy = devConfig.proxy || '';
|
|
27
44
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
const answer = await new Promise((resolve) => {
|
|
46
|
+
const hint = currentProxy ? ` (current: ${c.cyan}${currentProxy}${c.reset})` : '';
|
|
47
|
+
log(` ${c.yellow}Configure proxy to download PaperMC server JAR`);
|
|
48
|
+
rl.question(` ${c.yellow}Proxy address${c.reset}${hint} [blank to skip]: `, (val) => {
|
|
49
|
+
rl.close();
|
|
50
|
+
resolve(val);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
const val = answer.trim();
|
|
54
|
+
if (val) {
|
|
55
|
+
devConfig.proxy = val;
|
|
56
|
+
delete require.cache[YEOW_CONFIG_PATH];
|
|
57
|
+
const cfg = require(YEOW_CONFIG_PATH);
|
|
58
|
+
cfg.dev = cfg.dev || {};
|
|
59
|
+
cfg.dev.proxy = val;
|
|
60
|
+
fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n');
|
|
61
|
+
logInfo(`Proxy set to: ${val}`);
|
|
62
|
+
} else if (!currentProxy) {
|
|
63
|
+
log('No proxy, connecting directly');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function createAgent(proxyUrl) {
|
|
68
|
+
if (!proxyUrl) return null;
|
|
34
69
|
try {
|
|
35
|
-
await
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
const { HttpsProxyAgent } = await import('https-proxy-agent');
|
|
71
|
+
return new HttpsProxyAgent(proxyUrl);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
logWarn(`Invalid proxy URL "${proxyUrl}", connecting directly`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function downloadFile(url, dest, agent) {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const file = fs.createWriteStream(dest);
|
|
81
|
+
const options = {};
|
|
82
|
+
if (agent) {
|
|
83
|
+
options.agent = agent;
|
|
84
|
+
log(` Using proxy: ${c.cyan}${agent.proxy.href}${c.reset}`);
|
|
85
|
+
}
|
|
86
|
+
const request = https.get(url, options, (response) => {
|
|
87
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
88
|
+
file.close();
|
|
89
|
+
fs.unlinkSync(dest);
|
|
90
|
+
return downloadFile(response.headers.location, dest, agent).then(resolve).catch(reject);
|
|
91
|
+
}
|
|
92
|
+
if (response.statusCode !== 200) {
|
|
93
|
+
file.close();
|
|
94
|
+
fs.unlinkSync(dest);
|
|
95
|
+
return reject(new Error(`HTTP ${response.statusCode}`));
|
|
96
|
+
}
|
|
97
|
+
const total = parseInt(response.headers['content-length'], 10);
|
|
98
|
+
let downloaded = 0;
|
|
99
|
+
response.on('data', (chunk) => {
|
|
100
|
+
downloaded += chunk.length;
|
|
101
|
+
const pct = total ? (downloaded / total * 100).toFixed(1) : '?';
|
|
102
|
+
readline.cursorTo(process.stdout, 0);
|
|
103
|
+
process.stdout.write(` ${c.blue}Downloading...${c.reset} ${c.bold}${(downloaded / 1024 / 1024).toFixed(1)}MB${c.reset} / ${total ? (total / 1024 / 1024).toFixed(1) + 'MB' : '?'} (${c.yellow}${pct}%${c.reset})`);
|
|
43
104
|
});
|
|
44
|
-
|
|
105
|
+
response.pipe(file);
|
|
106
|
+
file.on('finish', () => { file.close(); process.stdout.write('\n'); resolve(); });
|
|
45
107
|
});
|
|
108
|
+
request.on('error', (err) => { file.close(); if (fs.existsSync(dest)) fs.unlinkSync(dest); reject(err); });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function ensurePaperJar(devConfig) {
|
|
113
|
+
if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) {
|
|
114
|
+
logOk('Paper jar found in cache');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await promptProxy(devConfig);
|
|
118
|
+
const agent = await createAgent(devConfig.proxy);
|
|
119
|
+
logInfo(agent ? `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD} via proxy...` : `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD}...`);
|
|
120
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
121
|
+
try {
|
|
122
|
+
await downloadFile(PAPER_URL, PAPER_CACHE_PATH, agent);
|
|
46
123
|
logOk('Paper jar downloaded');
|
|
47
|
-
} catch(err) {
|
|
48
|
-
logFail(
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logFail(`Download failed: ${err.message}`);
|
|
126
|
+
log(' You can manually download the jar and place it at:');
|
|
127
|
+
log(` ${c.cyan}${PAPER_CACHE_PATH}${c.reset}`);
|
|
49
128
|
process.exit(1);
|
|
50
129
|
}
|
|
51
130
|
}
|
|
52
131
|
|
|
53
132
|
async function ensureRuntimeJar(devConfig) {
|
|
54
|
-
|
|
55
|
-
if (fs.existsSync(
|
|
56
|
-
logFail(
|
|
133
|
+
const cfgPath = path.resolve(devConfig.runtimeJar);
|
|
134
|
+
if (fs.existsSync(cfgPath) && fs.statSync(cfgPath).size > 1000) return cfgPath;
|
|
135
|
+
logFail(`Yeow-Runtime.jar not found at ${cfgPath}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function runInitProcess(devConfig) {
|
|
140
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
141
|
+
logInfo('Initializing server for the first time...');
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
const initProc = spawn(devConfig.javaPath, [
|
|
144
|
+
`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`,
|
|
145
|
+
'-jar', paperJar, '--nogui'
|
|
146
|
+
], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
|
|
147
|
+
const timer = setTimeout(() => { if (!initProc.killed) initProc.kill('SIGTERM'); resolve(); }, 15000);
|
|
148
|
+
initProc.on('exit', () => { clearTimeout(timer); resolve(); });
|
|
149
|
+
initProc.on('error', () => { clearTimeout(timer); resolve(); });
|
|
150
|
+
});
|
|
57
151
|
}
|
|
58
152
|
|
|
59
|
-
function initServer(devConfig) {
|
|
60
|
-
|
|
153
|
+
async function initServer(devConfig) {
|
|
154
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
61
155
|
if (!fs.existsSync(paperJar)) fs.copyFileSync(PAPER_CACHE_PATH, paperJar);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
156
|
+
|
|
157
|
+
const eulaPath = path.join(SERVER_DIR, 'eula.txt');
|
|
158
|
+
if (fs.existsSync(eulaPath)) {
|
|
159
|
+
const eulaContent = fs.readFileSync(eulaPath, 'utf-8');
|
|
160
|
+
if (eulaContent.includes('eula=true')) return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(path.join(SERVER_DIR, 'server.properties'))) {
|
|
164
|
+
await runInitProcess(devConfig);
|
|
71
165
|
}
|
|
72
|
-
|
|
73
|
-
rl
|
|
166
|
+
|
|
167
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
168
|
+
await new Promise((resolve) => {
|
|
169
|
+
rl.question(` ${c.yellow}Press Enter to accept Mojang EULA and start the server...${c.reset} `, () => {
|
|
170
|
+
rl.close();
|
|
171
|
+
fs.writeFileSync(eulaPath, '#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).\neula=true\n');
|
|
172
|
+
logOk('EULA accepted');
|
|
173
|
+
resolve();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
74
176
|
}
|
|
75
177
|
|
|
76
178
|
function getServerProperties(port) {
|
|
77
|
-
|
|
78
|
-
|
|
179
|
+
const propsPath = path.join(SERVER_DIR, 'server.properties');
|
|
180
|
+
const merged = {};
|
|
79
181
|
if (fs.existsSync(propsPath)) {
|
|
80
|
-
fs.readFileSync(propsPath, 'utf-8')
|
|
81
|
-
|
|
82
|
-
|
|
182
|
+
const content = fs.readFileSync(propsPath, 'utf-8');
|
|
183
|
+
for (const line of content.split('\n')) {
|
|
184
|
+
const trimmed = line.trim();
|
|
185
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
186
|
+
const eqIdx = trimmed.indexOf('=');
|
|
187
|
+
if (eqIdx > 0) merged[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
83
190
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
191
|
+
Object.assign(merged, {
|
|
192
|
+
'difficulty': 'easy', 'enable-command-block': 'true', 'max-players': '10',
|
|
193
|
+
'motd': 'A Yeow Dev Server', 'online-mode': 'false', 'server-port': String(port), 'spawn-protection': '0'
|
|
194
|
+
});
|
|
195
|
+
let output = '#Minecraft server properties\n#Generated by Yeow Dev Server\n';
|
|
196
|
+
for (const [key, val] of Object.entries(merged)) output += `${key}=${val}\n`;
|
|
197
|
+
fs.writeFileSync(propsPath, output);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function copyToPlugins(sourcePath, label) {
|
|
201
|
+
const pluginsDir = path.join(SERVER_DIR, 'plugins');
|
|
202
|
+
fs.mkdirSync(pluginsDir, { recursive: true });
|
|
203
|
+
const destPath = path.join(pluginsDir, path.basename(sourcePath));
|
|
204
|
+
if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
|
|
205
|
+
fs.copyFileSync(sourcePath, destPath);
|
|
206
|
+
logOk(`${label} copied to plugins`);
|
|
87
207
|
}
|
|
88
208
|
|
|
89
209
|
function buildDevPlugin() {
|
|
90
210
|
logInfo('Building plugin...');
|
|
91
|
-
try {
|
|
92
|
-
|
|
211
|
+
try {
|
|
212
|
+
execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') });
|
|
213
|
+
logOk('Plugin built');
|
|
214
|
+
} catch (err) {
|
|
215
|
+
logFail('Build failed: ' + err.message);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
93
218
|
}
|
|
94
219
|
|
|
95
|
-
function
|
|
96
|
-
|
|
97
|
-
|
|
220
|
+
function handleCtrlC() {
|
|
221
|
+
ctrlCPressCount++;
|
|
222
|
+
if (ctrlCPressCount >= 2) {
|
|
223
|
+
logWarn('Force shutdown...');
|
|
224
|
+
if (srcWatcher) srcWatcher.close();
|
|
225
|
+
if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
logWarn('Sending stop command... (press Ctrl+C again to force kill)');
|
|
229
|
+
if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
|
|
230
|
+
if (ctrlCTimer) clearTimeout(ctrlCTimer);
|
|
231
|
+
ctrlCTimer = setTimeout(() => { ctrlCPressCount = 0; ctrlCTimer = null; }, DOUBLE_CTRL_C_WINDOW);
|
|
98
232
|
}
|
|
99
233
|
|
|
100
234
|
function startHotReloadWatcher() {
|
|
101
235
|
const srcDir = path.resolve(__dirname, '../src');
|
|
102
236
|
if (!fs.existsSync(srcDir)) return;
|
|
237
|
+
|
|
103
238
|
let debounceTimer = null;
|
|
104
|
-
srcWatcher = fs.watch(srcDir, { recursive: true },
|
|
105
|
-
if (!filename ||
|
|
239
|
+
srcWatcher = fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
|
|
240
|
+
if (!filename || !filename.endsWith('.js')) return;
|
|
241
|
+
if (isCompiling) return;
|
|
106
242
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
107
|
-
debounceTimer = setTimeout(
|
|
243
|
+
debounceTimer = setTimeout(async () => {
|
|
108
244
|
isCompiling = true;
|
|
109
|
-
logInfo('Source changed, rebuilding...');
|
|
110
|
-
try {
|
|
111
|
-
|
|
245
|
+
logInfo('Source file changed, rebuilding...');
|
|
246
|
+
try {
|
|
247
|
+
execSync('node .yeow/build.js', { stdio: 'pipe', cwd: path.resolve(__dirname, '..') });
|
|
248
|
+
logOk('Hot reload file updated');
|
|
249
|
+
} catch (e) {
|
|
250
|
+
logFail('Build failed: ' + e.message);
|
|
251
|
+
}
|
|
112
252
|
isCompiling = false;
|
|
113
253
|
}, 1000);
|
|
114
254
|
});
|
|
@@ -116,38 +256,74 @@ function startHotReloadWatcher() {
|
|
|
116
256
|
}
|
|
117
257
|
|
|
118
258
|
function startServer(devConfig) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
259
|
+
const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
|
|
260
|
+
const jvmArgs = [`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`, `-Dyeow.dev=true`];
|
|
261
|
+
|
|
262
|
+
logBright('\nStarting server (type commands to send to console)...\n');
|
|
263
|
+
|
|
264
|
+
serverProcess = spawn(devConfig.javaPath, [...jvmArgs, '-jar', paperJar, '--nogui'], {
|
|
265
|
+
cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit']
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
serverProcess.on('exit', (code) => {
|
|
269
|
+
logWarn(`Server exited with code ${code}`);
|
|
270
|
+
if (srcWatcher) srcWatcher.close();
|
|
271
|
+
process.exit(0);
|
|
272
|
+
});
|
|
273
|
+
serverProcess.on('error', (err) => { logFail('Failed to start server: ' + err.message); process.exit(1); });
|
|
274
|
+
|
|
275
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
276
|
+
rl.on('line', (line) => {
|
|
277
|
+
if (serverProcess && !serverProcess.killed && line.trim()) {
|
|
278
|
+
serverProcess.stdin.write(line.trim() + '\n');
|
|
279
|
+
}
|
|
280
|
+
});
|
|
126
281
|
}
|
|
127
282
|
|
|
128
|
-
process.on('SIGINT',
|
|
129
|
-
ctrlCPressCount++;
|
|
130
|
-
if (ctrlCPressCount >= 2) { if (serverProcess && !serverProcess.killed) serverProcess.kill(); process.exit(0); }
|
|
131
|
-
if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
|
|
132
|
-
if (ctrlCTimer) clearTimeout(ctrlCTimer);
|
|
133
|
-
ctrlCTimer = setTimeout(function() { ctrlCPressCount = 0; }, DOUBLE_CTRL_C_WINDOW);
|
|
134
|
-
});
|
|
283
|
+
process.on('SIGINT', () => handleCtrlC());
|
|
135
284
|
|
|
136
285
|
async function main() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
286
|
+
if (!fs.existsSync(YEOW_CONFIG_PATH)) { logFail('yeow.config.js not found'); process.exit(1); }
|
|
287
|
+
|
|
288
|
+
const yeowConfig = require(YEOW_CONFIG_PATH);
|
|
289
|
+
const devConfig = yeowConfig.dev || {};
|
|
290
|
+
devConfig.port = devConfig.port || 17367;
|
|
291
|
+
devConfig.memory = devConfig.memory || '8G';
|
|
292
|
+
devConfig.javaPath = devConfig.javaPath || 'java';
|
|
293
|
+
devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
|
|
294
|
+
|
|
295
|
+
console.log(`\n${c.bold}${c.blue} Yeow Dev Server${c.reset}`);
|
|
296
|
+
console.log(` ${c.dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
297
|
+
console.log(` Port: ${c.bold}${devConfig.port}${c.reset}`);
|
|
298
|
+
console.log(` Memory: ${c.bold}${devConfig.memory}${c.reset}`);
|
|
299
|
+
if (devConfig.proxy) console.log(` Proxy: ${c.cyan}${devConfig.proxy}${c.reset}`);
|
|
300
|
+
console.log();
|
|
301
|
+
|
|
302
|
+
const runtimeJarPath = await ensureRuntimeJar(devConfig);
|
|
142
303
|
await ensurePaperJar(devConfig);
|
|
304
|
+
|
|
143
305
|
fs.mkdirSync(SERVER_DIR, { recursive: true });
|
|
144
|
-
|
|
306
|
+
|
|
307
|
+
await initServer(devConfig);
|
|
145
308
|
getServerProperties(devConfig.port);
|
|
309
|
+
|
|
310
|
+
const pluginOutputPath = path.resolve(yeowConfig.build.output || `dist/${yeowConfig.plugin.name}-${yeowConfig.plugin.version}.jar`);
|
|
311
|
+
|
|
146
312
|
buildDevPlugin();
|
|
147
|
-
copyToPlugins(
|
|
313
|
+
copyToPlugins(pluginOutputPath, 'Plugin');
|
|
148
314
|
copyToPlugins(runtimeJarPath, 'Yeow-Runtime');
|
|
315
|
+
|
|
316
|
+
console.log(`\n${c.bold}${c.green} Starting Paper ${PAPER_VERSION} server...${c.reset}`);
|
|
317
|
+
console.log(` ${c.info} Local address: ${c.bold}${c.cyan}127.0.0.1:${devConfig.port}${c.reset}`);
|
|
318
|
+
console.log(` ${c.dim} Server directory: ${path.relative(process.cwd(), SERVER_DIR)}${c.reset}`);
|
|
319
|
+
|
|
149
320
|
startHotReloadWatcher();
|
|
150
321
|
startServer(devConfig);
|
|
151
322
|
}
|
|
152
323
|
|
|
153
|
-
|
|
324
|
+
process.on('exit', () => {
|
|
325
|
+
if (srcWatcher) srcWatcher.close();
|
|
326
|
+
if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
main().catch((err) => { logFail('Error: ' + err.message); process.exit(1); });
|