create-yeow 0.2.64 → 0.2.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-yeow",
3
- "version": "0.2.64",
3
+ "version": "0.2.65",
4
4
  "description": "Scaffold a Yeow plugin project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, createWriteStream, statSync, watch, readdirSync } from 'fs';
1
+ import { existsSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, createWriteStream, statSync, watch, readdirSync, rmSync } from 'fs';
2
2
  import { resolve, dirname, basename } from 'path';
3
3
  import { spawn, execSync } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
@@ -12,11 +12,6 @@ const ROOT = resolve(__dirname, '..');
12
12
  const DEVDIR = resolve(ROOT, '.yeow', 'dev');
13
13
  const CACHE = resolve(DEVDIR, 'cache');
14
14
  const SERVER = resolve(DEVDIR, 'server');
15
- const PAPER = '1.21.4';
16
- const BUILD = '232';
17
- const JAR = `paper-${PAPER}-${BUILD}.jar`;
18
- const URL = `https://fill-data.papermc.io/v1/objects/5ee4f542f628a14c644410b08c94ea42e772ef4d29fe92973636b6813d4eaffc/paper-1.21.4-232.jar`;
19
- const CACHEJAR = resolve(CACHE, JAR);
20
15
  const WS_PORT = 17368;
21
16
 
22
17
  const YES = process.argv.includes('-y') || process.env.CI === 'true';
