@yeow/create-yeow 0.1.0 → 0.1.2

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/bin/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { createInterface } from 'readline';
3
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, copyFileSync } from 'fs';
4
5
  import { resolve, dirname } from 'path';
5
6
  import { fileURLToPath } from 'url';
6
- import { createInterface } from 'readline';
7
- import { execSync } from 'child_process';
8
7
 
9
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
9
 
@@ -85,16 +84,12 @@ async function main() {
85
84
  }
86
85
 
87
86
  // npm install
88
- logInfo('Installing dependencies...');
89
- try {
90
- execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
91
- logOk('Dependencies installed');
92
- } catch {
93
- logInfo('npm install failed, run manually: cd ' + name + ' && npm install');
94
- }
87
+ logInfo('Run ' + cyan('npm install') + ' to install dependencies');
88
+ logInfo('Run ' + cyan('npm run dev') + ' to start development server');
95
89
 
96
90
  console.log(`\n ${green('\u2713')} ${bold('Done!')}\n`);
97
91
  console.log(` ${dim('$')} ${cyan('cd ' + name)}`);
92
+ console.log(` ${dim('$')} ${cyan('npm install')}`);
98
93
  console.log(` ${dim('$')} ${cyan('npm run dev')} ${dim('Start development server')}`);
99
94
  console.log(` ${dim('$')} ${cyan('npm run build')} ${dim('Build plugin JAR')}\n`);
100
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeow/create-yeow",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Scaffold a Yeow plugin project",
5
5
  "bin": {
6
6
  "create-yeow": "./bin/index.js"
@@ -13,4 +13,4 @@
13
13
  "node": ">=18"
14
14
  },
15
15
  "license": "MIT"
16
- }
16
+ }
@@ -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,320 @@ 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
+ rl.question(` ${c.yellow}Proxy address${c.reset}${hint} [blank to skip]: `, (val) => {
48
+ rl.close();
49
+ resolve(val);
50
+ });
51
+ });
52
+ const val = answer.trim();
53
+ if (val) {
54
+ devConfig.proxy = val;
55
+ delete require.cache[YEOW_CONFIG_PATH];
56
+ const cfg = require(YEOW_CONFIG_PATH);
57
+ cfg.dev = cfg.dev || {};
58
+ cfg.dev.proxy = val;
59
+ fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n');
60
+ logInfo(`Proxy set to: ${val}`);
61
+ } else if (!currentProxy) {
62
+ log('No proxy, connecting directly');
63
+ }
64
+ }
65
+
66
+ async function createAgent(proxyUrl) {
67
+ if (!proxyUrl) return null;
35
68
  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(); });
69
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
70
+ return new HttpsProxyAgent(proxyUrl);
71
+ } catch (e) {
72
+ logWarn(`Invalid proxy URL "${proxyUrl}", connecting directly`);
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function downloadFile(url, dest, agent) {
78
+ return new Promise((resolve, reject) => {
79
+ const file = fs.createWriteStream(dest);
80
+ const options = {};
81
+ if (agent) {
82
+ options.agent = agent;
83
+ log(` Using proxy: ${c.cyan}${agent.proxy.href}${c.reset}`);
84
+ }
85
+ const request = https.get(url, options, (response) => {
86
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
87
+ file.close();
88
+ fs.unlinkSync(dest);
89
+ return downloadFile(response.headers.location, dest, agent).then(resolve).catch(reject);
90
+ }
91
+ if (response.statusCode !== 200) {
92
+ file.close();
93
+ fs.unlinkSync(dest);
94
+ return reject(new Error(`HTTP ${response.statusCode}`));
95
+ }
96
+ const total = parseInt(response.headers['content-length'], 10);
97
+ let downloaded = 0;
98
+ response.on('data', (chunk) => {
99
+ downloaded += chunk.length;
100
+ const pct = total ? (downloaded / total * 100).toFixed(1) : '?';
101
+ readline.cursorTo(process.stdout, 0);
102
+ 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
103
  });
45
- req.on('error', function(err) { file.close(); if (fs.existsSync(PAPER_CACHE_PATH)) fs.unlinkSync(PAPER_CACHE_PATH); reject(err); });
104
+ response.pipe(file);
105
+ file.on('finish', () => { file.close(); process.stdout.write('\n'); resolve(); });
46
106
  });
107
+ request.on('error', (err) => { file.close(); if (fs.existsSync(dest)) fs.unlinkSync(dest); reject(err); });
108
+ });
109
+ }
110
+
111
+ async function ensurePaperJar(devConfig) {
112
+ if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) {
113
+ logOk('Paper jar found in cache');
114
+ return;
115
+ }
116
+ await promptProxy(devConfig);
117
+ const agent = await createAgent(devConfig.proxy);
118
+ logInfo(agent ? `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD} via proxy...` : `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD}...`);
119
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
120
+ try {
121
+ await downloadFile(PAPER_URL, PAPER_CACHE_PATH, agent);
47
122
  logOk('Paper jar downloaded');
48
- } catch(err) {
49
- logFail('Download failed: ' + err.message);
123
+ } catch (err) {
124
+ logFail(`Download failed: ${err.message}`);
125
+ log(' You can manually download the jar and place it at:');
126
+ log(` ${c.cyan}${PAPER_CACHE_PATH}${c.reset}`);
50
127
  process.exit(1);
51
128
  }
