create-yeow 0.2.121 → 0.2.122

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.121",
3
+ "version": "0.2.122",
4
4
  "description": "Scaffold a Yeow plugin project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, copyFileSync, writeFileSync, readFileSync, creat
2
2
  import { resolve, dirname, basename } from 'path';
3
3
  import { spawn, execSync } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
5
+ import { createInterface } from 'readline';
5
6
  import https from 'https';
6
7
  import { createServer } from 'http';
7
8
  import { WebSocketServer } from 'ws';
@@ -15,9 +16,24 @@ const SERVER = resolve(DEVDIR, 'server');
15
16
  const WS_PORT = 17368;
16
17
 
17
18
  const YES = process.argv.includes('-y') || process.env.CI === 'true';
19
+ const EULA = process.argv.includes('--eula') || YES;
18
20
  const PROXY = process.argv.find(a => a.startsWith('--proxy='))?.split('=').slice(1).join('=');
19
21
  const STOP = (() => { const a = process.argv.find(a => a.startsWith('--stop=')); if (!a) return null; const m = a.split('=')[1].match(/^(\d+)(s|m|h)?$/); return m ? parseInt(m[1]) * (m[2] === 'm' ? 60 : m[2] === 'h' ? 3600 : 1) : null; })();
20
22
 
23
+ // ── AI 工作流参数(headless 模式)─────────────────────────────
24
+ function parseDur(flag, def) {
25
+ const a = process.argv.find(a => a.startsWith(flag));
26
+ if (!a) return def;
27
+ const m = a.split('=')[1].match(/^(\d+)(s|m|h)?$/);
28
+ return m ? parseInt(m[1]) * (m[2] === 'm' ? 60 : m[2] === 'h' ? 3600 : 1) : def;
29
+ }
30
+ const TIMEOUT = parseDur('--timeout=', 120); // 服务器加载超时(秒,默认 2m)
31
+ const WAIT = parseDur('--wait=', 30); // 加载成功后等待(秒,默认 30s)
32
+ const OUTFILE = process.argv.find(a => a.startsWith('--outfile='))?.split('=').slice(1).join('=') || null;
33
+ const KEEP = process.argv.includes('--keep');
34
+ const HEADLESS = process.argv.includes('--eula') || process.argv.includes('--timeout')
35
+ || process.argv.includes('--wait') || process.argv.includes('--outfile') || KEEP;
36
+
21
37
  const cfg = JSON.parse(readFileSync(resolve(ROOT, 'yeow.config.json'), 'utf-8'));
22
38
  const RUNTIME = resolve(ROOT, '.yeow', 'assets', 'yeow-runtime-0.1.0.jar');
23
39
 
@@ -425,6 +441,8 @@ async function main() {
425
441
  console.log(`\n${c.b}${c.B} Yeow Dev Server${c.r}\n`);
426
442
  if (!existsSync(RUNTIME)) { fail(`Runtime JAR not found: ${RUNTIME}`); process.exit(1); }
427
443
 
444
+ if (HEADLESS) { await runHeadless(); return; }
445
+
428
446
  startWebSocket();
429
447
  await ensurePaper();
430
448
  mkdirSync(SERVER, { recursive: true });
@@ -439,4 +457,71 @@ async function main() {
439
457
  startServer();
440
458
  }
441
459
 
460
+ // ── AI 工作流(headless)─────────────────────────────────────────
461
+ // 适合 AI 代理/CI:--eula 自动接受 → 下载 → 启动 → 检测加载完成 →
462
+ // 等待 --wait 秒后命令自动结束(--keep 保留服务器子进程,日志见 --outfile)。
463
+ async function runHeadless() {
464
+ if (!EULA) {
465
+ fail('AI 模式需要 --eula(自动接受 EULA)');
466
+ process.exit(1);
467
+ }
468
+ const log = OUTFILE ? createWriteStream(OUTFILE, { flags: 'a' }) : null;
469
+ const out = (line) => { if (log) log.write(line + '\n'); else console.log(line); };
470
+
471
+ info('正在下载/准备服务端…');
472
+ await ensurePaper();
473
+ mkdirSync(SERVER, { recursive: true });
474
+ await initServer();
475
+ serverProps(cfg.dev?.port || 17367);
476
+ buildPlugin();
477
+ removeStaleDevJar();
478
+ copyToYeowDir(resolve(ROOT, 'dist', 'plugins', `${cfg.name}-${cfg.version}.yeow.zip`), 'Plugin (.yeow.zip → plugins/Yeow/)');
479
+ copyToPlugins(RUNTIME, 'Runtime');
480
+
481
+ const jvmArgs = ['-Xmx4G', '-Xms4G', '-Dyeow.dev=true', '-Dyeow.ws.port=' + WS_PORT];
482
+ info(`正在启动 Paper ${PAPER_VERSION}...`);
483
+ proc = spawn('java', [...jvmArgs, '-jar', resolve(SERVER, PAPER_JAR), '--nogui'], { cwd: SERVER, stdio: ['ignore', 'pipe', 'pipe'] });
484
+ info(`Server PID: ${proc.pid}`);
485
+
486
+ let started = false, done = false, waitTimer = null;
487
+ const failTimer = setTimeout(() => {
488
+ if (done) return;
489
+ fail(`服务器在 ${TIMEOUT}s 内未完成加载——请检查网络/依赖下载,或加大超时(--timeout=3m)`);
490
+ killProc();
491
+ process.exit(1);
492
+ }, TIMEOUT * 1000);
493
+
494
+ const onLine = (line) => {
495
+ out(line);
496
+ if (!started && line.includes('Starting org.bukkit.craftbukkit.Main')) {
497
+ started = true;
498
+ info('开始加载(Starting org.bukkit.craftbukkit.Main)');
499
+ }
500
+ if (!done && line.includes('Done (') && line.includes('For help')) {
501
+ done = true;
502
+ clearTimeout(failTimer);
503
+ info(`加载完成——等待 ${WAIT}s 后命令结束${KEEP ? '(--keep 保留服务器进程)' : '(关闭服务器进程)'}…`);
504
+ waitTimer = setTimeout(() => {
505
+ info(`等待结束。日志${OUTFILE ? ':' + OUTFILE : '输出于上方'};PID=${proc.pid}${KEEP ? '(服务器仍在运行,按需 kill)' : ''}`);
506
+ if (log) log.end();
507
+ if (KEEP) process.exit(0);
508
+ killProc();
509
+ process.exit(0);
510
+ }, WAIT * 1000);
511
+ }
512
+ };
513
+ readline.createInterface({ input: proc.stdout }).on('line', onLine);
514
+ if (proc.stderr) readline.createInterface({ input: proc.stderr }).on('line', (l) => out('[err] ' + l));
515
+
516
+ proc.on('exit', (code) => {
517
+ if (!done) fail(`服务器提前退出(code ${code})——见${OUTFILE ? '日志 ' + OUTFILE : '上方输出'}`);
518
+ if (log) log.end();
519
+ process.exit(1);
520
+ });
521
+ }
522
+
523
+ function killProc() {
524
+ if (proc && !proc.killed) { try { proc.kill('SIGKILL'); } catch {} }
525
+ }
526
+
442
527
  main().catch(e => { fail(e.message); process.exit(1); });
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  onInit, onLoad, onUnload, registerCommand, eventOn,
3
- Player, Location, pdcSet, pdcGet, log,
3
+ Location, pdcSet, pdcGet, log,
4
4
  } from 'yeow-api';
