webtalekit-alpha 0.2.11 → 0.2.14

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/README.md CHANGED
@@ -1,50 +1,84 @@
1
1
  # webTaleKit
2
2
 
3
- ![wabTaleKitロゴ](s-plan1-5Light-s-1.jpg)
3
+ ![webTaleKitロゴ](s-plan1-5Light-s-1.jpg)
4
+
5
+ **[English](README_EN.md) | 日本語**
4
6
 
5
7
  ## 目次
6
8
 
7
- - 概要
8
- - デモ
9
- - 環境構築手順
10
- - 動作確認手順
11
- - Quick Start(デモゲームを弄ってみよう)
12
- - できること
13
- - できないこと
14
- - フィードバックフォームのご案内
9
+ - [概要](#概要)
10
+ - [デモ](#デモ)
11
+ - [ドキュメント](#ドキュメント)
12
+ - [環境構築手順](#環境構築手順)
13
+ - [動作確認手順](#動作確認手順)
14
+ - [Quick Start(デモゲームを弄ってみよう)](#quick-startデモゲームを弄ってみよう)
15
+ - [開発コマンド](#開発コマンド)
16
+ - [シナリオ検証API](#シナリオ検証api)
17
+ - [現在の状況](#現在の状況)
18
+ - [ロードマップ](#ロードマップ実装予定)
19
+ - [できること](#アルファ版01x-02xでできること)
20
+ - [制限事項](#アルファ版01x-02xの制限事項)
15
21
 
16
22
  ## 概要
17
23
 
18
- TypeScript(JavaScript) ベースのビジュアルノベルゲームエンジンです。
19
- UIをHTML・CSS・JavaScriptで柔軟に作成でき、シナリオをマークアップ言語とJavaScriptで制御できます。
20
- 自動スケーリング機能で、様々なウインドウで遊ぶことができます。
21
- VS Codeの拡張機能を用いたGUIエディタやREST API呼び出しによる生成AI連携の追加を提供予定です。
24
+ TypeScript(JavaScript) ベースのビジュアルノベルゲームエンジンです。
25
+ UIをHTML・CSS・JavaScriptで柔軟に作成でき、シナリオをマークアップ言語とJavaScriptで制御できます。
26
+ 自動スケーリング機能で、様々なウィンドウサイズに対応します。
27
+ VS Codeの拡張機能を用いたGUIエディタやREST API呼び出しによる生成AI連携の追加を提供予定です。
28
+
29
+ ### 特徴
30
+
31
+ - 🎮 **柔軟なUI作成**: HTML・CSS・JavaScriptで自由にUIをデザイン
32
+ - 📝 **直感的なシナリオ記述**: マークアップ言語とJavaScriptでシナリオを制御
33
+ - 🔄 **自動スケーリング**: 様々なウィンドウサイズに自動対応
34
+ - 🎨 **豊富な画像処理**: フィルター・アニメーション機能を搭載
35
+ - 🔊 **サウンド対応**: BGM・SE・ボイス再生に対応
36
+ - 🛠️ **TypeScript対応**: TypeScriptでの開発をサポート
37
+ - 🤖 **AI連携**: REST API呼び出しによる生成AI連携(予定)
22
38
 
23
39
  ## デモ
24
40
 
25
- Firefoxでも、Chromeでも、Edgeでも、好きなブラウザを使いたまえ・・・!
41
+ Firefoxでも、Chromeでも、Edgeでも、好きなブラウザを使いたまえ・・・!
26
42
  <https://test-game-chi.vercel.app/>
27
- ![alt text](image.png)
43
+ ![デモゲーム画面](image.png)
44
+
45
+ ## ドキュメント
46
+
47
+ 📖 **オンラインドキュメント**: <https://endohizumi.github.io/webTaleKit/>
28
48
 
29
49
  ## 環境構築手順
30
50
 
31
- 1. Node.js(20以降)が必要です。(nvm等お好みの方法がある場合は、そちらでも構いません)
51
+ 1. Git が必要です。
52
+ - **インストール確認:** `git --version` でバージョンが表示されれば OK
53
+ - Windowsの場合は、Git公式サイト (<https://git-scm.com/>) からインストールしてください。
54
+ - Macの場合は、`brew install git` を実行してインストールしてください。
55
+ - Linuxの場合は、以下のコマンドを実行してインストールしてください。
56
+
57
+ ```bash
58
+ sudo apt-get update
59
+ sudo apt-get install git
60
+ ```
61
+
62
+ 2. Node.js(20以降)が必要です。(nvm等お好みの方法がある場合は、そちらでも構いません)
63
+ - **インストール確認:** `node --version` でv20以上のバージョンが表示されれば OK
32
64
  - Windowsの場合は、Node.js公式サイト (<https://nodejs.org/>) からインストールしてください。
33
65
  - Macの場合は、`brew install node` を実行してインストールしてください。
34
66
  - Linuxの場合は、以下のコマンドを実行して、インストールしてください。
35
67
 
36
68
  ```bash
37
- curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
38
- sudo apt-get install -y nodejs
69
+ curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
70
+ sudo apt-get install -y nodejs
39
71
  ```
40
72
 
41
- 2. 以下のコマンドを実行してください
73
+ 3. 以下のコマンドを実行してください
42
74
 
43
75
  ``` bash
44
76
  npm create tale-game your-game-title
77
+ cd your-game-title
78
+ npm run play
45
79
  ```
46
80
 
47
- プロジェクトに移動して、`npm run play`を実行して、デモゲームが起動すれば、構築は完了です。
81
+ デモゲームが起動すれば、構築は完了です。
48
82
 
49
83
  ## 動作確認手順
50
84
 
@@ -52,82 +86,172 @@ Firefoxでも、Chromeでも、Edgeでも、好きなブラウザを使いたま
52
86
 
53
87
  ```bash
54
88
  git clone https://github.com/EndoHizumi/testGame.git
89
+ cd testGame
55
90
  npm install
56
91
  npm run play
57
92
  ```
58
93
 
59
- ## Quick Start(デモゲームを弄ってみよう
60
-
61
- ### 画像を差し替える
62
-
63
- - キャラを変える場合
64
- - src/resource/chara/guide.png に上書きしてください。
65
- - 背景画像を変える
66
- - src/resource/background/title_bg.png に上書きしてください。
67
- - BGMを変える
68
- - src/resource/bgm/title_theme.mp3 に上書きしてください。
69
-
70
- - 選択肢の画像を変える
71
- - src\resource\system\systemPicture\02_button\button.png に上書きしてください。
72
- - 選択肢(マウスオーバー時)の画像を変える
73
- - src\resource\system\systemPicture\02_button\button2.png に上書きしてください。
74
- - 選択肢(クリック時)の画像を変える
75
- - src\resource\system\systemPicture\02_button\button3.png に上書きしてください。
76
-
77
- - キャラを増やす
78
- - src\resource\character 以下に表示したい画像を置きます。
79
- - 登場させたい行数で、`<show src="表示したい画像のパス"></show>` を記述する
80
- - セリフを増やす
81
- - セリフを表示させたい行数で、`<say name="キャラの名前">セリフをここに入れる</say>` を記述する
82
- - 地の文を増やす
83
- - 地の文を表示させたい行数で、`<text>セリフをここに入れる</text>` を記述する
84
-
85
- - 選択肢を増やす
86
- - 43行目のchoiceタグの中(44行-50行)で、以下のように記述すると、選択されたときに、地の文を表示する
87
-
88
- ``` html
89
- <item label='選択肢の文言'>
90
- <text>セリフをここに入れる</text>
91
- </item>
92
- ```
93
-
94
- 実装例
95
-
96
- ``` html
97
- <choice prompt="ゲームを始めますか?">
98
- <item label="はい">
99
- <jump index="5" />
100
- </item>
101
- <item label="いいえ">
102
- <jump index="16" />
103
- </item>
104
- <item label='ちょっと待ってくれ'>
105
- <text>承知しました。</text>
106
- <jump index="1" />
107
- </item>
108
- </choice>
109
- ```
94
+ ## Quick Start(デモゲームを弄ってみよう)
95
+
96
+ このセクションでは、プログラミングの知識がなくても簡単にゲームをカスタマイズできる方法を説明します。
97
+
98
+ ### 画像を差し替える(簡単なカスタマイズ)
99
+
100
+ **手順:** 既存の画像ファイルを新しい画像で置き換える(ファイル名は同じにしてください)
101
+
102
+ #### キャラクターや背景を変える
103
+
104
+ - **キャラを変える場合**
105
+ - `./src/resource/chara/guide.png` を新しいキャラ画像で上書きしてください(ファイル名は `guide.png` のまま)
106
+ - **背景画像を変える**
107
+ - `./src/resource/background/title_bg.png` を新しい背景画像で上書きしてください(ファイル名は `title_bg.png` のまま)
108
+ - **BGMを変える**
109
+ - `./src/resource/bgm/title_theme.mp3` を新しい音楽ファイルで上書きしてください(ファイル名は `title_theme.mp3` のまま)
110
+
111
+ #### ボタンの見た目を変える
112
+
113
+ - **選択肢の画像を変える**
114
+ - `./src/resource/system/systemPicture/02_button/button.png`(通常時) - ファイル名は `button.png` のまま上書き
115
+ - `./src/resource/system/systemPicture/02_button/button2.png`(マウスを乗せた時) - ファイル名は `button2.png` のまま上書き
116
+ - `./src/resource/system/systemPicture/02_button/button3.png`(クリック時) - ファイル名は `button3.png` のまま上書き
117
+
118
+ **パス表記について:**
119
+
120
+ - `./` は「現在のプロジェクトフォルダから」という意味です
121
+ - パス区切り文字は `/` (スラッシュ) を使用しています
122
+ - **Windowsをお使いの方:** `\` (バックスラッシュ) でも動作しますが、上記の `/` 形式を推奨します
123
+
124
+ ### シナリオファイルを編集する(テキストの変更)
125
+
126
+ シナリオファイル(`.scene`ファイル)をテキストエディタで開いて、以下の方法で内容を変更できます:
127
+
128
+ #### 基本的な要素の追加
129
+
130
+ - **キャラを増やす**
131
+ 1. `./src/resource/character` フォルダに新しいキャラ画像を保存
132
+ 2. シナリオファイルで `<show src="キャラ画像のファイル名"></show>` を記述
133
+ - **セリフを増やす**
134
+ - `<say name="キャラの名前">ここにセリフを入力</say>` を記述
135
+ - **地の文(ナレーション)を増やす**
136
+ - `<text>ここに地の文を入力</text>` を記述
137
+
138
+ **初心者の方へ:** まずは既存のテキストを変更することから始めることをお勧めします。
139
+
140
+ #### 選択肢を追加・変更する
141
+
142
+ 選択肢はプレイヤーがゲームの進行を選ぶ重要な要素です。シナリオファイル内の `<choice>` タグを編集することで変更できます。
143
+
144
+ **基本的な選択肢の書き方:**
145
+
146
+ ``` html
147
+ <item label='選択肢の文言'>
148
+ <text>選択後に表示される文章</text>
149
+ </item>
150
+ ```
151
+
152
+ **実用的な例:**
153
+
154
+ ```html
155
+ <choice prompt="ゲームを始めますか?">
156
+ <item label="はい">
157
+ <jump index="5" />
158
+ </item>
159
+ <item label="いいえ">
160
+ <jump index="16" />
161
+ </item>
162
+ <item label='ちょっと待ってくれ'>
163
+ <text>承知しました。</text>
164
+ <jump index="1" />
165
+ </item>
166
+ </choice>
167
+ ```
168
+
169
+ ## 開発コマンド
170
+
171
+ ### ビルドと開発
172
+
173
+ - `npm run build` - TypeScriptをJavaScriptにコンパイルし、配布ファイルを準備
174
+ - `npm run dev` - プロジェクトをビルドし、exampleフォルダで開発サーバーを起動
175
+ - `npm run lint` - ESLintでコード品質をチェック
176
+ - `npm run test` - Jestでテストを実行
177
+
178
+ ### CLIツール
179
+
180
+ - `wtc` - WebTaleScriptパーサーCLI (`parser/cli.js` から利用可能)
181
+ - 使用方法: `wtc <scene-file> [output-directory]` で `.scene` ファイルを `.js/.ts` ファイルに変換
182
+
183
+ ### ドキュメント
184
+
185
+ - `npm run docs:dev` - VitePressドキュメントサーバーを起動
186
+ - `npm run docs:build` - ドキュメントをビルド
187
+ - `npm run docs:preview` - ビルドしたドキュメントをプレビュー
188
+
189
+ ## シナリオ検証API
190
+
191
+ WebTaleKit には、シナリオ配列を検証しつつ、HTML風の文字列を非破壊でサニタイズできる公開APIがあります。
192
+
193
+ - `validateScenarioObjects` - 検証結果とサニタイズ済みシナリオを返します
194
+ - `formatValidationOutput` - エラーと警告を表示用文字列へ整形します
195
+ - `createScenarioValidationError` - 検証結果から Error を生成します
196
+ - `assertScenarioValidation` - エラーがある場合に例外を送出します
197
+ - `reportScenarioValidation` - logger 経由で警告とエラーを出力します
198
+
199
+ これらのAPIは、エンジン実行時に自動で強制適用されません。シナリオの読み込み時、エディタ連携時、独自ビルド処理時などに、利用者が必要に応じて呼び出す想定です。
200
+
201
+ ```ts
202
+ import {
203
+ assertScenarioValidation,
204
+ reportScenarioValidation,
205
+ validateScenarioObjects,
206
+ } from './src/utils/validateScenario'
207
+
208
+ const result = validateScenarioObjects(scenarioObjects, commandList)
209
+
210
+ await reportScenarioValidation(result, 'Scene import')
211
+ assertScenarioValidation(result, 'Scene import')
212
+
213
+ const safeScenario = result.sanitizedScenario
214
+ ```
215
+
216
+ `sanitizedScenario` は元の入力配列を破壊せずに返されます。`sanitized` が `true` の場合は、HTML風の文字列がエスケープされています。
110
217
 
111
218
  ## 現在の状況
112
219
 
113
- webTaleKitは、現在アルファ版です。
220
+ webTaleKitは、現在アルファ版です。
114
221
 
115
222
  開発進捗は、[@endo_hizumi](https://x.com/endo_hizumi) で行っております。
116
223
  実装予定の項目については、こちらの[Trello](https://trello.com/b/qYNGh7MY)からも確認できます。
117
224
 
118
- デモをプレイした感想・WebTaleKitを使って気になったことなど、意見・感想はこちらで受け付けています!
225
+ デモをプレイした感想・WebTaleKitを使って気になったことなど、意見・感想はこちらで受け付けています!
119
226
  [https://forms.gle/uejQwvwAb99wcJht7](https://forms.gle/uejQwvwAb99wcJht7)
120
227
 
121
228
  検索Hashtag: #webTalekit
122
229
 
230
+ ## ロードマップ(実装予定)
231
+
232
+ | バージョン | コードネーム | codeName | 内容
233
+ | :--- | :--- | :--- | :---
234
+ | 0.1.0 | 初音| HATUNE | 初期リリース
235
+ | 0.2.0 | 礎 | ISHIZUE | 基本機能アップデート<br>0.2.12〜<br>ダイアログ表示タグの追加<br>engineConfig反映バグの修正<br>未定義タグがある場合、undefineを呼び出すバグの修正<br>文字列以外を囲むとこける問題の修正<br>リンク切れでこける問題の修正<br>メッセージウィンドウオーバーフローの修正<br>if属性の実装<br>for属性の実装<br>既読管理の追加
236
+ | 0.3.0 | 舞踊 | BUYO | トランジション・アニメーション関連のアップデート<br>テキストスピードの調整タグの追加<br>テキスト表示フォントサイズの変更<br>Webフォントのサポート(フォント変更設定の追加)<br>動画再生のサポート<br>子要素でフィルター・アニメーション設定
237
+ | 0.4.0 | 狭間 | HAZAMA | Vue.jsやReact、SvelteなどのUIフレームワークとの連携追加のアップデート
238
+ | 0.5.0 | 操手 | AYATURI | ゲームパッドのサポート追加<br>キーコンフィグの追加<br>VOICEBOX APIの対応<br>npm run recの追加
239
+ | 0.6.0 | 絡繰 | KARAKURI | wtsLinterの追加<br>VSCodeとの連携追加<br>wst2htmlの追加<br>プラグイン機能の追加<br>クロスプラットホームへのビルド追加
240
+ | 0.7.0 | 綴り | TUDURI | GUIエディタの追加
241
+ | 0.8.0 | 迅雷 | JINRAI | パフォーマンスアップデート
242
+ | 0.9.0 | 出島 | DEJIMA | KAGタグコンバータの追加
243
+ | 1.0.0 | 暁月 | AKATUKI | メジャーアップデート
244
+
123
245
  ## アルファ版(0.1.x-0.2.x)で、できること
124
246
 
125
247
  ### テキスト表示
248
+
126
249
  - 地の文の表示
127
250
  - キャラクターのセリフの表示・ボイスの再生
128
251
  - 定義した変数の表示
129
252
 
130
253
  ### キャラクター・画像操作
254
+
131
255
  - キャラクターの画像の表示・位置変更・アニメーション
132
256
  - その他の画像の画像の表示・位置変更・アニメーション
133
257
  - キャラクターの複数表示・位置変更・アニメーション
@@ -135,6 +259,7 @@ webTaleKitは、現在アルファ版です。
135
259
  - 背景画像の表示・変更
136
260
 
137
261
  ### 画像処理
262
+
138
263
  - 画像のフィルター操作
139
264
  - モノクロ化
140
265
  - セピア化
@@ -142,6 +267,7 @@ webTaleKitは、現在アルファ版です。
142
267
  - サイズの変更
143
268
 
144
269
  ### ユーザーインタラクション
270
+
145
271
  - 選択肢の表示
146
272
  - 選択肢の画像の変更
147
273
  - 通常時
@@ -151,53 +277,64 @@ webTaleKitは、現在アルファ版です。
151
277
  - Enterキーで全文表示
152
278
 
153
279
  ### シナリオ制御
280
+
154
281
  - 表示する文章・画像の条件分岐
155
282
  - セリフのジャンプ
156
- - シナリオ(シーン)の切り替え
283
+ - シナリオ(シーン)の切り替え
157
284
 
158
285
  ### 音声
286
+
159
287
  - BGMの再生・停止
160
288
  - SEの再生・停止
161
289
 
290
+ ### セーブ&ロード
291
+
292
+ - セーブ機能
293
+ - ロード機能
294
+
162
295
  ### システム設定・UI
296
+
163
297
  - HTMLで作った画面の表示
164
298
  - 解像度の設定変更
165
299
 
166
300
  ### プログラミング連携
301
+
167
302
  - JavaScript連携
168
303
  - メソッドの呼び出し
169
304
  - 式の実行
170
305
  - 変数の定義・値の変更
171
306
  - JavaScript側での背景画像の変更
172
307
  - TypeScript連携
173
- - REST API呼び出し(レスポンスの表示)
308
+ - REST API呼び出し(レスポンスの表示)
174
309
 
175
- ## アルファ版(0.1.x-0.2.x)の制限事項
310
+ ## アルファ版(0.1.x-0.2.x)の制限事項
176
311
 
177
312
  ### ビルド・プラットフォーム
313
+
178
314
  - Desktopアプリケーションへのビルド
179
315
  - Android(iOS)向けのビルド
180
316
 
181
317
  ### ユーザーインターフェース (UI)
318
+
182
319
  - 画面各種のボタン
183
320
  - セーブファイルの一覧の取得
184
321
 
185
322
  ### キャラクター操作
323
+
186
324
  - sayタグの以下の機能
187
325
  - キャラが表示されていないときは、表示する
188
326
 
189
- ### セーブ&ロード
190
- - セーブ&ロード機能
191
-
192
327
  ### 視覚効果
328
+
193
329
  - showタグ / hideタグの以下の機能
194
330
  - 子要素でフィルター指定
195
331
  - 子要素でアニメーション指定
196
332
  - スラッシュで区切ってリソース種類を指定
197
- - quakeタグ(画面を揺らす)
198
- - maskタグ(画面の暗転)
333
+ - quakeタグ(画面を揺らす)
334
+ - maskタグ(画面の暗転)
199
335
 
200
336
  ### 音声
337
+
201
338
  - soundタグの以下の機能
202
339
  - pause
203
340
  - setVolume
@@ -207,19 +344,26 @@ webTaleKitは、現在アルファ版です。
207
344
  - seエイリアス
208
345
 
209
346
  ### リソース管理
210
- - JavaScript側での背景画像の変更
347
+
211
348
  - JavaScriptでのリソースの動的定義
212
349
 
213
350
  ### 設定・最適化
351
+
214
352
  - ゲーム設定ファイルの反映
215
353
  - 画面用HTMLのcss・jsのインライン化・minify化
216
354
 
217
- ## アイコン素材
355
+ ## ライセンス
356
+
357
+ MIT License
358
+
359
+ ## クレジット
360
+
361
+ ### アイコン素材
218
362
 
219
363
  - <https://www.silhouette-illust.com/>
220
364
 
221
- ## カラーコード
365
+ ### カラーコード
222
366
 
223
- 青: #3178C6 (TypeScript BLue)
224
- 緑: #02a889 (WebTaleKit Green)
225
- 白: #f8f8f8 (White Smoke)
367
+ - 青: #3178C6 (TypeScript Blue)
368
+ - 緑: #02a889 (WebTaleKit Green)
369
+ - 白: #f8f8f8 (White Smoke)
package/package.json CHANGED
@@ -1,12 +1,18 @@
1
1
  {
2
2
  "name": "webtalekit-alpha",
3
- "version": "0.2.11",
3
+ "version": "0.2.14",
4
4
  "description": "Web知識でノベルゲーを作ることを目指したやつ",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
7
7
  "lint": "eslint . --ext .ts --ext .js",
8
8
  "test": "jest",
9
- "build": "rimraf ./dist/ && tsc && cp -r package.json README.md engineConfig.json parser/ dist/ && cp src/core/index.js dist/src/core/"
9
+ "setup": "npm install && cd example && npm install",
10
+ "dev": "npm run build && cd example && npm run dev",
11
+ "api": "node server/api.js",
12
+ "build": "rimraf ./dist/ && tsc && cp -r package.json README.md engineConfig.json parser/ dist/ && cp src/core/index.js dist/src/core/",
13
+ "docs:dev": "vitepress dev docs",
14
+ "docs:build": "vitepress build docs",
15
+ "docs:preview": "vitepress preview docs"
10
16
  },
11
17
  "bin": {
12
18
  "wtc": "parser/cli.js"
@@ -14,6 +20,9 @@
14
20
  "author": "EndoHizumi",
15
21
  "license": "MIT",
16
22
  "dependencies": {
23
+ "express": "^5.2.1",
24
+ "gsap": "^3.14.2",
25
+ "html-inline": "^1.2.0",
17
26
  "html-minifier": "^4.0.0",
18
27
  "html-to-json-parser": "^2.0.1",
19
28
  "rimraf": "^6.0.1",
@@ -42,6 +51,7 @@
42
51
  "ts-loader": "^9.5.1",
43
52
  "ts-node": "^10.9.2",
44
53
  "typescript": "^5.4.2",
54
+ "vitepress": "^1.6.4",
45
55
  "webpack": "^5.90.3",
46
56
  "webpack-cli": "^5.1.4",
47
57
  "webpack-dev-server": "^5.0.2"
@@ -0,0 +1,184 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * トップレベルコマンドの一覧 (core/index.js の Core.commandList から抽出)。
5
+ * HTTP サブタグはこれらのコマンドいずれかの子要素として使用できる。
6
+ */
7
+ const TOP_LEVEL_COMMANDS = [
8
+ 'text', 'choice', 'show', 'newpage', 'hide', 'jump', 'sound', 'say',
9
+ 'if', 'call', 'moveto', 'route', 'wait', 'dialog', 'save', 'load',
10
+ ]
11
+
12
+ /**
13
+ * 各ノードタイプが配置できる親タイプを定義する。
14
+ * キー: 子ノードタイプ、値: 許可された親タイプの配列
15
+ */
16
+ const ALLOWED_PARENTS = {
17
+ // choice 構造
18
+ item: ['choice'],
19
+ // dialog 構造
20
+ prompt: ['dialog'],
21
+ actions: ['dialog'],
22
+ action: ['actions'],
23
+ // if 構造
24
+ else: ['if'],
25
+ // then は <if> の子要素としても、トップレベルコマンドの HTTP レスポンスの子要素としても使用できる
26
+ then: ['if', ...TOP_LEVEL_COMMANDS],
27
+ // インラインテキスト装飾タグ (drawer.ts の createDecoratedElement と textHandler から)
28
+ color: ['text', 'say'],
29
+ ruby: ['text', 'say'],
30
+ b: ['text', 'say'],
31
+ i: ['text', 'say'],
32
+ br: ['text', 'say'],
33
+ // HTTP サブタグ (httpHandler から) — トップレベルコマンドの子要素として有効
34
+ header: TOP_LEVEL_COMMANDS,
35
+ data: TOP_LEVEL_COMMANDS,
36
+ error: TOP_LEVEL_COMMANDS,
37
+ progress: TOP_LEVEL_COMMANDS,
38
+ }
39
+
40
+ /**
41
+ * すべてのノードタイプに共通して有効な属性。
42
+ * - type: タグタイプ自体
43
+ * - content: 子ノードの配列
44
+ * - if: 条件付き実行 (core/index.js の runScenario)
45
+ * - get/post/put/delete: HTTP メソッド属性 (core/index.js の httpHandler)
46
+ */
47
+ const GLOBAL_ATTRIBUTES = new Set(['type', 'content', 'if', 'get', 'post', 'put', 'delete'])
48
+
49
+ /**
50
+ * ノードタイプごとの既知の属性セット (GLOBAL_ATTRIBUTES を除く)。
51
+ * このセットに含まれない属性は unknown_attribute 警告を生成する。
52
+ * このマップにないノードタイプは属性チェックをスキップする
53
+ * (<header> や <data> の子要素のような自由形式の子要素に対応)。
54
+ */
55
+ const KNOWN_ATTRIBUTES = {
56
+ // トップレベルコマンド (core/index.js のハンドラ実装から取得)
57
+ text: new Set(['name', 'speed', 'time']),
58
+ say: new Set(['name', 'speed', 'voice']),
59
+ choice: new Set(['prompt', 'position']),
60
+ show: new Set(['src', 'name', 'mode', 'x', 'y', 'width', 'height', 'pos', 'look', 'entry', 'sepia', 'mono', 'blur', 'opacity', 'transition', 'duration']),
61
+ hide: new Set(['name', 'mode', 'transition', 'duration']),
62
+ moveto: new Set(['name', 'x', 'y', 'duration']),
63
+ sound: new Set(['src', 'name', 'mode', 'play', 'loop', 'stop', 'pause']),
64
+ jump: new Set(['index', 'sub']),
65
+ if: new Set(['condition']),
66
+ call: new Set(['method']),
67
+ route: new Set(['to']),
68
+ wait: new Set(['wait', 'time']),
69
+ newpage: new Set([]),
70
+ dialog: new Set(['name', 'template']),
71
+ save: new Set(['slot', 'name', 'message']),
72
+ load: new Set(['slot', 'message']),
73
+ // サブノード
74
+ item: new Set(['label', 'id', 'default', 'hover', 'select', 'color', 'position']),
75
+ action: new Set(['id', 'label', 'value']),
76
+ then: new Set([]),
77
+ else: new Set([]),
78
+ prompt: new Set([]),
79
+ actions: new Set([]),
80
+ // インラインテキスト装飾 (drawer.ts の createDecoratedElement から)
81
+ color: new Set(['value']),
82
+ ruby: new Set(['text']),
83
+ b: new Set([]),
84
+ i: new Set([]),
85
+ br: new Set([]),
86
+ // HTTP サブタグ (header/data の子要素は自由形式のキーを使用するため、チェックしない)
87
+ header: new Set([]),
88
+ data: new Set([]),
89
+ error: new Set([]),
90
+ progress: new Set([]),
91
+ }
92
+
93
+ /**
94
+ * 親子関係が不正な場合のエラーメッセージを生成する。
95
+ * @param {string} nodeType - 誤った位置に配置されたノードのタイプ
96
+ * @param {string|null} parentType - 実際の親タイプ。ルートの場合は null
97
+ * @param {string[]} allowedParents - 許可された親タイプの配列
98
+ * @returns {string}
99
+ */
100
+ const buildInvalidParentMessage = (nodeType, parentType, allowedParents) => {
101
+ const allowed = allowedParents.map((p) => `<${p}>`).join(' or ')
102
+ const actual = parentType ? `<${parentType}>` : 'scenario root'
103
+ return `<${nodeType}> must be inside ${allowed}, but found inside ${actual}`
104
+ }
105
+
106
+ /**
107
+ * 未知の属性 (エンジンに無視される属性) の警告メッセージを生成する。
108
+ * @param {string} nodeType
109
+ * @param {string} attrName
110
+ * @returns {string}
111
+ */
112
+ const buildUnknownAttributeMessage = (nodeType, attrName) => {
113
+ return `<${nodeType}> has unknown attribute "${attrName}" which will be ignored`
114
+ }
115
+
116
+ /**
117
+ * エンジンで認識されない属性をノード単位でチェックする。
118
+ * 該当する属性ごとに unknown_attribute 警告を結果配列に追加する。
119
+ * @param {Object} node
120
+ * @param {Array} results - 結果オブジェクトを蓄積する配列
121
+ */
122
+ const checkAttributes = (node, results) => {
123
+ const nodeType = node.type
124
+ if (!nodeType) return
125
+ const knownForType = KNOWN_ATTRIBUTES[nodeType]
126
+ if (knownForType === undefined) return // 未知のノードタイプはスキップ
127
+ for (const key of Object.keys(node)) {
128
+ if (GLOBAL_ATTRIBUTES.has(key)) continue
129
+ if (knownForType.has(key)) continue
130
+ results.push({
131
+ type: 'unknown_attribute',
132
+ node: nodeType,
133
+ attribute: key,
134
+ message: buildUnknownAttributeMessage(nodeType, key),
135
+ })
136
+ }
137
+ }
138
+
139
+ /**
140
+ * ノードの親子関係の違反と未知の属性を再帰的にチェックする。
141
+ * @param {Array} nodes - パース済みシナリオノードの配列
142
+ * @param {string|null} parentType - 親ノードのタイプ。ルートの場合は null
143
+ * @param {Array} results - 結果オブジェクトを蓄積する配列
144
+ */
145
+ const checkNodes = (nodes, parentType, results) => {
146
+ if (!Array.isArray(nodes)) return
147
+ for (const node of nodes) {
148
+ if (typeof node !== 'object' || node === null) continue
149
+ const nodeType = node.type
150
+ if (!nodeType) continue
151
+
152
+ const allowedParents = ALLOWED_PARENTS[nodeType]
153
+ if (allowedParents && !allowedParents.includes(parentType)) {
154
+ results.push({
155
+ type: 'invalid_parent',
156
+ node: nodeType,
157
+ parent: parentType,
158
+ message: buildInvalidParentMessage(nodeType, parentType, allowedParents),
159
+ })
160
+ }
161
+
162
+ checkAttributes(node, results)
163
+
164
+ if (Array.isArray(node.content)) {
165
+ checkNodes(node.content, nodeType, results)
166
+ }
167
+ }
168
+ }
169
+
170
+ /**
171
+ * パース済みシナリオ配列の構文エラーと属性警告をチェックする。
172
+ * 結果オブジェクトの配列を返す:
173
+ * - type 'invalid_parent': 構造エラー (不正な親子関係)
174
+ * - type 'unknown_attribute': 警告 (エンジンが読み取らない属性)
175
+ * @param {Array} scenario - パース・フラット化されたシナリオ配列
176
+ * @returns {Array}
177
+ */
178
+ const check = (scenario) => {
179
+ const results = []
180
+ checkNodes(scenario, null, results)
181
+ return results
182
+ }
183
+
184
+ module.exports = { check, ALLOWED_PARENTS, KNOWN_ATTRIBUTES, GLOBAL_ATTRIBUTES }