@@ -26,6 +21,50 @@ const STOP = (() => { const a = process.argv.find(a => a.startsWith('--stop='));
26
21
  const cfg = JSON.parse(readFileSync(resolve(ROOT, 'yeow.config.json'), 'utf-8'));
27
22
  const RUNTIME = resolve(ROOT, '.yeow', 'assets', 'yeow-runtime-0.1.0.jar');
28
23
 
24
+ // Dev server config (optional, from yeow.config.json)
25
+ const devCfg = cfg.dev || {};
26
+ const PAPER_VERSION = devCfg.paperVersion || '1.21.4';
27
+ const PAPER_URL = devCfg.paperJar || null;
28
+ let PAPER_PATH = null;
29
+ let PAPER_JAR = null;
30
+
31
+ if (PAPER_URL) {
32
+ if (PAPER_URL.startsWith('http://') || PAPER_URL.startsWith('https://')) {
33
+ PAPER_PATH = resolve(CACHE, basename(new URL(PAPER_URL).pathname));
34
+ PAPER_JAR = basename(new URL(PAPER_URL).pathname);
35
+ } else {
36
+ PAPER_PATH = PAPER_URL;
37
+ PAPER_JAR = basename(PAPER_URL);
38
+ }
39
+ } else {
40
+ PAPER_JAR = `paper-${PAPER_VERSION}.jar`;
41
+ PAPER_PATH = resolve(CACHE, PAPER_JAR);
42
+ }
43
+
44
+ // Config hash — detect changes and recreate dev server
45
+ const CONFIG_HASH_FILE = resolve(DEVDIR, '.config-hash');
46
+ function configHash() { return JSON.stringify({ paperUrl: PAPER_URL, paperVersion: PAPER_VERSION }); }
47
+
48
+ function checkConfigChanged() {
49
+ if (!existsSync(CONFIG_HASH_FILE)) return true;
50
+ try {
51
+ return readFileSync(CONFIG_HASH_FILE, 'utf-8').trim() !== configHash();
52
+ } catch { return true; }
53
+ }
54
+
55
+ function saveConfigHash() {
56
+ mkdirSync(DEVDIR, { recursive: true });
57
+ writeFileSync(CONFIG_HASH_FILE, configHash());
58
+ }
59
+
60
+ if (checkConfigChanged()) {
61
+ if (existsSync(SERVER)) {
62
+ console.log(' Paper config changed — recreating dev server...');
63
+ rmSync(SERVER, { recursive: true, force: true });
64
+ }
65
+ saveConfigHash();
66
+ }
67
+
29
68
  const c = { r: '\x1b[0m', b: '\x1b[1m', d: '\x1b[2m', g: '\x1b[32m', y: '\x1b[33m', B: '\x1b[34m', C: '\x1b[36m', R: '\x1b[31m', ok: '\x1b[32m✓\x1b[0m', fail: '\x1b[31m✗\x1b[0m', info: '\x1b[36mⓘ\x1b[0m', warn: '\x1b[33m⚠\x1b[0m' };
30
69
  const log = (msg, color = '') => console.log(`${c.d}[${new Date().toLocaleTimeString()}]${c.r} ${color}${msg}${c.r}`);
31
70
  const ok = msg => log(`${c.ok} ${msg}`, c.g);
@@ -36,6 +75,29 @@ const warn = msg => log(`${c.warn} ${msg}`, c.y);
36
75
  let proc = null;
37
76
  let wss = null;
38
77
 
78
+ // ── Graceful shutdown ────────────────────────────────────────────
79
+ function cleanup() {
80
+ if (proc && !proc.killed) {
81
+ try { proc.stdin.write('stop\n'); } catch {}
82
+ setTimeout(() => { if (proc && !proc.killed) try { proc.kill(); } catch {} }, 5000);
83
+ }
84
+ if (wss) { try { wss.close(); } catch {} }
85
+ process.exit(0);
86
+ }
87
+ process.on('SIGINT', cleanup);
88
+ process.on('SIGTERM', cleanup);
89
+ process.on('exit', () => { if (proc && !proc.killed) try { proc.kill(); } catch {} });
90
+ // Windows Ctrl+C in terminal
91
+ if (process.platform === 'win32') {
92
+ import('readline').then(rl => {
93
+ rl.emitKeypressEvents(process.stdin);
94
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
95
+ process.stdin.on('keypress', (str, key) => {
96
+ if ((key.ctrl && key.name === 'c') || key.name === 'escape') cleanup();
97
+ });
98
+ });
99
+ }
100
+
39
101
  // ── WebSocket Server ────────────────────────────────────────────
40
102
  function startWebSocket() {
41
103
  const server = createServer();
@@ -83,13 +145,38 @@ function download(url, dest, agent) {
83
145
  }
84
146
 
85
147
  async function ensurePaper() {
86
- if (existsSync(CACHEJAR) && statSync(CACHEJAR).size > 1_000_000) { ok('Paper found in cache'); return; }
87
- mkdirSync(CACHE, { recursive: true });
88
- let agent = null;
89
- if (PROXY) { info(`Proxy: ${PROXY}`); try { const { HttpsProxyAgent } = await import('https-proxy-agent'); agent = new HttpsProxyAgent(PROXY); } catch { } }
90
- info('Downloading Paper...');
91
- try { await download(URL, CACHEJAR, agent); ok('Paper downloaded'); }
92
- catch (e) { fail('Download failed: ' + e.message); console.log(' Manually: ' + CACHEJAR); process.exit(1); }
148
+ if (PAPER_URL) {
149
+ // User-specified URL or file path
150
+ if (PAPER_URL.startsWith('http://') || PAPER_URL.startsWith('https://')) {
151
+ if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { ok('Paper found in cache'); return; }
152
+ mkdirSync(CACHE, { recursive: true });
153
+ let agent = null;
154
+ if (PROXY) { info(`Proxy: ${PROXY}`); try { const { HttpsProxyAgent } = await import('https-proxy-agent'); agent = new HttpsProxyAgent(PROXY); } catch {} }
155
+ info('Downloading Paper...');
156
+ try { await download(PAPER_URL, PAPER_PATH, agent); ok('Paper downloaded'); }
157
+ catch (e) { fail('Download failed: ' + e.message); console.log(' Manually: ' + PAPER_PATH); process.exit(1); }
158
+ } else {
159
+ if (!existsSync(PAPER_PATH)) { fail(`Paper JAR not found: ${PAPER_PATH}`); process.exit(1); }
160
+ ok('Paper found at specified path');
161
+ }
162
+ } else {
163
+ if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { ok('Paper found in cache'); return; }
164
+ mkdirSync(CACHE, { recursive: true });
165
+ info('Downloading Paper...');
166
+ try {
167
+ const apiUrl = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}`;
168
+ const versions = await fetch(apiUrl).then(r => r.json());
169
+ const latestBuild = versions.builds[versions.builds.length - 1];
170
+ const dlUrl = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${latestBuild}/downloads/paper-${PAPER_VERSION}-${latestBuild}.jar`;
171
+ await download(dlUrl, PAPER_PATH, null);
172
+ ok('Paper downloaded');
173
+ } catch (e) {
174
+ fail('Download failed: ' + e.message);
175
+ console.log(' Manually download to: ' + PAPER_PATH);
176
+ console.log(' Or set "paperJar" in yeow.config.json dev section');
177
+ process.exit(1);
178
+ }
179
+ }
93
180
  }
94
181
 
95
182
  function buildPlugin() {
@@ -104,8 +191,8 @@ function copyToPlugins(src, label) {
104
191
  }
105
192
 
106
193
  async function initServer() {
107
- const jar = resolve(SERVER, JAR);
108
- if (!existsSync(jar)) copyFileSync(CACHEJAR, jar);
194
+ const jar = resolve(SERVER, PAPER_JAR);
195
+ if (!existsSync(jar)) copyFileSync(PAPER_PATH, jar);
109
196
  const eula = resolve(SERVER, 'eula.txt');
110
197
  if (existsSync(eula) && readFileSync(eula, 'utf-8').includes('eula=true')) return;
111
198
 
@@ -157,7 +244,7 @@ function startHotReload() {
157
244
  codeFile: compiled.replace(/\\/g, '/'),
158
245
  assetsDir: existsSync(assetsDir) ? assetsDir.replace(/\\/g, '/') : null,
159
246
  });
160
- _consumer = null; // invalidate stale source-map cache
247
+ _consumer = null;
161
248
  ok('Hot reload sent via WebSocket');
162
249
  }
163
250
  } catch (e) {
@@ -168,13 +255,11 @@ function startHotReload() {
168
255
  }, 300);
169
256
  };