52
129
  }
53
130
 
54
131
  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);
132
+ const cfgPath = path.resolve(devConfig.runtimeJar);
133
+ if (fs.existsSync(cfgPath) && fs.statSync(cfgPath).size > 1000) return cfgPath;
134
+ logFail(`Yeow-Runtime.jar not found at ${cfgPath}`);
135
+ process.exit(1);
136
+ }
137
+
138
+ async function runInitProcess(devConfig) {
139
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
140
+ logInfo('Initializing server for the first time...');
141
+ return new Promise((resolve) => {
142
+ const initProc = spawn(devConfig.javaPath, [
143
+ `-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`,
144
+ '-jar', paperJar, '--nogui'
145
+ ], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
146
+ const timer = setTimeout(() => { if (!initProc.killed) initProc.kill('SIGTERM'); resolve(); }, 15000);
147
+ initProc.on('exit', () => { clearTimeout(timer); resolve(); });
148
+ initProc.on('error', () => { clearTimeout(timer); resolve(); });
149
+ });
58
150
  }
59
151
 
60
- function initServer(devConfig) {
61
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
152
+ async function initServer(devConfig) {
153
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
62
154
  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); });
155
+
156
+ const eulaPath = path.join(SERVER_DIR, 'eula.txt');
157
+ if (fs.existsSync(eulaPath)) {
158
+ const eulaContent = fs.readFileSync(eulaPath, 'utf-8');
159
+ if (eulaContent.includes('eula=true')) return;
72
160
  }
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'); });
161
+
162
+ if (!fs.existsSync(path.join(SERVER_DIR, 'server.properties'))) {
163
+ await runInitProcess(devConfig);
164
+ }
165
+
166
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
167
+ await new Promise((resolve) => {
168
+ rl.question(` ${c.yellow}Press Enter to accept Mojang EULA and start the server...${c.reset} `, () => {
169
+ rl.close();
170
+ 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');
171
+ logOk('EULA accepted');
172
+ resolve();
173
+ });
174
+ });
75
175
  }
76
176
 
77
177
  function getServerProperties(port) {
78
- var propsPath = path.join(SERVER_DIR, 'server.properties');
79
- var merged = {};
178
+ const propsPath = path.join(SERVER_DIR, 'server.properties');
179
+ const merged = {};
80
180
  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
- });
181
+ const content = fs.readFileSync(propsPath, 'utf-8');
182
+ for (const line of content.split('\n')) {
183
+ const trimmed = line.trim();
184
+ if (trimmed && !trimmed.startsWith('#')) {
185
+ const eqIdx = trimmed.indexOf('=');
186
+ if (eqIdx > 0) merged[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
187
+ }
188
+ }
84
189
  }
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);
190
+ Object.assign(merged, {
191
+ 'difficulty': 'easy', 'enable-command-block': 'true', 'max-players': '10',
192
+ 'motd': 'A Yeow Dev Server', 'online-mode': 'false', 'server-port': String(port), 'spawn-protection': '0'
193
+ });
194
+ let output = '#Minecraft server properties\n#Generated by Yeow Dev Server\n';
195
+ for (const [key, val] of Object.entries(merged)) output += `${key}=${val}\n`;
196
+ fs.writeFileSync(propsPath, output);
197
+ }
198
+
199
+ function copyToPlugins(sourcePath, label) {
200
+ const pluginsDir = path.join(SERVER_DIR, 'plugins');
201
+ fs.mkdirSync(pluginsDir, { recursive: true });
202
+ const destPath = path.join(pluginsDir, path.basename(sourcePath));
203
+ if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
204
+ fs.copyFileSync(sourcePath, destPath);
205
+ logOk(`${label} copied to plugins`);
88
206
  }
