create-yeow 0.2.121 → 0.2.123

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.123",
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
 
@@ -132,9 +148,9 @@ function download(url, dest, agent) {
132
148
  if (res.statusCode !== 200) { f.close(); reject(new Error(`HTTP ${res.statusCode}`)); return; }
133
149
  const total = parseInt(res.headers['content-length'], 10);
134
150
  let dl = 0;
135
- res.on('data', chunk => { dl += chunk.length; if (total) process.stdout.write(`\r ${c.B}Downloading...${c.r} ${(dl / 1024 / 1024).toFixed(1)}MB / ${(total / 1024 / 1024).toFixed(1)}MB`); });
151
+ res.on('data', chunk => { dl += chunk.length; if (total && !HEADLESS) process.stdout.write(`\r ${c.B}Downloading...${c.r} ${(dl / 1024 / 1024).toFixed(1)}MB / ${(total / 1024 / 1024).toFixed(1)}MB`); });
136
152
  res.pipe(f);
137
- f.on('finish', () => { f.close(); process.stdout.write('\n'); resolve(); });
153
+ f.on('finish', () => { f.close(); if (!HEADLESS) process.stdout.write('\n'); resolve(); });
138
154
  }).on('error', e => { f.close(); reject(e); });
139
155
  });
140
156
  }
@@ -143,19 +159,19 @@ async function ensurePaper() {
143
159
  if (PAPER_URL) {
144
160
  // User-specified URL or file path
145
161
  if (PAPER_URL.startsWith('http://') || PAPER_URL.startsWith('https://')) {
146
- if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { ok('Paper found in cache'); return; }
162
+ if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { if (!HEADLESS) ok('Paper found in cache'); return; }
147
163
  mkdirSync(CACHE, { recursive: true });
148
164
  let agent = null;
149
165
  if (PROXY) { info(`Proxy: ${PROXY}`); try { const { HttpsProxyAgent } = await import('https-proxy-agent'); agent = new HttpsProxyAgent(PROXY); } catch {} }
150
- info('Downloading Paper...');
151
- try { await download(PAPER_URL, PAPER_PATH, agent); ok('Paper downloaded'); }
166
+ if (!HEADLESS) info('Downloading Paper...');
167
+ try { await download(PAPER_URL, PAPER_PATH, agent); if (!HEADLESS) ok('Paper downloaded'); }
152
168
  catch (e) { fail('Download failed: ' + e.message); console.log(' Manually: ' + PAPER_PATH); process.exit(1); }
153
169
  } else {
154
170
  if (!existsSync(PAPER_PATH)) { fail(`Paper JAR not found: ${PAPER_PATH}`); process.exit(1); }
155
- ok('Paper found at specified path');
171
+ if (!HEADLESS) ok('Paper found at specified path');
156
172
  }
157
173
  } else {
158
- if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { ok('Paper found in cache'); return; }
174
+ if (existsSync(PAPER_PATH) && statSync(PAPER_PATH).size > 1_000_000) { if (!HEADLESS) ok('Paper found in cache'); return; }
159
175
  mkdirSync(CACHE, { recursive: true });
160
176
  info('Downloading Paper...');
161
177
  try {
@@ -164,7 +180,7 @@ async function ensurePaper() {
164
180
  const latestBuild = versions.builds[versions.builds.length - 1];
165
181
  const dlUrl = `https://api.papermc.io/v2/projects/paper/versions/${PAPER_VERSION}/builds/${latestBuild}/downloads/paper-${PAPER_VERSION}-${latestBuild}.jar`;
166
182
  await download(dlUrl, PAPER_PATH, null);
167
- ok('Paper downloaded');
183
+ if (!HEADLESS) ok('Paper downloaded');
168
184
  } catch (e) {
169
185
  fail('Download failed: ' + e.message);
170
186
  console.log(' Manually download to: ' + PAPER_PATH);
@@ -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