170
257
 
171
- // Watch src/ for changes
172
258
  watch(srcDir, { recursive: true }, (event, file) => {
173
259
  if (!file || !file.endsWith('.' + ext)) return;
174
260
  rebuildAndNotify();
175
261
  });
176
262
 
177
- // Watch assets/ for changes
178
263
  if (existsSync(assetsDir)) {
179
264
  watch(assetsDir, { recursive: true }, (event, file) => {
180
265
  rebuildAndNotify();
@@ -203,7 +288,6 @@ async function printFormattedError(err) {
203
288
  if (err.context) out += ` ${c.D}context: ${err.context}${c.r}\n`;
204
289
  out += ` ${c.Y}${err.message}${c.r}\n`;
205
290
 
206
- // Check if stack contains any main.js frames — if so, load source-map consumer
207
291
  const hasMainJs = err.stack?.match(/main\.js:\d+:\d+/) || err.fileName === 'main.js';
208
292
  let consumer = null;
209
293
  if (hasMainJs) {
@@ -214,7 +298,6 @@ async function printFormattedError(err) {
214
298
  out += ` ${c.D}(source-map not found: ${existsSync(mapFile) ? 'exists but failed to parse' : 'missing at ' + mapFile})${c.r}\n`;
215
299
  }
216
300
 
217
- // Resolve all stack frames
218
301
  const frames = [];
219
302
  if (err.stack) {
220
303
  for (const rawLine of err.stack.split('\n')) {
@@ -222,7 +305,6 @@ async function printFormattedError(err) {
222
305
  if (m && consumer) {
223
306
  const orig = consumer.originalPositionFor({ line: parseInt(m[1]), column: parseInt(m[2]) });
224
307
  if (!orig?.source) {
225
- // Try adjusted column
226
308
  const orig2 = consumer.originalPositionFor({ line: parseInt(m[1]), column: parseInt(m[2]) - 1 });
227
309
  if (orig2?.source) { orig.source = orig2.source; orig.line = orig2.line; orig.column = orig2.column; }
228
310
  }
@@ -233,13 +315,10 @@ async function printFormattedError(err) {
233
315
  }
234
316
  }
235
317
 
236
- // Find first frame from user's src/ directory (not node_modules)
237
318
  let ctxFrame = frames.find(f => f.orig?.source?.match(/[\\/]src[\\/]/) && !f.orig.source.includes('node_modules'))?.orig || null;
238
319
 
239
- // Show context for the chosen frame
240
320
  if (ctxFrame?.source && ctxFrame.line && consumer) {
241
321
  const srcPath = ctxFrame.source.replace(/^\.\.\/\.\.\//, '');
242
- // Preserve function name
243
322
  const ctxRaw = frames.find(f => f.orig === ctxFrame)?.raw || '';
244
323
  const fnM = ctxRaw.match(/at\s+(\S+)\s+\(/);
245
324
  const fnS = fnM ? fnM[1] + ' ' : '';
@@ -262,7 +341,6 @@ async function printFormattedError(err) {
262
341
  }
263
342
  }
264
343
 
265
- // Print resolved stack (all frames)
266
344
  if (frames.length > 0) {
267
345
  out += ` ${c.D}Stack:${c.r}\n`;
268
346
  for (const f of frames) {
@@ -284,11 +362,10 @@ async function printFormattedError(err) {
284
362
 
285
363
  function startServer() {
286
364
  const jvmArgs = ['-Xmx4G', '-Xms4G', '-Dyeow.dev=true', '-Dyeow.ws.port=' + WS_PORT];
287
- info(`\nStarting Paper ${PAPER} server...`);
288
- proc = spawn('java', [...jvmArgs, '-jar', resolve(SERVER, JAR), '--nogui'], { cwd: SERVER, stdio: ['pipe', 'inherit', 'inherit'] });
365
+ info(`\nStarting Paper ${PAPER_VERSION} server...`);
366
+ proc = spawn('java', [...jvmArgs, '-jar', resolve(SERVER, PAPER_JAR), '--nogui'], { cwd: SERVER, stdio: ['pipe', 'inherit', 'inherit'] });
289
367
  proc.on('exit', code => { warn(`Server exited (${code})`); if (wss) wss.close(); process.exit(0); });
290
- process.stdin.on('data', d => { if (proc && !proc.killed) proc.stdin.write(d); });
291
- if (STOP) { info(`Auto-stop in ${STOP}s`); setTimeout(() => { warn('Auto-stop'); if (proc && !proc.killed) proc.stdin.write('stop\n'); setTimeout(() => { if (proc && !proc.killed) proc.kill(); process.exit(0); }, 10000); }, STOP * 1000); }
368
+ if (STOP) { info(`Auto-stop in ${STOP}s`); setTimeout(() => { warn('Auto-stop'); cleanup(); }, STOP * 1000); }
292
369
  }
293
370
 
294
371
  async function main() {
@@ -308,5 +385,4 @@ async function main() {
308
385
  startServer();
309
386
  }
310
387
 
311
- process.on('SIGINT', () => { if (proc) { proc.stdin.write('stop\n'); setTimeout(() => { if (proc && !proc.killed) proc.kill(); if (wss) wss.close(); process.exit(0); }, 5000); } });
312
388
  main().catch(e => { fail(e.message); process.exit(1); });
@@ -7,7 +7,7 @@
7
7
  "dev": "node .yeow/dev-server.js"
8
8
  },
9
9
  "dependencies": {
10
- "yeow-api": "^0.2.48",
10
+ "yeow-api": "^0.2.51",
11
11
  "yeow-utils": "^0.1.13"
12
12
  },
13
13
  "devDependencies": {
@@ -1,27 +1,55 @@
1
- import { onInit, onLoad, registerCommand, broadcast, eventOn, Player } from 'yeow-api';
1
+ import {
2
+ onInit, onLoad, onUnload, registerCommand, eventOn,
3
+ Player, Location, pdcSet, pdcGet, log,
4
+ } from 'yeow-api';
2
5
 
3
- onInit(() => { console.log('[init] Plugin loaded'); });
4
- onLoad(() => { console.log('[load] Ready'); });
6
+ onInit(() => { log.info('Init'); });
7
+ onLoad(() => { log.info('Ready'); });
5
8
 
6
9
  onLoad(() => {
7
- registerCommand('hello', {
8
- executor: async (p) => {
9
- p.sender.sendMessage(`Hello, ${p.args[0] || 'World'}!`);
10
- },
11
- });
12
-
13
- registerCommand('ping', {
14
- executor: async (p) => {
15
- const player = Player.get(p.sender.uuid);
16
- if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
17
- },
18
- });
19
-
20
- eventOn('playerJoin', (e) => {
21
- broadcast(e.joinMessage);
22
- });
23
-
24
- eventOn('blockBreak', (e) => {
25
- if (e.block === 'minecraft:bedrock') e.cancelled = true;
26
- });
10
+ // ── /back: 返回死亡位置 ──
11
+ eventOn('playerDeath', async (e) => {
12
+ const loc = e.player.location;
13
+ if (!loc) return;
14
+ const data = JSON.stringify({ x: loc.x, y: loc.y, z: loc.z, world: loc.world || e.player.world });
15
+ pdcSet(e.player.uuid, 'back.deathLocation', data);
16
+ await e.player.sendMessage(
17
+ '<red>You died!</red> <gray>Use</gray> <click:run_command:/back><aqua><u>/back</u></aqua></click> <gray>to return</gray>',
18
+ );
19
+ });
20
+
21
+ registerCommand('back', {
22
+ description: 'Teleport to your death location',
23
+ executor: async (p) => {
24
+ const raw = await pdcGet(p.sender.uuid, 'back.deathLocation');
25
+ if (!raw) return p.sender.sendMessage('<red>No death location recorded</red>');
26
+
27
+ const loc = JSON.parse(raw);
28
+ const player = await Player.get(p.sender.uuid);
29
+ if (!player) return;
30
+ await player.teleport(new Location(loc.x, loc.y, loc.z, 0, 0, loc.world));
31
+ p.sender.sendMessage('<green>Teleported to death location</green>');
32
+ },
33
+ });
34
+
35
+ // ── /ping ──
36
+ registerCommand('ping', {
37
+ executor: async (p) => {
38
+ const player = await Player.get(p.sender.uuid);
39
+ if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
40
+ },
41
+ });
42
+
43
+ // ── 事件 ──
44
+ eventOn('playerJoin', (e) => {
45
+ log.info(`${e.player.name} joined`);
46
+ });
47
+
48
+ eventOn('blockBreak', (e) => {
49
+ if (e.block === 'minecraft:bedrock') e.cancelled = true;
50
+ });
51
+ });
52
+
53
+ onUnload(() => {
54
+ log.info('Unloaded');
27
55
  });
@@ -1,28 +1,56 @@
1
- import { onInit, onLoad, registerCommand, broadcast, eventOn, Player } from 'yeow-api';
2
- import type { PlayerJoinEvent, BlockBreakEvent } from 'yeow-api';
1
+ import {
2
+ onInit, onLoad, onUnload, registerCommand, eventOn,
3
+ Player, Location, pdcSet, pdcGet, log,
4
+ } from 'yeow-api';
5
+ import type { PlayerDeathEvent } from 'yeow-api';
3
6
 
4
- onInit(() => { console.log('[init] Plugin loaded'); });
5
- onLoad(() => { console.log('[load] Ready'); });
7
+ onInit(() => { log.info('Init'); });
8
+ onLoad(() => { log.info('Ready'); });
6
9
 
7
10
  onLoad(() => {
8
- registerCommand('hello', {
9
- executor: async (p) => {
10
- p.sender.sendMessage(`Hello, ${p.args[0] || 'World'}!`);
11
- },
12
- });
13
-
14
- registerCommand('ping', {
15
- executor: async (p) => {
16
- const player = Player.get(p.sender.uuid);
17
- if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
18
- },
19
- });
20
-
21
- eventOn('playerJoin', (e: PlayerJoinEvent) => {
22
- broadcast(e.joinMessage);
23
- });
24
-
25
- eventOn('blockBreak', (e: BlockBreakEvent) => {
26
- if (e.block === 'minecraft:bedrock') e.cancelled = true;
27
- });
11
+ // ── /back: 返回死亡位置 ──
12
+ eventOn('playerDeath', async (e: PlayerDeathEvent) => {
13
+ const loc = e.player.location;
14
+ if (!loc) return;
15
+ const data = JSON.stringify({ x: loc.x, y: loc.y, z: loc.z, world: loc.world || e.player.world });
16
+ pdcSet(e.player.uuid, 'back.deathLocation', data);
17
+ await e.player.sendMessage(
18
+ '<red>You died!</red> <gray>Use</gray> <click:run_command:/back><aqua><u>/back</u></aqua></click> <gray>to return</gray>',
19
+ );
20
+ });
21
+
22
+ registerCommand('back', {
23
+ description: 'Teleport to your death location',
24
+ executor: async (p) => {
25
+ const raw = await pdcGet(p.sender.uuid, 'back.deathLocation');
26
+ if (!raw) return p.sender.sendMessage('<red>No death location recorded</red>');
27
+
28
+ const loc = JSON.parse(raw);
29
+ const player = await Player.get(p.sender.uuid);
30
+ if (!player) return;
31
+ await player.teleport(new Location(loc.x, loc.y, loc.z, 0, 0, loc.world));
32
+ p.sender.sendMessage('<green>Teleported to death location</green>');
33
+ },
34
+ });
35
+
36
+ // ── /ping ──
37
+ registerCommand('ping', {
38
+ executor: async (p) => {
39
+ const player = await Player.get(p.sender.uuid);
40
+ if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
41
+ },
42
+ });
43
+
44
+ // ── 事件 ──
45
+ eventOn('playerJoin', (e) => {
46
+ log.info(`${e.player.name} joined`);
47
+ });
48
+
49
+ eventOn('blockBreak', (e) => {
50
+ if (e.block === 'minecraft:bedrock') e.cancelled = true;
51
+ });
52
+ });
53
+
54
+ onUnload(() => {
55
+ log.info('Unloaded');
28
56
  });