89
207
 
90
208
  function buildDevPlugin() {
91
209
  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); }
210
+ try {
211
+ execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') });
212
+ logOk('Plugin built');
213
+ } catch (err) {
214
+ logFail('Build failed: ' + err.message);
215
+ process.exit(1);
216
+ }
217
+ }
218
+
219
+ function handleCtrlC() {
220
+ ctrlCPressCount++;
221
+ if (ctrlCPressCount >= 2) {
222
+ logWarn('Force shutdown...');
223
+ if (srcWatcher) srcWatcher.close();
224
+ if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
225
+ process.exit(0);
226
+ }
227
+ logWarn('Sending stop command... (press Ctrl+C again to force kill)');
228
+ if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
229
+ if (ctrlCTimer) clearTimeout(ctrlCTimer);
230
+ ctrlCTimer = setTimeout(() => { ctrlCPressCount = 0; ctrlCTimer = null; }, DOUBLE_CTRL_C_WINDOW);
94
231
  }
95
232
 
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');
233
+ function startHotReloadWatcher() {
234
+ const srcDir = path.resolve(__dirname, '../src');
235
+ if (!fs.existsSync(srcDir)) return;
236
+
237
+ let debounceTimer = null;
238
+ srcWatcher = fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
239
+ if (!filename || !filename.endsWith('.js')) return;
240
+ if (isCompiling) return;
241
+ if (debounceTimer) clearTimeout(debounceTimer);
242
+ debounceTimer = setTimeout(async () => {
243
+ isCompiling = true;
244
+ logInfo('Source file changed, rebuilding...');
245
+ try {
246
+ execSync('node .yeow/build.js', { stdio: 'pipe', cwd: path.resolve(__dirname, '..') });
247
+ logOk('Hot reload file updated');
248
+ } catch (e) {
249
+ logFail('Build failed: ' + e.message);
250
+ }
251
+ isCompiling = false;
252
+ }, 1000);
253
+ });
254
+ logInfo('Watching src/ for changes (hot reload)...');
99
255
  }
100
256
 
101
257
  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'); });
258
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
259
+ const jvmArgs = [`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`, `-Dyeow.dev=true`];
260
+
261
+ logBright('\nStarting server (type commands to send to console)...\n');
262
+
263
+ serverProcess = spawn(devConfig.javaPath, [...jvmArgs, '-jar', paperJar, '--nogui'], {
264
+ cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit']
265
+ });
266
+
267
+ serverProcess.on('exit', (code) => {
268
+ logWarn(`Server exited with code ${code}`);
269
+ if (srcWatcher) srcWatcher.close();
270
+ process.exit(0);
271
+ });
272
+ serverProcess.on('error', (err) => { logFail('Failed to start server: ' + err.message); process.exit(1); });
273
+
274
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
275
+ rl.on('line', (line) => {
276
+ if (serverProcess && !serverProcess.killed && line.trim()) {
277
+ serverProcess.stdin.write(line.trim() + '\n');
278
+ }
279
+ });
109
280
  }
110
281
 
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
- });
282
+ process.on('SIGINT', () => handleCtrlC());
118
283
 
119
284
  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);
