@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeow/create-yeow",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Scaffold a Yeow plugin project",
5
5
  "bin": {
6
6
  "create-yeow": "./bin/index.js"
@@ -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
- function loadConfig() { if (!fs.existsSync(configPath)) { console.error('yeow.config.js not found'); process.exit(1); } return require(configPath); }
7
- function compileUserCode() { console.log('Compiling user code with Rspack...'); try { execSync('npx rspack build', { stdio: 'inherit' }); console.log('User code compiled'); } catch (error) { console.error('Compilation failed:', error.message); process.exit(1); } }
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(); const { plugin, build } = config;
10
- console.log('\nBuilding Yeow plugin...'); console.log(' ' + plugin.name + ' v' + plugin.version);
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
- const templateJarPath = path.resolve(build.templateJar);
13
- if (!fs.existsSync(templateJarPath)) { console.error('Template JAR not found:', templateJarPath); process.exit(1); }
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
- templateZip.updateFile('plugin.yml', Buffer.from('name: ' + plugin.name + '\nversion: ' + plugin.version + '\nmain: yeow.template.YeowPluginBootstrap\napi-version: \"1.21\"\ndepend:\n - Yeow-Runtime\nauthor: ' + (plugin.author || 'Unknown') + '\ndescription: ' + (plugin.description || 'A Yeow plugin') + '\n'));
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)) { templateZip.addFile('.yeow/main.js', fs.readFileSync(userCodePath)); } else { console.error('Compiled user code not found'); process.exit(1); }
18
- const outputPath = build.output || 'dist/plugin.jar';
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
- if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
71
+
72
+ if (!fs.existsSync(outputDir)) {
73
+ fs.mkdirSync(outputDir, { recursive: true });
74
+ }
75
+
21
76
  templateZip.writeZip(outputPath);
