@uzuhq/code-cli 0.5.2 → 0.5.4
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/dist/cli.js +1 -21
- package/dist/dev-server/game-room.js +11 -4
- package/dist/dev-server/relay-room.js +11 -9
- package/dist/dev-server/sync-room.js +11 -9
- package/dist/harness/mount.js +36 -20
- package/dist/sdk-version.js +41 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import { buildServerLogic } from './build-server-logic.js';
|
|
|
16
16
|
import { create2dGame } from './create-2d-game.js';
|
|
17
17
|
import { uploadIconsToCfImages } from './cf-images-upload.js';
|
|
18
18
|
import { runDevCommand } from './dev.js';
|
|
19
|
+
import { readInstalledSdkVersion } from './sdk-version.js';
|
|
19
20
|
import { DEFAULT_ENV, authBaseURL, resolveEnv, studioHost } from './auth/env.js';
|
|
20
21
|
import { runLoginFlow } from './auth/login-flow.js';
|
|
21
22
|
import { saveCredentials, clearCredentials, credentialsPath } from './auth/config.js';
|
|
@@ -23,27 +24,6 @@ import { getLoginIdToken, getValidIdToken } from './auth/token-cache.js';
|
|
|
23
24
|
import { PUBLISH_TOKEN_ENV, createPublishToken, listPublishTokens, revokePublishToken, } from './auth/publish-token.js';
|
|
24
25
|
// publish / login / logout 共通の env オプション。既定 dev・help には出さない (誤って本番へ publish しないため)。
|
|
25
26
|
const envOption = () => new Option('--env <env>', '接続先環境 (dev | stg | prd)').default(DEFAULT_ENV).hideHelp();
|
|
26
|
-
/**
|
|
27
|
-
* ゲームの node_modules から SDK の実バージョンを読む。
|
|
28
|
-
* 開発者の自己申告ではなく install 済み実体から測ることで、backend に記録される
|
|
29
|
-
* sdkVersion (ホスト側のサポート範囲判定に使う) の信頼性を担保する。
|
|
30
|
-
* @uzupj/uzu-sdk は旧パッケージ名 (未移行 scenario 向けの両対応)。
|
|
31
|
-
*/
|
|
32
|
-
const readInstalledSdkVersion = (cwd) => {
|
|
33
|
-
const candidates = [
|
|
34
|
-
resolve(cwd, 'node_modules', '@uzuhq', 'code-sdk', 'package.json'),
|
|
35
|
-
resolve(cwd, 'node_modules', '@uzupj', 'uzu-sdk', 'package.json'),
|
|
36
|
-
];
|
|
37
|
-
const pkgPath = candidates.find(existsSync);
|
|
38
|
-
if (!pkgPath) {
|
|
39
|
-
throw new Error('@uzuhq/code-sdk が node_modules に見つかりません。依存を install してから publish してください。');
|
|
40
|
-
}
|
|
41
|
-
const version = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
|
|
42
|
-
if (!version) {
|
|
43
|
-
throw new Error(`${pkgPath} に version がありません。`);
|
|
44
|
-
}
|
|
45
|
-
return version;
|
|
46
|
-
};
|
|
47
27
|
/** publish に使われている uzu-cli 自身のバージョン (dist/../package.json)。 */
|
|
48
28
|
const readOwnCliVersion = () => {
|
|
49
29
|
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
@@ -290,6 +290,17 @@ export class GameRoom {
|
|
|
290
290
|
this.ensureTickLoop();
|
|
291
291
|
}
|
|
292
292
|
async handleMessage(ws, msg) {
|
|
293
|
+
// SDK は生文字列で送受信する (本番の setWebSocketAutoResponse と同じ wire 形式)。
|
|
294
|
+
// JSON.parse より前に返さないと heartbeat が落ちて 35 秒ごとに全席が再接続する。
|
|
295
|
+
if (msg === '__ping') {
|
|
296
|
+
try {
|
|
297
|
+
ws.send('__pong');
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* disconnected */
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
293
304
|
const attachment = this.attachments.get(ws);
|
|
294
305
|
if (!attachment)
|
|
295
306
|
return;
|
|
@@ -302,10 +313,6 @@ export class GameRoom {
|
|
|
302
313
|
return;
|
|
303
314
|
}
|
|
304
315
|
const msgType = parsed.type;
|
|
305
|
-
if (msgType === '__ping') {
|
|
306
|
-
this.sendTo(ws, { type: '__pong' });
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
316
|
console.log(`[GameRoom] ⬅ recv from=${senderId} type=${msgType}`);
|
|
310
317
|
if (msgType === '__action') {
|
|
311
318
|
if (!this.gameState) {
|
|
@@ -25,6 +25,17 @@ export class RelayRoom {
|
|
|
25
25
|
ws.send(JSON.stringify({ type: '__room_init', myId: playerId }));
|
|
26
26
|
}
|
|
27
27
|
handleMessage(ws, msg) {
|
|
28
|
+
// SDK は生文字列で送受信する (本番の setWebSocketAutoResponse と同じ wire 形式)。
|
|
29
|
+
// JSON.parse より前に返さないと heartbeat が落ちて 35 秒ごとに全席が再接続する。
|
|
30
|
+
if (msg === '__ping') {
|
|
31
|
+
try {
|
|
32
|
+
ws.send('__pong');
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
/* disconnected */
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
28
39
|
const attachment = this.attachments.get(ws);
|
|
29
40
|
if (!attachment)
|
|
30
41
|
return;
|
|
@@ -36,15 +47,6 @@ export class RelayRoom {
|
|
|
36
47
|
catch {
|
|
37
48
|
return;
|
|
38
49
|
}
|
|
39
|
-
if (parsed.type === '__ping') {
|
|
40
|
-
try {
|
|
41
|
-
ws.send(JSON.stringify({ type: '__pong' }));
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
/* disconnected */
|
|
45
|
-
}
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
50
|
console.log(`[RelayRoom] ⬅ recv from=${senderId}`, JSON.stringify(parsed));
|
|
49
51
|
const outData = JSON.stringify({ ...parsed, __from: senderId });
|
|
50
52
|
if (parsed.__to && typeof parsed.__to === 'string') {
|
|
@@ -78,6 +78,17 @@ export class SyncRoom {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
handleMessage(ws, msg) {
|
|
81
|
+
// SDK は生文字列で送受信する (本番の setWebSocketAutoResponse と同じ wire 形式)。
|
|
82
|
+
// JSON.parse より前に返さないと heartbeat が落ちて 35 秒ごとに全席が再接続する。
|
|
83
|
+
if (msg === '__ping') {
|
|
84
|
+
try {
|
|
85
|
+
ws.send('__pong');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* disconnected */
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
81
92
|
const attachment = this.attachments.get(ws);
|
|
82
93
|
if (!attachment)
|
|
83
94
|
return;
|
|
@@ -90,15 +101,6 @@ export class SyncRoom {
|
|
|
90
101
|
return;
|
|
91
102
|
}
|
|
92
103
|
const msgType = parsed.type;
|
|
93
|
-
if (msgType === '__ping') {
|
|
94
|
-
try {
|
|
95
|
-
ws.send(JSON.stringify({ type: '__pong' }));
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
/* disconnected */
|
|
99
|
-
}
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
104
|
console.log(`[SyncRoom] ⬅ recv from=${senderId} type=${msgType}`);
|
|
103
105
|
if (msgType === '__init_state') {
|
|
104
106
|
if (this.stateInitialized) {
|
package/dist/harness/mount.js
CHANGED
|
@@ -33,6 +33,14 @@ const PHONE_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" x
|
|
|
33
33
|
<rect x="6.5" y="2" width="11" height="20" rx="2.5" stroke="white" stroke-width="2"/>
|
|
34
34
|
<line x1="10" y1="18.5" x2="14" y2="18.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
35
35
|
</svg>`;
|
|
36
|
+
// 擬似ノッチ表示トグル。 createNotchDecor が実際に描く装飾 (横向き端末・左の
|
|
37
|
+
// Dynamic Island・下中央のホームインジケータ) をそのまま象る。 隣の PHONE_SVG
|
|
38
|
+
// (縦向き) とは向きで見分ける。
|
|
39
|
+
const NOTCH_SVG = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
40
|
+
<rect x="2" y="4.5" width="20" height="15" rx="3.5" stroke="white" stroke-width="1.8"/>
|
|
41
|
+
<rect x="4.8" y="8.5" width="2.2" height="7" rx="1.1" fill="white"/>
|
|
42
|
+
<line x1="11" y1="17" x2="17" y2="17" stroke="white" stroke-width="1.6" stroke-linecap="round"/>
|
|
43
|
+
</svg>`;
|
|
36
44
|
/** buildIframeUrl に渡す HUD の避け領域 (Flutter HudInsets と同じ計算)。 */
|
|
37
45
|
function hudInsetParams(hasMenu, actionCount) {
|
|
38
46
|
let x = HUD_LEFT;
|
|
@@ -429,19 +437,12 @@ function openPlayerSwitcher(opts, currentIndex, anchor) {
|
|
|
429
437
|
};
|
|
430
438
|
setTimeout(() => document.addEventListener('click', close), 0);
|
|
431
439
|
}
|
|
432
|
-
/**
|
|
433
|
-
* 各 iframe (= 各プレイヤー画面) の左上に載せる overlay chrome。
|
|
434
|
-
* 本番アプリ (Flutter GamePlayScreen) と同じ仕様:
|
|
435
|
-
* SafeArea top:8 / left:12 → [UZU ボタン 44] [gap 6] [ActionBar ピル: chat, mic]。
|
|
436
|
-
* UZU ボタンは dev メニュー (State/Reset/Emulator) + その player の「スマホで開く」を開く。
|
|
437
|
-
* chat / mic は本番の見た目に合わせた dev 表示 (機能は SDK 側が担うため配線しない)。
|
|
438
|
-
*/
|
|
439
440
|
function createCellChrome(opts, playerIndex,
|
|
440
441
|
// facade=true (実機の単体表示) では見た目だけ揃える。 admin channel が無いので
|
|
441
442
|
// UZU メニュー / 実機 QR は配線せず、 アイコンはタップ無効 (ハリボテ)。
|
|
442
443
|
facade = false,
|
|
443
|
-
// grid では no-op
|
|
444
|
-
|
|
444
|
+
// grid では no-op のマイク枠を擬似ノッチ表示トグルに転用する。
|
|
445
|
+
notchToggle,
|
|
445
446
|
// 単体表示 (実機) では UZU ボタンをプレイヤー切替に転用する。
|
|
446
447
|
onUzuClick) {
|
|
447
448
|
const wrap = document.createElement('div');
|
|
@@ -489,17 +490,24 @@ onUzuClick) {
|
|
|
489
490
|
'border-radius:18px',
|
|
490
491
|
'display:flex;align-items:center',
|
|
491
492
|
].join(';');
|
|
492
|
-
// 1つめ:
|
|
493
|
-
//
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
mic.style.cssText = onMicClick
|
|
493
|
+
// 1つめ: 本番は音声トグルだが harness には音声が無い。 grid ではこの枠を擬似ノッチ
|
|
494
|
+
// 表示のトグルに転用し、 アイコンも端末フレームへ差し替える (facade は本番の見た目)。
|
|
495
|
+
const micSlot = document.createElement(notchToggle ? 'button' : 'div');
|
|
496
|
+
micSlot.innerHTML = notchToggle ? NOTCH_SVG : MIC_SVG;
|
|
497
|
+
micSlot.style.cssText = notchToggle
|
|
498
498
|
? 'padding:0 10px;height:36px;background:none;border:none;cursor:pointer;display:flex;align-items:center;'
|
|
499
499
|
: 'padding:0 10px;display:flex;align-items:center;';
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
if (notchToggle) {
|
|
501
|
+
micSlot.onclick = notchToggle.toggle;
|
|
502
|
+
notchToggle.register((on) => {
|
|
503
|
+
micSlot.style.opacity = on ? '1' : '0.4';
|
|
504
|
+
micSlot.title = `擬似ノッチ表示: ${on ? 'ON' : 'OFF'}`;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
micSlot.title = 'マイク (dev 表示のみ)';
|
|
509
|
+
}
|
|
510
|
+
bar.appendChild(micSlot);
|
|
503
511
|
// 2つめ: 携帯 (その player を実機で開く QR)。 facade では装飾のみ。
|
|
504
512
|
const phone = document.createElement('button');
|
|
505
513
|
phone.innerHTML = PHONE_SVG;
|
|
@@ -528,7 +536,8 @@ export function mountIframeGrid(opts) {
|
|
|
528
536
|
const iframeSplashes = new Map();
|
|
529
537
|
const iframePredictionWarns = new Map();
|
|
530
538
|
// 擬似ノッチ (各 cell 共通トグル)。 iframe を再生成せず inset だけ切り替える。
|
|
531
|
-
|
|
539
|
+
// 既定は OFF: ノッチ帯のぶん画面が縮むので、 SafeArea を確認したいときだけ出す。
|
|
540
|
+
let notchSimOn = false;
|
|
532
541
|
const notchAppliers = [];
|
|
533
542
|
const toggleNotchSim = () => {
|
|
534
543
|
notchSimOn = !notchSimOn;
|
|
@@ -575,6 +584,7 @@ export function mountIframeGrid(opts) {
|
|
|
575
584
|
iframeStatusDots.clear();
|
|
576
585
|
iframeSplashes.clear();
|
|
577
586
|
iframePredictionWarns.clear();
|
|
587
|
+
notchAppliers.length = 0;
|
|
578
588
|
for (let i = 0; i < opts.seats.length; i++) {
|
|
579
589
|
const seat = seatAt(opts, i);
|
|
580
590
|
const cell = document.createElement('div');
|
|
@@ -616,7 +626,13 @@ export function mountIframeGrid(opts) {
|
|
|
616
626
|
screen.appendChild(splash);
|
|
617
627
|
// admin は端末を模す必要が無いので overlay chrome / 擬似ノッチを載せず素の画面にする。
|
|
618
628
|
if (seat.kind !== 'admin') {
|
|
619
|
-
screen.appendChild(createCellChrome(opts, i, false,
|
|
629
|
+
screen.appendChild(createCellChrome(opts, i, false, {
|
|
630
|
+
toggle: toggleNotchSim,
|
|
631
|
+
register: (apply) => {
|
|
632
|
+
apply(notchSimOn);
|
|
633
|
+
notchAppliers.push(apply);
|
|
634
|
+
},
|
|
635
|
+
}));
|
|
620
636
|
}
|
|
621
637
|
// 先読み警告の内訳パネル。 バッジクリックで開閉する。 画面の上に重ねるので
|
|
622
638
|
// ゲームの見た目は普段どおりのまま、 必要なときだけ前に出る。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
|
+
/** 新パッケージ名を先に見る。@uzupj/uzu-sdk は旧名 (未移行 scenario 向けの両対応)。 */
|
|
4
|
+
const SDK_PACKAGE_PATHS = [
|
|
5
|
+
['@uzuhq', 'code-sdk'],
|
|
6
|
+
['@uzupj', 'uzu-sdk'],
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* ゲームの node_modules から SDK の実バージョンを読む。
|
|
10
|
+
* 開発者の自己申告ではなく install 済み実体から測ることで、backend に記録される
|
|
11
|
+
* sdkVersion (ホスト側のサポート範囲判定に使う) の信頼性を担保する。
|
|
12
|
+
*
|
|
13
|
+
* cwd の node_modules だけでなく親方向へ辿るのは、npm workspaces が依存の実体を
|
|
14
|
+
* リポジトリ root へ hoist するため。manifest.json のある packages/<game>/ で publish しても、
|
|
15
|
+
* そこに node_modules は無い。Node のモジュール解決と同じく「近い node_modules が勝つ」。
|
|
16
|
+
*/
|
|
17
|
+
export const readInstalledSdkVersion = (cwd) => {
|
|
18
|
+
const pkgPath = findSdkPackageJson(cwd);
|
|
19
|
+
if (!pkgPath) {
|
|
20
|
+
throw new Error('@uzuhq/code-sdk が node_modules に見つかりません。依存を install してから publish してください。');
|
|
21
|
+
}
|
|
22
|
+
const version = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
|
|
23
|
+
if (!version) {
|
|
24
|
+
throw new Error(`${pkgPath} に version がありません。`);
|
|
25
|
+
}
|
|
26
|
+
return version;
|
|
27
|
+
};
|
|
28
|
+
const findSdkPackageJson = (cwd) => {
|
|
29
|
+
let dir = resolve(cwd);
|
|
30
|
+
for (;;) {
|
|
31
|
+
const found = SDK_PACKAGE_PATHS.map((segments) => resolve(dir, 'node_modules', ...segments, 'package.json')).find(existsSync);
|
|
32
|
+
if (found) {
|
|
33
|
+
return found;
|
|
34
|
+
}
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
dir = parent;
|
|
40
|
+
}
|
|
41
|
+
};
|