285
+ if (!fs.existsSync(YEOW_CONFIG_PATH)) { logFail('yeow.config.js not found'); process.exit(1); }
286
+
287
+ const yeowConfig = require(YEOW_CONFIG_PATH);
288
+ const devConfig = yeowConfig.dev || {};
289
+ devConfig.port = devConfig.port || 17367;
290
+ devConfig.memory = devConfig.memory || '8G';
291
+ devConfig.javaPath = devConfig.javaPath || 'java';
292
+ devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
293
+
294
+ console.log(`\n${c.bold}${c.blue} Yeow Dev Server${c.reset}`);
295
+ 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}`);
296
+ console.log(` Port: ${c.bold}${devConfig.port}${c.reset}`);
297
+ console.log(` Memory: ${c.bold}${devConfig.memory}${c.reset}`);
298
+ if (devConfig.proxy) console.log(` Proxy: ${c.cyan}${devConfig.proxy}${c.reset}`);
299
+ console.log();
300
+
301
+ const runtimeJarPath = await ensureRuntimeJar(devConfig);
125
302
  await ensurePaperJar(devConfig);
303
+
126
304
  fs.mkdirSync(SERVER_DIR, { recursive: true });
127
- initServer(devConfig);
305
+
306
+ await initServer(devConfig);
128
307
  getServerProperties(devConfig.port);
308
+
309
+ const pluginOutputPath = path.resolve(yeowConfig.build.output || `dist/${yeowConfig.plugin.name}-${yeowConfig.plugin.version}.jar`);
310
+
129
311
  buildDevPlugin();
130
- copyToPlugins(path.resolve(yeowConfig.build.output || 'dist/plugin.jar'), 'Plugin');
312
+ copyToPlugins(pluginOutputPath, 'Plugin');
131
313
  copyToPlugins(runtimeJarPath, 'Yeow-Runtime');
314
+
315
+ console.log(`\n${c.bold}${c.green} Starting Paper ${PAPER_VERSION} server...${c.reset}`);
316
+ console.log(` ${c.info} Local address: ${c.bold}${c.cyan}127.0.0.1:${devConfig.port}${c.reset}`);
317
+ console.log(` ${c.dim} Server directory: ${path.relative(process.cwd(), SERVER_DIR)}${c.reset}`);
318
+
319
+ startHotReloadWatcher();
132
320
  startServer(devConfig);
133
321
  }
134
322
 
135
- main().catch(function(err) { logFail(err.message); process.exit(1); });
323
+ process.on('exit', () => {
324
+ if (srcWatcher) srcWatcher.close();
325
+ if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
326
+ });
327
+
328
+ 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,245 @@ 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
+ rl.question(` ${c.yellow}Proxy address${c.reset}${hint} [blank to skip]: `, (val) => {
48
+ rl.close();
49
+ resolve(val);
50
+ });
51
+ });
52
+ const val = answer.trim();
53
+ if (val) {
54
+ devConfig.proxy = val;
55
+ delete require.cache[YEOW_CONFIG_PATH];
56
+ const cfg = require(YEOW_CONFIG_PATH);
57
+ cfg.dev = cfg.dev || {};
58
+ cfg.dev.proxy = val;
59
+ fs.writeFileSync(YEOW_CONFIG_PATH, 'module.exports = ' + JSON.stringify(cfg, null, 4) + ';\n');
60
+ logInfo(`Proxy set to: ${val}`);
61
+ } else if (!currentProxy) {
62
+ log('No proxy, connecting directly');
63
+ }
64
+ }
65
+
66
+ async function createAgent(proxyUrl) {
67
+ if (!proxyUrl) return null;
34
68
  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(); });
69
+ const { HttpsProxyAgent } = await import('https-proxy-agent');
70
+ return new HttpsProxyAgent(proxyUrl);
71
+ } catch (e) {
72
+ logWarn(`Invalid proxy URL "${proxyUrl}", connecting directly`);
73
+ return null;
74
+ }
75
+ }
76
+
77
+ function downloadFile(url, dest, agent) {
78
+ return new Promise((resolve, reject) => {
79
+ const file = fs.createWriteStream(dest);
80
+ const options = {};
81
+ if (agent) {
82
+ options.agent = agent;
83
+ log(` Using proxy: ${c.cyan}${agent.proxy.href}${c.reset}`);
84
+ }
85
+ const request = https.get(url, options, (response) => {
86
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
87
+ file.close();
88
+ fs.unlinkSync(dest);
89
+ return downloadFile(response.headers.location, dest, agent).then(resolve).catch(reject);
90
+ }
91
+ if (response.statusCode !== 200) {
92
+ file.close();
93
+ fs.unlinkSync(dest);
94
+ return reject(new Error(`HTTP ${response.statusCode}`));
95
+ }
96
+ const total = parseInt(response.headers['content-length'], 10);
97
+ let downloaded = 0;
98
+ response.on('data', (chunk) => {
99
+ downloaded += chunk.length;
100
+ const pct = total ? (downloaded / total * 100).toFixed(1) : '?';
101
+ readline.cursorTo(process.stdout, 0);
102
+ 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
103
  });
44
- req.on('error', function(err) { file.close(); if (fs.existsSync(PAPER_CACHE_PATH)) fs.unlinkSync(PAPER_CACHE_PATH); reject(err); });
104
+ response.pipe(file);
105
+ file.on('finish', () => { file.close(); process.stdout.write('\n'); resolve(); });
45
106
  });
107
+ request.on('error', (err) => { file.close(); if (fs.existsSync(dest)) fs.unlinkSync(dest); reject(err); });
108
+ });
109
+ }
110
+
111
+ async function ensurePaperJar(devConfig) {
112
+ if (fs.existsSync(PAPER_CACHE_PATH) && fs.statSync(PAPER_CACHE_PATH).size > 1000000) {
113
+ logOk('Paper jar found in cache');
114
+ return;
115
+ }
116
+ await promptProxy(devConfig);
117
+ const agent = await createAgent(devConfig.proxy);
118
+ logInfo(agent ? `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD} via proxy...` : `Downloading Paper ${PAPER_VERSION} build ${PAPER_BUILD}...`);
119
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
120
+ try {
121
+ await downloadFile(PAPER_URL, PAPER_CACHE_PATH, agent);
46
122
  logOk('Paper jar downloaded');
47
- } catch(err) {
48
- logFail('Download failed: ' + err.message);
123
+ } catch (err) {
124
+ logFail(`Download failed: ${err.message}`);
125
+ log(' You can manually download the jar and place it at:');
126
+ log(` ${c.cyan}${PAPER_CACHE_PATH}${c.reset}`);
49
127
  process.exit(1);
50
128
  }
51
129
  }
52
130
 
53
131
  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);
132
+ const cfgPath = path.resolve(devConfig.runtimeJar);
133
+ if (fs.existsSync(cfgPath) && fs.statSync(cfgPath).size > 1000) return cfgPath;
134
+ logFail(`Yeow-Runtime.jar not found at ${cfgPath}`);
135
+ process.exit(1);
136
+ }
137
+
138
+ async function runInitProcess(devConfig) {
139
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
140
+ logInfo('Initializing server for the first time...');
141
+ return new Promise((resolve) => {
142
+ const initProc = spawn(devConfig.javaPath, [
143
+ `-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`,
144
+ '-jar', paperJar, '--nogui'
145
+ ], { cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit'] });
146
+ const timer = setTimeout(() => { if (!initProc.killed) initProc.kill('SIGTERM'); resolve(); }, 15000);
147
+ initProc.on('exit', () => { clearTimeout(timer); resolve(); });
148
+ initProc.on('error', () => { clearTimeout(timer); resolve(); });
149
+ });
57
150
  }
58
151
 
59
- function initServer(devConfig) {
60
- var paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
152
+ async function initServer(devConfig) {
153
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
61
154
  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); });
155
+
156
+ const eulaPath = path.join(SERVER_DIR, 'eula.txt');
157
+ if (fs.existsSync(eulaPath)) {
158
+ const eulaContent = fs.readFileSync(eulaPath, 'utf-8');
159
+ if (eulaContent.includes('eula=true')) return;
160
+ }
161
+
162
+ if (!fs.existsSync(path.join(SERVER_DIR, 'server.properties'))) {
163
+ await runInitProcess(devConfig);
71
164
  }
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'); });
165
+
166
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
167
+ await new Promise((resolve) => {
168
+ rl.question(` ${c.yellow}Press Enter to accept Mojang EULA and start the server...${c.reset} `, () => {
169
+ rl.close();
170
+ 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');
171
+ logOk('EULA accepted');
172
+ resolve();
173
+ });
174
+ });
74
175
  }
75
176
 
76
177
  function getServerProperties(port) {
77
- var propsPath = path.join(SERVER_DIR, 'server.properties');
78
- var merged = {};
178
+ const propsPath = path.join(SERVER_DIR, 'server.properties');
179
+ const merged = {};
79
180
  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
- });
181
+ const content = fs.readFileSync(propsPath, 'utf-8');
182
+ for (const line of content.split('\n')) {
183
+ const trimmed = line.trim();
184
+ if (trimmed && !trimmed.startsWith('#')) {
185
+ const eqIdx = trimmed.indexOf('=');
186
+ if (eqIdx > 0) merged[trimmed.substring(0, eqIdx).trim()] = trimmed.substring(eqIdx + 1).trim();
187
+ }
188
+ }
83
189
  }
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);
190
+ Object.assign(merged, {
191
+ 'difficulty': 'easy', 'enable-command-block': 'true', 'max-players': '10',
192
+ 'motd': 'A Yeow Dev Server', 'online-mode': 'false', 'server-port': String(port), 'spawn-protection': '0'
193
+ });
194
+ let output = '#Minecraft server properties\n#Generated by Yeow Dev Server\n';
195
+ for (const [key, val] of Object.entries(merged)) output += `${key}=${val}\n`;
196
+ fs.writeFileSync(propsPath, output);
197
+ }
198
+
199
+ function copyToPlugins(sourcePath, label) {
200
+ const pluginsDir = path.join(SERVER_DIR, 'plugins');
201
+ fs.mkdirSync(pluginsDir, { recursive: true });
202
+ const destPath = path.join(pluginsDir, path.basename(sourcePath));
203
+ if (fs.existsSync(destPath)) fs.unlinkSync(destPath);
204
+ fs.copyFileSync(sourcePath, destPath);
205
+ logOk(`${label} copied to plugins`);
87
206
  }
88
207
 
89
208
  function buildDevPlugin() {
90
209
  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); }
210
+ try {
211
+ execSync('node .yeow/build.js', { stdio: 'inherit', cwd: path.resolve(__dirname, '..') });
212
+ logOk('Plugin built');
213
+ } catch (err) {
214
+ logFail('Build failed: ' + err.message);
215
+ process.exit(1);
216
+ }
93
217
  }
94
218
 
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');
219
+ function handleCtrlC() {
220
+ ctrlCPressCount++;
221
+ if (ctrlCPressCount >= 2) {
222
+ logWarn('Force shutdown...');
223
+ if (srcWatcher) srcWatcher.close();
224
+ if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
225
+ process.exit(0);
226
+ }
227
+ logWarn('Sending stop command... (press Ctrl+C again to force kill)');
228
+ if (serverProcess && !serverProcess.killed) serverProcess.stdin.write('stop\n');
229
+ if (ctrlCTimer) clearTimeout(ctrlCTimer);
230
+ ctrlCTimer = setTimeout(() => { ctrlCPressCount = 0; ctrlCTimer = null; }, DOUBLE_CTRL_C_WINDOW);
98
231
  }
99
232
 
100
233
  function startHotReloadWatcher() {
101
234
  const srcDir = path.resolve(__dirname, '../src');
102
235
  if (!fs.existsSync(srcDir)) return;
236
+
103
237
  let debounceTimer = null;
104
- srcWatcher = fs.watch(srcDir, { recursive: true }, function(eventType, filename) {
105
- if (!filename || !/\.ts$/.test(filename) || isCompiling) return;
238
+ srcWatcher = fs.watch(srcDir, { recursive: true }, (eventType, filename) => {
239
+ if (!filename || !filename.endsWith('.ts')) return;
240
+ if (isCompiling) return;
106
241
  if (debounceTimer) clearTimeout(debounceTimer);
107
- debounceTimer = setTimeout(function() {
242
+ debounceTimer = setTimeout(async () => {
108
243
  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); }
244
+ logInfo('Source file changed, rebuilding...');
245
+ try {
246
+ execSync('node .yeow/build.js', { stdio: 'pipe', cwd: path.resolve(__dirname, '..') });
247
+ logOk('Hot reload file updated');
248
+ } catch (e) {
249
+ logFail('Build failed: ' + e.message);
250
+ }
112
251
  isCompiling = false;
113
252
  }, 1000);
114
253
  });
@@ -116,38 +255,74 @@ function startHotReloadWatcher() {
116
255
  }
117
256
 
118
257
  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'); });
258
+ const paperJar = path.join(SERVER_DIR, PAPER_FILENAME);
259
+ const jvmArgs = [`-Xmx${devConfig.memory}`, `-Xms${devConfig.memory}`, `-Dyeow.dev=true`];
260
+
261
+ logBright('\nStarting server (type commands to send to console)...\n');
262
+
263
+ serverProcess = spawn(devConfig.javaPath, [...jvmArgs, '-jar', paperJar, '--nogui'], {
264
+ cwd: SERVER_DIR, stdio: ['pipe', 'inherit', 'inherit']
265
+ });
266
+
267
+ serverProcess.on('exit', (code) => {
268
+ logWarn(`Server exited with code ${code}`);
269
+ if (srcWatcher) srcWatcher.close();
270
+ process.exit(0);
271
+ });
272
+ serverProcess.on('error', (err) => { logFail('Failed to start server: ' + err.message); process.exit(1); });
273
+
274
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
275
+ rl.on('line', (line) => {
276
+ if (serverProcess && !serverProcess.killed && line.trim()) {
277
+ serverProcess.stdin.write(line.trim() + '\n');
278
+ }
279
+ });
126
280
  }
127
281
 
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
- });
282
+ process.on('SIGINT', () => handleCtrlC());
135
283
 
136
284
  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);
285
+ if (!fs.existsSync(YEOW_CONFIG_PATH)) { logFail('yeow.config.js not found'); process.exit(1); }
286
+
287
+ const yeowConfig = require(YEOW_CONFIG_PATH);
288
+ const devConfig = yeowConfig.dev || {};
289
+ devConfig.port = devConfig.port || 17367;
290
+ devConfig.memory = devConfig.memory || '8G';
291
+ devConfig.javaPath = devConfig.javaPath || 'java';
292
+ devConfig.runtimeJar = devConfig.runtimeJar || '.yeow/assets/yeow-runtime-1.0.0.jar';
293
+
294
+ console.log(`\n${c.bold}${c.blue} Yeow Dev Server${c.reset}`);
295
+ 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}`);
296
+ console.log(` Port: ${c.bold}${devConfig.port}${c.reset}`);
297
+ console.log(` Memory: ${c.bold}${devConfig.memory}${c.reset}`);
298
+ if (devConfig.proxy) console.log(` Proxy: ${c.cyan}${devConfig.proxy}${c.reset}`);
299
+ console.log();
300
+
301
+ const runtimeJarPath = await ensureRuntimeJar(devConfig);
142
302
  await ensurePaperJar(devConfig);