22
- console.log('Plugin built successfully!'); console.log(' Output: ' + outputPath); console.log(' Size: ' + (fs.statSync(outputPath).size / 1024).toFixed(2) + ' KB');
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
- buildPlugin();
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 = 'paper-' + PAPER_VERSION + '-' + PAPER_BUILD + '.jar';
12
- const PAPER_URL = 'https://api.papermc.io/v2/projects/paper/versions/' + PAPER_VERSION + '/builds/' + PAPER_BUILD + '/downloads/' + PAPER_FILENAME;
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
- const c = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m', ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m' };
17
- let serverProcess = null, ctrlCPressCount = 0, ctrlCTimer = null, srcWatcher = null, isCompiling = false;
18
- function log(msg, color) { if (!color) color = ''; console.log(c.dim + '[' + new Date().toLocaleTimeString() + ']' + c.reset + ' ' + color + msg + c.reset); }
19
- function logOk(msg) { log(c.ok + ' ' + msg, c.green); }
20
- function logFail(msg) { log(c.fail + ' ' + msg, c.red); }
21
- function logWarn(msg) { log(c.warn + ' ' + msg, c.yellow); }
22
- function logInfo(msg) { log(c.info + ' ' + msg, c.cyan); }
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
- async function ensurePaperJar(devConfig) {
26
- if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) { logOk('Paper jar found in cache'); return; }
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 proxy = await new Promise(function(r) { rl.question(' Proxy address [blank to skip]: ', function(v) { rl.close(); r(v.trim()); }); });
29
- if (proxy) { devConfig.proxy = proxy; var cfg = require(YEOW_CONFIG_PATH); cfg.dev = cfg.dev || {}; cfg.dev.proxy = proxy; fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n'); }
30
- fs.mkdirSync(CACHE_DIR, { recursive: true });
31
- logInfo('Downloading Paper ' + PAPER_VERSION + '...');
32
- var agent = null;
33
- if (proxy) { try { var m = await import('https-proxy-agent'); agent = new m.HttpsProxyAgent(proxy); } catch(e) {} }
34
- var done = false;
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 new Promise(function(resolve, reject) {
37
- var file = fs.createWriteStream(PAPER_CACHE_PATH);
38
- var opts = {}; if (agent) opts.agent = agent;
39
- var req = require('https').get(PAPER_URL, opts, function(res) {
40
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { file.close(); fs.unlinkSync(PAPER_CACHE_PATH); return reject(new Error('Redirect')); }
41
- if (res.statusCode !== 200) { file.close(); fs.unlinkSync(PAPER_CACHE_PATH); return reject(new Error('HTTP ' + res.statusCode)); }
42
- res.pipe(file);
43
- file.on('finish', function() { file.close(); resolve(); });
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
- req.on('error', function(err) { file.close(); if (fs.existsSync(PAPER_CACHE_PATH)) fs.unlinkSync(PAPER_CACHE_PATH); reject(err); });
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('Download failed: ' + err.message);
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
- var p = path.resolve(devConfig.runtimeJar);
56
- if (fs.existsSync(p) && fs.statSync(p).size > 1000) return p;
57
- logFail('Yeow-Runtime.jar not found at ' + p); process.exit(1);
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
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
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
- var eulaPath = path.join(SERVER_DIR, 'eula.txt');
64
- if (fs.existsSync(eulaPath) && fs.readFileSync(eulaPath, 'utf-8').includes('eula=true')) return;
65
- var propsPath = path.join(SERVER_DIR, 'server.properties');
66
- if (!fs.existsSync(propsPath)) {
67
- logInfo('Initializing server...');
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
- var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
74
- rl.question(' Press Enter to accept EULA... ', function() { rl.close(); fs.writeFileSync(eulaPath, 'eula=true\n'); logOk('EULA accepted'); });
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
- var propsPath = path.join(SERVER_DIR, 'server.properties');
79
- var merged = {};
179
+ const propsPath = path.join(SERVER_DIR, 'server.properties');
180
+ const merged = {};
80
181
  if (fs.existsSync(propsPath)) {
81
- fs.readFileSync(propsPath, 'utf-8').split('\n').forEach(function(line) {
82
- line = line.trim(); if (line && !line.startsWith('#')) { var i = line.indexOf('='); if (i > 0) merged[line.substring(0, i).trim()] = line.substring(i + 1).trim(); }
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
- merged.difficulty = 'easy'; merged['enable-command-block'] = 'true'; merged['max-players'] = '10'; merged.motd = 'A Yeow Dev Server'; merged['online-mode'] = 'false'; merged['server-port'] = String(port); merged['spawn-protection'] = '0';
86
- var out = '#Minecraft server properties\n';
87
- for (var k in merged) out += k + '=' + merged[k] + '\n'; fs.writeFileSync(propsPath, out);
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 { execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') }); logOk('Plugin built'); }
93
- catch (e) { logFail('Build failed: ' + e.message); process.exit(1); }
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 copyToPlugins(src, label) {
97
- var dir = path.join(SERVER_DIR, 'plugins'); fs.mkdirSync(dir, { recursive: true });
98
- var dest = path.join(dir, path.basename(src)); if (fs.existsSync(dest)) fs.unlinkSync(dest); fs.copyFileSync(src, dest); logOk(label + ' copied');
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
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
103
- logBright('\nStarting server (type commands to send)...\n');
104
- serverProcess = spawn(devConfig.javaPath, ['-Xmx' + devConfig.memory, '-Xms' + devConfig.memory, '-Dyeow.dev=true', '-jar', paperJar, '--nogui'], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
105
- serverProcess.on('exit', function(code) { logWarn('Server exited with code ' + code); process.exit(0); });
106
- serverProcess.on('error', function(err) { logFail('Failed: ' + err.message); process.exit(1); });
107
- var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
108
- rl.on('line', function(line) { if (serverProcess && !serverProcess.killed && line.trim()) serverProcess.stdin.write(line.trim() + '\n'); });
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', function() {
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
- var yeowConfig = require(YEOW_CONFIG_PATH);
121
- var devConfig = yeowConfig.dev || {};
122
- devConfig.port = devConfig.port || 17367; devConfig.memory = devConfig.memory || '8G'; devConfig.javaPath = devConfig.javaPath || 'java'; devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
123
- console.log('\n' + c.bold + c.blue + ' Yeow Dev Server' + c.reset + '\n ' + c.dim + '----------------------------------------' + c.reset + '\n Port: ' + c.bold + devConfig.port + c.reset + '\n Memory: ' + c.bold + devConfig.memory + c.reset + '\n');
124
- var runtimeJarPath = await ensureRuntimeJar(devConfig);
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
- initServer(devConfig);
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(path.resolve(yeowConfig.build.output || 'dist/plugin.jar'), 'Plugin');
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
- main().catch(function(err) { logFail(err.message); process.exit(1); });
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
- function loadConfig() { if (!fs.existsSync(configPath)) { console.error('yeow.config.js not found'); process.exit(1); } return require(configPath); }
7
- function compileUserCode() { console.log('Compiling user code with Rspack...'); try { execSync('npx rspack build', { stdio: 'inherit' }); console.log('User code compiled'); } catch (error) { console.error('Compilation failed:', error.message); process.exit(1); } }
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(); const { plugin, build } = config;
10
- console.log('\nBuilding Yeow plugin...'); console.log(' ' + plugin.name + ' v' + plugin.version);
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
- const templateJarPath = path.resolve(build.templateJar);
13
- if (!fs.existsSync(templateJarPath)) { console.error('Template JAR not found:', templateJarPath); process.exit(1); }
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
- templateZip.updateFile('plugin.yml', Buffer.from('name: ' + plugin.name + '\nversion: ' + plugin.version + '\nmain: yeow.template.YeowPluginBootstrap\napi-version: "1.21"\ndepend:\n - Yeow-Runtime\nauthor: ' + (plugin.author || 'Unknown') + '\ndescription: ' + (plugin.description || 'A Yeow plugin') + '\n'));
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)) { templateZip.addFile('.yeow/main.js', fs.readFileSync(userCodePath)); } else { console.error('Compiled user code not found'); process.exit(1); }
18
- const outputPath = build.output || 'dist/plugin.jar';
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
- if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
71
+
72
+ if (!fs.existsSync(outputDir)) {
73
+ fs.mkdirSync(outputDir, { recursive: true });
74
+ }
75
+
21
76
  templateZip.writeZip(outputPath);
22
- console.log('Plugin built successfully!'); console.log(' Output: ' + outputPath); console.log(' Size: ' + (fs.statSync(outputPath).size / 1024).toFixed(2) + ' KB');
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 = 'paper-' + PAPER_VERSION + '-' + PAPER_BUILD + '.jar';
12
- const PAPER_URL = 'https://api.papermc.io/v2/projects/paper/versions/' + PAPER_VERSION + '/builds/' + PAPER_BUILD + '/downloads/' + PAPER_FILENAME;
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
- const c = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m', ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m' };
17
- let serverProcess = null, ctrlCPressCount = 0, ctrlCTimer = null, srcWatcher = null, isCompiling = false;
18
- function log(msg, color) { if (!color) color = ''; console.log(c.dim + '[' + new Date().toLocaleTimeString() + ']' + c.reset + ' ' + color + msg + c.reset); }
19
- function logOk(msg) { log(c.ok + ' ' + msg, c.green); }
20
- function logFail(msg) { log(c.fail + ' ' + msg, c.red); }
21
- function logWarn(msg) { log(c.warn + ' ' + msg, c.yellow); }
22
- function logInfo(msg) { log(c.info + ' ' + msg, c.cyan); }
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
- async function ensurePaperJar(devConfig) {
26
- if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) { logOk('Paper jar found in cache'); return; }
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 proxy = await new Promise(function(r) { rl.question(' Proxy address [blank to skip]: ', function(v) { rl.close(); r(v.trim()); }); });
29
- if (proxy) { devConfig.proxy = proxy; var cfg = require(YEOW_CONFIG_PATH); cfg.dev = cfg.dev || {}; cfg.dev.proxy = proxy; fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n'); }
30
- fs.mkdirSync(CACHE_DIR, { recursive: true });
31
- logInfo('Downloading Paper ' + PAPER_VERSION + '...');
32
- var agent = null;
33
- if (proxy) { try { var m = await import('https-proxy-agent'); agent = new m.HttpsProxyAgent(proxy); } catch(e) {} }
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 new Promise(function(resolve, reject) {
36
- var file = fs.createWriteStream(PAPER_CACHE_PATH);
37
- var opts = {}; if (agent) opts.agent = agent;
38
- var req = require('https').get(PAPER_URL, opts, function(res) {
39
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { file.close(); fs.unlinkSync(PAPER_CACHE_PATH); return reject(new Error('Redirect')); }
40
- if (res.statusCode !== 200) { file.close(); fs.unlinkSync(PAPER_CACHE_PATH); return reject(new Error('HTTP ' + res.statusCode)); }
41
- res.pipe(file);
42
- file.on('finish', function() { file.close(); resolve(); });
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
- req.on('error', function(err) { file.close(); if (fs.existsSync(PAPER_CACHE_PATH)) fs.unlinkSync(PAPER_CACHE_PATH); reject(err); });
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('Download failed: ' + err.message);
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
- var p = path.resolve(devConfig.runtimeJar);
55
- if (fs.existsSync(p) && fs.statSync(p).size > 1000) return p;
56
- logFail('Yeow-Runtime.jar not found at ' + p); process.exit(1);
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
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
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
- var eulaPath = path.join(SERVER_DIR, 'eula.txt');
63
- if (fs.existsSync(eulaPath) && fs.readFileSync(eulaPath, 'utf-8').includes('eula=true')) return;
64
- var propsPath = path.join(SERVER_DIR, 'server.properties');
65
- if (!fs.existsSync(propsPath)) {
66
- logInfo('Initializing server...');
67
- var p = spawn(devConfig.javaPath, ['-Xmx' + devConfig.memory, '-Xms' + devConfig.memory, '-jar', paperJar, '--nogui'], { cwd: SERVER_DIR, stdio: 'pipe' });
68
- var t = setTimeout(function() { if (!p.killed) p.kill(); }, 15000);
69
- p.on('exit', function() { clearTimeout(t); });
70
- 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;
161
+ }
162
+
163
+ if (!fs.existsSync(path.join(SERVER_DIR, 'server.properties'))) {
164
+ await runInitProcess(devConfig);
71
165
  }
72
- var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
73
- rl.question(' Press Enter to accept EULA... ', function() { rl.close(); fs.writeFileSync(eulaPath, 'eula=true\n'); logOk('EULA accepted'); });
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
- var propsPath = path.join(SERVER_DIR, 'server.properties');
78
- var merged = {};
179
+ const propsPath = path.join(SERVER_DIR, 'server.properties');
180
+ const merged = {};
79
181
  if (fs.existsSync(propsPath)) {
80
- fs.readFileSync(propsPath, 'utf-8').split('\n').forEach(function(line) {
81
- line = line.trim(); if (line && !line.startsWith('#')) { var i = line.indexOf('='); if (i > 0) merged[line.substring(0, i).trim()] = line.substring(i + 1).trim(); }
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
- merged.difficulty = 'easy'; merged['enable-command-block'] = 'true'; merged['max-players'] = '10'; merged.motd = 'A Yeow Dev Server'; merged['online-mode'] = 'false'; merged['server-port'] = String(port); merged['spawn-protection'] = '0';
85
- var out = '#Minecraft server properties\n';
86
- for (var k in merged) out += k + '=' + merged[k] + '\n'; fs.writeFileSync(propsPath, out);
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 { execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') }); logOk('Plugin built'); }
92
- catch (e) { logFail('Build failed: ' + e.message); process.exit(1); }
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 copyToPlugins(src, label) {
96
- var dir = path.join(SERVER_DIR, 'plugins'); fs.mkdirSync(dir, { recursive: true });
97
- var dest = path.join(dir, path.basename(src)); if (fs.existsSync(dest)) fs.unlinkSync(dest); fs.copyFileSync(src, dest); logOk(label + ' copied');
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 }, function(eventType, filename) {
105
- if (!filename || !/\.ts$/.test(filename) || isCompiling) return;
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(function() {
243
+ debounceTimer = setTimeout(async () => {
108
244
  isCompiling = true;
109
- logInfo('Source changed, rebuilding...');
110
- try { execSync('node .yeow/build.js', { stdio: 'pipe', cwd: path.resolve(__dirname, '..') }); logOk('Hot reload file updated'); }
111
- catch (e) { logFail('Build failed: ' + e.message); }
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
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
120
- logBright('\nStarting server (type commands to send)...\n');
121
- serverProcess = spawn(devConfig.javaPath, ['-Xmx' + devConfig.memory, '-Xms' + devConfig.memory, '-Dyeow.dev=true', '-jar', paperJar, '--nogui'], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
122
- serverProcess.on('exit', function(code) { logWarn('Server exited with code ' + code); process.exit(0); });
123
- serverProcess.on('error', function(err) { logFail('Failed: ' + err.message); process.exit(1); });
124
- var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
125
- rl.on('line', function(line) { if (serverProcess && !serverProcess.killed && line.trim()) serverProcess.stdin.write(line.trim() + '\n'); });
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', function() {
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
- var yeowConfig = require(YEOW_CONFIG_PATH);
138
- var devConfig = yeowConfig.dev || {};
139
- devConfig.port = devConfig.port || 17367; devConfig.memory = devConfig.memory || '8G'; devConfig.javaPath = devConfig.javaPath || 'java'; devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
140
- console.log('\n' + c.bold + c.blue + ' Yeow Dev Server' + c.reset + '\n ' + c.dim + '----------------------------------------' + c.reset + '\n Port: ' + c.bold + devConfig.port + c.reset + '\n Memory: ' + c.bold + devConfig.memory + c.reset + '\n');
141
- var runtimeJarPath = await ensureRuntimeJar(devConfig);
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
- initServer(devConfig);
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(path.resolve(yeowConfig.build.output || 'dist/plugin.jar'), 'Plugin');
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
- main().catch(function(err) { logFail(err.message); process.exit(1); });
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); });