5
5
 
6
6
  onInit(() => { log.info('Init'); });
@@ -20,23 +20,26 @@ onLoad(() => {
20
20
 
21
21
  registerCommand('back', {
22
22
  description: 'Teleport to your death location',
23
+ permission: { node: 'back.use', default: 'all' }, // 声明权限节点:普通玩家默认可用,服主可经权限插件管理
23
24
  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>');
25
+ if (p.sender === 'CONSOLE') return;
26
+ const player = p.sender; // 已确认非 CONSOLE → Player
27
+ const raw = await pdcGet(player.uuid, 'back.deathLocation');
28
+ if (!raw) return player.sendMessage('<red>No death location recorded</red>');
26
29
 
27
30
  const loc = JSON.parse(raw);
28
- const player = await Player.get(p.sender.uuid);
29
- if (!player) return;
30
31
  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
+ await player.sendMessage('<green>Teleported to death location</green>');
32
33
  },
33
34
  });
34
35
 
35
36
  // ── /ping ──
36
37
  registerCommand('ping', {
38
+ permission: { node: 'ping.use', default: 'all' },
37
39
  executor: async (p) => {
38
- const player = await Player.get(p.sender.uuid);
39
- if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
40
+ if (p.sender === 'CONSOLE') { log.info('Ping: console'); return; }
41
+ const player = p.sender; // Player
42
+ await player.sendMessage(`Ping: ${player.ping}ms`);
40
43
  },
41
44
  });
42
45
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  onInit, onLoad, onUnload, registerCommand, eventOn,
3
- Player, Location, pdcSet, pdcGet, log,
3
+ Location, pdcSet, pdcGet, log,
4
4
  } from 'yeow-api';
5
5
  import type { PlayerDeathEvent } from 'yeow-api';
6
6
 
@@ -21,23 +21,26 @@ onLoad(() => {
21
21
 
22
22
  registerCommand('back', {
23
23
  description: 'Teleport to your death location',
24
+ permission: { node: 'back.use', default: 'all' }, // 声明权限节点:普通玩家默认可用,服主可经权限插件管理
24
25
  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>');
26
+ if (p.sender === 'CONSOLE') return;
27
+ const player = p.sender; // 已确认非 CONSOLE → Player
28
+ const raw = await pdcGet(player.uuid, 'back.deathLocation');
29
+ if (!raw) return player.sendMessage('<red>No death location recorded</red>');
27
30
 
28
31
  const loc = JSON.parse(raw);
29
- const player = await Player.get(p.sender.uuid);
30
- if (!player) return;
31
32
  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
+ await player.sendMessage('<green>Teleported to death location</green>');
33
34
  },
34
35
  });
35
36
 
36
37
  // ── /ping ──
37
38
  registerCommand('ping', {
39
+ permission: { node: 'ping.use', default: 'all' },
38
40
  executor: async (p) => {
39
- const player = await Player.get(p.sender.uuid);
40
- if (player) p.sender.sendMessage(`Ping: ${player.ping}ms`);
41
+ if (p.sender === 'CONSOLE') { log.info('Ping: console'); return; }
42
+ const player = p.sender; // Player
43
+ await player.sendMessage(`Ping: ${player.ping}ms`);
41
44
  },
42
45
  });
43
46