@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('
|
|
89
|
-
|
|
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
|
@@ -2,23 +2,85 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const AdmZip = require('adm-zip');
|
|
5
|
+
|
|
5
6
|
const configPath = path.resolve('yeow.config.js');
|
|
6
|
-
|
|
7
|
-
function
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error('\u274C yeow.config.js not found');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return require(configPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function compileUserCode() {
|
|
17
|
+
console.log('\uD83D\uDCE6 Compiling user code with Rspack...');
|
|
18
|
+
try {
|
|
19
|
+
execSync('npx rspack build', { stdio: 'inherit' });
|
|
20
|
+
console.log('\u2705 User code compiled');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('\u274C Compilation failed:', error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
function buildPlugin() {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const { plugin, build } = config;
|
|
30
|
+
|
|
31
|
+
console.log('\n\uD83D\uDE80 Building Yeow plugin...');
|
|
32
|
+
console.log(` Plugin: ${plugin.name} v${plugin.version}`);
|
|
33
|
+
|
|
11
34
|
compileUserCode();
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
|
|
36
|
+
const templateJarPath = path.resolve(build.templateJar || '.yeow/assets/yeow-template-1.0.0.jar');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(templateJarPath)) {
|
|
39
|
+
console.error('\u274C Template JAR not found:', templateJarPath);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n\uD83D\uDCE6 Packaging plugin JAR...');
|
|
44
|
+
|
|
14
45
|
const templateZip = new AdmZip(templateJarPath);
|
|
15
|
-
|
|
46
|
+
|
|
47
|
+
const pluginYml = `name: ${plugin.name}
|
|
48
|
+
version: ${plugin.version}
|
|
49
|
+
main: yeow.template.YeowPluginBootstrap
|
|
50
|
+
api-version: '1.21'
|
|
51
|
+
|
|
52
|
+
depend:
|
|
53
|
+
- Yeow-Runtime
|
|
54
|
+
|
|
55
|
+
author: ${plugin.author || 'Unknown'}
|
|
56
|
+
description: ${plugin.description || 'A Yeow plugin'}`;
|
|
57
|
+
|
|
58
|
+
templateZip.updateFile('plugin.yml', Buffer.from(pluginYml));
|
|
59
|
+
|
|
16
60
|
const userCodePath = path.resolve('dist/.yeow/main.js');
|
|
17
|
-
if (fs.existsSync(userCodePath)) {
|
|
18
|
-
|
|
61
|
+
if (fs.existsSync(userCodePath)) {
|
|
62
|
+
const userCode = fs.readFileSync(userCodePath);
|
|
63
|
+
templateZip.addFile('.yeow/main.js', userCode);
|
|
64
|
+
} else {
|
|
65
|
+
console.error('\u274C Compiled user code not found:', userCodePath);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputPath = build.output || `dist/${plugin.name}-${plugin.version}.jar`;
|
|
19
70
|
const outputDir = path.dirname(outputPath);
|
|
20
|
-
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(outputDir)) {
|
|
73
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
21
76
|
templateZip.writeZip(outputPath);
|
|
22
|
-
|
|
77
|
+
|
|
78
|
+
const stats = fs.statSync(outputPath);
|
|
79
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
80
|
+
|
|
81
|
+
console.log(`\n\u2705 Plugin built successfully!`);
|
|
82
|
+
console.log(` Output: ${outputPath}`);
|
|
83
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
23
84
|
}
|
|
24
|
-
|
|
85
|
+
|
|
86
|
+
buildPlugin();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { spawn, execSync } = require('child_process');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
@@ -8,128 +9,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 =
|
|
12
|
-
const PAPER_URL =
|
|
12
|
+
const PAPER_FILENAME = `paper-${PAPER_VERSION}-${PAPER_BUILD}.jar`;
|
|
13
|
+
const PAPER_URL = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${PAPER_BUILD}/downloads/${PAPER_FILENAME}`;
|
|
13
14
|
const PAPER_CACHE_PATH = path.join(CACHE_DIR, PAPER_FILENAME);
|
|
14
15
|
const DOUBLE_CTRL_C_WINDOW = 3000;
|
|
16
|
+
|
|
15
17
|
const YEOW_CONFIG_PATH = path.resolve('yeow.config.js');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
22
|
+
ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let serverProcess = null;
|
|
26
|
+
let ctrlCPressCount = 0;
|
|
27
|
+
let ctrlCTimer = null;
|
|
28
|
+
let srcWatcher = null;
|
|
29
|
+
let isCompiling = false;
|
|
30
|
+
|
|
31
|
+
function log(msg, color = '') {
|
|
32
|
+
console.log(`${c.dim}[${new Date().toLocaleTimeString()}]${c.reset} ${color}${msg}${c.reset}`);
|
|
33
|
+
}
|
|
34
|
+
function logOk(msg) { log(`${c.ok} ${msg}`, c.green); }
|
|
35
|
+
function logFail(msg) { log(`${c.fail} ${msg}`, c.red); }
|
|
36
|
+
function logWarn(msg) { log(`${c.warn} ${msg}`, c.yellow); }
|
|
37
|
+
function logInfo(msg) { log(`${c.info} ${msg}`, c.cyan); }
|
|
23
38
|
function logBright(msg) { log(msg, c.bold); }
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
41
|
+
|
|
42
|
+
async function promptProxy(devConfig) {
|
|
43
|
+
const currentProxy = devConfig.proxy || '';
|
|
27
44
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
const answer = await new Promise((resolve) => {
|
|
46
|
+
const hint = currentProxy ? ` (current: ${c.cyan}${currentProxy}${c.reset})` : '';
|
|
47
|
+
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
56
|
-
if (fs.existsSync(
|
|
57
|
-
logFail(
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
var p = spawn(devConfig.javaPath, ['-Xmx' + devConfig.memory, '-Xms' + devConfig.memory, '-jar', paperJar, '--nogui'], { cwd: SERVER_DIR, stdio: 'pipe' });
|
|
69
|
-
var t = setTimeout(function() { if (!p.killed) p.kill(); }, 15000);
|
|
70
|
-
p.on('exit', function() { clearTimeout(t); });
|
|
71
|
-
p.on('error', function() { clearTimeout(t); });
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
178
|
+
const propsPath = path.join(SERVER_DIR, 'server.properties');
|
|
179
|
+
const merged = {};
|
|
80
180
|
if (fs.existsSync(propsPath)) {
|
|
81
|
-
fs.readFileSync(propsPath, 'utf-8')
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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 {
|
|
93
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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',
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
function
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(configPath)) {
|
|
10
|
+
console.error('\u274C yeow.config.js not found');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return require(configPath);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function compileUserCode() {
|
|
17
|
+
console.log('\uD83D\uDCE6 Compiling user code with Rspack...');
|
|
18
|
+
try {
|
|
19
|
+
execSync('npx rspack build', { stdio: 'inherit' });
|
|
20
|
+
console.log('\u2705 User code compiled');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('\u274C Compilation failed:', error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
8
27
|
function buildPlugin() {
|
|
9
|
-
const config = loadConfig();
|
|
10
|
-
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const { plugin, build } = config;
|
|
30
|
+
|
|
31
|
+
console.log('\n\uD83D\uDE80 Building Yeow plugin...');
|
|
32
|
+
console.log(` Plugin: ${plugin.name} v${plugin.version}`);
|
|
33
|
+
|
|
11
34
|
compileUserCode();
|
|
12
|
-
|
|
13
|
-
|
|
35
|
+
|
|
36
|
+
const templateJarPath = path.resolve(build.templateJar || '.yeow/assets/yeow-template-1.0.0.jar');
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(templateJarPath)) {
|
|
39
|
+
console.error('\u274C Template JAR not found:', templateJarPath);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log('\n\uD83D\uDCE6 Packaging plugin JAR...');
|
|
44
|
+
|
|
14
45
|
const templateZip = new AdmZip(templateJarPath);
|
|
15
|
-
|
|
46
|
+
|
|
47
|
+
const pluginYml = `name: ${plugin.name}
|
|
48
|
+
version: ${plugin.version}
|
|
49
|
+
main: yeow.template.YeowPluginBootstrap
|
|
50
|
+
api-version: '1.21'
|
|
51
|
+
|
|
52
|
+
depend:
|
|
53
|
+
- Yeow-Runtime
|
|
54
|
+
|
|
55
|
+
author: ${plugin.author || 'Unknown'}
|
|
56
|
+
description: ${plugin.description || 'A Yeow plugin'}`;
|
|
57
|
+
|
|
58
|
+
templateZip.updateFile('plugin.yml', Buffer.from(pluginYml));
|
|
59
|
+
|
|
16
60
|
const userCodePath = path.resolve('dist/.yeow/main.js');
|
|
17
|
-
if (fs.existsSync(userCodePath)) {
|
|
18
|
-
|
|
61
|
+
if (fs.existsSync(userCodePath)) {
|
|
62
|
+
const userCode = fs.readFileSync(userCodePath);
|
|
63
|
+
templateZip.addFile('.yeow/main.js', userCode);
|
|
64
|
+
} else {
|
|
65
|
+
console.error('\u274C Compiled user code not found:', userCodePath);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputPath = build.output || `dist/${plugin.name}-${plugin.version}.jar`;
|
|
19
70
|
const outputDir = path.dirname(outputPath);
|
|
20
|
-
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(outputDir)) {
|
|
73
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
|
|
21
76
|
templateZip.writeZip(outputPath);
|
|
22
|
-
|
|
77
|
+
|
|
78
|
+
const stats = fs.statSync(outputPath);
|
|
79
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
80
|
+
|
|
81
|
+
console.log(`\n\u2705 Plugin built successfully!`);
|
|
82
|
+
console.log(` Output: ${outputPath}`);
|
|
83
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
23
84
|
}
|
|
85
|
+
|
|
24
86
|
buildPlugin();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const { spawn, execSync } = require('child_process');
|
|
4
5
|
const readline = require('readline');
|
|
5
6
|
|
|
@@ -8,107 +9,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 =
|
|
12
|
-
const PAPER_URL =
|
|
12
|
+
const PAPER_FILENAME = `paper-${PAPER_VERSION}-${PAPER_BUILD}.jar`;
|
|
13
|
+
const PAPER_URL = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${PAPER_BUILD}/downloads/${PAPER_FILENAME}`;
|
|
13
14
|
const PAPER_CACHE_PATH = path.join(CACHE_DIR, PAPER_FILENAME);
|
|
14
15
|
const DOUBLE_CTRL_C_WINDOW = 3000;
|
|
16
|
+
|
|
15
17
|
const YEOW_CONFIG_PATH = path.resolve('yeow.config.js');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
const c = {
|
|
20
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
21
|
+
green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', red: '\x1b[31m',
|
|
22
|
+
ok: '\x1b[32m\u2713\x1b[0m', fail: '\x1b[31m\u2717\x1b[0m', info: '\x1b[36m\u24D8\x1b[0m', warn: '\x1b[33m\u26A0\x1b[0m',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let serverProcess = null;
|
|
26
|
+
let ctrlCPressCount = 0;
|
|
27
|
+
let ctrlCTimer = null;
|
|
28
|
+
let srcWatcher = null;
|
|
29
|
+
let isCompiling = false;
|
|
30
|
+
|
|
31
|
+
function log(msg, color = '') {
|
|
32
|
+
console.log(`${c.dim}[${new Date().toLocaleTimeString()}]${c.reset} ${color}${msg}${c.reset}`);
|
|
33
|
+
}
|
|
34
|
+
function logOk(msg) { log(`${c.ok} ${msg}`, c.green); }
|
|
35
|
+
function logFail(msg) { log(`${c.fail} ${msg}`, c.red); }
|
|
36
|
+
function logWarn(msg) { log(`${c.warn} ${msg}`, c.yellow); }
|
|
37
|
+
function logInfo(msg) { log(`${c.info} ${msg}`, c.cyan); }
|
|
23
38
|
function logBright(msg) { log(msg, c.bold); }
|
|
24
39
|
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
41
|
+
|
|
42
|
+
async function promptProxy(devConfig) {
|
|
43
|
+
const currentProxy = devConfig.proxy || '';
|
|
27
44
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
const answer = await new Promise((resolve) => {
|
|
46
|
+
const hint = currentProxy ? ` (current: ${c.cyan}${currentProxy}${c.reset})` : '';
|
|
47
|
+
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
55
|
-
if (fs.existsSync(
|
|
56
|
-
logFail(
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
rl
|
|
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
|
-
|
|
78
|
-
|
|
178
|
+
const propsPath = path.join(SERVER_DIR, 'server.properties');
|
|
179
|
+
const merged = {};
|
|
79
180
|
if (fs.existsSync(propsPath)) {
|
|
80
|
-
fs.readFileSync(propsPath, 'utf-8')
|
|
81
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 {
|
|
92
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
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 },
|
|
105
|
-
if (!filename ||
|
|
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(
|
|
242
|
+
debounceTimer = setTimeout(async () => {
|
|
108
243
|
isCompiling = true;
|
|
109
|
-
logInfo('Source changed, rebuilding...');
|
|
110
|
-
try {
|
|
111
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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',
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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); });
|