303
+
143
304
  fs.mkdirSync(SERVER_DIR, { recursive: true });
144
- initServer(devConfig);
305
+
306
+ await initServer(devConfig);
145
307
  getServerProperties(devConfig.port);
308
+
309
+ const pluginOutputPath = path.resolve(yeowConfig.build.output || `dist/${yeowConfig.plugin.name}-${yeowConfig.plugin.version}.jar`);
310
+
146
311
  buildDevPlugin();
147
- copyToPlugins(path.resolve(yeowConfig.build.output || 'dist/plugin.jar'), 'Plugin');
312
+ copyToPlugins(pluginOutputPath, 'Plugin');
148
313
  copyToPlugins(runtimeJarPath, 'Yeow-Runtime');
314
+
315
+ console.log(`\n${c.bold}${c.green} Starting Paper ${PAPER_VERSION} server...${c.reset}`);
316
+ console.log(` ${c.info} Local address: ${c.bold}${c.cyan}127.0.0.1:${devConfig.port}${c.reset}`);
317
+ console.log(` ${c.dim} Server directory: ${path.relative(process.cwd(), SERVER_DIR)}${c.reset}`);
318
+
149
319
  startHotReloadWatcher();
150
320
  startServer(devConfig);
151
321
  }
152
322
 
153
- main().catch(function(err) { logFail(err.message); process.exit(1); });
323
+ process.on('exit', () => {
324
+ if (srcWatcher) srcWatcher.close();
325
+ if (serverProcess && !serverProcess.killed) serverProcess.kill('SIGKILL');
326
+ });
327
+
328
+ main().catch((err) => { logFail('Error: ' + err.message); process.exit(1); });