From 33f98d7d6bc0cd7f3640391e17a9c1f35cb1638a Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 11:20:45 +0900 Subject: [PATCH 1/9] =?UTF-8?q?Steam=E3=81=AE=E3=83=93=E3=83=AB=E3=83=89?= =?UTF-8?q?=E3=81=A8=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A3=85=E3=80=81=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E9=9B=86=E7=B4=84(WIP)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .npmignore | 3 +- README.md | 30 +- docs/capacitor.md | 82 + docs/steam.md | 380 ++ package-lock.json | 6725 ++++--------------------- package.json | 21 +- src/cli.ts | 77 +- src/colors.ts | 8 + src/context.ts | 16 +- src/description.ts | 8 + src/electron-config.ts | 133 + src/electron-host.ts | 46 + src/electron.ts | 251 +- src/index.ts | 21 + src/native.ts | 193 +- src/project-vite.ts | 22 + src/resolve-tool-packages.ts | 30 + src/steam-upload.ts | 136 + src/steam.ts | 214 + src/tool-packages.ts | 78 + src/web.ts | 14 +- src/xbox.ts | 124 +- templates/electron/entitlements.plist | 12 + templates/electron/icons/icon.icns | Bin 0 -> 148198 bytes templates/electron/icons/icon.ico | Bin 0 -> 27745 bytes templates/electron/icons/icon.png | Bin 0 -> 19387 bytes templates/electron/index.js | 95 + templates/electron/local-assets.cjs | 24 + templates/electron/package.json | 11 + tests/cli.test.ts | 5 + tests/electron.test.ts | 206 + tests/native.test.ts | 104 + tests/project-vite.test.ts | 56 + tests/steam-shared.test.ts | 149 + tests/steam-upload.test.ts | 161 + tests/tool-packages.test.ts | 70 + tests/xbox.test.ts | 8 +- 37 files changed, 3486 insertions(+), 6027 deletions(-) create mode 100644 docs/capacitor.md create mode 100644 docs/steam.md create mode 100644 src/colors.ts create mode 100644 src/description.ts create mode 100644 src/electron-config.ts create mode 100644 src/electron-host.ts create mode 100644 src/project-vite.ts create mode 100644 src/resolve-tool-packages.ts create mode 100644 src/steam-upload.ts create mode 100644 src/steam.ts create mode 100644 src/tool-packages.ts create mode 100644 templates/electron/entitlements.plist create mode 100644 templates/electron/icons/icon.icns create mode 100644 templates/electron/icons/icon.ico create mode 100644 templates/electron/icons/icon.png create mode 100644 templates/electron/index.js create mode 100644 templates/electron/local-assets.cjs create mode 100644 templates/electron/package.json create mode 100644 tests/electron.test.ts create mode 100644 tests/native.test.ts create mode 100644 tests/project-vite.test.ts create mode 100644 tests/steam-shared.test.ts create mode 100644 tests/steam-upload.test.ts create mode 100644 tests/tool-packages.test.ts diff --git a/.npmignore b/.npmignore index 25f2d55..1465056 100644 --- a/.npmignore +++ b/.npmignore @@ -4,4 +4,5 @@ src .gitattributes .gitignore eslint.config.mjs -tsconfig.json \ No newline at end of file +tsconfig.json +docs \ No newline at end of file diff --git a/README.md b/README.md index 3186dd8..3f28fda 100644 --- a/README.md +++ b/README.md @@ -30,15 +30,12 @@ With Next2D Framework, you can efficiently develop and deploy applications optim | platform | detail | |-----------------|-------------------------------------| -| Windows | Export an exe file | -| macOS | Export dmg, app file | -| Linux | Export deb file | | Steam:Windows | Export an exe file | -| Steam:macOS | Export dmg, app file | -| Steam:Linux | Export deb file | +| Steam:macOS | Export application bundle (.app) | +| Steam:Linux | Export executable + resources | | web | Export minfy'd JS files | | iOS | open Xcode, and Export ipa | -| iOS | open Android Studio, and Export apk | +| Android | open Android Studio, and Export apk | | Xbox | GDK native title (V8 + Dawn/WebGPU) | Xbox exports a GDK native title that runs the Next2D JavaScript on the V8 engine and @@ -55,6 +52,15 @@ The prebuilt V8 engine is downloaded automatically on the first Xbox build (published from the `build-v8` workflow). To use your own build, pass `--v8-root C:\path\to\v8`. +## Platform guides + +Platform-specific configuration, dependencies and workflows are documented in these guides: + +| Guide | Contents | +|---|---| +| [Electron / Steam](docs/steam.md) | Desktop exports, icons, architectures, signing, Steam depots, uploads and beta testing | +| [iOS / Android (Capacitor)](docs/capacitor.md) | Native configuration, SDKs, plugins, build commands, migration and Xcode setup | + ### Scheduled introduction | platform | @@ -74,4 +80,14 @@ npx @next2d/builder --preview --platform web --env prd ``` ## License -This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. \ No newline at end of file +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. + +## Build dependencies + +The builder uses the target project's installed Vite for configuration loading, +web builds and Xbox host-script minification. Run `npm install` in your game first; +Vite is not installed again as a builder runtime dependency. Vite 7 and 8 are supported. + +The builder has no npm runtime dependencies. Node.js with npm/npx is required. +Terminal colors use Node.js `util.styleText`; Node typings and the builder's own Vite +installation are development dependencies. diff --git a/docs/capacitor.md b/docs/capacitor.md new file mode 100644 index 0000000..4f0bc88 --- /dev/null +++ b/docs/capacitor.md @@ -0,0 +1,82 @@ +# iOS / Android(Capacitor) + +Capacitorの `cli`・`core`・`ios`・`android` はbuilderの `src/tool-packages.ts` で同じバージョンに固定し、 +iOS / Androidの操作時に `npx` でまとめて取得する。builderの `dependencies` には含めない。 +ゲームは `npx @next2d/builder` で起動し、`capacitor.config.json` で +アプリ名・App ID・プラットフォーム固有設定を管理する。 +builderやCapacitorをゲームの `package.json` に直接宣言する必要はない。 + +```sh +npm run open:ios -- --env prd +npm run open:android -- --env prd +npm run build:ios -- --env prd +npm run build:android -- --env prd +``` + +builderを直接使う場合: + +```sh +npx @next2d/builder --platform ios --env prd --open +npx @next2d/builder --platform android --env prd --build +``` + +builderはnpmキャッシュに取得したCapacitor CLIをNodeで起動し、ゲームルートを作業ディレクトリにする。 +ゲームからSDKを解決できない場合に備え、子プロセスの `NODE_PATH` に取得したSDKのパスを追加する。 +通常のnpmインストール、依存がネストされた配置、隣接リポジトリの `file:../builder` に対応する。 +ゲーム側にCapacitor用の依存宣言や補助スクリプト、SDKへのシンボリックリンクを追加する必要はない。 +npm/npxを利用できるNode.js環境が必要。初回はネットワーク接続が必要で、以降はnpmキャッシュを再利用する。 +取得先は一時ホストと異なり書き出し後も残り、Gradle / CocoaPodsから参照できる。 + +`webDir` を今回のWeb出力先へ更新してから、未作成のネイティブプロジェクトに `cap add` を実行する。 +`--open` / `--build` は `cap sync` の成功後に `cap open` / `cap build` を実行する。 +`--preview` は `cap run` を実行する。各コマンドの終了を待ち、失敗した場合はbuilderも失敗する。 + +## ゲーム側に残すもの + +- `capacitor.config.json`:ゲーム固有設定。`ios.path` / `android.path` のカスタム出力先も使用できる。 +- `ios/` / `android/`:ネイティブプロジェクト。既存のアイコン・署名設定・ネイティブコードを維持する。 +- ゲーム固有のCapacitor/Cordovaプラグイン:ゲームの依存に置く。CLIは従来どおりゲームの依存とフックを参照する。 + +ゲームのJavaScriptで `@capacitor/core` を直接importする場合は、ビルド時の依存解決に必要なため、 +`@capacitor/core` をゲームの直接依存としても宣言する。その場合はbuilderのCapacitorと +互換性のあるバージョンを指定する。今回のSlimeTenPuzzleにはそのimportはない。 + +Xcode、Android Studio、Android SDK、対応JDK、署名証明書などは引き続き実行環境に必要。 +SDKのnpm依存を集約しても、端末実行・APK/AAB/IPAの生成に必要なネイティブツールは変わらない。 + +## iOSで `xcodebuild requires Xcode` が出る場合 + +`xcode-select -p` が `/Library/Developer/CommandLineTools` を返す場合、 +Command Line Toolsが選択されている。iOSのアーカイブ・IPA生成にはXcode本体とiOS SDKが必要。 +Xcodeをインストールし、初回起動時のセットアップを完了させる。 + +`/Applications/Xcode.app` を今回のビルドだけで使う場合は、ゲームのルートで実行する: + +```sh +DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ + npx @next2d/builder --platform ios --env prd --build +``` + +コマンドライン全体の既定をXcodeに切り替える場合: + +```sh +sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer +xcodebuild -version +``` + +Xcodeの配置先が異なる場合はパスを変更する。builderは呼び出し元の `DEVELOPER_DIR` を引き継ぐ。 +選択方法の詳細は[Appleのコマンドラインツール設定](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings)を参照。 + +## 既存プロジェクトの移行 + +1. この機能を含む公開済みbuilderを `npx` で使用する。 +2. ゲームが直接importしていない `@capacitor/cli`・`@capacitor/core`・`@capacitor/ios`・`@capacitor/android` を直接依存から外す。 +3. npmコマンド経由で `open:ios` / `open:android` を実行し、ネイティブ依存の参照先を再同期する。 + +隣接リポジトリで開発する場合はbuilderで `npm ci` または `npm install` を実行し、 +ゲームでも `npm install` を実行する。builderのソース変更後はbuilderで `npm run build` を実行する。 +npxキャッシュを削除した場合は、IDEが参照するSDKのパスを更新するため、 +builderの `--open` / `--build` で再同期する。 + +[Capacitorの開発フロー](https://capacitorjs.com/docs/basics/workflow) / +[Capacitor設定](https://capacitorjs.com/docs/config) diff --git a/docs/steam.md b/docs/steam.md new file mode 100644 index 0000000..3ac27fb --- /dev/null +++ b/docs/steam.md @@ -0,0 +1,380 @@ +# Electron / Steam 書き出し + +プロジェクトルートの `electron.config.json` でゲーム固有設定を管理する。 +`appId` は macOS bundle ID・保存先識別子、`steam.appId` は Valve の数値 App ID。 +これらは別のID。バージョンはゲームルートの `package.json` を使う。 + +```json +{ + "appId": "app.example.game", + "appName": "My Game", + "executableName": "my-game", + "companyName": "Example Company", + "icons": { + "windows": "src/assets/icons/icon.ico", + "macos": "src/assets/icons/icon.icns", + "linux": "src/assets/icons/icon.png" + }, + "architectures": { "windows": "x64", "macos": "universal", "linux": "x64" }, + "window": { "width": 1280, "height": 720, "fullscreen": false }, + "steam": { + "appId": null, + "branch": "internal", + "depots": { "windows": null, "macos": null, "linux": null } + }, + "macos": { "sign": false, "notarize": false } +} +``` + +| 項目 | 用途・仕様 | 省略時・注意点 | +|---|---|---| +| `companyName` | Windows実行ファイルの会社名。 | 省略時は `package.json` の `author.name`、なければゲーム名。 | +| `appName` / `executableName` | `appName` はウィンドウ・アプリケーション名。`executableName` は実行ファイル名。 | 表示名と実行ファイル名を個別に設定できる。 | +| `description` | 任意の説明文。生成するElectronホストの `package.json` とWindowsのファイル説明に反映する。 | JSONの値を優先。未指定または `null` ならルートの `package.json` の `description` を使う。両方未指定なら空文字。明示的な `""` も空文字として扱う。 | +| `icons` | アイコン画像のパス。プロジェクトルートからの相対パス、または絶対パスを指定する。WindowsはICO、macOSはICNS、LinuxはPNG。 | 各キーは省略可。省略したOSにはbuilder同梱の仮のNext2Dアイコンを使う。`"icons": {}` なら全OSで仮アイコンを使える。指定したファイルがなければ失敗する。 | +| `architectures` | OSごとのCPU設定。対応する値と指定例は後述の「対応OS・CPU」を参照。 | 省略時はWindows/Linuxが `x64`、macOSが `universal`。`--arch` が優先する。 | +| `steam.appId` | Valveの数値App ID。未発行なら `null` を指定する。 | 未発行でもローカル書き出しは可能。SteamPipe用VDFは生成しない。 | +| `steam.branch` | `--steam-upload` の反映先betaブランチ。 | 省略時は `internal`。`--steam-branch` が優先する。ブランチの作成・パスワード設定はSteamworks側で行う。 | +| `steam.depots` | Steamworksで作成した実際のDepot ID。複数OSに同じIDを指定すると、同一Depotへまとめて配布する。 | App IDから推測しない。`null` / 未指定ならアプリと起動情報だけを生成し、アップロード用VDFは生成しない。 | +| `macos` | macOSアプリの署名・公証設定。 | ローカル検証用は `sign` / `notarize` ともに `false`。本番配布では両方を有効化する。 | + +### テンプレートとApp作成時の初期値 + +JavaScript / TypeScriptの両テンプレートは `electron.config.json` と +`src/assets/icons/` の仮のNext2Dアイコン(ICO / ICNS / PNG)を含む。 +配布前にアイコンとゲーム固有の値を差し替える。Steam App ID / Depot IDの初期値は `null`。 + +`create-next2d-app` はプロジェクト名から `appId`、`appName`、`executableName`、 +`companyName` を設定する。例えば `my-game` なら `appId` は `app.example.my-game`、 +他の3項目は `my-game` になる。配布前にbundle IDと会社名を自分の値へ変更する。 +CPU設定が省略されている場合はOSごとの既定値を補い、テンプレートに指定済みの値は保持する。 +アイコンパスやSteam IDなどの設定も保持する。 + +### 対応OS・CPU + +| OS | 既定値 | `architectures` / `--arch` の対応値 | +|---|---|---| +| Windows | `x64` | `x64`, `arm64` | +| macOS | `universal` | `x64`, `arm64`, `universal` | +| Linux | `x64` | `x64`, `arm64` | + +32bit版Windows(`ia32`)には対応しない。`win32` はElectron内部のWindowsのOS名で、 +32bitを意味しない。例えば `My Game-win32-x64/` は64bit版Windows向けの成果物。 +`--platform` には `windows` または `steam:windows` を指定する。 + +macOSの `universal` はx64とarm64を含む1つの `.app` で、macOS上で生成する。 +Windows/Linuxの既定値はx64。ARM64版は `architectures.windows` / `architectures.linux` +に `arm64` を設定するか、書き出し時に `--arch arm64` を指定する。 + +```sh +npx @next2d/builder --platform steam:windows --env prd --arch arm64 +``` + +### 書き出しコマンド + +```sh +npx @next2d/builder --platform steam:windows --env prd +npx @next2d/builder --platform steam:macos --env prd +npx @next2d/builder --platform steam:linux --env prd +npx @next2d/builder --platform macos --env prd --preview +``` + +更新済みのテンプレートと `create-next2d-app` が用意するnpmコマンドでも実行できる。 + +```sh +npm run build:steam:windows +npm run build:steam:macos +npm run build:steam:linux +npm run build:steam:windows -- --arch arm64 +npm run build:steam:windows -- --env dev +``` + +これらのnpmコマンドは `--env prd` を指定済み。末尾の `-- --env dev` などで上書きできる。 +builderを直接呼ぶ場合は `--env` を指定する。 +書き出しコマンドはSteamへのアップロードや公開を自動では行わない。 + +### builderが管理するElectronホスト + +Electron関連でゲーム側に必要な設定ファイルは `electron.config.json` だけ。 +ゲーム名・バージョン・説明はルートの `package.json` と合わせてホストへ反映する。 +説明は `electron.config.json` の `description` を優先する。 +アイコン画像はゲームの共通アセットを参照でき、未指定ならbuilderの仮アイコンを使う。 + +builderはOSの一時ディレクトリにホスト・runtime設定・Web資産を配置し、書き出し後に削除する。 +ゲームルートへ `electron/` やElectron用の `node_modules` / lockfileを作らない。 +失敗時も一時ホストを削除する。Electronバージョンはbuilderの +`templates/electron/package.json` で固定し、Packagerが実行ファイルをダウンロード・キャッシュする。 +ゲーム側でElectronをインストールする処理は不要。 + +`@electron/packager` もbuilderのnpm依存には含めず、Electronの書き出し時だけ `npx` で取得する。 +バージョンは `src/tool-packages.ts` で固定する。npm/npxを利用できるNode.js環境が必要で、 +初回取得にはネットワーク接続が必要。取得済みツールはnpmキャッシュを再利用する。 +Web / Xboxビルドや `--steam-manifest` だけの実行ではPackagerを取得しない。 +PackagerはOS別パッケージの生成、アイコンの適用、署名・公証、Electron実行ファイルの取得を担当する。 +生成するホストにはnpm依存やネイティブアドオンがないため、ABIに合わせた再ビルドは不要。 + +プレビューも実行ホストのOS/CPU向けアプリを書き出して起動する。 +一時ホストの削除後、`dist//build//` のアプリを実行するので、 +本番書き出しと同じホスト・資産読み込みを確認できる。 + +旧 `electron/` のホストコード・`config.forge`・独自npm依存は参照しない。 +移行時はアイコンを共通アセットへ移し、設定パスを更新してから `electron/` を削除する。 +独自のmain/preloadやネイティブアドオンを持つプロジェクトは、移行前にbuilder側への対応が必要。 + +builder自体を開発中なら、ゲームのdevDependencyに `"@next2d/builder": "file:../builder"` を指定し、 +builder→ゲームの順に `npm ci` する。builder側のprepareでコンパイルされる。 +builderのソース変更後はbuilderで `npm run build` を実行する。 +公開後はこの依存を公開バージョンへ変更でき、ゲームのコマンドはそのまま使える。 + +## 成果物と起動 + +`dist/steam//build//` に以下を生成する(デフォルトoutDirの場合)。 +下表のExecutableはOS別の独立したDepotを使う場合。共有Depotの場合は後述のOS名プレフィックスが付く。 + +| OS | 配布するフォルダ全体 | SteamworksのExecutable | +|---|---|---| +| Windows | `My Game-win32-x64/` | `my-game.exe` | +| macOS | `My Game-darwin-universal/` | `My Game.app` | +| Linux | `My Game-linux-x64/` | `my-game` | + +`*-steampipe/launch.json` に実際の起動パス・CPU・ContentRootが記録される。 +実行ファイルだけを取り出さず、Electronのライブラリ、locales、ライセンス、resourcesを含む +フォルダ全体を配布する。Steamにはインストーラ(DMG/DEB/MSI)は不要。 +macOSの `.app` 内のシンボリックリンクとLinuxの実行権限を保持する。 +CIの転送は `tar.gz` にしてからartifactへ格納する。 + +テンプレートのホストは絶対パスと固定origin `next2d://game` で資産を読む。 +起動時の作業ディレクトリに依存せず、ローカルfetch・Worker・localStorageが使える。 +F11/Alt+Enterで全画面、Escapeで解除。閉じるとmacOSでもプロセスを終了する。 +Node integration無効・context isolation/sandbox有効、外部ページ遷移と新規ウィンドウは禁止。 +Next2D用CSPを付与する。外部APIを使うゲームでは接続先を明示的に追加すること。 +[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / +[protocol](https://www.electronjs.org/docs/latest/api/protocol) + +保存先はElectronのappData配下の `appId` ディレクトリ。 +表示名を変えても保存先は変わらない。旧file-originの開発版セーブは自動移行しない。 +Steam Cloudは未統合。Chromiumプロファイル全体をCloud対象にせず、導入時は +ゲーム用のセーブファイルとアカウント単位の保存方式を別途設計する。 +[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) + +## 同じDepotを複数OSで使う場合 + +`steam.depots` の同じIDを共有するOSを1グループとして扱う。例えば全OSに同じDepot IDを +指定すると、Steamのインストール先を次のように分け、Electronの同名ファイルの衝突を避ける。 + +| OS | SteamworksのExecutable(例) | +|---|---| +| Windows | `windows/my-game.exe` | +| macOS | `macos/My Game.app` | +| Linux | `linux/my-game` | + +Steamworks側のDepot対象OSを共有するOSまたはAll OSesに設定し、Launch Optionには +生成された `launch.json` のExecutableを指定する。OS別の起動条件も設定する。 +同一Depot内の全OS分のファイルが配布されるため、OS別Depotよりダウンロード容量は増える。 +ValveはOS固有ファイルには別Depotを推奨しているが、OS共有自体は禁止していない。 +[公式DepotのOS設定](https://partner.steamgames.com/doc/store/application/depots) + +各OSの書き出しは単独で成功する。共有対象の全OSの成果物が揃うと自動で +`dist/steam/shared//depot-/` に `app_build.vdf`、`app_preview.vdf`、 +`depot_build.vdf`、`launch.json` を生成する。未完了なら `launch.json` に不足OSを記録し、 +アップロード用VDFは生成しない。共有DepotのOS単独VDFも生成しない。 + +```sh +npx @next2d/builder --platform steam:windows --env prd +npx @next2d/builder --platform steam:macos --env prd +npx @next2d/builder --platform steam:linux --env prd +``` + +統合VDFは各OSのパッケージを `FileMapping` で `windows/` / `macos/` / `linux/` へ配置する。 +資産の再コピーは行わず、元パッケージ内のmacOSシンボリックリンク・Linux実行権限を保持する。 +VDFだけではなく `dist/steam/` 全体のディレクトリ構成を保持して移動すること。 +[公式FileMapping仕様](https://partner.steamgames.com/doc/sdk/uploading) + +CI等で別のマシン上で書き出した場合は、各OSの `build//` を +`steam-package.json` と `*-steampipe/` を含めて同じ `dist/steam//build//` に集め、次を実行する。 + +```sh +npx @next2d/builder --platform steam:macos --env prd --steam-manifest +``` + +更新済みのテンプレートと `create-next2d-app` では、同じ処理を次のnpmコマンドで実行できる。 + +```sh +npm run build:steam:manifest +# prd以外の成果物を統合する場合 +npm run build:steam:manifest -- --env dev +``` + +このコマンドはWebビルドやElectron書き出しを行わず、共有DepotのVDFだけを再生成する。 +どのOS上でも実行できる。`--platform` はVite設定を読むためのSteamターゲットで、統合対象は +設定内のすべての共有Depot。共有対象のパッケージが不足していれば終了コード1になる。 +`build.outDir` と `--env` を使い、各OSの最後に成功した書き出しを選ぶ(`--arch` 指定も記録される)。 +同じOSのx64版とarm64版を順に書き出した場合、統合対象になるのは最後に成功したCPUの成果物。 +1つの共有Depotへ同じOSの複数CPU版を同時にまとめる処理は行わない。 +`--steam-manifest` は `--arch` / `--preview` / `--build` / `--open` と併用できない。 +App ID・Depot ID・ゲーム名・実行ファイル名・バージョンが現設定と一致する必要がある。 +同じバージョン番号で更新する場合も、配布する全OSを意図したリビジョンで揃えること。 + +一部OSだけIDを共有する構成にも対応する。共有しないOSは従来どおり単独VDFを生成する。 +設定やバージョンを変更した場合は対象OSを再度書き出す。古い統合VDFは再生成時に無効化する。 + +## macOSの配布用署名・公証 + +Steam用は `darwin` であり、Mac App Store向けの `mas` ではない。 +Valveは新規macOSアプリに64bitとAppleの公証を要求している。 +テンプレートのentitlementsはJITとSteam Overlayに必要なlibrary validation / DYLD設定を含み、 +`com.apple.security.app-sandbox` は付けない(Chromiumのrenderer sandboxとは別)。 +[Valveのプラットフォーム要件](https://partner.steamgames.com/doc/store/application/platforms) + +macOS上でDeveloper ID Application証明書をキーチェーンへ登録し、Appleの +`xcrun notarytool store-credentials` で公証用プロファイルを保存してから実行する。 +認証情報はJSONへ書かない。 + +```sh +export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' +export APPLE_NOTARY_PROFILE='your-notary-profile' +NEXT2D_STEAM_RELEASE=1 npx @next2d/builder --platform steam:macos --env prd +``` + +`NEXT2D_STEAM_RELEASE=1` はJSONのfalse指定に関わらず署名と公証を必須にする。 +またはJSONの `macos.sign` / `macos.notarize` を両方trueにする。 +認証情報不足・署名失敗・公証失敗はビルド失敗となる。 +署名、公証、staple後にSteamPipeへ渡す。確認例: + +```sh +codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +``` + +[Electron Packagerの署名・公証設定](https://electron.github.io/packager/main/interfaces/Options.html) + +## Steamworks / SteamPipe + +1. Steamworks > SteamPipe > Depotsで配布構成に合わせたDepotを作成し、対象OSを指定する。 + OS別Depotと、複数OS向けの同一Depotのどちらも使用できる。 + 言語共通ならAll languages。開発用・販売用Packageにも各Depotを含める。 +2. Installation > General InstallationにOS別Launch Optionを追加する。 + Executableには生成された `launch.json` の値を指定。Arguments/Working Directoryは空欄。 + x64版は64bit条件を指定し、macOS UniversalはIntel/Apple Silicon両方でテストする。 +3. 変更をSteamworksでPublishする。配布したいOS・最低OS要件は、同梱するElectronの + 対応バージョンと実機テストに合わせる(古いSteamページの最低OS表だけで決めない)。 +4. JSONへApp ID/Depot IDを設定して再ビルド。単独Depotは `*-steampipe/`、 + 共有Depotは全対象OSが揃った時点で `shared//depot-/` に次のVDFが出る。 + `app_preview.vdf`: ファイルマッピング検証。`app_build.vdf`: アップロード。 + `depot_build.vdf`: フォルダ内容をインストールルートへ再帰マッピングする。 + `steam_appid.txt` は配布対象から除外する。 +5. Steamworks SDKのContentBuilder/SteamCMDを取得し、権限のあるビルドアカウントでログインする。 + パスワードはコマンド・リポジトリへ保存しない。 + +```text +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit +``` + +ContentRootはVDFからの相対パス。単独Depotはアプリと `*-steampipe/` をセットで、 +共有Depotは `dist/steam/` 全体の構成を保持して移動する。 +通常の書き出し・`--steam-manifest` が生成するVDFにはSetLiveを入れていない。アップロード後にSteamworksでパスワード付きbetaへ設定し、 +各OSのSteamクライアントからインストールして検証する。 +OSごとのVDFは1つのDepotを更新するため、最終BuildのDepot manifestが全OSで意図した版になっているか確認する。 +[SteamPipe公式手順・VDF仕様](https://partner.steamgames.com/doc/sdk/uploading) / +[Depots](https://partner.steamgames.com/doc/store/application/depots) + +## builderからアップロードしてinternalでテストする + +`--steam-upload` は書き出し済みの成果物をSteamCMDで一括アップロードし、 +`SetLive` で指定したbetaブランチへの反映を要求する。Web/Electronの再ビルドは行わない。 +`--platform` は不要で、`--build` / `--preview` / `--open` / `--steam-manifest` / `--arch` とは併用しない。 +builderのnpm依存は追加していない。SteamCMDは別途用意する。 + +### Steamworksで関係者向けの配布先を用意する + +1. Steamworksのアプリ管理画面 > SteamPipe > Buildsで `internal` ブランチを作成する。 +2. **ブランチにパスワードを設定し、関係者だけに共有する。** `internal` という名前だけでは非公開にならない。 + builderはブランチの作成やパスワード設定・確認を行わない。パスワードはJSONやリポジトリに保存しない。 +3. テスターがゲームと対象Depotを利用できるPackageのライセンスを持っていることを確認する。 + 未発売ゲームの外部テスターにはRelease State Override(beta)キー等を使用する。 + ブランチのパスワードだけではゲームの利用権は付与されない。 +4. 関係者のSteamクライアントで、ゲームのプロパティ > ゲームバージョンとベータから + パスワードを入力して `internal` を選ぶ。 + +`default` ブランチはこのコマンドでは指定できない。一般公開用ビルドの切り替えはSteamworksで行う。 +betaのパスワードは共有秘密なので、第三者に渡ればアクセスできる。個々のテスターの利用権も管理する。 + +[公式betaブランチ設定](https://partner.steamgames.com/doc/store/application/branches) / +[Steamでのテスト](https://partner.steamgames.com/doc/store/testing) / +[Release State Overrideキー](https://partner.steamgames.com/doc/features/keys) + +### SteamCMDの準備と認証 + +[Steamworks SDKのContentBuilder](https://partner.steamgames.com/doc/sdk/uploading)にある実行OS用のSteamCMDを用意する。 +Windowsは `steamcmd.exe`、macOS / Linuxは `steamcmd.sh` を使用できる。 +PATH上の `steamcmd`(Windowsは `steamcmd.exe`)を使うか、環境変数 `STEAMCMD` に実行ファイルのパスを指定する。 +引数やシェルコマンドを含めず、パスだけを指定する。`.sh` は実行権限が必要。 +SteamCMD自体の実行に必要なOSライブラリはSteamCMDの手順に従って用意する。 + +アップロード用アカウントには対象アプリの `Edit App Metadata` と `Publish App Changes To Steam` の権限を付与する。 +macOS / Linuxの設定例(配置先とアカウント名を置き換える): + +```sh +export STEAMCMD="/absolute/path/to/steamcmd.sh" +export STEAM_USERNAME="your_build_account" +"$STEAMCMD" +login "$STEAM_USERNAME" +quit +``` + +初回ログインではSteamCMDにパスワードとSteam Guardコードを対話入力する。 +その後builderは同じSteamCMDの保存済みログインを使い、パスワードをコマンド引数へ渡さない。 +認証が失効した場合は同じSteamCMDで再ログインする。builderからの実行は対話入力を待たず失敗させる。 +CIではSteamCMDの `config/config.vdf` をSecretとして復元・管理し、ビルド成果物に含めない。 +Windows PowerShellでは `$env:STEAMCMD` と `$env:STEAM_USERNAME` を設定する。 + +### 検証とアップロード + +`electron.config.json` の `steam.depots` に設定された全OSの成果物を揃え、ゲームのルートで実行する。 +同じDepot IDを使うOSはOS別サブディレクトリへまとめ、異なるDepotも1回のAppBuildでアップロードする。 + +```sh +# ローカル検証だけ。SteamCMDや認証は不要、Steamへの接続・送信も行わない +npx @next2d/builder --steam-upload --env prd --dry-run + +# アップロードし、steam.branch(省略時internal)へ反映 +npx @next2d/builder --steam-upload --env prd + +# ブランチや成果物の収集先を明示する場合 +npx @next2d/builder --steam-upload --env prd --steam-branch internal --steam-root dist/steam +``` + +ローカル開発中は `npx @next2d/builder` を `node ../builder/dist/index.js` に置き換える。 +この機能を含むbuilderの公開後は、ゲーム側の `npm run upload:steam:check` / `npm run upload:steam` も使用できる。 + +`--steam-root` はゲームルートからの相対パス、または絶対パス。既定は `dist/steam`。 +アップロード処理ではVite設定を読み込まないため、独自の出力先を使う場合はこの引数を指定する。 +成果物の `steam-package.json` にあるApp ID・Depot ID・バージョン・ゲーム名・CPUと実行ファイルを検証し、 +不足や不整合があればSteamCMDを起動する前に停止する。VDFは既存ファイルを流用せず検証結果から再生成する。 + +各実行のVDFと `upload-plan.json` は `dist/steam/uploads//-<識別子>/` に保存する。 +dry-runでは `Preview=1` のVDFを生成して `SetLive` を付けない。これはローカル検証であり、 +SteamCMDの認証・サーバー側の権限・ブランチ・パスワード設定が正しいことを保証するものではない。 +通常の書き出し用VDFは変更しない。SteamPipeのログ・差分キャッシュは `uploads//cache//` に置く。 + +アップロード時は終了コードとSteamCMDの成功メッセージを確認し、BuildIDを `upload-result.json` に記録する。 +SteamworksのBuilds画面で、記録されたBuildIDが `internal` の現在のビルドになったことを確認する。 +アカウントやアプリの状態によって、Steam側で追加の確認が必要になる場合がある。 + +## リリース前の確認範囲 + +- 対象OSのSteamからインストール・起動・終了、オフライン起動、更新後の保存データ。 +- マウス/キーボード/コントローラ、解像度・全画面切替、音声、スリープ復帰。 +- Shift+Tab Overlay。Electronは複数プロセスなので、OS/GPUごとに実動作を確認する。 + この書き出しはSteamworks SDK・実績・DRM・Overlay APIを統合しない。 + SDK統合はSteam配布の必須条件ではない。 +- LinuxはSteam Linux Runtime環境で依存ライブラリとsandboxを確認する。 + `--no-sandbox` を配布用の回避策にしない。 +- Steam DeckのVerified判定は別審査。Linux版が生成できたことだけでは対応完了にならない。 + コントローラだけで操作できること、文字の可読性、画面表示等を実機で確認する。 +- Steamのビルド審査前に、ストアで宣言したOS・機能と動作が一致することを確認する。 + +[Steamworks API](https://partner.steamgames.com/doc/sdk/api) / +[Overlay](https://partner.steamgames.com/doc/features/overlay) / +[Linux開発](https://partner.steamgames.com/doc/store/application/platforms/linux) / +[Steam Deck互換性](https://partner.steamgames.com/doc/steamhardware/compat) / +[ビルド審査](https://partner.steamgames.com/doc/store/review_process) diff --git a/package-lock.json b/package-lock.json index 5764177..3781578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,620 +1,51 @@ { "name": "@next2d/builder", - "version": "0.1.11", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@next2d/builder", - "version": "0.1.11", + "version": "0.2.0", "license": "MIT", - "dependencies": { - "@electron-forge/core": "^7.11.2", - "@types/node": "^26.4.0", - "picocolors": "^1.1.1", - "vite": "^8.2.2" - }, "bin": { "builder": "dist/index.js" }, "devDependencies": { "@eslint/eslintrc": "^3.3.7", "@eslint/js": "^10.0.1", - "@typescript-eslint/eslint-plugin": "^8.69.0", - "@typescript-eslint/parser": "^8.69.0", - "eslint": "^10.9.1", + "@types/node": "^26.6.2", + "@typescript-eslint/eslint-plugin": "^8.70.1", + "@typescript-eslint/parser": "^8.70.1", + "eslint": "^10.11.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.12.0", - "typescript": "^6.0.3" - } - }, - "node_modules/@electron-forge/core": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-7.11.2.tgz", - "integrity": "sha512-RbOvlCahSlYBkY1XFgD5QuoifZltEY3ezYGqJYnV1z6RiUK1DfUXwdidmclBLI9d6u8NNr9xWPv79LHVc9ZA3Q==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/electron" - } - ], - "license": "MIT", - "dependencies": { - "@electron-forge/core-utils": "7.11.2", - "@electron-forge/maker-base": "7.11.2", - "@electron-forge/plugin-base": "7.11.2", - "@electron-forge/publisher-base": "7.11.2", - "@electron-forge/shared-types": "7.11.2", - "@electron-forge/template-base": "7.11.2", - "@electron-forge/template-vite": "7.11.2", - "@electron-forge/template-vite-typescript": "7.11.2", - "@electron-forge/template-webpack": "7.11.2", - "@electron-forge/template-webpack-typescript": "7.11.2", - "@electron-forge/tracer": "7.11.2", - "@electron/get": "^3.0.0", - "@electron/packager": "^18.3.5", - "@electron/rebuild": "^3.7.0", - "@malept/cross-spawn-promise": "^2.0.0", - "@vscode/sudo-prompt": "^9.3.1", - "chalk": "^4.0.0", - "debug": "^4.3.1", - "eta": "^3.5.0", - "fast-glob": "^3.2.7", - "filenamify": "^4.1.0", - "find-up": "^5.0.0", - "fs-extra": "^10.0.0", - "global-dirs": "^3.0.0", - "got": "^11.8.5", - "interpret": "^3.1.1", - "jiti": "^2.4.2", - "listr2": "^7.0.2", - "log-symbols": "^4.0.0", - "node-fetch": "^2.6.7", - "rechoir": "^0.8.0", - "semver": "^7.2.1", - "source-map-support": "^0.5.13", - "username": "^5.1.0" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/core-utils": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-7.11.2.tgz", - "integrity": "sha512-/Fpwo44an6ulUdq94co5OOcbRCohgYNci/E6eoZZuTO9f72X+PqJkMkghqkMX3iQ8Aq2QRLkGKFwrKWJNTjL7Q==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "@electron/rebuild": "^3.7.0", - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.3.1", - "find-up": "^5.0.0", - "fs-extra": "^10.0.0", - "log-symbols": "^4.0.0", - "parse-author": "^2.0.0", - "semver": "^7.2.1" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/maker-base": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-7.11.2.tgz", - "integrity": "sha512-9934zYu9WVdgCYQXvtS+eL1oyLagsY8JlWhZmoK8yWTYftSAydH7jb3seVpfy6n85SYmY/yjcAy2lvOTy5dUwA==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "fs-extra": "^10.0.0", - "which": "^2.0.2" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/plugin-base": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-7.11.2.tgz", - "integrity": "sha512-tIFzEE2+D9NnCAn/rLwSkh8H59IqN+G973JNl7xmCzquO6qa7/veitZOQFGO79Zmmgkc8R/fmiCbh7LIdLS9Tg==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/publisher-base": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-7.11.2.tgz", - "integrity": "sha512-YwK4ZF3+uW7PBEV/ho59NVTriP3fCahskORrztUaFIdG0QP3hqMsfmo01euv98FDsBEW9UXo7/EW8t5jpmYZ0Q==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/shared-types": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-7.11.2.tgz", - "integrity": "sha512-Tcles7y74xy3jN5dEC+Pt1duJYk4c7W2xu98tjWW8RewmfKD2uHkie6I1I3yifPFZXZ/QfTlaFOOoKIQ9ENZjg==", - "license": "MIT", - "dependencies": { - "@electron-forge/tracer": "7.11.2", - "@electron/packager": "^18.3.5", - "@electron/rebuild": "^3.7.0", - "listr2": "^7.0.2" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-base": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/template-base/-/template-base-7.11.2.tgz", - "integrity": "sha512-l10I+XZRbbxFGiDLMnuXmlOppmLYmimKj6FWjEGUvft4VJFXW2BIDrLIugIGdM1nbrl/0aYjen2xRg0nZlcWzg==", - "license": "MIT", - "dependencies": { - "@electron-forge/core-utils": "7.11.2", - "@electron-forge/shared-types": "7.11.2", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "fs-extra": "^10.0.0", - "semver": "^7.2.1", - "username": "^5.1.0" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-vite": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/template-vite/-/template-vite-7.11.2.tgz", - "integrity": "sha512-yFSDSu3IdyNpgLXzrwODSUyaWniHRSZI82gwcXdnJLx7D7DIDLtbx6KzEoy7QBmWZRULO3F7rLsYG+Ur7orvyA==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "@electron-forge/template-base": "7.11.2", - "fs-extra": "^10.0.0" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-vite-typescript": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.11.2.tgz", - "integrity": "sha512-QvvdmO9Gdv+3aISI9+bBLKPBTyKaucs6HhXxz+IDALcdykIL9wVN0/BrWuwwgbwuw4BiJTyXGSPNXuJ+EWnP6g==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "@electron-forge/template-base": "7.11.2", - "fs-extra": "^10.0.0" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-webpack": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack/-/template-webpack-7.11.2.tgz", - "integrity": "sha512-JjG8XIZctrSZvTlii7Hqvt/pHDKigRk4PoLTQCs1TiT05ZWsn40itBm8cbja3L7bfm0ccDd3JTWWOl2G7PhlmA==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "@electron-forge/template-base": "7.11.2", - "fs-extra": "^10.0.0" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-webpack-typescript": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.11.2.tgz", - "integrity": "sha512-2lwK+OrCeZgYM8WqsUXJzk94rdF0z/kA7WnAf79U3COEmAAMcFIwJtwF8c/n+52UecP3yrEE70LIGmM1sjGZJQ==", - "license": "MIT", - "dependencies": { - "@electron-forge/shared-types": "7.11.2", - "@electron-forge/template-base": "7.11.2", - "fs-extra": "^10.0.0", - "typescript": "~5.4.5", - "webpack": "^5.69.1" - }, - "engines": { - "node": ">= 16.4.0" - } - }, - "node_modules/@electron-forge/template-webpack-typescript/node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@electron-forge/tracer": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-7.11.2.tgz", - "integrity": "sha512-U8j5Hyj2Zt7I5PciJvPJfmEv69Gb/Da9v+k655z3Jj1cuY0UnToEJ61IhXrzlTYqo+jUKC+fgAjDJ6vltJTS0A==", - "license": "MIT", - "dependencies": { - "chrome-trace-event": "^1.0.3" - }, - "engines": { - "node": ">= 14.17.5" - } - }, - "node_modules/@electron/asar": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", - "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", - "license": "MIT", - "dependencies": { - "commander": "^5.0.0", - "glob": "^7.1.6", - "minimatch": "^3.0.4" - }, - "bin": { - "asar": "bin/asar.js" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/@electron/get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", - "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", - "progress": "^2.0.3", - "semver": "^6.2.0", - "sumchecker": "^3.0.1" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/@electron/get/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/@electron/get/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/get/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "typescript": "^6.0.3", + "vite": "^8.3.0" } }, - "node_modules/@electron/get/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/@electron/node-gyp": { - "version": "10.2.0-electron.1", - "resolved": "git+ssh://git@github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", - "integrity": "sha512-CrYo6TntjpoMO1SHjl5Pa/JoUsECNqNdB7Kx49WLQpWzPw53eEITJ2Hs9fh/ryUYDn4pxZz11StaBYBrLFJdqg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^8.1.0", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.2.1", - "nopt": "^6.0.0", - "proc-log": "^2.0.1", - "semver": "^7.3.5", - "tar": "^6.2.1", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@electron/node-gyp/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@electron/node-gyp/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/node-gyp/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" } }, - "node_modules/@electron/notarize": { + "node_modules/@cacheable/utils": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", - "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "fs-extra": "^9.0.1", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@electron/notarize/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/osx-sign": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", - "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", - "license": "BSD-2-Clause", - "dependencies": { - "compare-version": "^0.1.2", - "debug": "^4.3.4", - "fs-extra": "^10.0.0", - "isbinaryfile": "^4.0.8", - "minimist": "^1.2.6", - "plist": "^3.0.5" - }, - "bin": { - "electron-osx-flat": "bin/electron-osx-flat.js", - "electron-osx-sign": "bin/electron-osx-sign.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@electron/packager": { - "version": "18.4.4", - "resolved": "https://registry.npmjs.org/@electron/packager/-/packager-18.4.4.tgz", - "integrity": "sha512-fTUCmgL25WXTcFpM1M72VmFP8w3E4d+KNzWxmTDRpvwkfn/S206MAtM2cy0GF78KS9AwASMOUmlOIzCHeNxcGQ==", - "license": "BSD-2-Clause", - "dependencies": { - "@electron/asar": "^3.2.13", - "@electron/get": "^3.0.0", - "@electron/notarize": "^2.1.0", - "@electron/osx-sign": "^1.0.5", - "@electron/universal": "^2.0.1", - "@electron/windows-sign": "^1.0.0", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.0.1", - "extract-zip": "^2.0.0", - "filenamify": "^4.1.0", - "fs-extra": "^11.1.0", - "galactus": "^1.0.0", - "get-package-info": "^1.0.0", - "junk": "^3.1.0", - "parse-author": "^2.0.0", - "plist": "^3.0.0", - "prettier": "^3.4.2", - "resedit": "^2.0.0", - "resolve": "^1.1.6", - "semver": "^7.1.3", - "yargs-parser": "^21.1.1" - }, - "bin": { - "electron-packager": "bin/electron-packager.js" - }, - "engines": { - "node": ">= 16.13.0" - }, - "funding": { - "url": "https://github.com/electron/packager?sponsor=1" - } - }, - "node_modules/@electron/packager/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/rebuild": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.7.2.tgz", - "integrity": "sha512-19/KbIR/DAxbsCkiaGMXIdPnMCJLkcf8AvGnduJtWBs/CBwiAjY1apCqOLVxrXg+rtXFCngbXhBanWjxLUt1Mg==", - "license": "MIT", - "dependencies": { - "@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2", - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "fs-extra": "^10.0.0", - "got": "^11.7.0", - "node-abi": "^3.45.0", - "node-api-version": "^0.2.0", - "ora": "^5.1.0", - "read-binary-file-arch": "^1.0.6", - "semver": "^7.3.5", - "tar": "^6.0.5", - "yargs": "^17.0.1" - }, - "bin": { - "electron-rebuild": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@electron/universal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", - "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", - "license": "MIT", - "dependencies": { - "@electron/asar": "^3.3.1", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "dir-compare": "^4.2.0", - "fs-extra": "^11.1.1", - "minimatch": "^9.0.3", - "plist": "^3.1.0" - }, - "engines": { - "node": ">=16.4" - } - }, - "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@electron/windows-sign": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", - "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", - "license": "BSD-2-Clause", - "dependencies": { - "cross-dirname": "^0.1.0", - "debug": "^4.3.4", - "fs-extra": "^11.1.1", - "minimist": "^1.2.8", - "postject": "^1.0.0-alpha.6" - }, - "bin": { - "electron-windows-sign": "bin/electron-windows-sign.js" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", - "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" + "hashery": "^1.5.1", + "keyv": "^5.6.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -672,9 +103,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -795,9 +226,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -808,12 +239,6 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -880,151 +305,48 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", - "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@malept/cross-spawn-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", - "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/malept" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" - } - ], - "license": "Apache-2.0", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "license": "ISC", - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@oxc-project/types": { - "version": "0.147.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", - "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "version": "0.150.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.150.0.tgz", + "integrity": "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==", + "dev": true, "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", - "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.9.tgz", + "integrity": "sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1035,12 +357,13 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", - "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.9.tgz", + "integrity": "sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1051,12 +374,13 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", - "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.9.tgz", + "integrity": "sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1067,12 +391,13 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", - "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.9.tgz", + "integrity": "sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1083,12 +408,13 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", - "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.9.tgz", + "integrity": "sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1099,12 +425,13 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", - "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.9.tgz", + "integrity": "sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1115,12 +442,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", - "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.9.tgz", + "integrity": "sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==", "cpu": [ "arm64" ], + "dev": true, "libc": [ "glibc" ], @@ -1134,12 +462,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", - "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.9.tgz", + "integrity": "sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==", "cpu": [ "arm64" ], + "dev": true, "libc": [ "musl" ], @@ -1153,12 +482,13 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", - "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.9.tgz", + "integrity": "sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==", "cpu": [ "ppc64" ], + "dev": true, "libc": [ "glibc" ], @@ -1172,12 +502,13 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", - "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.9.tgz", + "integrity": "sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==", "cpu": [ "s390x" ], + "dev": true, "libc": [ "glibc" ], @@ -1191,12 +522,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", - "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.9.tgz", + "integrity": "sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==", "cpu": [ "x64" ], + "dev": true, "libc": [ "glibc" ], @@ -1210,12 +542,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", - "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.9.tgz", + "integrity": "sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==", "cpu": [ "x64" ], + "dev": true, "libc": [ "musl" ], @@ -1229,12 +562,13 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", - "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.9.tgz", + "integrity": "sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1245,12 +579,13 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", - "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.9.tgz", + "integrity": "sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1261,12 +596,13 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", - "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.9.tgz", + "integrity": "sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1280,53 +616,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", - "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -1338,69 +630,38 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "license": "MIT" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { - "version": "26.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", - "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", + "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@types/node": "*" + "undici-types": "~8.9.0" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.69.0.tgz", - "integrity": "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz", + "integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.69.0", - "@typescript-eslint/type-utils": "8.69.0", - "@typescript-eslint/utils": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/type-utils": "8.70.1", + "@typescript-eslint/utils": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1413,15 +674,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.69.0", + "@typescript-eslint/parser": "^8.70.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.8.tgz", - "integrity": "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.9.tgz", + "integrity": "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==", "dev": true, "license": "MIT", "engines": { @@ -1429,16 +690,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.69.0.tgz", - "integrity": "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz", + "integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.69.0", - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/typescript-estree": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1454,14 +715,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.69.0.tgz", - "integrity": "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.69.0", - "@typescript-eslint/types": "^8.69.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1476,14 +737,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.69.0.tgz", - "integrity": "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz", + "integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0" + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1494,9 +755,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.69.0.tgz", - "integrity": "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -1511,15 +772,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.69.0.tgz", - "integrity": "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz", + "integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/typescript-estree": "8.69.0", - "@typescript-eslint/utils": "8.69.0", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1536,9 +797,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.69.0.tgz", - "integrity": "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz", + "integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==", "dev": true, "license": "MIT", "engines": { @@ -1550,16 +811,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.69.0.tgz", - "integrity": "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.69.0", - "@typescript-eslint/tsconfig-utils": "8.69.0", - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/visitor-keys": "8.69.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1588,9 +849,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1617,16 +878,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.69.0.tgz", - "integrity": "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz", + "integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.69.0", - "@typescript-eslint/types": "8.69.0", - "@typescript-eslint/typescript-estree": "8.69.0" + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1641,13 +902,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.69.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.69.0.tgz", - "integrity": "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz", + "integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.69.0", + "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1671,189 +932,11 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@vscode/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.9.12", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", - "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", - "license": "MIT", - "engines": { - "node": ">=14.6" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1872,43 +955,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -1926,4045 +972,1014 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, "license": "MIT", "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, - "node_modules/ansi-escapes": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", - "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { - "type-fest": "^1.0.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/ansi-regex": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", - "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "ms": "^2.1.3" }, "engines": { - "node": ">=8" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "license": "Python-2.0" + "license": "MIT" }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "license": "ISC", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">= 4.0.0" + "node": ">=8" } }, - "node_modules/author-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", - "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/eslint": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", + "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" ], - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", - "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", - "license": "Apache-2.0", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/eslint-plugin-unused-imports": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", + "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", + "dev": true, "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "license": "MIT" - }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "optional": true - }, - "node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" - }, - "bin": { - "browserslist": "cli.js" - }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": "18 || 20 || >=22" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "license": "ISC", + "node_modules/eslint/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "brace-expansion": "^5.0.8" }, "engines": { - "node": ">=12" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "license": "ISC", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "brace-expansion": "^2.0.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=10" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.10" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, "engines": { - "node": ">=10" + "node": ">=4.0" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=6.0" + "node": ">=4.0" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "flat-cache": "^6.1.23" } }, - "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">=8" + "node": ">=10.13.0" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/globals": { + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "hookified": "^1.15.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": ">=20" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">= 4" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { - "mimic-response": "^1.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, "engines": { - "node": ">=7.0.0" + "node": ">=0.8.19" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/compare-version": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", - "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, - "node_modules/cross-dirname": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", - "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT", - "optional": true - }, - "node_modules/dir-compare": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", - "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.5", - "p-limit": "^3.1.0 " - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.418", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.418.tgz", - "integrity": "sha512-UzS26r3AEbG5wSoGVpJKqwHIU9zwQN7LHdVIThDrJpS0I5KdlXFMEb8543fhc9dVnIIAST6ar8rhwa00AL5MlA==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.24.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", - "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", - "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", - "license": "MIT" - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "license": "MIT", - "optional": true - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.1.tgz", - "integrity": "sha512-9VaAkDURekixUQJy0oJYl2DcN6oKMfxay7XzaGYAWQwsb6qfKf+x76R2k1L8kb1boc+FyCAaTA9GmiKaaiaF+A==", - "dev": true, - "license": "MIT", - "workspaces": [ - "packages/*" - ], - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.7.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-unused-imports": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz", - "integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", - "eslint": "^10.0.0 || ^9.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eta": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz", - "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", - "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", - "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "license": "MIT", - "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", - "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/flora-colossus": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz", - "integrity": "sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "fs-extra": "^10.1.0" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/galactus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz", - "integrity": "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "flora-colossus": "^2.0.0", - "fs-extra": "^10.1.0" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-info": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz", - "integrity": "sha512-SCbprXGAPdIhKAXiG+Mk6yeoFH61JlYunqdFQFHDtLjJlDjFf6x07dsS8acO+xWt52jpdVo49AlVDnUVK1sDNw==", - "license": "MIT", - "dependencies": { - "bluebird": "^3.1.1", - "debug": "^2.2.0", - "lodash.get": "^4.0.0", - "read-pkg-up": "^2.0.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/get-package-info/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/get-package-info/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "17.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", - "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "license": "ISC" - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ip-address": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", - "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "license": "MIT" - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "license": "MIT", - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC", - "optional": true - }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/listr2": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.2.tgz", - "integrity": "sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==", - "license": "MIT", - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^5.0.1", - "rfdc": "^1.3.0", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", - "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^5.0.0", - "cli-cursor": "^4.0.0", - "slice-ansi": "^5.0.0", - "strip-ansi": "^7.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "license": "ISC", - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "license": "MIT", - "dependencies": { - "p-defer": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "license": "MIT", - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "license": "MIT", - "dependencies": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minimizer-webpack-plugin": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz", - "integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.3", - "terser": "^5.51.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@minify-html/node": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "@swc/html": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "cssnano": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "html-minifier-terser": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "postcss": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "license": "MIT", - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.18", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", - "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "license": "MIT" - }, - "node_modules/node-abi": { - "version": "3.96.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", - "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.54", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", - "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "license": "ISC", - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "license": "MIT", - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-author": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", - "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==", - "license": "MIT", - "dependencies": { - "author-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "license": "MIT", - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", - "license": "MIT", - "dependencies": { - "pify": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pe-library": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-1.0.1.tgz", - "integrity": "sha512-nh39Mo1eGWmZS7y+mK/dQIqg7S1lp38DpRxkyoHf0ZcUs/HDc+yyTjuOtTvSMZHmfSLuSQaX945u05Y2Q6UWZg==", - "license": "MIT", - "engines": { - "node": ">=14", - "npm": ">=7" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/plist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", - "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", - "license": "MIT", - "dependencies": { - "@xmldom/xmldom": "^0.9.10", - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=10.4.0" - } - }, - "node_modules/postcss": { - "version": "8.5.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", - "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.17", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postject": { - "version": "1.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", - "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", - "license": "MIT", - "dependencies": { - "commander": "^9.4.0" - }, - "bin": { - "postject": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/postject/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/proc-log": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", - "integrity": "sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw==", - "license": "ISC", - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "license": "ISC" - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "url": "https://github.com/sponsors/puzrin" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/nodeca" } ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-binary-file-arch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", - "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "argparse": "^2.0.1" }, "bin": { - "read-binary-file-arch": "cli.js" + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==", - "license": "MIT", - "dependencies": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" }, - "node_modules/read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==", - "license": "MIT", - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" + "@keyv/serialize": "^1.1.1" } }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">=4" + "node": ">= 0.8.0" } }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "p-try": "^1.0.0" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "license": "MIT", - "dependencies": { - "p-limit": "^1.1.0" - }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/read-pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "license": "MIT", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 6" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 10.13.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/resedit": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resedit/-/resedit-2.0.3.tgz", - "integrity": "sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA==", - "license": "MIT", - "dependencies": { - "pe-library": "^1.0.1" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14", - "npm": ">=7" + "node": ">= 12.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "license": "MIT" - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT", + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", "dependencies": { - "glob": "^7.1.3" + "p-locate": "^5.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "license": "BSD-3-Clause", - "optional": true, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8.0" + "node": "*" } }, - "node_modules/rolldown": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", - "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.147.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.6", - "@rolldown/binding-android-arm64": "1.2.6", - "@rolldown/binding-darwin-arm64": "1.2.6", - "@rolldown/binding-darwin-x64": "1.2.6", - "@rolldown/binding-freebsd-x64": "1.2.6", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", - "@rolldown/binding-linux-arm64-gnu": "1.2.6", - "@rolldown/binding-linux-arm64-musl": "1.2.6", - "@rolldown/binding-linux-ppc64-gnu": "1.2.6", - "@rolldown/binding-linux-s390x-gnu": "1.2.6", - "@rolldown/binding-linux-x64-gnu": "1.2.6", - "@rolldown/binding-linux-x64-musl": "1.2.6", - "@rolldown/binding-openharmony-arm64": "1.2.6", - "@rolldown/binding-win32-arm64-msvc": "1.2.6", - "@rolldown/binding-win32-x64-msvc": "1.2.6" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" + "url": "https://github.com/sponsors/ai" } ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "optional": true - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, "license": "MIT" }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">=10" + "node": ">= 0.8.0" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "license": "MIT", - "optional": true - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "type-fest": "^0.13.1" + "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" @@ -5973,12 +1988,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "license": "(MIT OR CC0-1.0)", - "optional": true, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, "engines": { "node": ">=10" }, @@ -5986,375 +2004,236 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "callsites": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=8" } }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12 || >=14" } }, - "node_modules/source-map-js": { + "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "license": "ISC", - "dependencies": { - "minipass": "^3.1.1" - }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "node": ">= 0.8.0" } }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "hookified": "^2.1.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=20" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "node_modules/rolldown": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", + "integrity": "sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==", + "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "^1.0.2" + "@oxc-project/types": "=0.150.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-outer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.9", + "@rolldown/binding-android-arm64": "1.2.9", + "@rolldown/binding-darwin-arm64": "1.2.9", + "@rolldown/binding-darwin-x64": "1.2.9", + "@rolldown/binding-freebsd-x64": "1.2.9", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.9", + "@rolldown/binding-linux-arm64-gnu": "1.2.9", + "@rolldown/binding-linux-arm64-musl": "1.2.9", + "@rolldown/binding-linux-ppc64-gnu": "1.2.9", + "@rolldown/binding-linux-s390x-gnu": "1.2.9", + "@rolldown/binding-linux-x64-gnu": "1.2.9", + "@rolldown/binding-linux-x64-musl": "1.2.9", + "@rolldown/binding-openharmony-arm64": "1.2.9", + "@rolldown/binding-win32-arm64-msvc": "1.2.9", + "@rolldown/binding-win32-x64-msvc": "1.2.9" } }, - "node_modules/sumchecker": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", - "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.1.0" + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 8.0" + "node": ">=10" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=8" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" - } - }, - "node_modules/terser": { - "version": "5.51.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", - "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -6367,74 +2246,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trim-repeated/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6461,18 +2272,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -6488,74 +2287,12 @@ } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "dev": true, "license": "MIT" }, - "node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "license": "ISC", - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6566,45 +2303,17 @@ "punycode": "^2.1.0" } }, - "node_modules/username": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/username/-/username-5.1.0.tgz", - "integrity": "sha512-PCKbdWw85JsYMvmCv5GH3kXmM66rCd9m1hBEDutPNv94b/pqCMT4NtcKyeWYvLFiE8b+ha1Jdl8XAaUdPn5QTg==", - "license": "MIT", - "dependencies": { - "execa": "^1.0.0", - "mem": "^4.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.0.tgz", + "integrity": "sha512-lhZBVvEHefgE+HQZC9O7EBJgCU/nVzFNl7vkS4RE0APtWLP02/8QVIkQtzBxPquh7lq5/78NHipTj7ODQ6XuyQ==", + "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.6", "tinyglobby": "^0.2.17" }, "bin": { @@ -6621,7 +2330,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -6672,110 +2381,11 @@ } } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/watchpack": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", - "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/webpack": { - "version": "5.110.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.2.tgz", - "integrity": "sha512-TciLrfM7zgEjqGdY851HkirDsSPQgTFsWQpl9oHqMAMYsHhEC0bKjscvjpnz+pzx10hLC8qISApGrsnrCP4UtQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.24.4", - "es-module-lexer": "^2.1.0", - "events": "^3.2.0", - "graceful-fs": "^4.2.11", - "mime-db": "^1.54.0", - "minimizer-webpack-plugin": "^5.7.0", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "watchpack": "^2.5.2", - "webpack-sources": "^3.5.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", - "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6797,156 +2407,11 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "license": "MIT", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 7dc37af..85a7d58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@next2d/builder", - "version": "0.1.11", + "version": "0.2.0", "description": "Multi-platform builder for Next2d Framework, supporting export to various platforms such as macOS, Windows, iOS, Android and Web(HTML)", "author": "Toshiyuki Ienaga ", "license": "MIT", @@ -29,7 +29,8 @@ }, "files": [ "dist", - "templates" + "templates", + "docs" ], "repository": { "type": "git", @@ -38,20 +39,16 @@ "bin": { "@next2d/builder": "dist/index.js" }, - "dependencies": { - "@electron-forge/core": "^7.11.2", - "@types/node": "^26.4.0", - "picocolors": "^1.1.1", - "vite": "^8.2.2" - }, "devDependencies": { "@eslint/eslintrc": "^3.3.7", "@eslint/js": "^10.0.1", - "@typescript-eslint/eslint-plugin": "^8.69.0", - "@typescript-eslint/parser": "^8.69.0", - "eslint": "^10.9.1", + "@types/node": "^26.6.2", + "@typescript-eslint/eslint-plugin": "^8.70.1", + "@typescript-eslint/parser": "^8.70.1", + "eslint": "^10.11.0", "eslint-plugin-unused-imports": "^4.4.1", "globals": "^17.12.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vite": "^8.3.0" } } diff --git a/src/cli.ts b/src/cli.ts index c7d5c07..1877b20 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,5 @@ // CLI 引数の解析とヘルプ表示。process.argv を読み取り ctx を初期化する。 -import pc from "picocolors"; +import pc from "./colors.js"; import { ctx } from "./context.js"; import { RECOMMENDED_NODE_VERSION } from "./constants.js"; @@ -26,6 +26,11 @@ export const echoHelp = (): void => console.log("For Xbox build example (prebuilt V8 is downloaded automatically):"); console.log("npx @next2d/builder --platform xbox --env prd"); console.log("To use your own V8 build: --v8-root C:\\path\\to\\v8"); + console.log("Steam: --platform steam:windows|steam:macos|steam:linux --env prd"); + console.log("Electron: --arch x64|arm64|universal (default: macOS universal, others x64)"); + console.log("Combine existing Steam packages only: --platform steam:macos --env prd --steam-manifest"); + console.log("Upload existing packages: --steam-upload --env prd [--steam-branch internal] [--steam-root dist/steam] [--dry-run]"); + console.log("Steam upload uses STEAM_USERNAME and optional STEAMCMD (executable path); no --platform is needed."); console.log(); process.exit(1); }; @@ -40,6 +45,12 @@ export interface ParsedArgs { open: boolean; build: boolean; v8Root: string; + arch: string; + steamManifest: boolean; + steamUpload: boolean; + steamBranch: string; + steamRoot: string; + dryRun: boolean; hasHelp: boolean; } @@ -72,6 +83,12 @@ export const parseArgv = (argv: string[]): ParsedArgs => "open": false, "build": false, "v8Root": "", + "arch": "", + "steamManifest": false, + "steamUpload": false, + "steamBranch": "", + "steamRoot": "", + "dryRun": false, "hasHelp": false }; @@ -79,6 +96,33 @@ export const parseArgv = (argv: string[]): ParsedArgs => switch (argv[idx]) { + case "--steam-upload": + result.steamUpload = true; + break; + + case "--dry-run": + result.dryRun = true; + break; + + case "--steam-branch": + case "--steam-root": { + const flag = argv[idx]; + const value = argv[++idx] || ""; + if (!value || value.startsWith("--")) { + result.hasHelp = true; + } + if (flag === "--steam-branch") { + result.steamBranch = value; + } else { + result.steamRoot = value; + } + break; + } + + case "--steam-manifest": + result.steamManifest = true; + break; + case "--preview": result.preview = true; break; @@ -109,7 +153,17 @@ export const parseArgv = (argv: string[]): ParsedArgs => result.v8Root = argv[++idx] || ""; break; + case "--arch": + result.arch = argv[++idx] || ""; + if (!result.arch || result.arch.startsWith("--")) { + result.hasHelp = true; + } + break; + default: + if (argv.includes("--steam-upload") && argv[idx].startsWith("-")) { + result.hasHelp = true; + } break; } @@ -119,7 +173,16 @@ export const parseArgv = (argv: string[]): ParsedArgs => } } - if (!result.platform || !result.environment) { + if (!result.platform && !result.steamUpload || !result.environment || result.environment.startsWith("--")) { + result.hasHelp = true; + } + if (result.steamManifest && (!result.platform.startsWith("steam:") || result.preview || result.open || result.build || result.arch)) { + result.hasHelp = true; + } + if (result.steamUpload && (result.platform || result.steamManifest || result.preview || result.open || result.build || result.arch || result.v8Root)) { + result.hasHelp = true; + } + if (!result.steamUpload && (result.steamBranch || result.steamRoot || result.dryRun)) { result.hasHelp = true; } @@ -169,8 +232,14 @@ Please update your version of Node.`); ctx.open = parsed.open; ctx.build = parsed.build; ctx.v8Root = parsed.v8Root; - - if (parsed.hasHelp || !SUPPORTED_PLATFORMS.has(ctx.platform)) { + ctx.arch = parsed.arch; + ctx.steamManifest = parsed.steamManifest; + ctx.steamUpload = parsed.steamUpload; + ctx.steamBranch = parsed.steamBranch; + ctx.steamRoot = parsed.steamRoot; + ctx.dryRun = parsed.dryRun; + + if (parsed.hasHelp || !ctx.steamUpload && !SUPPORTED_PLATFORMS.has(ctx.platform)) { echoHelp(); } diff --git a/src/colors.ts b/src/colors.ts new file mode 100644 index 0000000..8e69ccf --- /dev/null +++ b/src/colors.ts @@ -0,0 +1,8 @@ +// Node handles TTY detection, NO_COLOR and FORCE_COLOR without a logging dependency. +import { styleText } from "node:util"; + +export default { + "red": (value: unknown): string => styleText("red", String(value)), + "green": (value: unknown): string => styleText("green", String(value)), + "yellow": (value: unknown): string => styleText("yellow", String(value)) +}; diff --git a/src/context.ts b/src/context.ts index f721a15..03f836e 100644 --- a/src/context.ts +++ b/src/context.ts @@ -19,8 +19,16 @@ export interface BuildContext { build: boolean; /** --v8-root (Xbox) */ v8Root: string; + /** Electron CPU architecture (--arch); empty selects a platform default. */ + arch: string; + /** Generate shared Steam depot manifests from existing packages only. */ + steamManifest: boolean; + steamUpload: boolean; + steamBranch: string; + steamRoot: string; + dryRun: boolean; /** vite の loadConfigFromFile 結果 */ - configObject: any | null; + configObject: Awaited>; /** ビルド出力ディレクトリ (dist//) */ buildDir: string; /** vite build.outDir (既定 "dist") */ @@ -39,6 +47,12 @@ export const ctx: BuildContext = { "open": false, "build": false, "v8Root": "", + "arch": "", + "steamManifest": false, + "steamUpload": false, + "steamBranch": "", + "steamRoot": "", + "dryRun": false, "configObject": null, "buildDir": "", "outDir": "dist", diff --git a/src/description.ts b/src/description.ts new file mode 100644 index 0000000..7a7ea0b --- /dev/null +++ b/src/description.ts @@ -0,0 +1,8 @@ +/** An explicit empty string is intentional; null/undefined use package metadata. */ +export const resolveDescription = (description: unknown, fallback: unknown): string => { + const value = description ?? fallback ?? ""; + if (typeof value !== "string") { + throw new Error("App description must be a string."); + } + return value; +}; diff --git a/src/electron-config.ts b/src/electron-config.ts new file mode 100644 index 0000000..820c3ee --- /dev/null +++ b/src/electron-config.ts @@ -0,0 +1,133 @@ +import fs from "node:fs"; +import path from "node:path"; +import { getTemplateDir } from "./utils.js"; +import { resolveDescription } from "./description.js"; + +export type ElectronOS = "windows" | "macos" | "linux"; +export interface IElectronConfig { + appId: string; + appName: string; + description: string; + executableName: string; + companyName: string; + icons: Partial>; + architectures: Partial>; + window: { width: number; height: number; fullscreen: boolean }; + steam?: { appId: string | null; depots: Partial>; branch?: string }; + macos: { sign: boolean; notarize: boolean }; +} + +export const validateSteamId = (value: unknown): string => { + const id = String(value); + if (!/^[1-9][0-9]*$/.test(id) || !Number.isSafeInteger(Number(id)) || Number(id) > 4294967295) { + throw new Error("Steam App ID and Depot IDs must be positive 32-bit integers."); + } + return id; +}; + +export const readElectronConfig = (root: string): IElectronConfig => { + const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")); + const file = path.join(root, "electron.config.json"); + const input = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : {}; + const config: IElectronConfig = { + "appId": input.appId ?? `app.next2d.${pkg.name}`, + "appName": input.appName ?? pkg.name, + "description": resolveDescription(input.description, pkg.description), + "executableName": input.executableName ?? pkg.name, + "companyName": input.companyName ?? pkg.author?.name ?? input.appName ?? pkg.name, + "icons": { + "windows": path.join(getTemplateDir("electron"), "icons/icon.ico"), + "macos": path.join(getTemplateDir("electron"), "icons/icon.icns"), + "linux": path.join(getTemplateDir("electron"), "icons/icon.png"), + ...input.icons + }, + "architectures": input.architectures ?? {}, + "window": { "width": 1280, "height": 720, "fullscreen": false, ...input.window }, + "macos": { "sign": false, "notarize": false, ...input.macos }, + "steam": input.steam + }; + if (typeof config.appId !== "string" || !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.appId)) { + throw new Error("electron.config.json appId must be a reverse-domain bundle identifier."); + } + for (const [key, value] of [["appName", config.appName], ["executableName", config.executableName]]) { + if (typeof value !== "string" || !value.trim() || /[<>:"/\\|?*\x00-\x1f]/.test(value) || /[. ]$/.test(value) || value === "..") { + throw new Error(`electron.config.json ${key} must be a valid portable file name.`); + } + } + if (typeof config.companyName !== "string" || !config.companyName.trim()) { + throw new Error("electron.config.json companyName must be a nonempty string."); + } + for (const dimension of [config.window.width, config.window.height]) { + if (!Number.isInteger(dimension) || dimension < 1 || dimension > 16384) { + throw new Error("Electron window width/height must be integers between 1 and 16384."); + } + } + for (const value of [config.window.fullscreen, config.macos.sign, config.macos.notarize]) { + if (typeof value !== "boolean") { + throw new Error("Electron fullscreen/sign/notarize options must be booleans."); + } + } + if (config.macos.notarize && !config.macos.sign) { + throw new Error("macos.notarize requires macos.sign."); + } + for (const os of ["windows", "macos", "linux"] as const) { + const icon = config.icons[os]; + if (icon !== undefined) { + const extension = { "windows": ".ico", "macos": ".icns", "linux": ".png" }[os]; + if (typeof icon !== "string" || path.extname(icon).toLowerCase() !== extension || !fs.statSync(path.resolve(root, icon), { "throwIfNoEntry": false })?.isFile()) { + throw new Error(`electron.config.json icons.${os} must point to an existing ${extension} file (relative to the project root).`); + } + } + const arch = config.architectures[os]; + if (arch !== undefined && !(os === "macos" ? ["x64", "arm64", "universal"] : ["x64", "arm64"]).includes(arch)) { + throw new Error(`Invalid electron.config.json architectures.${os}.`); + } + } + if (config.steam) { + config.steam.appId = config.steam.appId === null || config.steam.appId === undefined ? null : validateSteamId(config.steam.appId); + const depots = config.steam.depots ?? {}; + const ids = Object.values(depots).filter((value) => value !== null).map(validateSteamId); + if (ids.length && !config.steam.appId) { + throw new Error("Set steam.appId before configuring Steam Depot IDs."); + } + config.steam.depots = depots; + } + return config; +}; + +/** JSON is the source of game metadata; secrets stay in the environment/keychain. */ +export const createElectronPackagerOptions = ( + root: string, config: IElectronConfig, os: ElectronOS, resources: string +) => { + const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")); + const release = process.env.NEXT2D_STEAM_RELEASE === "1"; + const sign = config.macos.sign || release; + const notarize = config.macos.notarize || release; + if (os === "macos" && sign && !process.env.APPLE_SIGNING_IDENTITY) { + throw new Error("macOS signing requires APPLE_SIGNING_IDENTITY."); + } + if (os === "macos" && notarize && !process.env.APPLE_NOTARY_PROFILE) { + throw new Error("macOS notarization requires APPLE_NOTARY_PROFILE (notarytool keychain profile)."); + } + return { + "name": config.appName, + "executableName": config.executableName, + "appBundleId": config.appId, + "appVersion": pkg.version, + "win32metadata": { "CompanyName": config.companyName, "FileDescription": config.description }, + "asar": true, + "extraResource": [resources], + "ignore": [/^\/resources(?:\/|$)/, /^\/icons(?:\/|$)/, /^\/forge\.config\.[cm]?js$/, /^\/entitlements\.plist$/, /(?:^|\/)steam_appid\.txt$/], + ...os !== "linux" && config.icons[os] ? { "icon": path.resolve(root, config.icons[os]) } : {}, + ...os === "macos" && sign ? { + "osxSign": { + "identity": process.env.APPLE_SIGNING_IDENTITY, + "optionsForFile": () => ({ + "hardenedRuntime": true, + "entitlements": path.join(getTemplateDir("electron"), "entitlements.plist") + }) + } + } : {}, + ...os === "macos" && notarize ? { "osxNotarize": { "keychainProfile": process.env.APPLE_NOTARY_PROFILE! } } : {} + }; +}; diff --git a/src/electron-host.ts b/src/electron-host.ts new file mode 100644 index 0000000..9048a1d --- /dev/null +++ b/src/electron-host.ts @@ -0,0 +1,46 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { getTemplateDir } from "./utils.js"; +import type { IElectronConfig } from "./electron-config.js"; + +/** The builder pins the runtime; games need neither Electron nor its npm dependency tree. */ +export const getElectronVersion = (): string => { + const pkg = JSON.parse(fs.readFileSync(path.join(getTemplateDir("electron"), "package.json"), "utf8")); + const version = pkg.devDependencies.electron; + if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version)) { + throw new Error("The builder's Electron template must pin an exact runtime version."); + } + return version; +}; + +/** No user files are written or removed. Each build gets an isolated, disposable host. */ +export const withElectronHost = async ( + root: string, resources: string, config: IElectronConfig, use: (directory: string) => Promise +): Promise => { + if (!fs.statSync(path.join(resources, "index.html"), { "throwIfNoEntry": false })?.isFile()) { + throw new Error(`Electron web entry point is missing: ${resources}/index.html`); + } + const game = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-electron-")); + try { + for (const file of ["index.js", "local-assets.cjs"]) { + fs.copyFileSync(path.join(getTemplateDir("electron"), file), path.join(dir, file)); + } + const pkg = { + "name": game.name, "version": game.version, "description": config.description, + "private": true, "type": "commonjs", "main": "index.js" + }; + fs.writeFileSync(path.join(dir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`); + fs.cpSync(resources, path.join(dir, "resources"), { "recursive": true }); + const runtime = { "appId": config.appId, "appName": config.appName, "window": config.window, "icon": "" }; + if (config.icons.linux) { + fs.copyFileSync(path.resolve(root, config.icons.linux), path.join(dir, "resources/desktop-icon.png")); + runtime.icon = "desktop-icon.png"; + } + fs.writeFileSync(path.join(dir, "runtime-config.json"), `${JSON.stringify(runtime, null, 2)}\n`); + return await use(dir); + } finally { + fs.rmSync(dir, { "recursive": true, "force": true }); + } +}; diff --git a/src/electron.ts b/src/electron.ts index bce08e7..dc9be0b 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -1,178 +1,101 @@ -// Electron ベースのデスクトップ配布 (Steam / Epic Games Store など)。 -import pc from "picocolors"; -import fs from "fs"; -import cp from "child_process"; -import { api } from "@electron-forge/core"; +// Electron desktop packages are unpacked application folders, ready for SteamPipe. +import pc from "./colors.js"; +import fs from "node:fs"; +import path from "node:path"; +import { createRequire } from "node:module"; +import { pathToFileURL } from "node:url"; +import { createElectronPackagerOptions, readElectronConfig } from "./electron-config.js"; +import type { ElectronOS } from "./electron-config.js"; import { ctx } from "./context.js"; import { $spawn } from "./utils.js"; - -/** - * @description electronがインストールされてなければインストールを実行 - * If electron is not installed, run install. - * - * @return {Promise} - * @method - * @private - */ -const installElectron = (): Promise => -{ - return new Promise((resolve, reject): void => - { - if (fs.existsSync(`${process.cwd()}/electron/node_modules`)) { - return resolve(); - } - - const stream = $spawn("npm", [ - "--prefix", - `${process.cwd()}/electron`, - "install", - `${process.cwd()}/electron` - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - reject("`Electron` installation failed."); - } - - console.log(pc.green("`Electron` successfully installed.")); - resolve(); - }); - }); +import { getElectronVersion, withElectronHost } from "./electron-host.js"; +import { findSteamLaunch, recordSteamPackage, writeSharedSteamDepots } from "./steam.js"; +import { resolveToolPackages } from "./tool-packages.js"; + +export const resolveElectronTarget = (platform: string, arch = "", preview = false): { + platform: "win32" | "darwin" | "linux"; + arch: "x64" | "arm64" | "universal"; +} => { + const os = platform.replace(/^steam:/, ""); + const target = { "windows": "win32", "macos": "darwin", "linux": "linux" }[os]; + if (target !== "win32" && target !== "darwin" && target !== "linux") { + throw new Error(`Unsupported Electron platform: ${platform}`); + } + const cpu = arch || (preview ? process.arch : target === "darwin" ? "universal" : "x64"); + if (cpu !== "x64" && cpu !== "arm64" && cpu !== "universal") { + throw new Error(`Unsupported Electron architecture: ${cpu}`); + } + if (cpu === "universal" && target !== "darwin") { + throw new Error("--arch universal is only supported for macOS."); + } + if (preview && (target !== process.platform || cpu !== process.arch)) { + throw new Error("Electron preview must use the host OS and architecture."); + } + return { "platform": target, "arch": cpu }; }; -/** - * @description electronに含むリソースを初期化 - * Initialize resources included in electron - * - * @return {Promise} - * @method - * @private - */ -const removeResources = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const stream = cp.spawn("rm", [ - "-rf", - `${process.cwd()}/electron/resources/` - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { +const run = (command: string, args: string[], cwd: string): Promise => + new Promise((resolve, reject) => { + const child = $spawn(command, args, { cwd, "stdio": "inherit" }); + child.once("error", reject); + child.once("close", (code, signal) => { if (code !== 0) { - reject("Failed to remove built resources."); + reject(new Error(`${command} failed (${signal || code}).`)); + return; } - - console.log(pc.green("Successfully remove built resources.")); resolve(); }); }); -}; - -/** - * @description ビルドしたリソースをコピー - * Copy built resources - * - * @return {Promise} - * @method - * @private - */ -const copyResources = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const stream = cp.spawn("cp", [ - "-r", - `${ctx.buildDir}/`, - `${process.cwd()}/electron/resources` - ], { "stdio": "inherit" }); - stream.on("close", (code: number): void => - { - if (code !== 0) { - reject("Failed to copy built resources."); - } - - console.log(pc.green("Successfully copy built resources.")); - resolve(); - }); +export const buildElectron = async (): Promise => { + const root = process.cwd(); + const config = readElectronConfig(root); + const os = ctx.platform.replace(/^steam:/, "") as ElectronOS; + const target = resolveElectronTarget(ctx.platform, ctx.arch || (ctx.preview ? "" : config.architectures[os]), ctx.preview); + if (target.arch === "universal" && process.platform !== "darwin") { + throw new Error("Build universal macOS applications on macOS."); + } + const steam = ctx.platform.startsWith("steam:"); + const version: string = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version; + const steamRoot = path.resolve(ctx.outDir, "steam"); + const outDir = path.resolve(ctx.outDir, ctx.platformDir, "build", ctx.environment); + if (steam) { + // A failed rebuild must not leave a shared manifest pointing at partially replaced files. + fs.rmSync(path.join(outDir, "steam-package.json"), { "force": true }); + writeSharedSteamDepots(steamRoot, ctx.environment, config, version); + } + const outputPaths = await withElectronHost(root, ctx.buildDir, config, async (dir) => { + const options = createElectronPackagerOptions(root, config, os, path.join(dir, "resources")); + if (steam && os === "macos" && !options.osxNotarize) { + console.log(pc.yellow("Local macOS build: unsigned/unnotarized builds must not be submitted for Steam release. See docs/steam.md.")); + } + console.log(pc.green(`Packaging Electron (${target.platform}/${target.arch})`)); + const packages = await resolveToolPackages("electron"); + const require = createRequire(packages["@electron/packager"]); + const { packager } = await import(pathToFileURL(require.resolve("@electron/packager")).href); + // The generated host has no npm dependencies or native addons. Packager downloads + // the pinned Electron runtime directly and uses its normal download cache. + return await packager({ + ...options, dir, ...target, + "out": outDir, + "electronVersion": getElectronVersion(), + "overwrite": true, + "prune": false + }) as string[]; }); -}; - -/** - * @description Electron ベースのデスクトップアプリ書き出し関数 - * (Windows / macOS / Linux、Steam・Epic Games Store 等の配布に共通)。 - * Export function for Electron-based desktop apps. - * - * @return {Promise} - * @method - * @public - */ -export const buildElectron = async (): Promise => -{ - // reset - await removeResources(); - - // copy HTML, JavaScript - await copyResources(); - - if (ctx.preview) { - - $spawn("npx", [ - "electron", - `${process.cwd()}/electron/index.js` - ], { "stdio": "inherit" }); - - } else { - - await installElectron(); - - console.log(pc.green("Start the `Electron` build process.")); - console.log(); - - const packageOptions = { - "dir": `${process.cwd()}/electron`, - "outDir": `${ctx.outDir}/${ctx.platformDir}/build`, - "platform": "", - "arch": "all" - }; - - switch (ctx.platform) { - - case "windows": - case "steam:windows": - packageOptions.platform = "win32"; - break; - - case "macos": - case "steam:macos": - packageOptions.platform = "mas"; - break; - - case "linux": - case "steam:linux": - packageOptions.platform = "linux"; - break; - - default: - console.log(pc.red("There is an error in the export platform settings.")); - console.log(); - process.exit(1); - + if (steam) { + for (const content of outputPaths) { + recordSteamPackage(content, ctx.platform, target.arch, config, version); } - - api - .package(packageOptions) - .then((): void => - { - console.log(pc.green(`Finished building \`Electron\` for ${ctx.platform}.`)); - console.log(); - }) - .catch((error: any): void => - { - console.log(pc.red("Export of Electron failed.")); - console.log(pc.red(error)); - }); + writeSharedSteamDepots(steamRoot, ctx.environment, config, version); + } + console.log(pc.green(`Finished Electron export: ${outDir}`)); + if (ctx.preview) { + const content = outputPaths[0]; + const launch = findSteamLaunch(content, `steam:${os}`, config.executableName); + const executable = target.platform === "darwin" + ? path.join(content, launch, "Contents/MacOS", config.executableName) + : path.join(content, launch); + await run(executable, [], root); } }; diff --git a/src/index.ts b/src/index.ts index 1d83416..71b9398 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,11 @@ import { loadConfig, buildWeb } from "./web.js"; import { buildElectron } from "./electron.js"; import { runNative, openNative, buildNative } from "./native.js"; import { buildXbox } from "./xbox.js"; +import fs from "node:fs"; +import path from "node:path"; +import { readElectronConfig } from "./electron-config.js"; +import { writeSharedSteamDepots } from "./steam.js"; +import { uploadSteam } from "./steam-upload.js"; // Node バージョン検証・引数解析・ctx 初期化 (不正時はここで終了)。 initCli(); @@ -82,7 +87,23 @@ const multiBuild = async (): Promise => const execute = async (): Promise => { try { + if (ctx.steamUpload) { + await uploadSteam({ + "root": process.cwd(), "steamRoot": ctx.steamRoot || "dist/steam", "environment": ctx.environment, + "branch": ctx.steamBranch || undefined, "dryRun": ctx.dryRun + }); + return; + } await loadConfig(); + if (ctx.steamManifest) { + const config = readElectronConfig(process.cwd()); + const version: string = JSON.parse(fs.readFileSync("package.json", "utf8")).version; + const pending = writeSharedSteamDepots(path.resolve(ctx.outDir, "steam"), ctx.environment, config, version); + if (pending.length) { + throw new Error(`Shared Steam depots are incomplete. Export or collect all required OS packages first.\n${pending.join("\n")}`); + } + return; + } await buildWeb(); await multiBuild(); } catch (error) { diff --git a/src/native.ts b/src/native.ts index 07f468a..4d5fb6c 100644 --- a/src/native.ts +++ b/src/native.ts @@ -1,158 +1,73 @@ -// iOS / Android (Capacitor) ビルド。 -import pc from "picocolors"; -import fs from "fs"; +// iOS / Android (Capacitor) ビルド。SDKとCLIはnpxで必要時に取得する。 +import fs from "node:fs"; +import path from "node:path"; import { ctx } from "./context.js"; import { $spawn } from "./utils.js"; import { CAPACITOR_CONFIG_NAME } from "./constants.js"; +import { resolveToolPackages } from "./tool-packages.js"; + +/** Resolve from npm's persistent tool cache without installing into the game. */ +export const getCapacitorCommand = async (): Promise<{ cli: string; env: NodeJS.ProcessEnv }> => { + const resolved = await resolveToolPackages("capacitor"); + const packages = ["cli", "core", "ios", "android"].map((name) => + resolved[`@capacitor/${name}`]); + // Keep the game's cwd for plugin discovery/hooks; supply SDKs through NODE_PATH. + const roots = [...new Set(packages.map((file) => path.dirname(path.dirname(path.dirname(file)))))]; + return { + "cli": path.join(path.dirname(packages[0]), "bin", "capacitor"), + "env": { ...process.env, "NODE_PATH": [...roots, process.env.NODE_PATH].filter(Boolean).join(path.delimiter) } + }; +}; -/** - * @description iOS/Androidのプロジェクトを生成 - * Generate iOS/Android project - * - * @return {Promise} - * @method - * @private - */ -const generateNativeProject = (): Promise => -{ - return new Promise((resolve, reject): void => - { - if (fs.existsSync(`${process.cwd()}/${ctx.platform}`)) { - return resolve(); - } - - const stream = $spawn("npx", [ - "cap", - "add", - ctx.platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { +/** Await every CLI operation so failures propagate to the builder and CI. */ +export const runCapacitor = async (args: string[], root = process.cwd()): Promise => { + const { cli, env } = await getCapacitorCommand(); + return new Promise((resolve, reject) => { + const child = $spawn(process.execPath, [cli, ...args], { "cwd": root, env, "stdio": "inherit" }); + child.once("error", reject); + child.once("close", (code, signal) => { if (code !== 0) { - reject(`Failed generated ${ctx.platform} project.`); + reject(new Error(`Capacitor ${args.join(" ")} failed (${signal || code}).`)); + return; } - - console.log(pc.green(`Successfully generated ${ctx.platform} project.`)); - console.log(); - resolve(); }); }); }; -/** - * @description Capacitor の webDir を現在のビルド出力へ書き換える - * Point the Capacitor webDir at the current build output - * - * @return {void} - * @method - * @private - */ -const applyCapacitorWebDir = (): void => -{ - const config = JSON.parse( - fs.readFileSync(`${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, { "encoding": "utf8" }) - ); - - config.webDir = `${ctx.outDir}/${ctx.platformDir}/${ctx.environment}/`; - - fs.writeFileSync( - `${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, - JSON.stringify(config, null, 2) - ); +const prepareNativeProject = async (): Promise => { + if (ctx.platform !== "ios" && ctx.platform !== "android") { + throw new Error(`Unsupported Capacitor platform: ${ctx.platform}`); + } + const root = process.cwd(); + const file = path.join(root, CAPACITOR_CONFIG_NAME); + const config = JSON.parse(fs.readFileSync(file, "utf8")); + // cap add performs an initial sync, so it must see the new webDir too. + config.webDir = path.relative(root, path.resolve(ctx.outDir, ctx.platformDir, ctx.environment)).replace(/\\/g, "/") + "/"; + fs.writeFileSync(file, JSON.stringify(config, null, 2) + "\n"); + const platformDir = path.resolve(root, config[ctx.platform]?.path ?? ctx.platform); + if (!fs.existsSync(platformDir)) { + await runCapacitor(["add", ctx.platform]); + } }; -/** - * @description iOS用アプリの書き出し関数 - * Export function for iOS apps - * - * @return {Promise} - * @method - * @public - */ -export const runNative = async (): Promise => -{ - await generateNativeProject(); - - // Capacitorの書き出しに必要な設定を生成 - applyCapacitorWebDir(); - - $spawn("npx", [ - "cap", - "run", - ctx.platform - ], { "stdio": "inherit" }); +/** Keep native customizations and update web assets/dependency paths in place. */ +export const syncNative = async (): Promise => { + await prepareNativeProject(); + await runCapacitor(["sync", ctx.platform]); }; -/** - * @description iOS/Androidアプリのオープン関数 - * Open function for iOS/Android apps - * - * @return {Promise} - * @method - * @public - */ -export const openNative = async (): Promise => -{ - await generateNativeProject(); - - // Capacitorの書き出しに必要な設定を生成 - applyCapacitorWebDir(); - - const stream = $spawn("npx", [ - "cap", - "sync", - ctx.platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - console.log(pc.red(`Failed to sync ${ctx.platform} project.`)); - return; - } - - $spawn("npx", [ - "cap", - "open", - ctx.platform - ], { "stdio": "inherit" }); - }); +export const runNative = async (): Promise => { + await prepareNativeProject(); + await runCapacitor(["run", ctx.platform]); }; -/** - * @description iOS/Androidアプリのビルド関数 - * Build function for iOS/Android apps - * - * @return {Promise} - * @method - * @public - */ -export const buildNative = async (): Promise => -{ - await generateNativeProject(); - - // Capacitorの書き出しに必要な設定を生成 - applyCapacitorWebDir(); - - const stream = $spawn("npx", [ - "cap", - "sync", - ctx.platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - console.log(pc.red(`Failed to sync ${ctx.platform} project.`)); - return; - } +export const openNative = async (): Promise => { + await syncNative(); + await runCapacitor(["open", ctx.platform]); +}; - $spawn("npx", [ - "cap", - "build", - ctx.platform - ], { "stdio": "inherit" }); - }); +export const buildNative = async (): Promise => { + await syncNative(); + await runCapacitor(["build", ctx.platform]); }; diff --git a/src/project-vite.ts b/src/project-vite.ts new file mode 100644 index 0000000..05f0686 --- /dev/null +++ b/src/project-vite.ts @@ -0,0 +1,22 @@ +import { createRequire } from "node:module"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import type * as Vite from "vite"; + +/** Resolve from the game, including when builder is launched through npx or a sibling checkout. */ +export const loadProjectVite = async (root = process.cwd()): Promise => { + const require = createRequire(path.join(root, "package.json")); + let entry: string; + try { + entry = require.resolve("vite"); + } catch { + throw new Error(`Vite is not installed in ${root}. Install the game's dependencies with npm install first.`); + } + const imported = await import(pathToFileURL(entry).href); + // Vite 7's require condition resolves to its CJS API wrapper; Vite 8 is ESM. + const vite = imported.default ?? imported; + if (typeof vite.loadConfigFromFile !== "function") { + throw new Error(`The Vite installation in ${root} does not expose loadConfigFromFile.`); + } + return vite; +}; diff --git a/src/resolve-tool-packages.ts b/src/resolve-tool-packages.ts new file mode 100644 index 0000000..4549431 --- /dev/null +++ b/src/resolve-tool-packages.ts @@ -0,0 +1,30 @@ +// Runs inside npx, where the downloaded tools' node_modules/.bin is on PATH. +import fs from "node:fs"; +import path from "node:path"; + +const packages: Record = JSON.parse(process.env.NEXT2D_TOOL_PACKAGES!); +let resolved: Record | undefined; +for (const bin of (process.env.PATH ?? "").split(path.delimiter)) { + if (path.basename(bin) !== ".bin") { + continue; + } + const files: Record = {}; + for (const [name, version] of Object.entries(packages)) { + const file = path.join(path.dirname(bin), name, "package.json"); + if (fs.existsSync(file)) { + const pkg = JSON.parse(fs.readFileSync(file, "utf8")); + if (pkg.name === name && pkg.version === version) { + files[name] = file; + } + } + } + // All SDKs must come from the same installation, with the requested exact versions. + if (Object.keys(files).length === Object.keys(packages).length) { + resolved = files; + break; + } +} +if (!resolved) { + throw new Error("Could not resolve the requested build tools from npx's PATH."); +} +fs.writeFileSync(process.env.NEXT2D_TOOL_RESULT!, JSON.stringify(resolved)); diff --git a/src/steam-upload.ts b/src/steam-upload.ts new file mode 100644 index 0000000..8376a85 --- /dev/null +++ b/src/steam-upload.ts @@ -0,0 +1,136 @@ +import fs from "node:fs"; +import path from "node:path"; +import { spawn } from "node:child_process"; +import { readElectronConfig, validateSteamId } from "./electron-config.js"; +import { quoteSteamValue as quote, readSteamPackage } from "./steam.js"; + +export interface ISteamUploadOptions { + root: string; + steamRoot: string; + environment: string; + branch?: string; + dryRun?: boolean; +} + +export const validateSteamBranch = (branch: unknown): string => { + if (typeof branch !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/.test(branch) + || ["default", "public", "none"].includes(branch.toLowerCase())) { + throw new Error("Steam upload requires a named beta branch such as internal (letters, numbers, - or _). The default branch is not supported."); + } + return branch; +}; + +/** Recreate manifests from validated package records, never from stale or edited VDF files. */ +export const prepareSteamUpload = (options: ISteamUploadOptions) => { + const root = path.resolve(options.root); + const steamRoot = path.resolve(root, options.steamRoot); + if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(options.environment)) { + throw new Error("Steam upload --env must contain only letters, numbers, - or _."); + } + const config = readElectronConfig(root); + const branch = validateSteamBranch(options.branch ?? config.steam?.branch ?? "internal"); + if (!config.steam?.appId) { + throw new Error("Set steam.appId and steam.depots in electron.config.json before uploading."); + } + const version = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version; + if (typeof version !== "string" || !version) { + throw new Error("Set package.json version before uploading."); + } + const groups = new Map(); + for (const os of ["windows", "macos", "linux"] as const) { + const id = config.steam.depots[os]; + if (id === null || id === undefined) { + continue; + } + const depot = validateSteamId(id); + try { + const record = readSteamPackage(steamRoot, options.environment, config, version, os); + groups.set(depot, [...groups.get(depot) ?? [], { os, ...record }]); + } catch (error) { + throw new Error(`Steam upload: ${os} package is missing or outdated. Export/collect every configured OS first. ${error instanceof Error ? error.message : error}`); + } + } + if (!groups.size) { + throw new Error("Set at least one Steam Depot ID in electron.config.json before uploading."); + } + // Unique scripts prevent a dry-run or another invocation from overwriting an active upload. + const uploads = path.join(steamRoot, "uploads", options.environment); + fs.mkdirSync(uploads, { "recursive": true }); + const scripts = fs.mkdtempSync(path.join(uploads, `${branch}-`)); + const depotFiles: string[] = []; + const launches: { platform: string; arch: string; executable: string; depotId: string }[] = []; + for (const [depot, members] of groups) { + const mappings = members.map(({ os, content, arch, localExecutable }) => { + const prefix = members.length > 1 ? `${os}/` : ""; + launches.push({ "platform": `steam:${os}`, arch, "executable": `${prefix}${localExecutable}`, "depotId": depot }); + return ` "FileMapping"\n {\n "LocalPath" ${quote(`${path.relative(steamRoot, content)}/*`)}\n "DepotPath" ${quote(prefix || ".")}\n "Recursive" "1"\n }`; + }); + const name = `depot_${depot}.vdf`; + fs.writeFileSync(path.join(scripts, name), + `"DepotBuild"\n{\n "DepotID" "${depot}"\n${mappings.join("\n")}\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`); + depotFiles.push(` "${depot}" ${quote(name)}`); + } + const manifest = path.join(scripts, options.dryRun ? "app_preview.vdf" : "app_upload.vdf"); + // A stable cache directory allows SteamPipe to reuse chunks across uploads to a branch. + const cache = path.join(uploads, "cache", branch); + fs.writeFileSync(manifest, + `"AppBuild"\n{\n "AppID" "${config.steam.appId}"\n "Desc" ${quote(`${config.appName} ${version} (${options.environment}, ${branch})`)}\n` + + ` "ContentRoot" ${quote(steamRoot)}\n "BuildOutput" ${quote(cache)}\n "Preview" "${options.dryRun ? "1" : "0"}"\n` + + (options.dryRun ? "" : ` "SetLive" ${quote(branch)}\n`) + + ` "Depots"\n {\n${depotFiles.join("\n")}\n }\n}\n`); + const plan = { "appId": config.steam.appId, branch, version, "environment": options.environment, "dryRun": !!options.dryRun, manifest, launches }; + fs.writeFileSync(path.join(scripts, "upload-plan.json"), `${JSON.stringify(plan, null, 2)}\n`); + return plan; +}; + +/** Use SteamCMD's existing login session; passwords/Steam Guard codes never enter project config. */ +export const uploadSteam = async (options: ISteamUploadOptions): Promise => { + const plan = prepareSteamUpload(options); + console.log(`Steam App ${plan.appId} -> beta branch ${plan.branch}`); + console.log(`Manifest: ${plan.manifest}`); + for (const launch of plan.launches) { + console.log(` Depot ${launch.depotId}: ${launch.platform}/${launch.arch} -> ${launch.executable}`); + } + if (options.dryRun) { + console.log("Dry run complete: SteamCMD was not started. Branch existence/password protection has not been checked on Steamworks."); + return; + } + const username = process.env.STEAM_USERNAME; + if (!username || !/^[a-zA-Z0-9_][a-zA-Z0-9_.@-]*$/.test(username)) { + throw new Error("Set STEAM_USERNAME to your Steam build account name. Log in with SteamCMD once before uploading; see docs/steam.md."); + } + const command = process.env.STEAMCMD || (process.platform === "win32" ? "steamcmd.exe" : "steamcmd"); + console.log(`Uploading and requesting activation on ${plan.branch}. Configure the beta password in Steamworks to restrict access.`); + const buildId = await new Promise((resolve, reject) => { + let output = ""; + let reportedError = false; + const child = spawn(command, [ + "+@ShutdownOnFailedCommand", "1", "+@NoPromptForPassword", "1", + "+login", username, "+run_app_build", plan.manifest, "+quit" + ], { "cwd": path.resolve(options.root), "stdio": ["ignore", "pipe", "pipe"], "shell": false }); + const collect = (chunk: Buffer, stream: NodeJS.WriteStream): void => { + stream.write(chunk); + output = (output + chunk.toString()).slice(-1024 * 1024); + reportedError ||= /ERROR[!:]|Failed to set[^\r\n]*live/i.test(output); + }; + child.stdout.on("data", (chunk: Buffer) => collect(chunk, process.stdout)); + child.stderr.on("data", (chunk: Buffer) => collect(chunk, process.stderr)); + child.once("error", (error) => reject(new Error(`Cannot start SteamCMD: ${error.message}. Set STEAMCMD to the executable path; see docs/steam.md.`))); + child.once("close", (code, signal) => { + if (code !== 0) { + reject(new Error(`SteamCMD failed (${signal || code}). Inspect its output and SteamPipe logs; check login, app permissions and beta branch settings.`)); + return; + } + const success = output.match(new RegExp(`Success! App '${plan.appId}' fully built \\(BuildID ([0-9]+)\\)`, "i")); + if (reportedError || !success) { + reject(new Error("SteamCMD did not report a successful app build, or reported an error. Check SteamPipe logs and the branch's active BuildID in Steamworks before retrying.")); + return; + } + resolve(success[1]); + }); + }); + fs.writeFileSync(path.join(path.dirname(plan.manifest), "upload-result.json"), `${JSON.stringify({ + "appId": plan.appId, buildId, "requestedBranch": plan.branch, "completedAt": new Date().toISOString() + }, null, 2)}\n`); + console.log(`Uploaded BuildID ${buildId}. Verify the active ${plan.branch} build at https://partner.steamgames.com/apps/builds/${plan.appId}`); +}; diff --git a/src/steam.ts b/src/steam.ts new file mode 100644 index 0000000..4ee18f1 --- /dev/null +++ b/src/steam.ts @@ -0,0 +1,214 @@ +import fs from "node:fs"; +import path from "node:path"; +import { validateSteamId } from "./electron-config.js"; +import type { IElectronConfig, ElectronOS } from "./electron-config.js"; + +export interface ISteamConfig { + appId: string; + depotId: string | null; + sharedPlatforms?: ElectronOS[]; +} + +const platforms: ElectronOS[] = ["windows", "macos", "linux"]; + +export const readSteamConfig = (config: IElectronConfig, platform: string): ISteamConfig | null => { + if (!config.steam?.appId) { + return null; + } + const os = platform.replace(/^steam:/, "") as ElectronOS; + const depot = config.steam.depots[os]; + const depotId = depot ? validateSteamId(depot) : null; + const sharedPlatforms = depotId ? platforms.filter((key) => String(config.steam?.depots[key]) === depotId) : []; + return { "appId": config.steam.appId, depotId, ...sharedPlatforms.length > 1 ? { sharedPlatforms } : {} }; +}; + +export const quoteSteamValue = (value: string): string => { + if (/["\r\n\0]/.test(value)) { + throw new Error("Unsupported quote or control character in SteamPipe path."); + } + return `"${value.replace(/\\/g, "/")}"`; +}; +const quote = quoteSteamValue; + +/** Discover the executable from the output, including customized executable names. */ +export const findSteamLaunch = (content: string, platform: string, executableName?: string): string => { + const entries = fs.readdirSync(content, { "withFileTypes": true }); + if (platform === "steam:macos") { + const apps = entries.filter((entry) => entry.isDirectory() && entry.name.endsWith(".app")); + if (apps.length !== 1) { + throw new Error("Expected exactly one macOS .app in the packaged folder."); + } + return apps[0].name; + } + if (executableName) { + const executable = `${executableName}${platform === "steam:windows" ? ".exe" : ""}`; + if (!fs.statSync(path.join(content, executable), { "throwIfNoEntry": false })?.isFile()) { + throw new Error(`Packaged Steam executable is missing: ${executable}`); + } + return executable; + } + const candidates = entries.filter((entry) => { + if (!entry.isFile()) { + return false; + } + if (platform === "steam:windows") { + return entry.name.endsWith(".exe") && entry.name !== "chrome_crashpad_handler.exe"; + } + // Electron's other Linux binaries have extensions or known helper names. + return !entry.name.includes(".") && !["chrome-sandbox", "chrome_crashpad_handler", "LICENSE", "version"].includes(entry.name) + && (fs.statSync(path.join(content, entry.name)).mode & 0o111) !== 0; + }); + if (candidates.length !== 1) { + throw new Error(`Cannot determine the Steam executable in ${content}`); + } + return candidates[0].name; +}; + +/** Keep scripts outside the content directory so Steam never installs build metadata. */ +export const writeSteamMetadata = (content: string, platform: string, arch: string, config: ISteamConfig | null, executableName?: string): void => { + const localExecutable = findSteamLaunch(content, platform, executableName); + const depotPath = config?.sharedPlatforms ? platform.replace("steam:", "") : ""; + const executable = depotPath ? `${depotPath}/${localExecutable}` : localExecutable; + const scripts = `${content}-steampipe`; + fs.mkdirSync(scripts, { "recursive": true }); + const metadata = { + platform, arch, executable, + localExecutable, depotPath, + "contentRoot": `../${path.basename(content)}`, + "workingDirectory": "", + "arguments": "", + "appId": config?.appId || null, + "depotId": config?.depotId || null + }; + fs.writeFileSync(path.join(scripts, "launch.json"), `${JSON.stringify(metadata, null, 2)}\n`); + // Remove scripts from an earlier configured run if IDs are no longer configured. + for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf"]) { + fs.rmSync(path.join(scripts, name), { "force": true }); + } + if (!config?.depotId) { + console.log("Steam package ready. Set steam.appId and steam.depots in electron.config.json to generate SteamPipe VDFs."); + return; + } + // Upload a shared depot only through the combined manifest, never one OS at a time. + if (config.sharedPlatforms) { + return; + } + fs.writeFileSync(path.join(scripts, "depot_build.vdf"), `"DepotBuild"\n{\n "DepotID" "${config.depotId}"\n "FileMapping"\n {\n "LocalPath" "*"\n "DepotPath" "."\n "Recursive" "1"\n }\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`); + for (const preview of [false, true]) { + fs.writeFileSync(path.join(scripts, preview ? "app_preview.vdf" : "app_build.vdf"), + `"AppBuild"\n{\n "AppID" "${config.appId}"\n "Desc" ${quote(`${platform} ${arch}`)}\n "ContentRoot" ${quote(metadata.contentRoot)}\n "BuildOutput" "cache"\n "Preview" "${preview ? "1" : "0"}"\n "Depots"\n {\n "${config.depotId}" "depot_build.vdf"\n }\n}\n`); + } +}; + +/** A portable pointer to the most recently exported architecture for this OS/environment. */ +export const recordSteamPackage = ( + content: string, platform: string, arch: string, config: IElectronConfig, version: string +): void => { + const steam = readSteamConfig(config, platform); + writeSteamMetadata(content, platform, arch, steam, config.executableName); + const record = { + platform, arch, version, + "appId": steam?.appId ?? null, "depotId": steam?.depotId ?? null, + "appName": config.appName, "executableName": config.executableName, + "contentRoot": path.basename(content) + }; + fs.writeFileSync(path.join(path.dirname(content), "steam-package.json"), `${JSON.stringify(record, null, 2)}\n`); +}; + +/** Validate that an exported package still matches the current project and depot configuration. */ +export const readSteamPackage = (steamRoot: string, environment: string, config: IElectronConfig, version: string, os: ElectronOS) => { + const output = path.join(steamRoot, os, "build", environment); + const record = JSON.parse(fs.readFileSync(path.join(output, "steam-package.json"), "utf8")); + if (typeof record.contentRoot !== "string" || path.basename(record.contentRoot) !== record.contentRoot + || /[\\/*?"\r\n\0]/.test(record.contentRoot) || record.contentRoot === "." || record.contentRoot === "..") { + throw new Error("Invalid package path"); + } + const content = path.join(output, record.contentRoot); + if (record.platform !== `steam:${os}` || record.appId !== config.steam?.appId + || record.depotId !== validateSteamId(config.steam?.depots[os]) + || record.version !== version || record.appName !== config.appName || record.executableName !== config.executableName + || !(os === "macos" ? ["x64", "arm64", "universal"] : ["x64", "arm64"]).includes(record.arch)) { + throw new Error("Package metadata no longer matches the project configuration"); + } + const relativeRealPath = path.relative(fs.realpathSync(steamRoot), fs.realpathSync(content)); + if (path.isAbsolute(relativeRealPath) || relativeRealPath === ".." || relativeRealPath.startsWith(`..${path.sep}`)) { + throw new Error("Package directory must be inside the Steam output root"); + } + return { content, "arch": record.arch as string, "localExecutable": findSteamLaunch(content, `steam:${os}`, config.executableName) }; +}; + +/** Combine existing packages with multiple FileMappings, preserving app symlinks and permissions. */ +export const writeSharedSteamDepots = ( + steamRoot: string, environment: string, config: IElectronConfig, version: string +): string[] => { + const pending: string[] = []; + const groups = new Map(); + for (const os of platforms) { + const id = config.steam?.depots[os]; + if (id) { + const key = validateSteamId(id); + groups.set(key, [...groups.get(key) ?? [], os]); + } + } + const sharedRoot = path.join(steamRoot, "shared", environment); + // Remove obsolete generated VDFs after a depot's membership or IDs change. + if (fs.existsSync(sharedRoot)) { + for (const entry of fs.readdirSync(sharedRoot, { "withFileTypes": true })) { + if (entry.isDirectory() && /^depot-[0-9]+$/.test(entry.name)) { + for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf", "launch.json"]) { + fs.rmSync(path.join(sharedRoot, entry.name, name), { "force": true }); + } + } + } + } + for (const [depotId, members] of groups) { + if (members.length < 2) { + continue; + } + const scripts = path.join(sharedRoot, `depot-${depotId}`); + fs.mkdirSync(scripts, { "recursive": true }); + const mappings: string[] = []; + const launches: { platform: string; arch: string; executable: string; workingDirectory: string; arguments: string }[] = []; + const missing: string[] = []; + for (const os of members) { + const output = path.join(steamRoot, os, "build", environment); + // Also invalidate legacy exports created before steam-package.json existed. + if (fs.existsSync(output)) { + for (const entry of fs.readdirSync(output, { "withFileTypes": true })) { + if (entry.isDirectory() && entry.name.startsWith(`${config.appName}-`) && entry.name.endsWith("-steampipe")) { + for (const name of ["app_build.vdf", "app_preview.vdf", "depot_build.vdf"]) { + fs.rmSync(path.join(output, entry.name, name), { "force": true }); + } + } + } + } + try { + const { content, arch, localExecutable } = readSteamPackage(steamRoot, environment, config, version, os); + const relative = path.relative(steamRoot, content).replace(/\\/g, "/"); + mappings.push(` "FileMapping"\n {\n "LocalPath" ${quote(`${relative}/*`)}\n "DepotPath" "${os}/"\n "Recursive" "1"\n }`); + launches.push({ "platform": `steam:${os}`, arch, "executable": `${os}/${localExecutable}`, "workingDirectory": "", "arguments": "" }); + writeSteamMetadata(content, `steam:${os}`, arch, readSteamConfig(config, `steam:${os}`), config.executableName); + } catch (error) { + missing.push(`${os}: ${error instanceof Error ? error.message : String(error)}`); + } + } + fs.writeFileSync(path.join(scripts, "launch.json"), `${JSON.stringify({ + "appId": config.steam?.appId, depotId, "ready": !missing.length, + "requiredPlatforms": members, missing, "launches": launches + }, null, 2)}\n`); + if (missing.length) { + pending.push(`Depot ${depotId}: ${missing.join("; ")}`); + console.log(`Shared Steam depot ${depotId}: waiting for all OS packages. See ${path.join(scripts, "launch.json")}`); + continue; + } + fs.writeFileSync(path.join(scripts, "depot_build.vdf"), + `"DepotBuild"\n{\n "DepotID" "${depotId}"\n${mappings.join("\n")}\n "FileExclusion" "steam_appid.txt"\n "FileExclusion" "*.pdb"\n "FileExclusion" ".DS_Store"\n}\n`); + const contentRoot = path.relative(scripts, steamRoot); + for (const preview of [false, true]) { + fs.writeFileSync(path.join(scripts, preview ? "app_preview.vdf" : "app_build.vdf"), + `"AppBuild"\n{\n "AppID" "${config.steam?.appId}"\n "Desc" ${quote(`Shared depot ${depotId}: ${members.join(", ")} ${version}`)}\n "ContentRoot" ${quote(contentRoot)}\n "BuildOutput" "cache"\n "Preview" "${preview ? "1" : "0"}"\n "Depots"\n {\n "${depotId}" "depot_build.vdf"\n }\n}\n`); + } + console.log(`Shared Steam depot ready: ${scripts}`); + } + return pending; +}; diff --git a/src/tool-packages.ts b/src/tool-packages.ts new file mode 100644 index 0000000..3e65f88 --- /dev/null +++ b/src/tool-packages.ts @@ -0,0 +1,78 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawn } from "node:child_process"; + +// Keep native SDKs together and pin tools independently of the game's dependencies. +const packages = { + "electron": { "@electron/packager": "20.3.0" }, + "capacitor": { + "@capacitor/cli": "8.5.2", "@capacitor/core": "8.5.2", + "@capacitor/ios": "8.5.2", "@capacitor/android": "8.5.2" + } +}; +type Tool = keyof typeof packages; +const pending = new Map>>(); + +/** Launch npm's npx script with Node, avoiding cmd.exe quoting on Windows. */ +const resolveNpxCli = (): string => { + const candidates = [ + ...process.env.npm_execpath ? [path.join(path.dirname(process.env.npm_execpath), "npx-cli.js")] : [], + path.join(path.dirname(process.execPath), "node_modules/npm/bin/npx-cli.js"), + ...(process.env.PATH ?? "").split(path.delimiter).flatMap((dir) => [ + path.join(dir, "npx"), path.join(dir, "node_modules/npm/bin/npx-cli.js") + ]) + ]; + for (const file of candidates) { + if (fs.statSync(file, { "throwIfNoEntry": false })?.isFile()) { + const resolved = fs.realpathSync(file); + if (path.basename(resolved) === "npx-cli.js") { + return resolved; + } + } + } + throw new Error("npm/npx is required to download build tools. Install Node.js with npm and add it to PATH."); +}; + +const acquire = async (tool: Tool): Promise> => { + const cli = resolveNpxCli(); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-tools-")); + const result = path.join(dir, "packages.json"); + const specs = Object.entries(packages[tool]).map(([name, version]) => `--package=${name}@${version}`); + try { + console.log(`Preparing ${tool} tools via npx (${specs.map((spec) => spec.slice(10)).join(", ")})`); + await new Promise((resolve, reject) => { + const child = spawn(process.execPath, [cli, "--yes", ...specs, "--call", + "node --input-type=module -e \"import(process.env.NEXT2D_TOOL_RESOLVER)\"" + ], { + // A neutral cwd prevents project dependencies and npm hooks influencing tool acquisition. + "cwd": dir, "stdio": "inherit", + "env": { + ...process.env, + "NEXT2D_TOOL_RESOLVER": new URL("./resolve-tool-packages.js", import.meta.url).href, + "NEXT2D_TOOL_PACKAGES": JSON.stringify(packages[tool]), + "NEXT2D_TOOL_RESULT": result + } + }); + child.once("error", reject); + child.once("close", (code, signal) => code === 0 ? resolve() + : reject(new Error(`npx could not prepare ${tool} tools (${signal || code}). Check npm connectivity/cache and retry.`))); + }); + return JSON.parse(fs.readFileSync(result, "utf8")); + } finally { + fs.rmSync(dir, { "recursive": true, "force": true }); + } +}; + +/** Download only on first use; npm retains the packages after the temporary request is removed. */ +export const resolveToolPackages = (tool: Tool): Promise> => { + let request = pending.get(tool); + if (!request) { + request = acquire(tool).catch((error) => { + pending.delete(tool); + throw error; + }); + pending.set(tool, request); + } + return request; +}; diff --git a/src/web.ts b/src/web.ts index 504768d..7bb0a84 100644 --- a/src/web.ts +++ b/src/web.ts @@ -1,7 +1,8 @@ // Web ビルド: vite 設定の読み込みと HTML/JavaScript の書き出し。 -import pc from "picocolors"; +import pc from "./colors.js"; import fs from "fs"; -import { loadConfigFromFile } from "vite"; +import path from "node:path"; +import { loadProjectVite } from "./project-vite.js"; import { ctx } from "./context.js"; import { $spawn } from "./utils.js"; @@ -31,6 +32,7 @@ export const loadConfig = async (): Promise => const ext: string = fs.existsSync(`${process.cwd()}/vite.config.ts`) ? "ts" : "js"; + const { loadConfigFromFile } = await loadProjectVite(); const config = await loadConfigFromFile( { "command": "build", @@ -41,8 +43,8 @@ export const loadConfig = async (): Promise => // update config ctx.configObject = config; - ctx.outDir = ctx.configObject.config?.build?.outDir || "dist"; - ctx.buildDir = `${process.cwd()}/${ctx.outDir}/${ctx.platformDir}/${ctx.environment}`; + ctx.outDir = ctx.configObject?.config.build?.outDir || "dist"; + ctx.buildDir = path.resolve(ctx.outDir, ctx.platformDir, ctx.environment); if (!fs.existsSync(`${ctx.buildDir}`)) { fs.mkdirSync(`${ctx.buildDir}`, { "recursive": true }); @@ -67,10 +69,12 @@ export const buildWeb = (): Promise => "@next2d/vite-plugin-next2d-auto-loader" ], { "stdio": "inherit" }); + stream.once("error", reject); stream.on("close", (code: number): void => { if (code !== 0) { reject("vite plugin command failed."); + return; } const stream = $spawn("npx", [ @@ -80,10 +84,12 @@ export const buildWeb = (): Promise => "build" ], { "stdio": "inherit" }); + stream.once("error", reject); stream.on("close", (code: number): void => { if (code !== 0) { reject("Export of `HTML` and `JavaScript` failed."); + return; } console.log(); diff --git a/src/xbox.ts b/src/xbox.ts index b338148..bd94371 100644 --- a/src/xbox.ts +++ b/src/xbox.ts @@ -1,12 +1,12 @@ // Xbox (GDK ネイティブ) ビルド。V8 に Next2D の JS を載せ、Dawn(WebGPU/D3D12)で // 描画する C++ ホストを、テンプレートのスキャフォールド → 資材配置 → pak 埋め込み → // CMake/GDK ビルドの順で書き出す。 -import pc from "picocolors"; +import pc from "./colors.js"; import fs from "fs"; import os from "os"; import path from "path"; import cp from "child_process"; -import { minifySync } from "vite"; +import { loadProjectVite } from "./project-vite.js"; import { ctx } from "./context.js"; import { getTemplateDir } from "./utils.js"; import { @@ -208,12 +208,18 @@ export const renderAssetsRc = (pakAbsPath: string): string => * @method * @public */ -export const minifyJs = (name: string, code: string): string => +export const minifyJs = async (name: string, code: string): Promise => { try { - const result = minifySync(name, code); + const vite = await loadProjectVite(); + if (typeof vite.minifySync !== "function") { + // Vite 7 exposes esbuild rather than the Oxc minifier used by Vite 8. + const result = await vite.transformWithEsbuild(code, name, { "minify": true, "legalComments": "none" }); + return result.code; + } + const result = vite.minifySync(name, code); if (result.errors && result.errors.length > 0) { - const msg = (result.errors[0] as any)?.message ?? String(result.errors[0]); + const msg = result.errors[0]?.message ?? String(result.errors[0]); console.log(pc.yellow(`Xbox: minify skipped for ${name} (${msg}); embedding as-is.`)); return code; } @@ -240,74 +246,70 @@ export const minifyJs = (name: string, code: string): string => * @method * @private */ -const embedXboxAssets = (): Promise => +const embedXboxAssets = async (): Promise => { - return new Promise((resolve, reject): void => - { - const xboxDir: string = `${process.cwd()}/${XBOX_DIR_NAME}`; - const pakPath: string = `${xboxDir}/assets.pak`; - const rcPath: string = `${xboxDir}/assets.rc`; + const xboxDir: string = `${process.cwd()}/${XBOX_DIR_NAME}`; + const pakPath: string = `${xboxDir}/assets.pak`; + const rcPath: string = `${xboxDir}/assets.rc`; - try { - // 収集: assets/app 一式 (キーは app 基準) + host スクリプト (js/ 基準)。 - const entries: [string, string][] = []; - entries.push(...walkFiles(`${xboxDir}/assets/app`, "")); - const bootstrap: string = `${xboxDir}/js/bootstrap.js`; - if (fs.existsSync(bootstrap)) { - entries.push(["js/bootstrap.js", bootstrap]); - } - const selftest: string = `${xboxDir}/js/selftest.js`; - if (fs.existsSync(selftest)) { - entries.push(["js/selftest.js", selftest]); - } + try { + // 収集: assets/app 一式 (キーは app 基準) + host スクリプト (js/ 基準)。 + const entries: [string, string][] = []; + entries.push(...walkFiles(`${xboxDir}/assets/app`, "")); + const bootstrap: string = `${xboxDir}/js/bootstrap.js`; + if (fs.existsSync(bootstrap)) { + entries.push(["js/bootstrap.js", bootstrap]); + } + const selftest: string = `${xboxDir}/js/selftest.js`; + if (fs.existsSync(selftest)) { + entries.push(["js/selftest.js", selftest]); + } - if (entries.length === 0) { - return reject("No Xbox assets found to embed (assets/app is empty)."); - } + if (entries.length === 0) { + throw new Error("No Xbox assets found to embed (assets/app is empty)."); + } - // (key, データ) を読み込み、pak バイナリへ直列化する。 - // js/ のホストスクリプト(bootstrap.js/selftest.js)は minify + 難読化する。 - // assets/app の JS は vite が本番ビルドで minify 済みのためそのまま。 - let minified = 0; - const pakEntries: [string, Buffer][] = entries.map( - ([key, abs]): [string, Buffer] => { - if (key.startsWith("js/") && key.endsWith(".js")) { - const src = fs.readFileSync(abs, { "encoding": "utf8" }); - const out = minifyJs(key, src); - if (out.length < src.length) { - ++minified; - } - return [key, Buffer.from(out, "utf8")]; + // (key, データ) を読み込み、pak バイナリへ直列化する。 + // js/ のホストスクリプト(bootstrap.js/selftest.js)は minify + 難読化する。 + // assets/app の JS は vite が本番ビルドで minify 済みのためそのまま。 + let minified = 0; + const pakEntries: [string, Buffer][] = await Promise.all(entries.map( + async ([key, abs]): Promise<[string, Buffer]> => { + if (key.startsWith("js/") && key.endsWith(".js")) { + const src = fs.readFileSync(abs, { "encoding": "utf8" }); + const out = await minifyJs(key, src); + if (out.length < src.length) { + ++minified; } - return [key, fs.readFileSync(abs)]; + return [key, Buffer.from(out, "utf8")]; } - ); - const pak: Buffer = buildPak(pakEntries); - if (minified > 0) { - console.log(pc.green(`Minified ${minified} host script(s) before embedding.`)); + return [key, fs.readFileSync(abs)]; } - fs.writeFileSync(pakPath, pak); + )); + const pak: Buffer = buildPak(pakEntries); + if (minified > 0) { + console.log(pc.green(`Minified ${minified} host script(s) before embedding.`)); + } + fs.writeFileSync(pakPath, pak); - // rc.exe が assets.pak を RCDATA "N2DASSETS" として取り込む .rc を生成する。 - fs.writeFileSync(rcPath, renderAssetsRc(path.resolve(pakPath)), "utf8"); + // rc.exe が assets.pak を RCDATA "N2DASSETS" として取り込む .rc を生成する。 + fs.writeFileSync(rcPath, renderAssetsRc(path.resolve(pakPath)), "utf8"); - console.log(pc.green( - `Embedded ${entries.length} Xbox asset file(s) into assets.pak ` + console.log(pc.green( + `Embedded ${entries.length} Xbox asset file(s) into assets.pak ` + `(${(pak.length / 1024 / 1024).toFixed(2)} MB).` - )); + )); - // 埋め込み済みの平文 `assets/`・`js/` は書き出し一覧から除外する。 - // これらは assets.pak (=exe 内 RCDATA) に格納済みで、CMake も埋め込みモードでは - // exe 隣へステージしないため、xbox/ 直下に残しても不要かつ平文流出になる。 - fs.rmSync(`${xboxDir}/assets`, { "recursive": true, "force": true }); - fs.rmSync(`${xboxDir}/js`, { "recursive": true, "force": true }); - console.log(pc.green("Excluded plaintext `assets/` and `js/` from the export (embedded in exe).")); + // 埋め込み済みの平文 `assets/`・`js/` は書き出し一覧から除外する。 + // これらは assets.pak (=exe 内 RCDATA) に格納済みで、CMake も埋め込みモードでは + // exe 隣へステージしないため、xbox/ 直下に残しても不要かつ平文流出になる。 + fs.rmSync(`${xboxDir}/assets`, { "recursive": true, "force": true }); + fs.rmSync(`${xboxDir}/js`, { "recursive": true, "force": true }); + console.log(pc.green("Excluded plaintext `assets/` and `js/` from the export (embedded in exe).")); - resolve(); - } catch (error) { - reject(`Failed to embed Xbox assets. ${error}`); - } - }); + } catch (error) { + throw new Error(`Failed to embed Xbox assets. ${error}`); + } }; /** diff --git a/templates/electron/entitlements.plist b/templates/electron/entitlements.plist new file mode 100644 index 0000000..f795eb2 --- /dev/null +++ b/templates/electron/entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + + diff --git a/templates/electron/icons/icon.icns b/templates/electron/icons/icon.icns new file mode 100644 index 0000000000000000000000000000000000000000..dc30a3489b83ca257fd5b5825c585b15a4e78ba9 GIT binary patch literal 148198 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 z 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 CHP+hz literal 0 HcmV?d00001 diff --git a/templates/electron/icons/icon.ico b/templates/electron/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3d07c9da42d6cd953b3060e8a1796c3d0cd1986d GIT binary patch literal 27745 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%0D#3L2d+oRg zYa^Owuu54MVelvZQQ5uY>D$juH&gl(z0kRoA8^LSZMI)utE*kkXFAUT13^Tm3OC!p ze>E*dj4SByA+*PXgV2p1K=$yiqx02T=PqLG)g$)JgMl(k2X)TSUYknwnJ7T7TsM zCMm(cErePjUklh36ey-Pi@w0@3>@X1QjB~HY*770+xT04FEOc1FpZpbsqiI>!e2fP z3L6MCe`a-Y$FeR(TKa7NUP&O5OTb*FZ8EZvcVL5dC(LBuWW^9 z(iwlIYe5l)c0!1HASCCzb;xMmmT6zn84k4Xp#)%qO1Gs)T09IXu+e%cG9DPE?%gTY zvDd*OM>4(%=JM#dzXyx@&6P$!G*iV}S!Mm^DKd@L;)T^eymKdsZR2nkrxw{bZQxls zTehmbt-SJ6x0dJej+U=M7Tf3V_PSx;6-5{ZKtVZempgHs@;q${%ZpvUGu@M=&yIb{ zJ4kkU%C7&~_V=ekJBI#31~&y5{Z0FgcY)){sBZ}7iHiH1X#Vq-T#@-i5cxA2be%Li zV4QT<;O;>nbqMe#f$A5x{DT14SLgRt&O9$j<7>%O{P{3jTkcHfOYuw`zD5p*lYPEr z+ZDOw$rbHIMZMrp^!3CzKp3&uyd?i~F~#1H}y#he3k0X0ZDwl}q~O zOYt0QvX8#LkI2TtN)^f~bOksId>G!{;}?fe7zO>=%gMuGvow@xdE-n$$o zcPWX(;8Y%g^oHo6rRR;&Aa0I3f2!WH16<;U-}LA8X{6r%(*mz~ib}|xm=?=uG^Cs` zV!GZ(edGICG;!Q?)@4d$gw#5cdqGp!&Ej>L@=Rvi_HnaltgISWeCa#CzzJ?~i6u_3 zQDV+LMH*UAhWIwo?n|dTrptt`)kqL}H#}PQRXwI~H-z&Y^N5J>FUGO@QgXk~Ie@Y~ zYBiiHKJ3?87FD+#0B-B0_k7zFy4!h5Slw@~+_K$^;1dQi^Pj-80;3f-3jV*@^zCqm zV1?gDGxW(7ap!yG_BA>Sfw51T`Qvk-KPOU{b}r00K$?pc56-x_lt1Ac@h7mJ&F#l4 z`^d>xtfYn%_dOsrn1?|^M%yoW>bxg~2k&~Em`2bANme}RM3FhuE#3`I4T``b#%erwr`d${IjC08-zp%;mw&fqM!ohaee$+t>%;Lb_zr|V)2QyEK4Tlz0 zJFAv32RedITX*w!Xc)<63)l-W60%WqU>OjZc@B`iH`%BDiNb5YB;+o&=Qc%yyPnEz zMc9{$(})U4W2n=j+10!i^l z(#F;g+3dXz1j8Nvi}F{x+HYm%Phns0R+l7I+f7sH>e`MwGOw38xPZNZ-?F_CgaVcr zEh`ItN|ye)j14IEK4D@97O<@Xzf=Cf1 z3s%P=L@F+XCir&CNW=@f)N|mIrdpc@T0t93@bPs2FLiEn&)7bwkm;W zi5(5Q<&8U$8-*;8vD4VoXFkc|Ka*NtjA)PksemPo6lgYHung7%Z5=<+fKY-K!8JEW zV$^HQW9H5-nb@ljM=cE!`ZmIhvdn zSpQ<(`nrh{i8DjB<%%*6D_{N$WOn~l?n%+bc3(Z#wy64k2`^qTA_ zF-^FdS|spm`3;9@9v~LWc--xkDZb)QbHF;~!aEm;(ImW`;nC#5xuc=5e~yJJQD$|3O_^;(+z( ziH+GcW;+ zh{C)OZ%Et!peINPrHlv1WPw|&teNi0G@$|#Pjc~ForeJv(qDmMwzJn)zhZv8$2>e-J zI^?EEC}pn3>8|4Sg7P;Z%dMEYUp=rp%L%}<+}>V`K?esIPOhYPM;Tq^kV?*-Nl7m& zLs&6d>UhK~!6RzdI)4+~=C!RcZcu2m$O;I>X2`(cc%7uJA}+iQ%qU% zX>4N8{pLcN4cdi?o7V?M$D0JY4S_9FNLN7ZaJx;m|K2_~d$Z-Iy_&-WXLR?S)aCeV fP^nH_ES}qJ=9f)J%>tM}*8*s%>#9|%*na*$bies~ literal 0 HcmV?d00001 diff --git a/templates/electron/icons/icon.png b/templates/electron/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..552ab201494d4622ed4476dffbc38f5acd55a009 GIT binary patch literal 19387 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

6J9Nhv=}t^o_(kg&ahxOQ1Mc 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 { + const { width, height } = screen.getPrimaryDisplay().workAreaSize; + mainWindow = new BrowserWindow({ + width: Math.min(config.window.width, width), + height: Math.min(config.window.height, height), + title: config.appName, + fullscreen: config.window.fullscreen, + ...(config.icon ? { icon: path.join(assetRoot, config.icon) } : {}), + useContentSize: true, + backgroundColor: "#000000", + autoHideMenuBar: true, + show: false, + webPreferences: { nodeIntegration: false, contextIsolation: true, sandbox: true } + }); + mainWindow.webContents.setWindowOpenHandler(() => ({ action: "deny" })); + mainWindow.webContents.on("will-navigate", (event, url) => { + if (!resolveAssetPath(assetRoot, url)) { + event.preventDefault(); + } + }); + mainWindow.webContents.on("before-input-event", (event, input) => { + if (input.type !== "keyDown") { + return; + } + if (input.key === "F11" || (input.alt && input.key === "Enter")) { + mainWindow.setFullScreen(!mainWindow.isFullScreen()); + event.preventDefault(); + } else if (input.key === "Escape" && mainWindow.isFullScreen()) { + mainWindow.setFullScreen(false); + event.preventDefault(); + } + }); + mainWindow.webContents.on("render-process-gone", (_event, details) => { + console.error("Renderer exited:", details.reason); + app.exit(1); + }); + mainWindow.once("ready-to-show", () => mainWindow.show()); + await mainWindow.loadURL(startURL); +}; + +app.whenReady().then(async () => { + Menu.setApplicationMenu(process.platform === "darwin" ? Menu.buildFromTemplate([ + { role: "appMenu" }, { role: "editMenu" }, { role: "windowMenu" } + ]) : null); + session.defaultSession.setPermissionRequestHandler((_contents, permission, callback) => { + callback(permission === "fullscreen"); + }); + session.defaultSession.setPermissionCheckHandler((_contents, permission) => permission === "fullscreen"); + protocol.handle("next2d", async (request) => { + let asset = resolveAssetPath(assetRoot, request.url); + if (!asset || request.method !== "GET") { + return new Response("Not found", { status: 404 }); + } + // Framework SPA routes use history.pushState (e.g. /home); reload the entry. + if (!path.extname(asset) && !fs.existsSync(asset)) { + asset = path.join(assetRoot, "index.html"); + } + try { + const response = await net.fetch(pathToFileURL(asset).href); + const headers = new Headers(response.headers); + headers.set("Content-Security-Policy", CSP); + return new Response(response.body, { status: response.status, headers }); + } catch { + return new Response("Not found", { status: 404 }); + } + }); + await createWindow(); +}).catch((error) => { + console.error(error); + app.exit(1); +}); + +// Closing the game must stop Steam's "Running" status, including on macOS. +app.on("window-all-closed", () => app.quit()); diff --git a/templates/electron/local-assets.cjs b/templates/electron/local-assets.cjs new file mode 100644 index 0000000..4ad8810 --- /dev/null +++ b/templates/electron/local-assets.cjs @@ -0,0 +1,24 @@ +const path = require("node:path"); + +const CSP = "default-src 'self' data: blob:; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'none'"; +const resolveAssetPath = (root, requestURL) => { + try { + const url = new URL(requestURL); + if (url.protocol !== "next2d:" || url.host !== "game" || url.username || url.password) { + return null; + } + const name = decodeURIComponent(url.pathname); + if (name.includes("\0") || name.includes("\\")) { + return null; + } + const file = path.resolve(root, `.${name === "/" ? "/index.html" : name}`); + const relative = path.relative(root, file); + if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) { + return null; + } + return file; + } catch { + return null; + } +}; +module.exports = { CSP, resolveAssetPath }; diff --git a/templates/electron/package.json b/templates/electron/package.json new file mode 100644 index 0000000..adae14e --- /dev/null +++ b/templates/electron/package.json @@ -0,0 +1,11 @@ +{ + "name": "next2d-game", + "version": "1.0.0", + "description": "Next2D desktop game", + "private": true, + "type": "commonjs", + "main": "index.js", + "devDependencies": { + "electron": "44.4.3" + } +} diff --git a/tests/cli.test.ts b/tests/cli.test.ts index f4d13b0..142ca66 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -55,3 +55,8 @@ test("SUPPORTED_PLATFORMS: 期待するプラットフォームを含む", () => } assert.equal(SUPPORTED_PLATFORMS.has("bogus"), false); }); + +test("parseArgv: Electron architecture and missing value", () => { + assert.equal(parseArgv(["--platform", "steam:macos", "--env", "prd", "--arch", "universal"]).arch, "universal"); + assert.equal(parseArgv(["--platform", "steam:linux", "--env", "prd", "--arch"]).hasHelp, true); +}); diff --git a/tests/electron.test.ts b/tests/electron.test.ts new file mode 100644 index 0000000..f38e5f4 --- /dev/null +++ b/tests/electron.test.ts @@ -0,0 +1,206 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { createRequire } from "node:module"; +import { resolveElectronTarget } from "../dist/electron.js"; +import { withElectronHost, getElectronVersion } from "../dist/electron-host.js"; +import { readElectronConfig, createElectronPackagerOptions } from "../dist/electron-config.js"; +import { readSteamConfig, writeSteamMetadata } from "../dist/steam.js"; +const require = createRequire(import.meta.url); +const { resolveAssetPath } = require("../templates/electron/local-assets.cjs"); + +const fixture = (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d electron ")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "test-game", version: "1.2.3" })); + return root; +}; + +test("Steam targets use darwin, universal macOS and x64 Windows/Linux", () => { + assert.deepEqual(resolveElectronTarget("steam:macos"), { platform: "darwin", arch: "universal" }); + assert.deepEqual(resolveElectronTarget("steam:windows"), { platform: "win32", arch: "x64" }); + assert.deepEqual(resolveElectronTarget("steam:linux"), { platform: "linux", arch: "x64" }); + assert.throws(() => resolveElectronTarget("steam:linux", "universal")); + assert.throws(() => resolveElectronTarget("steam:windows", "all")); + assert.throws(() => resolveElectronTarget("mas")); + const foreign = process.platform === "win32" ? "linux" : "windows"; + assert.throws(() => resolveElectronTarget(foreign, "", true)); +}); + +test("temporary host uses current metadata and assets without creating a game electron directory", async (t) => { + const root = fixture(t); + const source = path.join(root, "web"); + fs.mkdirSync(source); + fs.writeFileSync(path.join(source, "index.html"), "hello"); + const config = readElectronConfig(root); + let generated; + const result = await withElectronHost(root, source, config, async (dir) => { + generated = dir; + const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); + assert.equal(pkg.version, "1.2.3"); + assert.equal(pkg.name, "test-game"); + assert.equal(pkg.dependencies, undefined); + assert.equal(pkg.devDependencies, undefined); + assert.equal(fs.readFileSync(path.join(dir, "resources/index.html"), "utf8"), "hello"); + const runtime = JSON.parse(fs.readFileSync(path.join(dir, "runtime-config.json"), "utf8")); + assert.equal(runtime.appId, config.appId); + assert.ok(fs.existsSync(path.join(dir, "resources", runtime.icon))); + assert.match(fs.readFileSync(path.join(dir, "index.js"), "utf8"), /sandbox: true/); + assert.equal(fs.existsSync(path.join(dir, "node_modules")), false); + const options = createElectronPackagerOptions(root, config, "windows", path.join(dir, "resources")); + assert.deepEqual(options.extraResource, [path.join(dir, "resources")]); + return "packaged"; + }); + assert.equal(result, "packaged"); + assert.equal(fs.existsSync(generated), false); + assert.equal(fs.existsSync(path.join(root, "electron")), false); + assert.match(getElectronVersion(), /^\d+\.\d+\.\d+$/); +}); + +test("temporary host is removed on failure and separate builds never reuse host files", async (t) => { + const root = fixture(t); + const config = readElectronConfig(root); + await assert.rejects(withElectronHost(root, root, config, async () => assert.fail("must not be called")), /entry point is missing/); + fs.writeFileSync(path.join(root, "index.html"), "hello"); + const generated = []; + await assert.rejects(withElectronHost(root, root, config, async (first) => { + generated.push(first); + return withElectronHost(root, root, config, async (second) => { + generated.push(second); + assert.notEqual(first, second); + assert.ok(fs.existsSync(first)); + throw new Error("packaging failed"); + }); + }), /packaging failed/); + for (const dir of generated) { + assert.equal(fs.existsSync(dir), false); + } + assert.deepEqual(fs.readdirSync(root).sort(), ["index.html", "package.json"]); +}); + +test("JSON metadata drives packaging; missing Depot IDs still allow export", (t) => { + const root = fixture(t); + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify({ + appId: "app.test.game", appName: "My Game", executableName: "my-game", + steam: { appId: 2409460, depots: { windows: null } } + })); + const config = readElectronConfig(root); + const options = createElectronPackagerOptions(root, config, "windows", path.join(root, "resources")); + assert.equal(options.name, "My Game"); + assert.equal(options.appBundleId, "app.test.game"); + assert.equal(options.executableName, "my-game"); + assert.deepEqual(readSteamConfig(config, "steam:windows"), { appId: "2409460", depotId: null }); +}); + +test("Electron host and Windows description prefer config and fall back to package metadata", async (t) => { + const root = fixture(t); + const web = path.join(root, "web"); + fs.mkdirSync(web); + fs.writeFileSync(path.join(web, "index.html"), "game"); + for (const [override, fallback, expected] of [ + ["Desktop description", "Package description", "Desktop description"], + [undefined, "Package description", "Package description"], + [null, "Package description", "Package description"], + ["", "Package description", ""], + [undefined, undefined, ""] + ]) { + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "test-game", version: "1.2.3", description: fallback })); + const source = JSON.stringify({ description: override }); + fs.writeFileSync(path.join(root, "electron.config.json"), source); + const config = readElectronConfig(root); + await withElectronHost(root, web, config, async (dir) => { + assert.equal(JSON.parse(fs.readFileSync(path.join(dir, "package.json"))).description, expected); + assert.equal(createElectronPackagerOptions(root, config, "windows", web).win32metadata.FileDescription, expected); + }); + assert.equal(fs.readFileSync(path.join(root, "electron.config.json"), "utf8"), source); + } + fs.writeFileSync(path.join(root, "electron.config.json"), '{"description":123}'); + assert.throws(() => readElectronConfig(root), /description must be a string/); +}); + +test("invalid IDs, invalid icons and unsafe names fail", (t) => { + const root = fixture(t); + for (const input of [ + { steam: { appId: 0 } }, + { steam: { appId: null, depots: { windows: 123 } } }, + { icons: { windows: "missing.ico" } }, + { appName: "../escape" }, + { architectures: { linux: "universal" } }, + { macos: { notarize: true, sign: false } } + ]) { + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify(input)); + assert.throws(() => readElectronConfig(root)); + } +}); + +test("unassigned Steam IDs in templates allow local packaging", (t) => { + const root = fixture(t); + for (const appId of [null, undefined]) { + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify({ + steam: { appId, depots: { windows: null, macos: null, linux: null } } + })); + const config = readElectronConfig(root); + assert.equal(readSteamConfig(config, "steam:windows"), null); + assert.equal(createElectronPackagerOptions(root, config, "windows", path.join(root, "resources")).name, "test-game"); + } +}); + +test("SteamPipe maps package contents to install root and excludes development AppID", (t) => { + const root = fixture(t); + for (const [platform, arch, executable] of [ + ["windows", "x64", "test-game.exe"], ["linux", "x64", "test-game"], ["macos", "universal", "My Game.app"] + ]) { + const content = path.join(root, platform); + fs.mkdirSync(content); + if (platform === "macos") { + fs.mkdirSync(path.join(content, executable)); + } else { + fs.writeFileSync(path.join(content, executable), "binary", { mode: 0o755 }); + } + const scripts = `${content}-steampipe`; + writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: "1234" }); + const launch = JSON.parse(fs.readFileSync(path.join(scripts, "launch.json"), "utf8")); + assert.equal(launch.executable, executable); + assert.equal(path.resolve(scripts, launch.contentRoot), content); + const depot = fs.readFileSync(path.join(scripts, "depot_build.vdf"), "utf8"); + assert.match(depot, /"Recursive" "1"/); + assert.match(depot, /"FileExclusion" "steam_appid.txt"/); + const app = fs.readFileSync(path.join(scripts, "app_build.vdf"), "utf8"); + assert.match(app, /"Preview" "0"/); + assert.doesNotMatch(app, /SetLive/); + assert.match(fs.readFileSync(path.join(scripts, "app_preview.vdf"), "utf8"), /"Preview" "1"/); + writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: null }); + assert.equal(fs.existsSync(path.join(scripts, "app_build.vdf")), false); + } +}); + +test("local protocol rejects foreign origins, encoded traversal and malformed paths", () => { + const root = path.resolve(os.tmpdir(), "game/resources"); + assert.equal(resolveAssetPath(root, "next2d://game/assets/test%20file.js"), path.join(root, "assets/test file.js")); + assert.equal(resolveAssetPath(root, "next2d://game/"), path.join(root, "index.html")); + for (const url of [ + "https://game/index.html", "next2d://other/index.html", "next2d://user@game/index.html", + "next2d://game/%2e%2e%2fsecret", "next2d://game/%5c..%5csecret", "next2d://game/%00", "next2d://game/%ZZ" + ]) { + assert.equal(resolveAssetPath(root, url), null, url); + } +}); + +test("release mode requires signing and notarization credentials", (t) => { + const root = fixture(t); + const previous = { ...process.env }; + t.after(() => { process.env = previous; }); + process.env.NEXT2D_STEAM_RELEASE = "1"; + delete process.env.APPLE_SIGNING_IDENTITY; + delete process.env.APPLE_NOTARY_PROFILE; + const config = readElectronConfig(root); + assert.throws(() => createElectronPackagerOptions(root, config, "macos", path.join(root, "resources")), /APPLE_SIGNING_IDENTITY/); + process.env.APPLE_SIGNING_IDENTITY = "test identity"; + assert.throws(() => createElectronPackagerOptions(root, config, "macos", path.join(root, "resources")), /APPLE_NOTARY_PROFILE/); + process.env.APPLE_NOTARY_PROFILE = "test profile"; + const options = createElectronPackagerOptions(root, config, "macos", path.join(root, "resources")); + assert.ok(options.osxSign); + assert.deepEqual(options.osxNotarize, { keychainProfile: "test profile" }); +}); diff --git a/tests/native.test.ts b/tests/native.test.ts new file mode 100644 index 0000000..a5645c5 --- /dev/null +++ b/tests/native.test.ts @@ -0,0 +1,104 @@ +import { before, test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { getCapacitorCommand } from "../dist/native.js"; + +// Allow a cold npm cache to download SDKs before the short per-command timeouts. +before(async () => { await getCapacitorCommand(); }, { timeout: 180000 }); + +const fixture = (t, platform) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d native ")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "test-game", version: "1.0.0" })); + fs.writeFileSync(path.join(root, "capacitor.config.json"), JSON.stringify({ + appId: "app.example.game", appName: "Test Game", webDir: "obsolete", + [platform]: { path: `native/${platform}` } + })); + const web = path.join(root, "custom output", platform, "prd"); + fs.mkdirSync(web, { recursive: true }); + fs.writeFileSync(path.join(web, "index.html"), "Game content"); + return root; +}; + +const run = (root, platform, action = "syncNative") => spawnSync(process.execPath, ["--input-type=module", "-e", ` + import { ctx } from ${JSON.stringify(new URL("../dist/context.js", import.meta.url).href)}; + import { ${action} } from ${JSON.stringify(new URL("../dist/native.js", import.meta.url).href)}; + Object.assign(ctx, { platform: ${JSON.stringify(platform)}, platformDir: ${JSON.stringify(platform)}, environment: "prd", outDir: "custom output" }); + await ${action}(); +`], { + cwd: root, encoding: "utf8", timeout: 30000, + // Test add/sync only. Capacitor's optional Gradle discovery must not start a native build. + env: { ...process.env, JAVA_HOME: path.join(root, "no-jdk") } +}); + +const succeeded = (result) => assert.equal(result.status, 0, result.stdout + result.stderr); + +test("Android add/sync uses builder SDKs with no game Capacitor installation and keeps plugins/hooks", (t) => { + const root = fixture(t, "android"); + const plugin = path.join(root, "node_modules/test-plugin"); + fs.mkdirSync(path.join(plugin, "android/src/main"), { recursive: true }); + fs.writeFileSync(path.join(plugin, "package.json"), JSON.stringify({ + name: "test-plugin", version: "1.0.0", capacitor: { android: { src: "android" } } + })); + fs.writeFileSync(path.join(plugin, "android/build.gradle"), "// Test plugin"); + const packageFile = path.join(root, "package.json"); + const pkg = JSON.parse(fs.readFileSync(packageFile, "utf8")); + pkg.dependencies = { "test-plugin": "1.0.0" }; + pkg.scripts = { "capacitor:sync:before": "node hook.cjs" }; + fs.writeFileSync(packageFile, JSON.stringify(pkg)); + fs.writeFileSync(path.join(root, "hook.cjs"), 'require("node:fs").writeFileSync("hook-cwd.txt", process.cwd());'); + const originalPackage = fs.readFileSync(packageFile, "utf8"); + succeeded(run(root, "android")); + assert.equal(fs.realpathSync(fs.readFileSync(path.join(root, "hook-cwd.txt"), "utf8")), fs.realpathSync(root)); + const native = path.join(root, "native/android"); + const settings = fs.readFileSync(path.join(native, "capacitor.settings.gradle"), "utf8"); + assert.match(settings, /include ':capacitor-android'/); + assert.match(settings, /include ':test-plugin'/); + const sdkPath = settings.match(/project\(':capacitor-android'\)\.projectDir = new File\('([^']+)'\)/)[1]; + assert.ok(fs.existsSync(path.resolve(native, sdkPath, "build.gradle"))); + assert.match(fs.readFileSync(path.join(native, "app/src/main/assets/public/index.html"), "utf8"), /Game content/); + assert.equal(JSON.parse(fs.readFileSync(path.join(root, "capacitor.config.json"))).webDir, "custom output/android/prd/"); + fs.writeFileSync(path.join(native, "custom-native.txt"), "keep"); + succeeded(run(root, "android")); + assert.equal(fs.readFileSync(path.join(native, "custom-native.txt"), "utf8"), "keep"); + assert.equal(fs.readFileSync(packageFile, "utf8"), originalPackage); + assert.equal(fs.existsSync(path.join(root, "node_modules/@capacitor")), false); +}); + +test("iOS SPM add/sync uses builder SDKs without touching game dependencies", { skip: process.platform !== "darwin" }, (t) => { + const root = fixture(t, "ios"); + succeeded(run(root, "ios")); + const native = path.join(root, "native/ios/App"); + assert.match(fs.readFileSync(path.join(native, "CapApp-SPM/Package.swift"), "utf8"), /capacitor-swift-pm/); + assert.match(fs.readFileSync(path.join(native, "App/public/index.html"), "utf8"), /Game content/); + fs.writeFileSync(path.join(native, "custom-native.txt"), "keep"); + succeeded(run(root, "ios")); + assert.equal(fs.readFileSync(path.join(native, "custom-native.txt"), "utf8"), "keep"); + assert.equal(fs.existsSync(path.join(root, "node_modules")), false); +}); + +test("native CLI failures stop build/open and return a nonzero exit", (t) => { + const root = fixture(t, "android"); + fs.rmSync(path.join(root, "custom output"), { recursive: true }); + for (const action of ["buildNative", "openNative", "runNative"]) { + const result = run(root, "android", action); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /Capacitor (?:sync|run|add) android failed/); + } +}); + +test("builder CLI resolution is independent of the game and supplies NODE_PATH", async (t) => { + const root = fixture(t, "android"); + const { cli, env } = await getCapacitorCommand(); + assert.ok(fs.existsSync(cli)); + const result = spawnSync(process.execPath, ["-e", ` + for (const name of ['core', 'ios', 'android']) { + console.log(require.resolve('@capacitor/' + name + '/package.json', { paths: [process.cwd()] })); + } + `], { cwd: root, env, encoding: "utf8" }); + succeeded(result); + assert.equal(result.stdout.trim().split("\n").length, 3); +}); diff --git a/tests/project-vite.test.ts b/tests/project-vite.test.ts new file mode 100644 index 0000000..d0ec169 --- /dev/null +++ b/tests/project-vite.test.ts @@ -0,0 +1,56 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import os from "node:os"; +import { loadProjectVite } from "../dist/project-vite.js"; +import { minifyJs } from "../dist/xbox.js"; + +const fixture = (t, type = "module", source = "export const loadConfigFromFile = () => ({ config: { build: { outDir: 'game-output' } } });") => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d-project-vite-")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const vite = path.join(root, "node_modules/vite"); + fs.mkdirSync(vite, { recursive: true }); + fs.writeFileSync(path.join(root, "package.json"), "{}"); + fs.writeFileSync(path.join(vite, "package.json"), JSON.stringify({ name: "vite", type, exports: "./index.js" })); + fs.writeFileSync(path.join(vite, "index.js"), source); + return root; +}; + +test("resolves the game's Vite instead of the builder's development dependency", async (t) => { + const vite = await loadProjectVite(fixture(t)); + const result = await vite.loadConfigFromFile({ command: "build", mode: "build" }); + assert.equal(result.config.build.outDir, "game-output"); +}); + +test("accepts the CJS API wrapper used by older Vite versions", async (t) => { + const vite = await loadProjectVite(fixture(t, "commonjs", "module.exports = { loadConfigFromFile: () => 'cjs' };")); + assert.equal(await vite.loadConfigFromFile({ command: "build", mode: "build" }), "cjs"); +}); + +test("missing or broken project Vite reports a useful error instead of using builder's copy", async (t) => { + const root = fixture(t); + fs.rmSync(path.join(root, "node_modules"), { recursive: true, force: true }); + await assert.rejects(loadProjectVite(root), /Vite is not installed/); + const broken = fixture(t, "module", "export const wrong = true;"); + await assert.rejects(loadProjectVite(broken), /does not expose loadConfigFromFile/); +}); + +test("Xbox uses the older Vite transform API when minifySync is unavailable", async (t) => { + const original = process.cwd(); + const root = fixture(t, "commonjs", `module.exports = { + loadConfigFromFile() {}, + transformWithEsbuild(code, name, options) { + if (name !== "bootstrap.js" || !options.minify || options.legalComments !== "none") { + throw new Error("Wrong minifier options"); + } + return Promise.resolve({ code: "globalThis.result=3;" }); + } + };`); + process.chdir(root); + try { + assert.equal(await minifyJs("bootstrap.js", "globalThis.result = 1 + 2;"), "globalThis.result=3;"); + } finally { + process.chdir(original); + } +}); diff --git a/tests/steam-shared.test.ts b/tests/steam-shared.test.ts new file mode 100644 index 0000000..fe4fa1d --- /dev/null +++ b/tests/steam-shared.test.ts @@ -0,0 +1,149 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { readElectronConfig } from "../dist/electron-config.js"; +import { readSteamConfig, recordSteamPackage, writeSharedSteamDepots } from "../dist/steam.js"; +import { parseArgv } from "../dist/cli.js"; + +const fixture = (t, depots = { windows: 2409461, macos: "2409461", linux: 2409461 }) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d shared depot ")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "test-game", version: "1.0.0" })); + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify({ + appName: "My Game", executableName: "my-game", steam: { appId: 2409460, depots } + })); + return { root, steamRoot: path.join(root, "custom output/steam"), config: readElectronConfig(root) }; +}; + +const addPackage = (f, os, arch = os === "macos" ? "universal" : "x64", version = "1.0.0") => { + const native = { windows: "win32", macos: "darwin", linux: "linux" }[os]; + const content = path.join(f.steamRoot, os, "build/prd", `My Game-${native}-${arch}`); + fs.mkdirSync(content, { recursive: true }); + const exe = os === "macos" ? "My Game.app" : os === "windows" ? "my-game.exe" : "my-game"; + if (os === "macos") fs.mkdirSync(path.join(content, exe)); + else fs.writeFileSync(path.join(content, exe), "binary", { mode: 0o755 }); + fs.writeFileSync(path.join(content, "resources.pak"), os); + recordSteamPackage(content, `steam:${os}`, arch, f.config, version); + return content; +}; + +const scriptDir = (f) => path.join(f.steamRoot, "shared/prd/depot-2409461"); + +test("one OS with a shared Depot ID exports metadata but no partial upload VDF", (t) => { + const f = fixture(t); + assert.deepEqual(readSteamConfig(f.config, "steam:macos").sharedPlatforms, ["windows", "macos", "linux"]); + const content = addPackage(f, "macos"); + assert.equal(fs.existsSync(`${content}-steampipe/app_build.vdf`), false); + const launch = JSON.parse(fs.readFileSync(`${content}-steampipe/launch.json`, "utf8")); + assert.equal(launch.executable, "macos/My Game.app"); + assert.equal(launch.localExecutable, "My Game.app"); + const pending = writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + assert.equal(pending.length, 1); + assert.equal(fs.existsSync(path.join(scriptDir(f), "app_build.vdf")), false); + const status = JSON.parse(fs.readFileSync(path.join(scriptDir(f), "launch.json"), "utf8")); + assert.equal(status.ready, false); + assert.equal(status.missing.length, 2); +}); + +test("shared manifest maps every OS into separate directories and uses latest architecture", (t) => { + const f = fixture(t); + for (const os of ["windows", "macos", "linux"]) addPackage(f, os); + const latest = addPackage(f, "macos", "arm64"); + assert.deepEqual(writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"), []); + const dir = scriptDir(f); + const depot = fs.readFileSync(path.join(dir, "depot_build.vdf"), "utf8"); + assert.equal((depot.match(/"FileMapping"/g) || []).length, 3); + for (const os of ["windows", "macos", "linux"]) { + assert.ok(depot.includes(`"DepotPath" "${os}/"`)); + assert.ok(depot.includes(`"LocalPath" "${os}/build/prd/My Game-`)); + } + assert.ok(depot.includes(path.relative(f.steamRoot, latest).replaceAll("\\", "/") + "/*")); + assert.doesNotMatch(depot, /darwin-universal/); + assert.doesNotMatch(depot, /steampipe|steam-package\.json/); + const app = fs.readFileSync(path.join(dir, "app_build.vdf"), "utf8"); + assert.match(app, /"ContentRoot" "\.\.\/\.\.\/\.\."/); + assert.equal((app.match(/"2409461" "depot_build.vdf"/g) || []).length, 1); + assert.doesNotMatch(app, /SetLive/); + assert.match(fs.readFileSync(path.join(dir, "app_preview.vdf"), "utf8"), /"Preview" "1"/); + const status = JSON.parse(fs.readFileSync(path.join(dir, "launch.json"), "utf8")); + assert.equal(status.ready, true); + assert.deepEqual(status.launches.map(x => x.executable), ["windows/my-game.exe", "macos/My Game.app", "linux/my-game"]); +}); + +test("missing or outdated packages invalidate previously ready manifests", (t) => { + const f = fixture(t); + for (const os of ["windows", "macos", "linux"]) addPackage(f, os); + writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + assert.ok(fs.existsSync(path.join(scriptDir(f), "app_build.vdf"))); + assert.equal(writeSharedSteamDepots(f.steamRoot, "prd", f.config, "2.0.0").length, 1); + assert.equal(fs.existsSync(path.join(scriptDir(f), "app_build.vdf")), false); + writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + fs.rmSync(path.join(f.steamRoot, "windows/build/prd/steam-package.json")); + assert.equal(writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0").length, 1); + assert.equal(fs.existsSync(path.join(scriptDir(f), "depot_build.vdf")), false); +}); + +test("mixed shared and separate depots remain independent; changing config removes obsolete VDFs", (t) => { + const f = fixture(t, { windows: 2409461, macos: 2409461, linux: 2409462 }); + const windows = addPackage(f, "windows"); + addPackage(f, "macos"); + const linux = addPackage(f, "linux"); + fs.writeFileSync(`${windows}-steampipe/app_build.vdf`, "obsolete standalone upload"); + writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + assert.equal(fs.existsSync(`${windows}-steampipe/app_build.vdf`), false); + assert.ok(fs.existsSync(`${linux}-steampipe/app_build.vdf`)); + const depot = fs.readFileSync(path.join(scriptDir(f), "depot_build.vdf"), "utf8"); + assert.doesNotMatch(depot, /linux/); + f.config.steam.depots.macos = "2409463"; + writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + assert.equal(fs.existsSync(path.join(scriptDir(f), "app_build.vdf")), false); +}); + +test("manifest CLI requires a Steam target and cannot rebuild or preview", () => { + const args = ["--platform", "steam:macos", "--env", "prd", "--steam-manifest"]; + assert.equal(parseArgv(args).steamManifest, true); + assert.equal(parseArgv(args).hasHelp, false); + for (const flag of ["--preview", "--build", "--open"]) assert.equal(parseArgv([...args, flag]).hasHelp, true); + assert.equal(parseArgv([...args, "--arch", "arm64"]).hasHelp, true); + assert.equal(parseArgv(["--platform", "web", "--env", "prd", "--steam-manifest"]).hasHelp, true); +}); + +test("legacy standalone VDFs are removed even without a package pointer", (t) => { + const f = fixture(t); + const scripts = path.join(f.steamRoot, "windows/build/prd/My Game-win32-x64-steampipe"); + fs.mkdirSync(scripts, { recursive: true }); + fs.writeFileSync(path.join(scripts, "app_build.vdf"), "old upload"); + writeSharedSteamDepots(f.steamRoot, "prd", f.config, "1.0.0"); + assert.equal(fs.existsSync(path.join(scripts, "app_build.vdf")), false); +}); + +test("manifest CLI assembles relocated CI packages without a web build, and fails on incomplete input", async (t) => { + const { spawnSync } = await import("node:child_process"); + const { fileURLToPath } = await import("node:url"); + const f = fixture(t); + for (const os of ["windows", "macos", "linux"]) addPackage(f, os); + const destination = path.join(f.root, "collected/steam"); + fs.mkdirSync(path.dirname(destination)); + fs.renameSync(f.steamRoot, destination); + const vite = path.join(f.root, "node_modules/vite"); + fs.mkdirSync(vite, { recursive: true }); + fs.writeFileSync(path.join(vite, "package.json"), JSON.stringify({ name: "vite", type: "module", main: "index.js" })); + fs.writeFileSync(path.join(vite, "index.js"), 'export const loadConfigFromFile = async () => ({ config: { build: { outDir: "collected" } } });'); + const run = () => spawnSync(process.execPath, [fileURLToPath(new URL("../dist/index.js", import.meta.url)), + "--platform", "steam:macos", "--env", "prd", "--steam-manifest"], { + cwd: f.root, encoding: "utf8", timeout: 10000, env: { ...process.env, npm_config_offline: "true" } + }); + let result = run(); + assert.equal(result.status, 0, result.stderr); + assert.doesNotMatch(result.stdout, /Packaging Electron|vite .*building/); + const script = path.join(destination, "shared/prd/depot-2409461/app_build.vdf"); + assert.ok(fs.existsSync(script)); + assert.doesNotMatch(fs.readFileSync(script, "utf8"), /custom output/); + fs.rmSync(path.join(destination, "linux/build/prd/steam-package.json")); + result = run(); + assert.equal(result.status, 1); + assert.match(result.stderr, /Shared Steam depots are incomplete/); + assert.equal(fs.existsSync(script), false); +}); diff --git a/tests/steam-upload.test.ts b/tests/steam-upload.test.ts new file mode 100644 index 0000000..e28cb0b --- /dev/null +++ b/tests/steam-upload.test.ts @@ -0,0 +1,161 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { readElectronConfig } from "../dist/electron-config.js"; +import { recordSteamPackage } from "../dist/steam.js"; +import { prepareSteamUpload, validateSteamBranch } from "../dist/steam-upload.js"; +import { parseArgv } from "../dist/cli.js"; + +const fixture = (t, depots = { windows: 2409461, macos: 2409461, linux: 2409461 }) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d upload ")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "test-game", version: "1.0.0" })); + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify({ + appName: "Test Game", executableName: "my-game", steam: { appId: 2409460, branch: "internal", depots } + })); + const config = readElectronConfig(root); + const steamRoot = path.join(root, "custom output/steam"); + const contents = {}; + for (const platform of Object.keys(depots)) { + if (!depots[platform]) continue; + const content = path.join(steamRoot, platform, "build/prd", `Test Game-${platform}`); + fs.mkdirSync(content, { recursive: true }); + if (platform === "macos") fs.mkdirSync(path.join(content, "Test Game.app")); + else fs.writeFileSync(path.join(content, platform === "windows" ? "my-game.exe" : "my-game"), "binary", { mode: 0o755 }); + recordSteamPackage(content, `steam:${platform}`, "x64", config, "1.0.0"); + contents[platform] = content; + } + return { root, steamRoot, environment: "prd", contents }; +}; + +const cli = (f, args = [], env = {}) => spawnSync(process.execPath, [ + fileURLToPath(new URL("../dist/index.js", import.meta.url)), + "--steam-upload", "--env", "prd", "--steam-root", f.steamRoot, ...args +], { + cwd: f.root, encoding: "utf8", timeout: 10000, + env: { ...process.env, STEAM_USERNAME: "", STEAMCMD: path.join(f.root, "missing-steamcmd"), ...env } +}); + +test("Steam upload CLI is platform independent and rejects mixed actions, missing values and unknown flags", () => { + const args = ["--steam-upload", "--env", "prd"]; + assert.equal(parseArgv(args).hasHelp, false); + assert.equal(parseArgv([...args, "--dry-run"]).dryRun, true); + assert.equal(parseArgv([...args, "--steam-branch", "internal"]).steamBranch, "internal"); + for (const extra of [["--platform", "steam:macos"], ["--steam-manifest"], ["--preview"], ["--build"], ["--open"], + ["--arch", "x64"], ["--steam-branch"], ["--steam-root"], ["--dryrun"]]) { + assert.equal(parseArgv([...args, ...extra]).hasHelp, true, extra.join(" ")); + } + assert.equal(parseArgv(["--steam-upload"]).hasHelp, true); + assert.equal(parseArgv(["--platform", "web", "--env", "prd", "--dry-run"]).hasHelp, true); +}); + +test("upload combines a shared depot once, sets internal, and preserves ordinary build manifests", (t) => { + const f = fixture(t); + const plan = prepareSteamUpload(f); + const manifest = fs.readFileSync(plan.manifest, "utf8"); + assert.match(manifest, /"SetLive" "internal"/); + assert.match(manifest, /"Preview" "0"/); + assert.equal((manifest.match(/"2409461" "depot_2409461.vdf"/g) || []).length, 1); + assert.equal(plan.launches.length, 3); + const depot = fs.readFileSync(path.join(path.dirname(plan.manifest), "depot_2409461.vdf"), "utf8"); + for (const os of ["windows", "macos", "linux"]) assert.match(depot, new RegExp(`"DepotPath" "${os}/"`)); + assert.doesNotMatch(depot, /steam-package\.json|uploads\//); + assert.match(depot, /"FileExclusion" "steam_appid.txt"/); + const preview = prepareSteamUpload({ ...f, dryRun: true }); + assert.notEqual(preview.manifest, plan.manifest); + assert.doesNotMatch(fs.readFileSync(preview.manifest, "utf8"), /SetLive/); + assert.match(fs.readFileSync(preview.manifest, "utf8"), /"Preview" "1"/); + assert.equal(fs.readFileSync(plan.manifest, "utf8"), manifest); +}); + +test("separate and shared depots are uploaded in the same app build with correct install paths", (t) => { + const f = fixture(t, { windows: 2409461, macos: 2409461, linux: 2409462 }); + const plan = prepareSteamUpload({ ...f, branch: "qa" }); + const manifest = fs.readFileSync(plan.manifest, "utf8"); + for (const depot of [2409461, 2409462]) assert.ok(manifest.includes(`"${depot}" "depot_${depot}.vdf"`)); + assert.match(manifest, /"SetLive" "qa"/); + const linux = fs.readFileSync(path.join(path.dirname(plan.manifest), "depot_2409462.vdf"), "utf8"); + assert.match(linux, /"DepotPath" "\."/); + assert.equal(plan.launches.find(x => x.platform === "steam:linux").executable, "my-game"); + assert.doesNotMatch(fs.readFileSync(`${f.contents.linux}-steampipe/app_build.vdf`, "utf8"), /SetLive/); +}); + +test("missing OS, stale version/IDs, invalid arch and escaped package paths block upload before SteamCMD", (t) => { + const f = fixture(t); + const file = path.join(path.dirname(f.contents.windows), "steam-package.json"); + const original = JSON.parse(fs.readFileSync(file, "utf8")); + for (const patch of [{ version: "0.9.0" }, { appId: "480" }, { depotId: "2409462" }, { arch: "ia32" }, + { contentRoot: "../secret" }, { contentRoot: "..\\secret" }]) { + fs.writeFileSync(file, JSON.stringify({ ...original, ...patch })); + const result = cli(f, [], { STEAM_USERNAME: "test_builder" }); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /windows package is missing or outdated/); + assert.doesNotMatch(result.stderr, /Cannot start SteamCMD/); + } + fs.rmSync(file); + assert.throws(() => prepareSteamUpload(f), /windows package is missing or outdated/); + assert.equal(fs.existsSync(path.join(f.steamRoot, "uploads")), false); +}); + +test("unsafe/default branches, invalid env and absent IDs are rejected", (t) => { + for (const branch of ["default", "Default", "public", "none", "", "test beta", "x\"\nSetLive", "../internal", null]) { + assert.throws(() => validateSteamBranch(branch)); + } + const f = fixture(t, {}); + assert.throws(() => prepareSteamUpload(f), /at least one Steam Depot/); + assert.throws(() => prepareSteamUpload({ ...f, environment: "../prd" }), /--env/); + const config = JSON.parse(fs.readFileSync(path.join(f.root, "electron.config.json"), "utf8")); + config.steam.appId = null; + fs.writeFileSync(path.join(f.root, "electron.config.json"), JSON.stringify(config)); + assert.throws(() => prepareSteamUpload(f), /steam.appId/); +}); + +test("dry-run CLI needs no SteamCMD, credentials, Vite or tool downloads", (t) => { + const f = fixture(t); + const result = cli(f, ["--dry-run"], { npm_config_cache: path.join(f.root, "empty-cache"), npm_config_offline: "true" }); + assert.equal(result.status, 0, result.stdout + result.stderr); + assert.match(result.stdout, /SteamCMD was not started/); + assert.match(result.stdout, /beta branch internal/); + assert.equal(fs.existsSync(path.join(f.root, "empty-cache")), false); + assert.equal(fs.existsSync(path.join(f.root, "node_modules")), false); +}); + +test("upload requires a cached-login account and propagates missing executable errors", (t) => { + const f = fixture(t); + assert.match(cli(f).stderr, /Set STEAM_USERNAME/); + assert.match(cli(f, [], { STEAM_USERNAME: "user +quit" }).stderr, /Set STEAM_USERNAME/); + assert.match(cli(f, [], { STEAM_USERNAME: "test_builder" }).stderr, /Cannot start SteamCMD/); +}); + +test("SteamCMD receives literal paths, runs once, saves BuildID, and propagates exit/output failures", { skip: process.platform === "win32" }, (t) => { + const f = fixture(t); + const command = path.join(f.root, "fake steamcmd"); + const calls = path.join(f.root, "calls.json"); + fs.writeFileSync(command, `#!/usr/bin/env node +const fs = require('node:fs'); +fs.writeFileSync(process.env.TEST_CALLS, JSON.stringify(process.argv.slice(2))); +console.log(process.env.TEST_OUTPUT); +process.exit(Number(process.env.TEST_CODE || 0)); +`, { mode: 0o755 }); + const env = { STEAM_USERNAME: "test_builder", STEAMCMD: command, TEST_CALLS: calls, + TEST_OUTPUT: "Success! App '2409460' fully built (BuildID 123456)." }; + const result = cli(f, [], env); + assert.equal(result.status, 0, result.stdout + result.stderr); + const args = JSON.parse(fs.readFileSync(calls, "utf8")); + assert.deepEqual(args.slice(0, 7), ["+@ShutdownOnFailedCommand", "1", "+@NoPromptForPassword", "1", "+login", "test_builder", "+run_app_build"]); + assert.equal(args.length, 9); + assert.equal(args[8], "+quit"); + assert.ok(fs.existsSync(args[7])); + assert.equal(JSON.parse(fs.readFileSync(path.join(path.dirname(args[7]), "upload-result.json"), "utf8")).buildId, "123456"); + for (const failure of [{ TEST_CODE: "7" }, { TEST_OUTPUT: "ERROR! Failed to build App." }, + { TEST_OUTPUT: "Logged in but no build happened" }, { TEST_OUTPUT: env.TEST_OUTPUT + "\nFailed to set build live" }]) { + const failed = cli(f, [], { ...env, ...failure }); + assert.notEqual(failed.status, 0, failed.stdout + failed.stderr); + const failedArgs = JSON.parse(fs.readFileSync(calls, "utf8")); + assert.equal(fs.existsSync(path.join(path.dirname(failedArgs[7]), "upload-result.json")), false); + } +}); diff --git a/tests/tool-packages.test.ts b/tests/tool-packages.test.ts new file mode 100644 index 0000000..a72fe46 --- /dev/null +++ b/tests/tool-packages.test.ts @@ -0,0 +1,70 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const fixture = (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "next2d tool paths ")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + return root; +}; + +const installation = (root, directory, packages) => { + const modules = path.join(root, directory, "node_modules"); + fs.mkdirSync(path.join(modules, ".bin"), { recursive: true }); + for (const [name, version] of Object.entries(packages)) { + fs.mkdirSync(path.join(modules, name), { recursive: true }); + fs.writeFileSync(path.join(modules, name, "package.json"), JSON.stringify({ name, version })); + } + return path.join(modules, ".bin"); +}; + +const resolve = (root, bins, packages) => spawnSync(process.execPath, [ + fileURLToPath(new URL("../dist/resolve-tool-packages.js", import.meta.url)) +], { + encoding: "utf8", + env: { + ...process.env, PATH: bins.join(path.delimiter), + NEXT2D_TOOL_PACKAGES: JSON.stringify(packages), NEXT2D_TOOL_RESULT: path.join(root, "result.json") + } +}); + +test("tool resolution skips unrelated project versions and returns the pinned npx installation", (t) => { + const root = fixture(t); + const old = installation(root, "game", { "@electron/packager": "18.4.4" }); + const pinned = installation(root, "npm cache", { "@electron/packager": "20.3.0" }); + const result = resolve(root, [old, pinned], { "@electron/packager": "20.3.0" }); + assert.equal(result.status, 0, result.stderr); + const paths = JSON.parse(fs.readFileSync(path.join(root, "result.json"), "utf8")); + assert.equal(paths["@electron/packager"], path.join(path.dirname(pinned), "@electron/packager/package.json")); +}); + +test("tool resolution rejects incomplete or mismatched SDK installations", (t) => { + const root = fixture(t); + const cli = installation(root, "first", { "@capacitor/cli": "8.5.2" }); + const sdk = installation(root, "second", { "@capacitor/core": "8.5.2" }); + const mismatched = installation(root, "third", { "@capacitor/cli": "8.5.2", "@capacitor/core": "8.0.0" }); + const result = resolve(root, [cli, sdk, mismatched], { "@capacitor/cli": "8.5.2", "@capacitor/core": "8.5.2" }); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /Could not resolve the requested build tools/); + assert.equal(fs.existsSync(path.join(root, "result.json")), false); +}); + +test("loading platform modules works offline with an empty npm cache without acquiring tools", (t) => { + const root = fixture(t); + const cache = path.join(root, "empty-cache"); + const result = spawnSync(process.execPath, [ + "--input-type=module", "-e", + `await import(${JSON.stringify(new URL("../dist/electron.js", import.meta.url).href)}); + await import(${JSON.stringify(new URL("../dist/native.js", import.meta.url).href)});` + ], { + cwd: root, encoding: "utf8", timeout: 10000, + env: { ...process.env, npm_config_cache: cache, npm_config_offline: "true" } + }); + assert.equal(result.status, 0, result.stdout + result.stderr); + assert.doesNotMatch(result.stdout, /Preparing .* tools via npx/); + assert.equal(fs.existsSync(cache), false); +}); diff --git a/tests/xbox.test.ts b/tests/xbox.test.ts index 7b9db1d..8a22428 100644 --- a/tests/xbox.test.ts +++ b/tests/xbox.test.ts @@ -82,7 +82,7 @@ test("walkFiles: 存在しないディレクトリは空配列", () => { assert.deepEqual(walkFiles(path.join(os.tmpdir(), "n2d-does-not-exist-xyz")), []); }); -test("minifyJs: 圧縮しコメント除去、グローバル名は保持", () => { +test("minifyJs: 圧縮しコメント除去、グローバル名は保持", async () => { const src = ` // このコメントは除去される globalThis.__next2d_boot = function () { @@ -90,14 +90,14 @@ globalThis.__next2d_boot = function () { return longLocalVariableName; }; `; - const out = minifyJs("js/bootstrap.js", src); + const out = await minifyJs("js/bootstrap.js", src); assert.ok(out.length < src.length, "縮む"); assert.ok(!out.includes("除去される"), "コメント除去"); assert.ok(out.includes("__next2d_boot"), "グローバル名は保持 (ゲームが参照)"); assert.ok(!/\blongLocalVariableName\b/.test(out), "ローカル名はマングル"); }); -test("minifyJs: 不正な JS は元コードのまま返す (埋め込み継続)", () => { +test("minifyJs: 不正な JS は元コードのまま返す (埋め込み継続)", async () => { const broken = "globalThis.x = (((;"; - assert.equal(minifyJs("js/bootstrap.js", broken), broken); + assert.equal(await minifyJs("js/bootstrap.js", broken), broken); }); From 2f5416f12341720d7c75b6fb7ad234354105c02b Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 13:33:05 +0900 Subject: [PATCH 2/9] =?UTF-8?q?xbox=E3=81=AE=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++---------------- docs/xbox.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 docs/xbox.md diff --git a/README.md b/README.md index 3f28fda..2c4cd4f 100644 --- a/README.md +++ b/README.md @@ -36,21 +36,7 @@ With Next2D Framework, you can efficiently develop and deploy applications optim | web | Export minfy'd JS files | | iOS | open Xcode, and Export ipa | | Android | open Android Studio, and Export apk | -| Xbox | GDK native title (V8 + Dawn/WebGPU) | - -Xbox exports a GDK native title that runs the Next2D JavaScript on the V8 engine and -renders with Dawn (WebGPU → D3D12), packaged as a C++ executable (no Electron/WebView). -Building the GDK package requires Windows + Visual Studio 2022 + Microsoft GDK + a devkit. -On other platforms the builder scaffolds the host project and stages assets only. -See the generated `xbox/README.md` for full build steps. - -```bat -npx @next2d/builder --platform xbox --env prd -``` - -The prebuilt V8 engine is downloaded automatically on the first Xbox build -(published from the `build-v8` workflow). To use your own build, pass -`--v8-root C:\path\to\v8`. +| Xbox | Prototype GDK host; under development | ## Platform guides @@ -60,6 +46,7 @@ Platform-specific configuration, dependencies and workflows are documented in th |---|---| | [Electron / Steam](docs/steam.md) | Desktop exports, icons, architectures, signing, Steam depots, uploads and beta testing | | [iOS / Android (Capacitor)](docs/capacitor.md) | Native configuration, SDKs, plugins, build commands, migration and Xcode setup | +| [Xbox (prototype)](docs/xbox.md) | Development status, host generation, build environment and validation scope | ### Scheduled introduction @@ -81,13 +68,3 @@ npx @next2d/builder --preview --platform web --env prd ## License This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. - -## Build dependencies - -The builder uses the target project's installed Vite for configuration loading, -web builds and Xbox host-script minification. Run `npm install` in your game first; -Vite is not installed again as a builder runtime dependency. Vite 7 and 8 are supported. - -The builder has no npm runtime dependencies. Node.js with npm/npx is required. -Terminal colors use Node.js `util.styleText`; Node typings and the builder's own Vite -installation are development dependencies. diff --git a/docs/xbox.md b/docs/xbox.md new file mode 100644 index 0000000..9572004 --- /dev/null +++ b/docs/xbox.md @@ -0,0 +1,79 @@ +# Xbox(試作・開発段階) + +Xbox対応は現在、GDK向けネイティブホストを試作・検証している開発フェーズ。 +ホスト生成やビルド処理は実装されているが、Xbox実機での動作確認・配布対応は完了していない。 +現時点では、開発環境での検証を目的として使用する。 + +## 構成と現在の対応範囲 + +Next2DのJavaScriptをV8で実行し、Dawn(WebGPU → D3D12)で描画するC++ホストを生成する。 +ElectronやWebViewは使用しない。 + +| 項目 | 現在の状態 | +|---|---| +| ホスト生成 | builder同梱のC++ / CMakeテンプレートから、ゲーム側の `xbox/` を生成・更新する。 | +| ゲーム設定 | ゲームルートの `MicrosoftGame.config` をホストに反映する。未配置の場合はテンプレートから初期ファイルを用意する。 | +| アセット | Webビルド結果とホストスクリプトを `assets.pak` / `assets.rc` にまとめ、実行ファイルへの埋め込み用に配置する。 | +| Windows上のビルド | CMakeでVisual Studio / GDK向けの構成・ビルドを実行する処理を実装している。対象環境での検証が必要。 | +| macOS / Linux上の実行 | ホスト生成とアセットの準備まで。GDK向け実行ファイルのビルドは行わない。 | +| Xbox実機・配布 | 開発機での動作、性能、保存処理、配布設定などの検証・調整が残っている。 | + +## 検証に使う環境 + +以下は現行テンプレートが想定する構成。 + +| 項目 | 用途 | +|---|---| +| Windows + Visual Studio 2022 | C++ホストのビルド。現行builderはVisual Studio 2022ジェネレーターを指定する。 | +| Microsoft GDK | GDK向けビルド。PC向けの検証とXboxコンソール向けの環境を区別して用意する。 | +| CMake 3.26以降 | ホストの構成・ビルド。 | +| V8 | JavaScript実行エンジン。ビルド済みライブラリの取得、または自前ビルドを利用する。 | +| Dawn | WebGPU実装。CMakeのFetchContentで取得するため、初回取得にはネットワーク接続が必要。 | +| Xbox開発機(devkit) | Xbox実機上での動作・性能検証。 | + +## 試作用コマンド + +ゲームのルートで実行する。 + +```sh +npx @next2d/builder --platform xbox --env prd +``` + +Windowsではホストとアセットを準備した後、V8の解決とCMakeビルドへ進む。 +macOS / Linuxではホスト生成とアセットの準備を終えた時点で終了する。 +コマンドの終了だけでXbox実機向けの書き出しが完了したとは判断せず、生成物とログを確認する。 + +Windowsで `--open` または `--preview` を付けると、CMakeによる構成後にVisual Studioのソリューションを開く。 +実行・デバッグはVisual Studio側で行う。 + +## V8の取得 + +Windowsでのビルド時は、次の優先順でV8を解決する。 + +1. `--v8-root` で指定したパス +2. 環境変数 `V8_ROOT` +3. キャッシュ済みのビルド済みV8 +4. Next2D/builderのGitHub Releasesからダウンロード + +ビルド済みV8は `build-v8` ワークフローで作成・公開する構成になっている。 +取得対象のバージョンとリビジョンはbuilder側で固定し、取得後はキャッシュを再利用する。 +自前のV8を使用する場合: + +```bat +npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8 +``` + +公開しているWindows x64用V8の利用と、Xboxコンソール向けの互換性検証は別の作業。 +コンソール向けにはGDKツールチェーンでの調整・再ビルドを含む検証が残っている。 + +## 検証範囲と残っている作業 + +`xbox-host-ci` ワークフローには、ラスタライザーの単体テスト、Windows APIを使う一部機能のテスト、 +V8依存ソースのコンパイル確認、Game Core APIの確認を用意している。 +これらはホスト全体のリンクやXbox実機での動作を保証するものではない。 +Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必要。 + +今後の検証・調整対象は、描画、Worker、入力、音声、テキスト、保存処理、性能、配布設定など。 +詳細なホスト構成・自前V8のビルド・機能ごとの実装状況は、 +[ホストテンプレートの開発資料](../templates/xbox/README.md)を参照する。 +同じ資料は生成先の `xbox/README.md` にも配置される。 From a6082603836e97e09fe79eaa60acd7daf9027724 Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 14:00:01 +0900 Subject: [PATCH 3/9] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 118 ++++++++---- docs/capacitor.md | 92 ++++++++- docs/steam.md | 414 +++++++++++++++++++++++++++++++++++++++-- docs/xbox.md | 96 +++++++++- tests/electron.test.ts | 19 +- 5 files changed, 674 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 2c4cd4f..3841cca 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,108 @@ -Multi-platform Builder -============= +# Multi-platform Builder [![CodeQL](https://github.com/Next2D/builder/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/Next2D/builder/actions/workflows/github-code-scanning/codeql) [![Lint](https://github.com/Next2D/builder/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/Next2D/builder/actions/workflows/lint.yml) [![release](https://img.shields.io/github/v/release/Next2D/builder)](https://github.com/Next2D/builder/releases) [![license](https://img.shields.io/github/license/Next2D/builder)](https://github.com/Next2D/builder/blob/main/LICENSE) -[日本語]\ -1つのプロジェクトから多種多様なプラットフォーム向けのアプリケーションを作成するマルチプラットフォームビルダー。 +[日本語](#日本語) | [English](#english) -* Steam:Steamに対応したWindows版、macOS版、Linux版のアプリケーションの書き出しが可能。 -* デスクトップ:デスクトップ環境向けに、Windows、macOS、Linux向けのアプリケーションを出力できます。 -* スマートフォン:iOSおよびAndroidに対応したスマートフォン用アプリケーションとしての書き出しもサポート。 -* Web(HTML):ブラウザ上で動作するHTML形式のコンテンツも簡単に生成可能。 +## 日本語 -Next2D Frameworkを使えば、1つのソースコードから多様なデバイスやOSに最適化されたアプリケーションを効率的に開発・展開することができます。 +Next2D Frameworkの1つのプロジェクトから、複数のプラットフォーム向けにアプリケーションを書き出すビルダーです。 +Steamおよびデスクトップ向けのWindows / macOS / Linux、スマートフォン向けのiOS / Android、Web(HTML)に対応しています。 +Xbox対応は試作・開発段階です。 -[English]\ -Multi-platform builder to create applications for a wide variety of platforms from a single project. +### 対応プラットフォーム -* Steam: Allows export of applications for Windows, macOS, and Linux versions that are compatible with Steam. -* Desktop: Export of applications for Windows, macOS, and Linux for desktop environments is supported. -* Smartphone: Export as smartphone applications compatible with iOS and Android is also supported. -* Web (HTML): Easily generate HTML-format content that runs in a browser. +| プラットフォーム | 出力・対応状況 | +|---|---| +| Steam:Windows | 実行ファイル(.exe)と必要なリソース | +| Steam:macOS | アプリケーションバンドル(.app) | +| Steam:Linux | 実行ファイルと必要なリソース | +| Web | 最適化されたJavaScriptとWebアセット | +| iOS | Xcodeプロジェクトを開く、IPAを書き出す | +| Android | Android Studioプロジェクトを開く、APKを書き出す | +| Xbox | GDKネイティブホストの試作・開発段階 | + +### プラットフォーム別ガイド + +設定、依存ツール、書き出し・配布手順の詳細は、以下のドキュメントにまとめています。 -With Next2D Framework, you can efficiently develop and deploy applications optimized for diverse devices and operating systems from a single source code. +| ガイド | 内容 | +|---|---| +| [Electron / Steam](docs/steam.md#日本語) | デスクトップ書き出し、アイコン、CPU、署名、Steam Depot、アップロード、ベータテスト | +| [iOS / Android(Capacitor)](docs/capacitor.md#日本語) | ネイティブ設定、SDK、プラグイン、ビルドコマンド、移行、Xcodeの設定 | +| [Xbox(試作)](docs/xbox.md#日本語) | 開発状況、ホスト生成、ビルド環境、検証範囲 | -## Supported +### 今後の対応予定 -| platform | detail | -|-----------------|-------------------------------------| -| Steam:Windows | Export an exe file | -| Steam:macOS | Export application bundle (.app) | -| Steam:Linux | Export executable + resources | -| web | Export minfy'd JS files | -| iOS | open Xcode, and Export ipa | -| Android | open Android Studio, and Export apk | -| Xbox | Prototype GDK host; under development | +| プラットフォーム | +|---| +| Nintendo Switch | -## Platform guides +### ビルド例 -Platform-specific configuration, dependencies and workflows are documented in these guides: +```sh +npx @next2d/builder --platform web --env prd +``` + +### プレビュー例 + +```sh +npx @next2d/builder --preview --platform web --env prd +``` + +### ライセンス + +このプロジェクトは[MIT License](https://opensource.org/licenses/MIT)で公開しています。詳細は[LICENSE](LICENSE)を参照してください。 + +## English + +Build applications for multiple platforms from a single Next2D Framework project. +The builder supports Windows / macOS / Linux for Steam and desktop distribution, iOS / Android for mobile devices, and Web (HTML). +Xbox support is a prototype under development. + +### Supported platforms + +| Platform | Output / status | +|---|---| +| Steam:Windows | Executable (.exe) and required resources | +| Steam:macOS | Application bundle (.app) | +| Steam:Linux | Executable and required resources | +| Web | Optimized JavaScript and web assets | +| iOS | Open the Xcode project and export an IPA | +| Android | Open the Android Studio project and export an APK | +| Xbox | Prototype GDK native host; under development | + +### Platform guides + +These guides cover platform-specific configuration, dependencies, builds and distribution. | Guide | Contents | |---|---| -| [Electron / Steam](docs/steam.md) | Desktop exports, icons, architectures, signing, Steam depots, uploads and beta testing | -| [iOS / Android (Capacitor)](docs/capacitor.md) | Native configuration, SDKs, plugins, build commands, migration and Xcode setup | -| [Xbox (prototype)](docs/xbox.md) | Development status, host generation, build environment and validation scope | +| [Electron / Steam](docs/steam.md#english) | Desktop exports, icons, architectures, signing, Steam depots, uploads and beta testing | +| [iOS / Android (Capacitor)](docs/capacitor.md#english) | Native configuration, SDKs, plugins, build commands, migration and Xcode setup | +| [Xbox (prototype)](docs/xbox.md#english) | Development status, host generation, build environment and validation scope | -### Scheduled introduction +### Planned support -| platform | -|-----------------| +| Platform | +|---| | Nintendo Switch | -### build example. +### Build example -```linux +```sh npx @next2d/builder --platform web --env prd ``` -### preview example. +### Preview example -```linux +```sh npx @next2d/builder --preview --platform web --env prd ``` -## License -This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [LICENSE](LICENSE) file for details. +### License + +This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). See [LICENSE](LICENSE) for details. diff --git a/docs/capacitor.md b/docs/capacitor.md index 4f0bc88..72a7e24 100644 --- a/docs/capacitor.md +++ b/docs/capacitor.md @@ -1,5 +1,9 @@ # iOS / Android(Capacitor) +[日本語](#日本語) | [English](#english) + +## 日本語 + Capacitorの `cli`・`core`・`ios`・`android` はbuilderの `src/tool-packages.ts` で同じバージョンに固定し、 iOS / Androidの操作時に `npx` でまとめて取得する。builderの `dependencies` には含めない。 ゲームは `npx @next2d/builder` で起動し、`capacitor.config.json` で @@ -31,7 +35,7 @@ npm/npxを利用できるNode.js環境が必要。初回はネットワーク接 `--open` / `--build` は `cap sync` の成功後に `cap open` / `cap build` を実行する。 `--preview` は `cap run` を実行する。各コマンドの終了を待ち、失敗した場合はbuilderも失敗する。 -## ゲーム側に残すもの +### ゲーム側に残すもの - `capacitor.config.json`:ゲーム固有設定。`ios.path` / `android.path` のカスタム出力先も使用できる。 - `ios/` / `android/`:ネイティブプロジェクト。既存のアイコン・署名設定・ネイティブコードを維持する。 @@ -44,7 +48,7 @@ npm/npxを利用できるNode.js環境が必要。初回はネットワーク接 Xcode、Android Studio、Android SDK、対応JDK、署名証明書などは引き続き実行環境に必要。 SDKのnpm依存を集約しても、端末実行・APK/AAB/IPAの生成に必要なネイティブツールは変わらない。 -## iOSで `xcodebuild requires Xcode` が出る場合 +### iOSで `xcodebuild requires Xcode` が出る場合 `xcode-select -p` が `/Library/Developer/CommandLineTools` を返す場合、 Command Line Toolsが選択されている。iOSのアーカイブ・IPA生成にはXcode本体とiOS SDKが必要。 @@ -67,7 +71,7 @@ xcodebuild -version Xcodeの配置先が異なる場合はパスを変更する。builderは呼び出し元の `DEVELOPER_DIR` を引き継ぐ。 選択方法の詳細は[Appleのコマンドラインツール設定](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings)を参照。 -## 既存プロジェクトの移行 +### 既存プロジェクトの移行 1. この機能を含む公開済みbuilderを `npx` で使用する。 2. ゲームが直接importしていない `@capacitor/cli`・`@capacitor/core`・`@capacitor/ios`・`@capacitor/android` を直接依存から外す。 @@ -80,3 +84,85 @@ builderの `--open` / `--build` で再同期する。 [Capacitorの開発フロー](https://capacitorjs.com/docs/basics/workflow) / [Capacitor設定](https://capacitorjs.com/docs/config) + +## English + +The builder pins Capacitor's `cli`, `core`, `ios` and `android` packages to the same version in +`src/tool-packages.ts` and acquires them together through `npx` when an iOS / Android operation runs. +They are not included in the builder's `dependencies`. +Games invoke `npx @next2d/builder` and manage the app name, App ID and platform-specific settings in +`capacitor.config.json`. Neither the builder nor Capacitor needs to be declared directly in the game's `package.json`. + +```sh +npm run open:ios -- --env prd +npm run open:android -- --env prd +npm run build:ios -- --env prd +npm run build:android -- --env prd +``` + +To invoke the builder directly: + +```sh +npx @next2d/builder --platform ios --env prd --open +npx @next2d/builder --platform android --env prd --build +``` + +The builder launches the cached Capacitor CLI with Node, using the game root as the working directory. +It adds the acquired SDK paths to the child process's `NODE_PATH` so the game can resolve the SDKs when needed. +This supports regular npm installations, nested dependencies and a sibling repository linked through `file:../builder`. +The game does not need extra Capacitor dependency declarations, helper scripts or SDK symlinks. +Node.js with npm/npx is required. The first download needs network access; subsequent runs reuse npm's cache. +Unlike a temporary host, the acquired SDKs remain after export so Gradle / CocoaPods can keep referencing them. + +The builder updates `webDir` to the current web output directory before running `cap add` for a native project that does not exist yet. +`--open` / `--build` run `cap open` / `cap build` after `cap sync` succeeds. +`--preview` runs `cap run`. The builder waits for each command and fails if a command fails. + +### What stays in the game + +- `capacitor.config.json`: Game-specific settings, including custom native output paths through `ios.path` / `android.path`. +- `ios/` / `android/`: Native projects. Existing icons, signing settings and native code are preserved. +- Game-specific Capacitor/Cordova plugins: Keep these in the game's dependencies. The CLI continues to discover the game's dependencies and hooks. + +If the game's JavaScript imports `@capacitor/core` directly, declare `@capacitor/core` as a direct game dependency +so the web build can resolve it. Choose a version compatible with the builder's Capacitor version. +SlimeTenPuzzle does not currently have this import. + +Xcode, Android Studio, the Android SDK, a compatible JDK and signing certificates are still required in the build environment. +Centralizing the SDK npm dependencies does not change the native tools needed for device execution or APK/AAB/IPA generation. + +### If iOS reports `xcodebuild requires Xcode` + +If `xcode-select -p` returns `/Library/Developer/CommandLineTools`, the standalone Command Line Tools are selected. +Creating an iOS archive or IPA requires the full Xcode application and the iOS SDK. +Install Xcode and complete its first-launch setup. + +To use `/Applications/Xcode.app` for a single build, run this from the game root: + +```sh +DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ + npx @next2d/builder --platform ios --env prd --build +``` + +To select Xcode as the system-wide command-line default: + +```sh +sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer +xcodebuild -version +``` + +Adjust the path if Xcode is installed elsewhere. The builder inherits the caller's `DEVELOPER_DIR`. +See [Apple's command-line tools settings](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings) for details. + +### Migrating an existing project + +1. Use a published builder version that includes this feature through `npx`. +2. Remove direct dependencies on `@capacitor/cli`, `@capacitor/core`, `@capacitor/ios` and `@capacitor/android` that the game does not import directly. +3. Run the `open:ios` / `open:android` npm scripts to resynchronize native dependency paths. + +When developing with a sibling builder repository, run `npm ci` or `npm install` in the builder, +then run `npm install` in the game. After changing builder source files, run `npm run build` in the builder. +If the npx cache is deleted, use the builder's `--open` / `--build` to resynchronize the SDK paths referenced by the IDE. + +[Capacitor development workflow](https://capacitorjs.com/docs/basics/workflow) / +[Capacitor configuration](https://capacitorjs.com/docs/config) diff --git a/docs/steam.md b/docs/steam.md index 3ac27fb..af62d96 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -1,4 +1,10 @@ -# Electron / Steam 書き出し +# Electron / Steam 書き出し / Export + +[日本語](#日本語) | [English](#english) + +## 日本語 + +### 設定 プロジェクトルートの `electron.config.json` でゲーム固有設定を管理する。 `appId` は macOS bundle ID・保存先識別子、`steam.appId` は Valve の数値 App ID。 @@ -38,7 +44,7 @@ | `steam.depots` | Steamworksで作成した実際のDepot ID。複数OSに同じIDを指定すると、同一Depotへまとめて配布する。 | App IDから推測しない。`null` / 未指定ならアプリと起動情報だけを生成し、アップロード用VDFは生成しない。 | | `macos` | macOSアプリの署名・公証設定。 | ローカル検証用は `sign` / `notarize` ともに `false`。本番配布では両方を有効化する。 | -### テンプレートとApp作成時の初期値 +#### テンプレートとApp作成時の初期値 JavaScript / TypeScriptの両テンプレートは `electron.config.json` と `src/assets/icons/` の仮のNext2Dアイコン(ICO / ICNS / PNG)を含む。 @@ -50,7 +56,7 @@ JavaScript / TypeScriptの両テンプレートは `electron.config.json` と CPU設定が省略されている場合はOSごとの既定値を補い、テンプレートに指定済みの値は保持する。 アイコンパスやSteam IDなどの設定も保持する。 -### 対応OS・CPU +#### 対応OS・CPU | OS | 既定値 | `architectures` / `--arch` の対応値 | |---|---|---| @@ -70,7 +76,7 @@ Windows/Linuxの既定値はx64。ARM64版は `architectures.windows` / `archite npx @next2d/builder --platform steam:windows --env prd --arch arm64 ``` -### 書き出しコマンド +#### 書き出しコマンド ```sh npx @next2d/builder --platform steam:windows --env prd @@ -93,7 +99,7 @@ npm run build:steam:windows -- --env dev builderを直接呼ぶ場合は `--env` を指定する。 書き出しコマンドはSteamへのアップロードや公開を自動では行わない。 -### builderが管理するElectronホスト +#### builderが管理するElectronホスト Electron関連でゲーム側に必要な設定ファイルは `electron.config.json` だけ。 ゲーム名・バージョン・説明はルートの `package.json` と合わせてホストへ反映する。 @@ -126,7 +132,7 @@ builder→ゲームの順に `npm ci` する。builder側のprepareでコンパ builderのソース変更後はbuilderで `npm run build` を実行する。 公開後はこの依存を公開バージョンへ変更でき、ゲームのコマンドはそのまま使える。 -## 成果物と起動 +### 成果物と起動 `dist/steam//build//` に以下を生成する(デフォルトoutDirの場合)。 下表のExecutableはOS別の独立したDepotを使う場合。共有Depotの場合は後述のOS名プレフィックスが付く。 @@ -157,7 +163,7 @@ Steam Cloudは未統合。Chromiumプロファイル全体をCloud対象にせ ゲーム用のセーブファイルとアカウント単位の保存方式を別途設計する。 [Steam Cloud](https://partner.steamgames.com/doc/features/cloud) -## 同じDepotを複数OSで使う場合 +### 同じDepotを複数OSで使う場合 `steam.depots` の同じIDを共有するOSを1グループとして扱う。例えば全OSに同じDepot IDを 指定すると、Steamのインストール先を次のように分け、Electronの同名ファイルの衝突を避ける。 @@ -218,7 +224,7 @@ App ID・Depot ID・ゲーム名・実行ファイル名・バージョンが現 一部OSだけIDを共有する構成にも対応する。共有しないOSは従来どおり単独VDFを生成する。 設定やバージョンを変更した場合は対象OSを再度書き出す。古い統合VDFは再生成時に無効化する。 -## macOSの配布用署名・公証 +### macOSの配布用署名・公証 Steam用は `darwin` であり、Mac App Store向けの `mas` ではない。 Valveは新規macOSアプリに64bitとAppleの公証を要求している。 @@ -248,7 +254,7 @@ xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My G [Electron Packagerの署名・公証設定](https://electron.github.io/packager/main/interfaces/Options.html) -## Steamworks / SteamPipe +### Steamworks / SteamPipe 1. Steamworks > SteamPipe > Depotsで配布構成に合わせたDepotを作成し、対象OSを指定する。 OS別Depotと、複数OS向けの同一Depotのどちらも使用できる。 @@ -279,14 +285,14 @@ OSごとのVDFは1つのDepotを更新するため、最終BuildのDepot manifes [SteamPipe公式手順・VDF仕様](https://partner.steamgames.com/doc/sdk/uploading) / [Depots](https://partner.steamgames.com/doc/store/application/depots) -## builderからアップロードしてinternalでテストする +### builderからアップロードしてinternalでテストする `--steam-upload` は書き出し済みの成果物をSteamCMDで一括アップロードし、 `SetLive` で指定したbetaブランチへの反映を要求する。Web/Electronの再ビルドは行わない。 `--platform` は不要で、`--build` / `--preview` / `--open` / `--steam-manifest` / `--arch` とは併用しない。 builderのnpm依存は追加していない。SteamCMDは別途用意する。 -### Steamworksで関係者向けの配布先を用意する +#### Steamworksで関係者向けの配布先を用意する 1. Steamworksのアプリ管理画面 > SteamPipe > Buildsで `internal` ブランチを作成する。 2. **ブランチにパスワードを設定し、関係者だけに共有する。** `internal` という名前だけでは非公開にならない。 @@ -304,7 +310,7 @@ betaのパスワードは共有秘密なので、第三者に渡ればアクセ [Steamでのテスト](https://partner.steamgames.com/doc/store/testing) / [Release State Overrideキー](https://partner.steamgames.com/doc/features/keys) -### SteamCMDの準備と認証 +#### SteamCMDの準備と認証 [Steamworks SDKのContentBuilder](https://partner.steamgames.com/doc/sdk/uploading)にある実行OS用のSteamCMDを用意する。 Windowsは `steamcmd.exe`、macOS / Linuxは `steamcmd.sh` を使用できる。 @@ -327,7 +333,7 @@ export STEAM_USERNAME="your_build_account" CIではSteamCMDの `config/config.vdf` をSecretとして復元・管理し、ビルド成果物に含めない。 Windows PowerShellでは `$env:STEAMCMD` と `$env:STEAM_USERNAME` を設定する。 -### 検証とアップロード +#### 検証とアップロード `electron.config.json` の `steam.depots` に設定された全OSの成果物を揃え、ゲームのルートで実行する。 同じDepot IDを使うOSはOS別サブディレクトリへまとめ、異なるDepotも1回のAppBuildでアップロードする。 @@ -360,7 +366,7 @@ SteamCMDの認証・サーバー側の権限・ブランチ・パスワード設 SteamworksのBuilds画面で、記録されたBuildIDが `internal` の現在のビルドになったことを確認する。 アカウントやアプリの状態によって、Steam側で追加の確認が必要になる場合がある。 -## リリース前の確認範囲 +### リリース前の確認範囲 - 対象OSのSteamからインストール・起動・終了、オフライン起動、更新後の保存データ。 - マウス/キーボード/コントローラ、解像度・全画面切替、音声、スリープ復帰。 @@ -378,3 +384,383 @@ SteamworksのBuilds画面で、記録されたBuildIDが `internal` の現在の [Linux開発](https://partner.steamgames.com/doc/store/application/platforms/linux) / [Steam Deck互換性](https://partner.steamgames.com/doc/steamhardware/compat) / [ビルド審査](https://partner.steamgames.com/doc/store/review_process) + +## English + +### Configuration + +Manage game-specific settings in `electron.config.json` at the project root. +`appId` is the macOS bundle ID and the identifier used for the save location; `steam.appId` is Valve's numeric App ID. +These are separate identifiers. The game version comes from the root `package.json`. + +```json +{ + "appId": "app.example.game", + "appName": "My Game", + "executableName": "my-game", + "companyName": "Example Company", + "icons": { + "windows": "src/assets/icons/icon.ico", + "macos": "src/assets/icons/icon.icns", + "linux": "src/assets/icons/icon.png" + }, + "architectures": { "windows": "x64", "macos": "universal", "linux": "x64" }, + "window": { "width": 1280, "height": 720, "fullscreen": false }, + "steam": { + "appId": null, + "branch": "internal", + "depots": { "windows": null, "macos": null, "linux": null } + }, + "macos": { "sign": false, "notarize": false } +} +``` + +| Field | Purpose / behavior | Defaults / notes | +|---|---|---| +| `companyName` | Company name in the Windows executable metadata. | Falls back to `author.name` in `package.json`, then to the game name. | +| `appName` / `executableName` | `appName` is the window and application name. `executableName` is the executable file name. | The display name and executable name can be set independently. | +| `description` | Optional description used in the generated Electron host's `package.json` and the Windows file description. | The JSON value takes priority. If omitted or `null`, uses `description` from the root `package.json`. If both are absent, uses an empty string. An explicit `""` also remains empty. | +| `icons` | Icon paths, relative to the project root or absolute. Use ICO for Windows, ICNS for macOS and PNG for Linux. | Each key is optional. Omitted platforms use the builder's placeholder Next2D icons; `"icons": {}` uses placeholders for every OS. A specified file that does not exist causes an error. | +| `architectures` | CPU architecture per OS. See “Supported operating systems and architectures” below for accepted values and examples. | Defaults to `x64` for Windows/Linux and `universal` for macOS. `--arch` takes priority. | +| `steam.appId` | Valve's numeric App ID. Set to `null` if it has not been issued. | Local exports still work without an App ID, but SteamPipe VDFs are not generated. | +| `steam.branch` | Target beta branch for `--steam-upload`. | Defaults to `internal`. `--steam-branch` takes priority. Create the branch and configure its password in Steamworks. | +| `steam.depots` | Actual Depot IDs created in Steamworks. Assigning the same ID to multiple operating systems combines them in one depot. | Do not infer Depot IDs from the App ID. If `null` or omitted, only the application and launch metadata are generated, without upload VDFs. | +| `macos` | macOS signing and notarization settings. | Use `false` for both `sign` / `notarize` during local testing. Enable both for production distribution. | + +#### Templates and defaults for new apps + +Both the JavaScript and TypeScript templates include `electron.config.json` and placeholder Next2D icons +(ICO / ICNS / PNG) in `src/assets/icons/`. Replace the icons and game-specific values before distribution. +Steam App ID / Depot IDs initially use `null`. + +`create-next2d-app` initializes `appId`, `appName`, `executableName` and `companyName` from the project name. +For example, `my-game` produces `app.example.my-game` for `appId` and `my-game` for the other three fields. +Replace the bundle ID and company name with your own values before distribution. +Missing CPU settings receive the defaults for each OS; values already provided by the template are preserved. +Other settings, such as icon paths and Steam IDs, are also preserved. + +#### Supported operating systems and architectures + +| OS | Default | Accepted `architectures` / `--arch` values | +|---|---|---| +| Windows | `x64` | `x64`, `arm64` | +| macOS | `universal` | `x64`, `arm64`, `universal` | +| Linux | `x64` | `x64`, `arm64` | + +32-bit Windows (`ia32`) is not supported. `win32` is Electron's internal OS name for Windows, not a CPU bitness indicator. +For example, `My Game-win32-x64/` is a 64-bit Windows package. +Use `windows` or `steam:windows` for `--platform`. + +macOS `universal` produces one `.app` containing both x64 and arm64, and must be built on macOS. +Windows/Linux default to x64. For ARM64, set `architectures.windows` / `architectures.linux` to `arm64` +or pass `--arch arm64` when exporting. + +```sh +npx @next2d/builder --platform steam:windows --env prd --arch arm64 +``` + +#### Export commands + +```sh +npx @next2d/builder --platform steam:windows --env prd +npx @next2d/builder --platform steam:macos --env prd +npx @next2d/builder --platform steam:linux --env prd +npx @next2d/builder --platform macos --env prd --preview +``` + +The updated templates and `create-next2d-app` also provide these npm scripts: + +```sh +npm run build:steam:windows +npm run build:steam:macos +npm run build:steam:linux +npm run build:steam:windows -- --arch arm64 +npm run build:steam:windows -- --env dev +``` + +These npm scripts already specify `--env prd`; append an override such as `-- --env dev` when needed. +Specify `--env` when invoking the builder directly. +Export commands do not automatically upload to or publish on Steam. + +#### The Electron host managed by the builder + +The only Electron-specific configuration file needed in the game is `electron.config.json`. +The builder combines it with the root `package.json` to populate the host's game name, version and description. +For the description, `description` in `electron.config.json` takes priority. +Icons may reference shared game assets; unspecified icons use the builder's placeholders. + +The builder stages the host, runtime configuration and web assets in an OS temporary directory and deletes it after export. +It does not create an `electron/` directory or Electron-specific `node_modules` / lockfile in the game root. +The temporary host is also removed on failure. The Electron version is pinned in the builder's +`templates/electron/package.json`, and Packager downloads and caches the runtime. +The game does not need a separate Electron installation step. + +`@electron/packager` is not an npm dependency of the builder either; it is acquired through `npx` only for Electron exports. +Its version is pinned in `src/tool-packages.ts`. Node.js with npm/npx is required, and the initial download needs network access. +Downloaded tools are reused from npm's cache. Web / Xbox builds and standalone `--steam-manifest` runs do not acquire Packager. +Packager handles OS-specific packages, icons, signing/notarization and Electron runtime downloads. +The generated host has no npm dependencies or native addons, so rebuilding for a specific ABI is unnecessary. + +Preview also exports an application for the host OS/CPU and launches it. +After the temporary host has been removed, it runs the application in `dist//build//`, +allowing you to test the same host and asset loading used by production exports. + +Legacy host code, `config.forge` and custom npm dependencies under `electron/` are not used. +When migrating, move icons into shared assets and update their paths before deleting `electron/`. +Projects with a custom main/preload or native addons need support in the builder before migration. + +When developing the builder itself, you can set `"@next2d/builder": "file:../builder"` in the game's devDependencies +and run `npm ci` in the builder, then in the game. The builder's prepare script compiles its source. +After changing builder source files, run `npm run build` in the builder. +After publication, this dependency can point to a published version while keeping the same game commands. + +### Output and launching + +With the default outDir, packages are generated under `dist/steam//build//`. +The Executable values below apply to separate depots for each OS. Shared depots add the OS prefix described later. + +| OS | Entire folder to distribute | Steamworks Executable | +|---|---|---| +| Windows | `My Game-win32-x64/` | `my-game.exe` | +| macOS | `My Game-darwin-universal/` | `My Game.app` | +| Linux | `My Game-linux-x64/` | `my-game` | + +`*-steampipe/launch.json` records the actual launch path, CPU architecture and ContentRoot. +Distribute the entire folder, including Electron libraries, locales, licenses and resources, rather than extracting only the executable. +Steam does not require an installer (DMG/DEB/MSI). +Preserve symlinks inside the macOS `.app` and executable permissions on Linux. +For CI transfers, create a `tar.gz` before storing the output as an artifact. + +The template host loads assets using absolute paths and the fixed origin `next2d://game`. +Local fetch, Workers and localStorage work independently of the startup working directory. +F11/Alt+Enter enters fullscreen; Escape exits it. Closing the window also terminates the process on macOS. +Node integration is disabled, context isolation and sandboxing are enabled, and external navigation and new windows are blocked. +A Next2D CSP is applied. Games using external APIs must explicitly add their destinations. +[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / +[protocol](https://www.electronjs.org/docs/latest/api/protocol) + +Save data is stored in the `appId` directory under Electron's appData location. +Changing the display name does not change that location. Saves from older file-origin development builds are not migrated automatically. +Steam Cloud is not integrated. When adding it, design game-specific save files and per-account storage separately, +rather than syncing the entire Chromium profile. +[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) + +### Sharing one depot across multiple operating systems + +Operating systems assigned the same ID in `steam.depots` form a group. +For example, assigning the same Depot ID to all operating systems separates their Steam installation paths as follows, +avoiding collisions between Electron files with identical names. + +| OS | Example Steamworks Executable | +|---|---| +| Windows | `windows/my-game.exe` | +| macOS | `macos/My Game.app` | +| Linux | `linux/my-game` | + +In Steamworks, configure the depot's OS setting for the shared operating systems or All OSes. +Use the Executable values from the generated `launch.json` for the Launch Options and set OS-specific launch conditions. +Every OS's files in the shared depot are distributed, so downloads are larger than with separate depots. +Valve recommends separate depots for OS-specific files, but does not prohibit sharing a depot across operating systems. +[Official depot OS settings](https://partner.steamgames.com/doc/store/application/depots) + +Each OS export can succeed independently. Once every OS in a shared group has been exported, the builder automatically generates +`app_build.vdf`, `app_preview.vdf`, `depot_build.vdf` and `launch.json` in `dist/steam/shared//depot-/`. +If the group is incomplete, `launch.json` lists the missing operating systems and no upload VDFs are generated. +Standalone per-OS VDFs are not generated for a shared depot either. + +```sh +npx @next2d/builder --platform steam:windows --env prd +npx @next2d/builder --platform steam:macos --env prd +npx @next2d/builder --platform steam:linux --env prd +``` + +The combined VDF uses `FileMapping` to place the OS packages under `windows/` / `macos/` / `linux/`. +Assets are not copied again, preserving the original packages' macOS symlinks and Linux executable permissions. +When moving the output, preserve the entire `dist/steam/` directory structure, not just the VDF files. +[Official FileMapping specification](https://partner.steamgames.com/doc/sdk/uploading) + +For exports made on separate machines, such as in CI, collect each OS's `build//`, including `steam-package.json` +and `*-steampipe/`, under the same `dist/steam//build//` structure, then run: + +```sh +npx @next2d/builder --platform steam:macos --env prd --steam-manifest +``` + +The updated templates and `create-next2d-app` provide an npm script for the same operation: + +```sh +npm run build:steam:manifest +# Combine packages from an environment other than prd +npm run build:steam:manifest -- --env dev +``` + +This command only regenerates shared depot VDFs; it does not build the web application or export Electron packages. +It can run on any OS. `--platform` supplies a Steam target for loading the Vite configuration; all shared depots in the configuration are combined. +Missing packages in a shared group cause exit code 1. +The command uses `build.outDir` and `--env` to select the most recent successful export for each OS, including its recorded `--arch`. +If x64 and arm64 are exported successively for the same OS, only the last successfully exported architecture is included. +It does not combine multiple CPU variants of the same OS into one shared depot. +`--steam-manifest` cannot be combined with `--arch` / `--preview` / `--build` / `--open`. +The App ID, Depot ID, game name, executable name and version must match the current configuration. +Even when retaining the same version number, make sure all distributed OS packages come from the intended revision. + +Configurations where only some operating systems share an ID are supported. Other operating systems still get standalone VDFs. +Re-export the affected operating systems after changing the configuration or version. Old combined VDFs are invalidated during regeneration. + +### Signing and notarizing macOS distribution builds + +Steam uses the `darwin` target, not the Mac App Store target `mas`. +Valve requires 64-bit support and Apple notarization for new macOS applications. +The template entitlements include the library validation / DYLD settings needed for JIT and Steam Overlay, +but do not include `com.apple.security.app-sandbox`, which is separate from Chromium's renderer sandbox. +[Valve platform requirements](https://partner.steamgames.com/doc/store/application/platforms) + +On macOS, add a Developer ID Application certificate to the keychain and save a notarization profile with +Apple's `xcrun notarytool store-credentials` before running the build. Do not store credentials in JSON. + +```sh +export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' +export APPLE_NOTARY_PROFILE='your-notary-profile' +NEXT2D_STEAM_RELEASE=1 npx @next2d/builder --platform steam:macos --env prd +``` + +`NEXT2D_STEAM_RELEASE=1` requires signing and notarization even when the JSON values are false. +Alternatively, set both `macos.sign` / `macos.notarize` to true in JSON. +Missing credentials, signing failures or notarization failures cause the build to fail. +Submit the package to SteamPipe after signing, notarization and stapling. Example checks: + +```sh +codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +``` + +[Electron Packager signing and notarization options](https://electron.github.io/packager/main/interfaces/Options.html) + +### Steamworks / SteamPipe + +1. In Steamworks > SteamPipe > Depots, create depots matching your distribution layout and select their operating systems. + Both separate OS depots and shared depots are supported. Use All languages for language-independent content. + Include each depot in the development and retail Packages as well. +2. Add OS-specific Launch Options under Installation > General Installation. + Set Executable to the generated `launch.json` value; leave Arguments/Working Directory empty. + Select the 64-bit condition for x64 builds and test macOS Universal on both Intel and Apple Silicon. +3. Publish the changes in Steamworks. Match the supported operating systems and minimum OS requirements to + the bundled Electron version and hardware testing, rather than relying only on older Steam minimum-OS tables. +4. Set the App ID/Depot IDs in JSON and rebuild. Standalone depot VDFs appear in `*-steampipe/`; + shared depot VDFs appear in `shared//depot-/` once every required OS package exists. + `app_preview.vdf` validates file mappings; `app_build.vdf` uploads the build. + `depot_build.vdf` recursively maps the folder contents into the install root. `steam_appid.txt` is excluded from distribution. +5. Obtain ContentBuilder/SteamCMD from the Steamworks SDK and log in with an authorized build account. + Do not store passwords in commands or the repository. + +```text +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit +``` + +ContentRoot is relative to the VDF. Move standalone applications together with their `*-steampipe/` directories; +for shared depots, preserve the entire `dist/steam/` layout. +VDFs produced by ordinary exports and `--steam-manifest` do not include SetLive. +After uploading, assign the build to a password-protected beta in Steamworks and test installation using the Steam client on each OS. +Each per-OS VDF updates one depot, so verify that the final build references the intended depot manifest versions for every OS. +[Official SteamPipe workflow and VDF specification](https://partner.steamgames.com/doc/sdk/uploading) / +[Depots](https://partner.steamgames.com/doc/store/application/depots) + +### Uploading with the builder and testing on internal + +`--steam-upload` uploads existing exported packages together through SteamCMD and uses `SetLive` to request activation on the selected beta branch. +It does not rebuild the web application or Electron packages. +`--platform` is unnecessary; do not combine this command with `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`. +This feature adds no npm dependencies to the builder. Install SteamCMD separately. + +#### Preparing a private testing branch in Steamworks + +1. Create an `internal` branch in Steamworks App Admin > SteamPipe > Builds. +2. **Set a branch password and share it only with the intended testers.** Naming a branch `internal` does not make it private. + The builder does not create branches, set passwords or verify password protection. Do not store the password in JSON or the repository. +3. Make sure testers hold a Package license that grants access to the game and its depots. + For external testers of an unreleased game, use Release State Override (beta) keys or another appropriate access method. + A branch password alone does not grant ownership of the game. +4. In the Steam client, testers open the game's Properties > Game Versions & Betas, enter the password and select `internal`. + +This command does not allow the `default` branch. Switch the public release build through Steamworks. +A beta password is a shared secret; another person who receives it can access the branch. Also manage each tester's game access rights. + +[Official beta branch settings](https://partner.steamgames.com/doc/store/application/branches) / +[Testing on Steam](https://partner.steamgames.com/doc/store/testing) / +[Release State Override keys](https://partner.steamgames.com/doc/features/keys) + +#### SteamCMD setup and authentication + +Obtain SteamCMD for your host OS from the [Steamworks SDK ContentBuilder](https://partner.steamgames.com/doc/sdk/uploading). +Use `steamcmd.exe` on Windows or `steamcmd.sh` on macOS / Linux. +The builder uses `steamcmd` on PATH (`steamcmd.exe` on Windows), or the executable path set in `STEAMCMD`. +Specify only the path, without arguments or a shell command. A `.sh` file needs executable permission. +Install any OS libraries required by SteamCMD according to its setup instructions. + +Grant the upload account `Edit App Metadata` and `Publish App Changes To Steam` permissions for the target app. +Example for macOS / Linux; replace the path and account name: + +```sh +export STEAMCMD="/absolute/path/to/steamcmd.sh" +export STEAM_USERNAME="your_build_account" +"$STEAMCMD" +login "$STEAM_USERNAME" +quit +``` + +During the first login, enter the password and Steam Guard code interactively in SteamCMD. +The builder then reuses that SteamCMD installation's saved login without passing a password as a command-line argument. +If authentication expires, log in again with the same SteamCMD. Builder uploads fail instead of waiting for interactive input. +In CI, restore and manage SteamCMD's `config/config.vdf` as a Secret and exclude it from build artifacts. +In Windows PowerShell, set `$env:STEAMCMD` and `$env:STEAM_USERNAME`. + +#### Validation and upload + +Collect the packages for every OS configured in `electron.config.json` under `steam.depots`, then run from the game root. +Operating systems sharing a Depot ID are placed in separate OS subdirectories; distinct depots are also uploaded in a single AppBuild. + +```sh +# Local validation only: no SteamCMD, credentials, Steam connection or upload +npx @next2d/builder --steam-upload --env prd --dry-run + +# Upload and request activation on steam.branch (internal by default) +npx @next2d/builder --steam-upload --env prd + +# Specify a branch or the directory containing collected packages +npx @next2d/builder --steam-upload --env prd --steam-branch internal --steam-root dist/steam +``` + +During local development, replace `npx @next2d/builder` with `node ../builder/dist/index.js`. +After a builder version containing this feature is published, the game's `npm run upload:steam:check` / `npm run upload:steam` scripts can also be used. + +`--steam-root` accepts a path relative to the game root or an absolute path; it defaults to `dist/steam`. +The upload operation does not read the Vite configuration, so specify this argument for a custom output directory. +It validates the App ID, Depot ID, version, game name and CPU recorded in `steam-package.json`, along with the executable. +Missing or inconsistent packages stop the operation before SteamCMD starts. VDFs are regenerated from validated packages instead of reusing existing files. + +Each run saves its VDFs and `upload-plan.json` in `dist/steam/uploads//-/`. +A dry run generates a VDF with `Preview=1` and no `SetLive`. This is local validation only; +it does not verify SteamCMD authentication, server-side permissions, branch existence or password settings. +Ordinary export VDFs are left unchanged. SteamPipe logs and incremental upload caches go in `uploads//cache//`. + +Uploads check the exit code and SteamCMD's success message, then record the BuildID in `upload-result.json`. +On the Steamworks Builds page, verify that the recorded BuildID is the current build for `internal`. +Steam may require additional confirmation depending on the account or app state. + +### Pre-release validation + +- Installation, launch and exit through Steam on each target OS, offline launch and save data after updates. +- Mouse/keyboard/controller input, resolution and fullscreen switching, audio and sleep/resume. +- Shift+Tab Overlay. Electron uses multiple processes, so verify actual behavior on each OS/GPU. + This export does not integrate the Steamworks SDK, achievements, DRM or Overlay APIs. + SDK integration is not required for Steam distribution. +- On Linux, check dependent libraries and sandboxing in the Steam Linux Runtime environment. + Do not use `--no-sandbox` as a distribution workaround. +- Steam Deck Verified requires a separate review. Producing a Linux package alone does not establish compatibility. + Test controller-only operation, text readability and display behavior on hardware. +- Before Steam's build review, verify that the supported operating systems and features advertised on the store match the application. + +[Steamworks API](https://partner.steamgames.com/doc/sdk/api) / +[Overlay](https://partner.steamgames.com/doc/features/overlay) / +[Linux development](https://partner.steamgames.com/doc/store/application/platforms/linux) / +[Steam Deck compatibility](https://partner.steamgames.com/doc/steamhardware/compat) / +[Build review](https://partner.steamgames.com/doc/store/review_process) diff --git a/docs/xbox.md b/docs/xbox.md index 9572004..5e887e6 100644 --- a/docs/xbox.md +++ b/docs/xbox.md @@ -1,10 +1,14 @@ -# Xbox(試作・開発段階) +# Xbox(試作・開発段階 / Prototype under development) + +[日本語](#日本語) | [English](#english) + +## 日本語 Xbox対応は現在、GDK向けネイティブホストを試作・検証している開発フェーズ。 ホスト生成やビルド処理は実装されているが、Xbox実機での動作確認・配布対応は完了していない。 現時点では、開発環境での検証を目的として使用する。 -## 構成と現在の対応範囲 +### 構成と現在の対応範囲 Next2DのJavaScriptをV8で実行し、Dawn(WebGPU → D3D12)で描画するC++ホストを生成する。 ElectronやWebViewは使用しない。 @@ -18,7 +22,7 @@ ElectronやWebViewは使用しない。 | macOS / Linux上の実行 | ホスト生成とアセットの準備まで。GDK向け実行ファイルのビルドは行わない。 | | Xbox実機・配布 | 開発機での動作、性能、保存処理、配布設定などの検証・調整が残っている。 | -## 検証に使う環境 +### 検証に使う環境 以下は現行テンプレートが想定する構成。 @@ -31,7 +35,7 @@ ElectronやWebViewは使用しない。 | Dawn | WebGPU実装。CMakeのFetchContentで取得するため、初回取得にはネットワーク接続が必要。 | | Xbox開発機(devkit) | Xbox実機上での動作・性能検証。 | -## 試作用コマンド +### 試作用コマンド ゲームのルートで実行する。 @@ -46,7 +50,7 @@ macOS / Linuxではホスト生成とアセットの準備を終えた時点で Windowsで `--open` または `--preview` を付けると、CMakeによる構成後にVisual Studioのソリューションを開く。 実行・デバッグはVisual Studio側で行う。 -## V8の取得 +### V8の取得 Windowsでのビルド時は、次の優先順でV8を解決する。 @@ -66,7 +70,7 @@ npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8 公開しているWindows x64用V8の利用と、Xboxコンソール向けの互換性検証は別の作業。 コンソール向けにはGDKツールチェーンでの調整・再ビルドを含む検証が残っている。 -## 検証範囲と残っている作業 +### 検証範囲と残っている作業 `xbox-host-ci` ワークフローには、ラスタライザーの単体テスト、Windows APIを使う一部機能のテスト、 V8依存ソースのコンパイル確認、Game Core APIの確認を用意している。 @@ -77,3 +81,83 @@ Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必 詳細なホスト構成・自前V8のビルド・機能ごとの実装状況は、 [ホストテンプレートの開発資料](../templates/xbox/README.md)を参照する。 同じ資料は生成先の `xbox/README.md` にも配置される。 + +## English + +Xbox support is currently in the development phase, focused on prototyping and testing a native GDK host. +Host generation and build operations are implemented, but validation on Xbox hardware and distribution support are not complete. +Use this feature for testing in a development environment at this stage. + +### Architecture and current scope + +The builder generates a C++ host that runs Next2D JavaScript on V8 and renders with Dawn (WebGPU → D3D12). +It does not use Electron or WebView. + +| Item | Current status | +|---|---| +| Host generation | Creates or updates the game's `xbox/` directory from the C++ / CMake templates bundled with the builder. | +| Game configuration | Applies `MicrosoftGame.config` from the game root to the host. If it is missing, an initial file is created from the template. | +| Assets | Packs the web build output and host scripts into `assets.pak` / `assets.rc` for embedding into the executable. | +| Building on Windows | CMake configuration and build operations for Visual Studio / GDK are implemented. Validation in the target environment is still needed. | +| Running on macOS / Linux | Generates the host and prepares assets only. Does not build a GDK executable. | +| Xbox hardware and distribution | Device behavior, performance, save handling and distribution settings still need validation and adjustment. | + +### Development environment + +The current template targets the following setup. + +| Component | Purpose | +|---|---| +| Windows + Visual Studio 2022 | Builds the C++ host. The current builder selects the Visual Studio 2022 generator. | +| Microsoft GDK | Builds the GDK target. Prepare the appropriate environment for PC testing or Xbox console development. | +| CMake 3.26 or later | Configures and builds the host. | +| V8 | JavaScript engine. Use the downloaded prebuilt library or your own build. | +| Dawn | WebGPU implementation. CMake FetchContent downloads it, so the first download needs network access. | +| Xbox development hardware (devkit) | Validates behavior and performance on Xbox hardware. | + +### Prototype commands + +Run from the game root: + +```sh +npx @next2d/builder --platform xbox --env prd +``` + +On Windows, the builder prepares the host and assets, then resolves V8 and starts the CMake build. +On macOS / Linux, it stops after generating the host and preparing assets. +Command completion alone does not mean an Xbox hardware export is ready; inspect the output files and logs. + +On Windows, `--open` or `--preview` opens the Visual Studio solution after CMake configuration. +Run and debug the application from Visual Studio. + +### Acquiring V8 + +For Windows builds, V8 is resolved in this order: + +1. The path passed with `--v8-root` +2. The `V8_ROOT` environment variable +3. A cached prebuilt V8 +4. A download from the Next2D/builder GitHub Releases + +The `build-v8` workflow is configured to build and publish prebuilt V8 libraries. +The builder pins the download version and revision and reuses the cache after downloading. +To use your own V8 build: + +```bat +npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8 +``` + +Using the published Windows x64 V8 library and validating Xbox console compatibility are separate tasks. +Console support still needs validation, including adjustments or rebuilds with the GDK toolchain. + +### Validation scope and remaining work + +The `xbox-host-ci` workflow includes rasterizer unit tests, tests for selected Windows API functionality, +compilation checks for V8-dependent sources and Game Core API checks. +These checks do not guarantee that the complete host links or runs on Xbox hardware. +An integrated build with Dawn / GDK and testing with a game on the target hardware are still required. + +Remaining validation and adjustments cover rendering, Workers, input, audio, text, save handling, performance and distribution settings. +For the detailed host architecture, instructions for building V8 yourself and implementation status by feature, +see the [host template development notes](../templates/xbox/README.md). +The same notes are copied to the generated `xbox/README.md`. diff --git a/tests/electron.test.ts b/tests/electron.test.ts index f38e5f4..429635f 100644 --- a/tests/electron.test.ts +++ b/tests/electron.test.ts @@ -160,7 +160,8 @@ test("SteamPipe maps package contents to install root and excludes development A fs.writeFileSync(path.join(content, executable), "binary", { mode: 0o755 }); } const scripts = `${content}-steampipe`; - writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: "1234" }); + // Match the builder's configured executableName; Windows cannot preserve POSIX execute bits. + writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: "1234" }, "test-game"); const launch = JSON.parse(fs.readFileSync(path.join(scripts, "launch.json"), "utf8")); assert.equal(launch.executable, executable); assert.equal(path.resolve(scripts, launch.contentRoot), content); @@ -171,11 +172,25 @@ test("SteamPipe maps package contents to install root and excludes development A assert.match(app, /"Preview" "0"/); assert.doesNotMatch(app, /SetLive/); assert.match(fs.readFileSync(path.join(scripts, "app_preview.vdf"), "utf8"), /"Preview" "1"/); - writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: null }); + writeSteamMetadata(content, `steam:${platform}`, arch, { appId: "2409460", depotId: null }, "test-game"); assert.equal(fs.existsSync(path.join(scripts, "app_build.vdf")), false); } }); +test("Linux launch metadata uses the configured name even without POSIX execute permissions", (t) => { + const root = fixture(t); + const content = path.join(root, "linux"); + fs.mkdirSync(content); + // Reproduce permissions seen on Windows without requiring a Windows test runner. + fs.writeFileSync(path.join(content, "custom-game"), "binary", { mode: 0o644 }); + fs.writeFileSync(path.join(content, "resources.pak"), "resources"); + writeSteamMetadata(content, "steam:linux", "x64", { appId: "2409460", depotId: "1234" }, "custom-game"); + const launch = JSON.parse(fs.readFileSync(`${content}-steampipe/launch.json`, "utf8")); + assert.equal(launch.executable, "custom-game"); + assert.equal(path.resolve(`${content}-steampipe`, launch.contentRoot), content); + assert.throws(() => writeSteamMetadata(content, "steam:linux", "x64", null, "missing-game"), /Packaged Steam executable is missing/); +}); + test("local protocol rejects foreign origins, encoded traversal and malformed paths", () => { const root = path.resolve(os.tmpdir(), "game/resources"); assert.equal(resolveAssetPath(root, "next2d://game/assets/test%20file.js"), path.join(root, "assets/test file.js")); From 0a0e8454e47dafb321c067f8421b3bd1fb80f0f2 Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 14:07:45 +0900 Subject: [PATCH 4/9] =?UTF-8?q?CodeScan=E3=81=AE=E8=AD=A6=E5=91=8A?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/xbox-host-ci.yml | 13 +- .../xbox/src/platform/StbTextRasterizer.cpp | 24 ++-- templates/xbox/tests/check_local.sh | 16 ++- templates/xbox/tests/stb_safety_test.cpp | 126 ++++++++++++++++++ templates/xbox/third_party/LICENSES.md | 15 +++ templates/xbox/third_party/stb_image.h | 25 ++-- templates/xbox/third_party/stb_truetype.h | 8 +- templates/xbox/third_party/stb_vorbis.c | 2 +- 8 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 templates/xbox/tests/stb_safety_test.cpp diff --git a/.github/workflows/xbox-host-ci.yml b/.github/workflows/xbox-host-ci.yml index b57fe29..d218a2e 100644 --- a/.github/workflows/xbox-host-ci.yml +++ b/.github/workflows/xbox-host-ci.yml @@ -24,6 +24,9 @@ on: - ".github/workflows/xbox-host-ci.yml" workflow_dispatch: +permissions: + contents: read + env: # コンパイル検証に使う V8 ヘッダのバージョン (README「V8 の用意」と揃える) V8_TAG: 13.7.152.19 @@ -39,7 +42,7 @@ jobs: git clone --depth 1 --branch "$V8_TAG" --filter=blob:none --sparse \ https://github.com/v8/v8.git v8_headers cd v8_headers && git sparse-checkout set include - - name: Run raster tests + ASan + V8 syntax check + - name: Run raster/stb tests + ASan + V8 syntax check working-directory: templates/xbox run: tests/check_local.sh "$GITHUB_WORKSPACE/v8_headers/include" @@ -75,6 +78,14 @@ jobs: if errorlevel 1 exit /b 1 raster_test.exe + - name: Build & run stb safety regression tests + shell: cmd + working-directory: templates/xbox + run: | + cl /nologo /std:c++17 /EHsc /W3 /O1 tests\stb_safety_test.cpp /Fe:stb_safety_test.exe + if errorlevel 1 exit /b 1 + stb_safety_test.exe + - name: Build & run Windows platform tests (WIC/DirectWrite/MF/XAudio2/stb/dr) shell: cmd working-directory: templates/xbox diff --git a/templates/xbox/src/platform/StbTextRasterizer.cpp b/templates/xbox/src/platform/StbTextRasterizer.cpp index 6f5fc72..75b0167 100644 --- a/templates/xbox/src/platform/StbTextRasterizer.cpp +++ b/templates/xbox/src/platform/StbTextRasterizer.cpp @@ -192,16 +192,18 @@ bool ComputeLayout(const std::string& css_font, const std::wstring& text, Layout stbtt_GetFontVMetrics(&lo.font->info, &lo.ascent, &lo.descent, &line_gap); lo.cps = ToCodepoints(text); + // Keep layout arithmetic in double; stb's bitmap API still takes float. + const double scale = lo.scale; double x = 0; for (size_t i = 0; i < lo.cps.size(); ++i) { int advance = 0, lsb = 0; stbtt_GetCodepointHMetrics(&lo.font->info, static_cast(lo.cps[i]), &advance, &lsb); - x += advance * lo.scale; + x += advance * scale; if (i + 1 < lo.cps.size()) { x += stbtt_GetCodepointKernAdvance( &lo.font->info, static_cast(lo.cps[i]), - static_cast(lo.cps[i + 1])) * lo.scale; + static_cast(lo.cps[i + 1])) * scale; } } lo.width = x; @@ -239,9 +241,10 @@ bool MeasureText(const std::string& css_font, const std::wstring& text, if (!ComputeLayout(css_font, text, lo)) { return false; } + const double scale = lo.scale; out.width = lo.width; - out.ascent = lo.ascent * lo.scale; - out.descent = -lo.descent * lo.scale; // stbtt の descent は負値 + out.ascent = lo.ascent * scale; + out.descent = -static_cast(lo.descent) * scale; // stbtt の descent は負値 return true; } @@ -256,8 +259,9 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text, return false; } - const double ascent_px = lo.ascent * lo.scale; - const double descent_px = -lo.descent * lo.scale; + const double scale = lo.scale; + const double ascent_px = lo.ascent * scale; + const double descent_px = -static_cast(lo.descent) * scale; const int width = std::max(1, static_cast(std::ceil(lo.width)) + 2); const int height = std::max(1, static_cast(std::ceil(ascent_px + descent_px)) + 2); const int baseline = static_cast(std::ceil(ascent_px)) + 1; @@ -272,7 +276,7 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text, int x0 = 0, y0 = 0, x1 = 0, y1 = 0; const float shift_x = - static_cast(pen_x + lsb * lo.scale - std::floor(pen_x + lsb * lo.scale)); + static_cast(pen_x + lsb * scale - std::floor(pen_x + lsb * scale)); stbtt_GetCodepointBitmapBoxSubpixel( &lo.font->info, cp, lo.scale, lo.scale, shift_x, 0, &x0, &y0, &x1, &y1); @@ -284,7 +288,7 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text, &lo.font->info, glyph.data(), gw, gh, gw, lo.scale, lo.scale, shift_x, 0, cp); - const int dst_x = static_cast(std::floor(pen_x + lsb * lo.scale)); + const int dst_x = static_cast(std::floor(pen_x + lsb * scale)); const int dst_y = baseline + y0; for (int yy = 0; yy < gh; ++yy) { const int py = dst_y + yy; @@ -298,10 +302,10 @@ bool RasterizeText(const std::string& css_font, const std::wstring& text, } } - pen_x += advance * lo.scale; + pen_x += advance * scale; if (i + 1 < lo.cps.size()) { pen_x += stbtt_GetCodepointKernAdvance( - &lo.font->info, cp, static_cast(lo.cps[i + 1])) * lo.scale; + &lo.font->info, cp, static_cast(lo.cps[i + 1])) * scale; } } diff --git a/templates/xbox/tests/check_local.sh b/templates/xbox/tests/check_local.sh index ad5c2d4..63fbe6e 100755 --- a/templates/xbox/tests/check_local.sh +++ b/templates/xbox/tests/check_local.sh @@ -2,7 +2,8 @@ # macOS / Linux で Windows 実機なしに実行できる検証をまとめて回す。 # 1. RasterCore 単体テスト (実行) # 2. RasterCore 単体テスト (ASan/UBSan) -# 3. V8 依存ソースの構文チェック (実 V8 ヘッダに対して。Windows API 非依存の12ファイル) +# 3. stb 境界値・デコード回帰テスト (通常 + ASan/UBSan) +# 4. V8 依存ソースの構文チェック (実 V8 ヘッダに対して) # # 使い方: # tests/check_local.sh [V8_INCLUDE_DIR] @@ -13,16 +14,23 @@ cd "$(dirname "$0")/.." V8_TAG="13.7.152.19" V8INC="${1:-}" -echo "== 1/3 RasterCore unit tests ==" +echo "== 1/4 RasterCore unit tests ==" c++ -std=c++17 -Wall -Wextra -O1 -o /tmp/next2d_raster_test tests/raster_test.cpp /tmp/next2d_raster_test -echo "== 2/3 RasterCore unit tests (ASan/UBSan) ==" +echo "== 2/4 RasterCore unit tests (ASan/UBSan) ==" c++ -std=c++17 -g -fsanitize=address,undefined -fno-omit-frame-pointer \ -o /tmp/next2d_raster_test_asan tests/raster_test.cpp /tmp/next2d_raster_test_asan -echo "== 3/3 V8-dependent sources syntax check (V8 ${V8_TAG}) ==" +echo "== 3/4 stb safety regression tests ==" +c++ -std=c++17 -O1 -o /tmp/next2d_stb_safety_test tests/stb_safety_test.cpp +/tmp/next2d_stb_safety_test +c++ -std=c++17 -g -fsanitize=address,undefined -fno-omit-frame-pointer \ + -o /tmp/next2d_stb_safety_test_asan tests/stb_safety_test.cpp +/tmp/next2d_stb_safety_test_asan + +echo "== 4/4 V8-dependent sources syntax check (V8 ${V8_TAG}) ==" if [ -z "${V8INC}" ]; then if [ ! -d ".v8_headers/include" ]; then git clone --depth 1 --branch "${V8_TAG}" --filter=blob:none --sparse \ diff --git a/templates/xbox/tests/stb_safety_test.cpp b/templates/xbox/tests/stb_safety_test.cpp new file mode 100644 index 0000000..00d0b9d --- /dev/null +++ b/templates/xbox/tests/stb_safety_test.cpp @@ -0,0 +1,126 @@ +// Regression tests for local stb patches. No Windows SDK, V8 or external assets. +#include +#include +#include +#include + +// Record allocation attempts without allocating huge buffers in boundary tests. +static size_t last_allocation = 0; +static void* TestMalloc(size_t size) +{ + last_allocation = size; + return size > 64 * 1024 * 1024 ? nullptr : std::malloc(size); +} +static void* TestRealloc(void* data, size_t size) +{ + last_allocation = size; + return size > 64 * 1024 * 1024 ? nullptr : std::realloc(data, size); +} + +#define STBI_MALLOC TestMalloc +#define STBI_REALLOC TestRealloc +#define STBI_FREE std::free +#define STBI_NO_STDIO +#define STB_IMAGE_IMPLEMENTATION +#include "../third_party/stb_image.h" + +#define STB_VORBIS_NO_STDIO +#define STB_VORBIS_NO_PUSHDATA_API +#include "../third_party/stb_vorbis.c" + +static int failures = 0; +static int checks = 0; +#define EXPECT(condition) do { ++checks; if (!(condition)) { \ + ++failures; std::printf("not ok - line %d: %s\n", __LINE__, #condition); } } while (0) + +static void Test16BitConversion() +{ + auto* pixel = static_cast(std::malloc(3 * sizeof(stbi__uint16))); + pixel[0] = 123; pixel[1] = 456; pixel[2] = 789; + auto* rgba = stbi__convert_format16(pixel, 3, 4, 1, 1); + EXPECT(rgba != nullptr); + if (rgba) { + EXPECT(rgba[0] == 123 && rgba[1] == 456 && rgba[2] == 789 && rgba[3] == 65535); + std::free(rgba); + } + + // The original unsigned product wraps to zero for these dimensions. + // Conversion must reject the size before allocating or reading input pixels. + const unsigned int dimensions[][2] = {{65536, 8192}, {UINT_MAX, 1}, {1, UINT_MAX}}; + for (const auto& size : dimensions) { + pixel = static_cast(std::malloc(3 * sizeof(stbi__uint16))); + last_allocation = 0; + rgba = stbi__convert_format16(pixel, 3, 4, size[0], size[1]); + EXPECT(rgba == nullptr); + EXPECT(last_allocation == 0); + std::free(rgba); + } +} + +static void TestAnimatedGif() +{ + // Original fixture: 1x1, black/red palette. Frame 2 restores frame 1; + // transparent frame 3 must therefore show red, not read before the buffer. + std::vector gif = { + 'G', 'I', 'F', '8', '9', 'a', 1, 0, 1, 0, 0x80, 0, 0, + 0, 0, 0, 255, 0, 0 + }; + const stbi_uc controls[] = {4, 12, 5}; + const stbi_uc pixels[] = {0x4c, 0x44, 0x44}; + for (int i = 0; i < 3; ++i) { + gif.insert(gif.end(), {0x21, 0xf9, 4, controls[i], 1, 0, 0, 0, + 0x2c, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, pixels[i], 1, 0}); + } + gif.push_back(0x3b); + int* delays = nullptr; + int width = 0, height = 0, frames = 0, components = 0; + stbi_uc* rgba = stbi_load_gif_from_memory(gif.data(), static_cast(gif.size()), + &delays, &width, &height, &frames, &components, 4); + EXPECT(rgba != nullptr); + EXPECT(width == 1 && height == 1 && frames == 3 && components == 4); + if (rgba && frames == 3) { + EXPECT(rgba[0] == 255 && rgba[1] == 0 && rgba[2] == 0 && rgba[3] == 255); + EXPECT(rgba[4] == 0 && rgba[5] == 0 && rgba[6] == 0 && rgba[7] == 255); + EXPECT(rgba[8] == 255 && rgba[9] == 0 && rgba[10] == 0 && rgba[11] == 255); + } + EXPECT(delays != nullptr); + if (delays && frames == 3) EXPECT(delays[0] == 10 && delays[1] == 10 && delays[2] == 10); + stbi_image_free(rgba); + stbi_image_free(delays); + + // Oversized canvas is rejected before any pixel buffer is allocated. + gif[6] = gif[7] = gif[8] = gif[9] = 255; + last_allocation = 0; + rgba = stbi_load_gif_from_memory(gif.data(), static_cast(gif.size()), + &delays, &width, &height, &frames, &components, 4); + EXPECT(rgba == nullptr); + EXPECT(last_allocation == 0); + stbi_image_free(rgba); + stbi_image_free(delays); +} + +static void TestVorbisSeekBounds() +{ + unsigned char bytes[16] = {}; + stb_vorbis decoder = {}; + decoder.stream_start = bytes; + decoder.stream_end = bytes + sizeof(bytes); + for (unsigned int offset : {0U, 15U, 16U, 17U, UINT_MAX, 0U}) { + const bool valid = offset < sizeof(bytes); + EXPECT(set_file_offset(&decoder, offset) == static_cast(valid)); + EXPECT(decoder.eof == static_cast(!valid)); + EXPECT(decoder.stream == (valid ? bytes + offset : decoder.stream_end)); + } + decoder.stream_end = decoder.stream_start; + EXPECT(set_file_offset(&decoder, 0) == 0); + EXPECT(decoder.eof == 1 && decoder.stream == decoder.stream_end); +} + +int main() +{ + Test16BitConversion(); + TestAnimatedGif(); + TestVorbisSeekBounds(); + std::printf("stb safety: %d checks, %d failures\n", checks, failures); + return failures ? 1 : 0; +} diff --git a/templates/xbox/third_party/LICENSES.md b/templates/xbox/third_party/LICENSES.md index 9b12d8b..21eaee0 100644 --- a/templates/xbox/third_party/LICENSES.md +++ b/templates/xbox/third_party/LICENSES.md @@ -12,3 +12,18 @@ | dr_wav.h | https://github.com/mackron/dr_libs | public domain (Unlicense) / MIT-0 のデュアル | いずれも各ファイル末尾に原文のライセンス条文が含まれる。 + +## ローカル修正 / Local patches + +同梱の stb には以下の修正を適用している。更新時は再適用の要否を確認し、 +`tests/stb_safety_test.cpp` と既存のプラットフォームテストを実行する。 + +The vendored stb files include these local patches. When updating upstream code, +check whether they are still needed and run `tests/stb_safety_test.cpp` and the +existing platform tests. + +| ファイル / File | 修正 / Patch | +|---|---| +| stb_image.h | メモリサイズを乗算前に拡張し、16bit変換・GIFフレーム追加時のオーバーフローを検証。GIFの過去フレーム参照をバッファ内に修正。 / Widen operands before size multiplication, validate overflow in 16-bit conversion and GIF frame growth, and keep GIF previous-frame references inside the buffer. | +| stb_truetype.h | ビットマップの確保・初期化サイズを `size_t` で乗算。 / Multiply bitmap allocation and initialization sizes in `size_t`. | +| stb_vorbis.c | ポインタ加算前に入力サイズとシーク位置を比較。 / Check seek offsets against the input size before pointer arithmetic. | diff --git a/templates/xbox/third_party/stb_image.h b/templates/xbox/third_party/stb_image.h index 9eedabe..a5c6456 100644 --- a/templates/xbox/third_party/stb_image.h +++ b/templates/xbox/third_party/stb_image.h @@ -1817,7 +1817,8 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + good = (x > INT_MAX || y > INT_MAX) ? NULL : + (stbi__uint16 *) stbi__malloc_mad4(req_comp, (int) x, (int) y, 2, 0); if (good == NULL) { STBI_FREE(data); return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); @@ -4821,7 +4822,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r stbi__create_png_alpha_expand8(dest, dest, x, img_n); } else if (depth == 8) { if (img_n == out_n) - memcpy(dest, cur, x*img_n); + memcpy(dest, cur, (size_t) x*img_n); else stbi__create_png_alpha_expand8(dest, cur, x, img_n); } else if (depth == 16) { @@ -6201,7 +6202,7 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); ri->bits_per_channel = 16; } else - out = (stbi_uc *) stbi__malloc(4 * w*h); + out = (stbi_uc *) stbi__malloc_mad3(4, w, h, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); pixelCount = w*h; @@ -6524,7 +6525,7 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c // intermediate buffer is RGBA result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); if (!result) return stbi__errpuc("outofmem", "Out of memory"); - memset(result, 0xff, x*y*4); + memset(result, 0xff, (size_t) x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { STBI_FREE(result); @@ -6833,11 +6834,11 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i } // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); + memcpy( g->background, g->out, (size_t) 4 * g->w * g->h ); } // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + memset( g->history, 0x00, (size_t) g->w * g->h ); // pixels that were affected previous frame for (;;) { int tag = stbi__get8(s); @@ -6987,11 +6988,15 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, if (u) { *x = g.w; *y = g.h; - ++layers; stride = g.w * g.h * 4; + // Keep the frame count, byte offsets and allocator sizes representable. + if (layers == INT_MAX || !stbi__mul2sizes_valid(layers + 1, stride) || + !stbi__mul2sizes_valid(layers + 1, sizeof(int))) + return stbi__load_gif_main_outofmem(&g, out, delays); + ++layers; if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, (size_t) layers * stride ); if (!tmp) return stbi__load_gif_main_outofmem(&g, out, delays); else { @@ -7007,7 +7012,7 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, delays_size = layers * sizeof(int); } } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); + out = (stbi_uc*)stbi__malloc( (size_t) layers * stride ); if (!out) return stbi__load_gif_main_outofmem(&g, out, delays); out_size = layers * stride; @@ -7020,7 +7025,7 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, } memcpy( out + ((layers - 1) * stride), u, stride ); if (layers >= 2) { - two_back = out - 2 * stride; + two_back = out + (layers - 2) * stride; } if (delays) { diff --git a/templates/xbox/third_party/stb_truetype.h b/templates/xbox/third_party/stb_truetype.h index 90a5c2e..02b3e7e 100644 --- a/templates/xbox/third_party/stb_truetype.h +++ b/templates/xbox/third_party/stb_truetype.h @@ -3739,7 +3739,7 @@ STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info if (yoff ) *yoff = iy0; if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + gbm.pixels = (unsigned char *) STBTT_malloc((size_t) gbm.w * gbm.h, info->userdata); if (gbm.pixels) { gbm.stride = gbm.w; @@ -3822,7 +3822,7 @@ static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // fo f.userdata = NULL; if (!stbtt_InitFont(&f, data, offset)) return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + STBTT_memset(pixels, 0, (size_t) pw*ph); // background of 0 around pixels x=y=1; bottom_y = 1; @@ -3981,7 +3981,7 @@ STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, in stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + STBTT_memset(pixels, 0, (size_t) pw*ph); // background of 0 around pixels return 1; } @@ -4610,7 +4610,7 @@ STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float sc float *precompute; stbtt_vertex *verts; int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + data = (unsigned char *) STBTT_malloc((size_t) w * h, info->userdata); precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); for (i=0,j=num_verts-1; i < num_verts; j=i++) { diff --git a/templates/xbox/third_party/stb_vorbis.c b/templates/xbox/third_party/stb_vorbis.c index 3e5c250..65afd96 100644 --- a/templates/xbox/third_party/stb_vorbis.c +++ b/templates/xbox/third_party/stb_vorbis.c @@ -1401,7 +1401,7 @@ static int set_file_offset(stb_vorbis *f, unsigned int loc) #endif f->eof = 0; if (USE_MEMORY(f)) { - if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + if (loc >= (size_t) (f->stream_end - f->stream_start)) { f->stream = f->stream_end; f->eof = 1; return 0; From d0a29fa5d8d98b1ca8e1a6ca1184cbba0a33e9e9 Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 14:24:15 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/capacitor.md | 243 +++++++------ docs/setup.md | 91 +++++ docs/steam.md | 900 +++++++++++++++++++--------------------------- docs/xbox.md | 154 ++++---- 4 files changed, 670 insertions(+), 718 deletions(-) create mode 100644 docs/setup.md diff --git a/docs/capacitor.md b/docs/capacitor.md index 72a7e24..f8f69f4 100644 --- a/docs/capacitor.md +++ b/docs/capacitor.md @@ -4,165 +4,202 @@ ## 日本語 -Capacitorの `cli`・`core`・`ios`・`android` はbuilderの `src/tool-packages.ts` で同じバージョンに固定し、 -iOS / Androidの操作時に `npx` でまとめて取得する。builderの `dependencies` には含めない。 -ゲームは `npx @next2d/builder` で起動し、`capacitor.config.json` で -アプリ名・App ID・プラットフォーム固有設定を管理する。 -builderやCapacitorをゲームの `package.json` に直接宣言する必要はない。 +[共通準備](setup.md#日本語)を済ませてから、対象OSの手順を進める。 + +### STEP1:対象OSのIDE・SDKを用意する + +| 対象 | ユーザーが用意するもの | +|---|---| +| iOS | macOS、Xcode本体とiOS SDK。Xcodeを初回起動してセットアップを完了する。 | +| Android | Android Studio、Android SDK、使用するCapacitorに対応したJDK。 | +| 実機・配布ビルド | 対象端末と、その配布方法に必要な署名証明書・プロビジョニング設定またはAndroidの署名鍵。 | + +使用するCapacitorのバージョンはbuilderの `src/tool-packages.ts` で固定している。 +対応するネイティブ環境は[Capacitorの開発フロー](https://capacitorjs.com/docs/basics/workflow)から確認する。 +Capacitorの `cli`・`core`・`ios`・`android` はbuilderが同じバージョンで取得するため、手動インストールは不要。 + +#### iOS:Xcodeの選択を確認する ```sh -npm run open:ios -- --env prd -npm run open:android -- --env prd -npm run build:ios -- --env prd -npm run build:android -- --env prd +xcode-select -p +xcodebuild -version ``` -builderを直接使う場合: +`/Library/Developer/CommandLineTools` が選択されていると、IPA生成時に `xcodebuild requires Xcode` で失敗する。 +今回のシェルだけXcodeを指定する場合は、次を設定してからSTEP3へ進む: ```sh -npx @next2d/builder --platform ios --env prd --open -npx @next2d/builder --platform android --env prd --build +export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer +xcodebuild -version ``` -builderはnpmキャッシュに取得したCapacitor CLIをNodeで起動し、ゲームルートを作業ディレクトリにする。 -ゲームからSDKを解決できない場合に備え、子プロセスの `NODE_PATH` に取得したSDKのパスを追加する。 -通常のnpmインストール、依存がネストされた配置、隣接リポジトリの `file:../builder` に対応する。 -ゲーム側にCapacitor用の依存宣言や補助スクリプト、SDKへのシンボリックリンクを追加する必要はない。 -npm/npxを利用できるNode.js環境が必要。初回はネットワーク接続が必要で、以降はnpmキャッシュを再利用する。 -取得先は一時ホストと異なり書き出し後も残り、Gradle / CocoaPodsから参照できる。 +システム全体の既定を変える場合は、代わりに次を使う: -`webDir` を今回のWeb出力先へ更新してから、未作成のネイティブプロジェクトに `cap add` を実行する。 -`--open` / `--build` は `cap sync` の成功後に `cap open` / `cap build` を実行する。 -`--preview` は `cap run` を実行する。各コマンドの終了を待ち、失敗した場合はbuilderも失敗する。 +```sh +sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer +xcodebuild -version +``` -### ゲーム側に残すもの +Xcodeの配置先が異なる場合はパスを変更する。builderは呼び出し元の `DEVELOPER_DIR` を引き継ぐ。 +[Appleのコマンドラインツール設定](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings) -- `capacitor.config.json`:ゲーム固有設定。`ios.path` / `android.path` のカスタム出力先も使用できる。 -- `ios/` / `android/`:ネイティブプロジェクト。既存のアイコン・署名設定・ネイティブコードを維持する。 -- ゲーム固有のCapacitor/Cordovaプラグイン:ゲームの依存に置く。CLIは従来どおりゲームの依存とフックを参照する。 +### STEP2:アプリ設定とプラグインを用意する -ゲームのJavaScriptで `@capacitor/core` を直接importする場合は、ビルド時の依存解決に必要なため、 -`@capacitor/core` をゲームの直接依存としても宣言する。その場合はbuilderのCapacitorと -互換性のあるバージョンを指定する。今回のSlimeTenPuzzleにはそのimportはない。 +ゲームルートの `capacitor.config.json` にアプリ固有の値を設定する。 -Xcode、Android Studio、Android SDK、対応JDK、署名証明書などは引き続き実行環境に必要。 -SDKのnpm依存を集約しても、端末実行・APK/AAB/IPAの生成に必要なネイティブツールは変わらない。 +```json +{ + "appId": "app.example.game", + "appName": "My Game", + "webDir": "dist/ios/prd/" +} +``` -### iOSで `xcodebuild requires Xcode` が出る場合 +| 項目 | 設定・管理方法 | +|---|---| +| `appId` / `appName` | 自分のアプリ識別子・表示名に変更する。 | +| `webDir` | builderが今回のWeb出力先に更新するため、OSごとの手動切り替えは不要。 | +| `ios.path` / `android.path` | 必要な場合だけネイティブプロジェクトのカスタム出力先を指定する。 | +| ゲーム固有のCapacitor/Cordovaプラグイン | ゲームの依存として管理する。ゲーム側のフックも引き続き使用する。 | +| `@capacitor/core` | ゲームのJavaScriptで直接importする場合だけ、builderのCapacitorと互換性のあるバージョンをゲームの直接依存に追加する。 | -`xcode-select -p` が `/Library/Developer/CommandLineTools` を返す場合、 -Command Line Toolsが選択されている。iOSのアーカイブ・IPA生成にはXcode本体とiOS SDKが必要。 -Xcodeをインストールし、初回起動時のセットアップを完了させる。 +その他の項目は[Capacitor設定](https://capacitorjs.com/docs/config)を参照。 -`/Applications/Xcode.app` を今回のビルドだけで使う場合は、ゲームのルートで実行する: +### STEP3:ネイティブプロジェクトを開き、署名・アイコンを設定する + +対象OSのコマンドを実行する。 ```sh -DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ - npx @next2d/builder --platform ios --env prd --build +npx @next2d/builder --platform ios --env prd --open +npx @next2d/builder --platform android --env prd --open ``` -コマンドライン全体の既定をXcodeに切り替える場合: +未作成の `ios/` / `android/` を生成し、Web資産とネイティブ依存を同期してIDEを開く。 +Xcode / Android Studioで、アプリのアイコン・署名・配布方法に必要な設定を行う。 +これらのネイティブプロジェクトはゲーム側で保持する。再実行時も既存の設定・ネイティブコードを維持する。 + +### STEP4:ビルドして対象環境で確認する ```sh -sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer -xcodebuild -version +npx @next2d/builder --platform ios --env prd --build +npx @next2d/builder --platform android --env prd --build ``` -Xcodeの配置先が異なる場合はパスを変更する。builderは呼び出し元の `DEVELOPER_DIR` を引き継ぐ。 -選択方法の詳細は[Appleのコマンドラインツール設定](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings)を参照。 +実行前にWeb資産・ネイティブ依存を再同期する。対象端末やシミュレーター/エミュレーターで起動・入力・保存を確認する。 +Capacitorの `cap run` を使う場合は `--build` を `--preview` に置き換える。 +途中のコマンドが失敗した場合はbuilderも失敗する。 -### 既存プロジェクトの移行 +テンプレートのnpmスクリプト `open:ios` / `open:android` / `build:ios` / `build:android` も使用できる。 +環境指定は `npm run build:ios -- --env prd` のように渡す。 -1. この機能を含む公開済みbuilderを `npx` で使用する。 -2. ゲームが直接importしていない `@capacitor/cli`・`@capacitor/core`・`@capacitor/ios`・`@capacitor/android` を直接依存から外す。 -3. npmコマンド経由で `open:ios` / `open:android` を実行し、ネイティブ依存の参照先を再同期する。 +### 補足:既存プロジェクトの移行とSDKキャッシュ -隣接リポジトリで開発する場合はbuilderで `npm ci` または `npm install` を実行し、 -ゲームでも `npm install` を実行する。builderのソース変更後はbuilderで `npm run build` を実行する。 -npxキャッシュを削除した場合は、IDEが参照するSDKのパスを更新するため、 -builderの `--open` / `--build` で再同期する。 +1. ゲームから不要になった `@capacitor/cli`・`@capacitor/ios`・`@capacitor/android` の直接依存を外す。 + `@capacitor/core` とゲーム固有プラグインはSTEP2の条件に従う。 +2. 設定と `ios/` / `android/` は保持し、STEP3でネイティブ依存の参照先を再同期する。 -[Capacitorの開発フロー](https://capacitorjs.com/docs/basics/workflow) / -[Capacitor設定](https://capacitorjs.com/docs/config) +builderはゲームルートでCapacitor CLIを実行し、取得したSDKのパスを子プロセスの `NODE_PATH` に追加する。 +通常・ネストされたnpm依存配置や `file:../builder` に対応し、ゲーム側の補助スクリプトやSDKシンボリックリンクは不要。 +SDKキャッシュは書き出し後もGradle / CocoaPodsが参照するため保持する。 +npxキャッシュを削除した場合は、STEP3またはSTEP4で参照先を再同期する。 ## English -The builder pins Capacitor's `cli`, `core`, `ios` and `android` packages to the same version in -`src/tool-packages.ts` and acquires them together through `npx` when an iOS / Android operation runs. -They are not included in the builder's `dependencies`. -Games invoke `npx @next2d/builder` and manage the app name, App ID and platform-specific settings in -`capacitor.config.json`. Neither the builder nor Capacitor needs to be declared directly in the game's `package.json`. +Complete the [common setup](setup.md#english), then follow the steps for your target OS. + +### STEP1: Prepare the platform IDE and SDK + +| Target | What you need to prepare | +|---|---| +| iOS | macOS, the full Xcode application and the iOS SDK. Complete Xcode's first-launch setup. | +| Android | Android Studio, the Android SDK and a JDK compatible with the Capacitor version in use. | +| Device and distribution builds | Target devices and signing certificates/provisioning settings or an Android signing key, as required by the distribution method. | + +The builder pins the Capacitor version in `src/tool-packages.ts`. +Check the corresponding native environment through the [Capacitor development workflow](https://capacitorjs.com/docs/basics/workflow). +The builder acquires matching versions of Capacitor's `cli`, `core`, `ios` and `android`; no manual installation is needed. + +#### iOS: Check the selected Xcode ```sh -npm run open:ios -- --env prd -npm run open:android -- --env prd -npm run build:ios -- --env prd -npm run build:android -- --env prd +xcode-select -p +xcodebuild -version ``` -To invoke the builder directly: +If `/Library/Developer/CommandLineTools` is selected, IPA generation fails with `xcodebuild requires Xcode`. +To select Xcode for the current shell, set this before STEP3: ```sh -npx @next2d/builder --platform ios --env prd --open -npx @next2d/builder --platform android --env prd --build +export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer +xcodebuild -version ``` -The builder launches the cached Capacitor CLI with Node, using the game root as the working directory. -It adds the acquired SDK paths to the child process's `NODE_PATH` so the game can resolve the SDKs when needed. -This supports regular npm installations, nested dependencies and a sibling repository linked through `file:../builder`. -The game does not need extra Capacitor dependency declarations, helper scripts or SDK symlinks. -Node.js with npm/npx is required. The first download needs network access; subsequent runs reuse npm's cache. -Unlike a temporary host, the acquired SDKs remain after export so Gradle / CocoaPods can keep referencing them. +Alternatively, to change the system-wide default: -The builder updates `webDir` to the current web output directory before running `cap add` for a native project that does not exist yet. -`--open` / `--build` run `cap open` / `cap build` after `cap sync` succeeds. -`--preview` runs `cap run`. The builder waits for each command and fails if a command fails. +```sh +sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer +xcodebuild -version +``` -### What stays in the game +Adjust the path if Xcode is installed elsewhere. The builder inherits the caller's `DEVELOPER_DIR`. +[Apple's command-line tools settings](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings) -- `capacitor.config.json`: Game-specific settings, including custom native output paths through `ios.path` / `android.path`. -- `ios/` / `android/`: Native projects. Existing icons, signing settings and native code are preserved. -- Game-specific Capacitor/Cordova plugins: Keep these in the game's dependencies. The CLI continues to discover the game's dependencies and hooks. +### STEP2: Prepare app configuration and plugins -If the game's JavaScript imports `@capacitor/core` directly, declare `@capacitor/core` as a direct game dependency -so the web build can resolve it. Choose a version compatible with the builder's Capacitor version. -SlimeTenPuzzle does not currently have this import. +Set game-specific values in `capacitor.config.json` at the game root. -Xcode, Android Studio, the Android SDK, a compatible JDK and signing certificates are still required in the build environment. -Centralizing the SDK npm dependencies does not change the native tools needed for device execution or APK/AAB/IPA generation. +```json +{ + "appId": "app.example.game", + "appName": "My Game", + "webDir": "dist/ios/prd/" +} +``` -### If iOS reports `xcodebuild requires Xcode` +| Setting | Configuration / ownership | +|---|---| +| `appId` / `appName` | Replace with your app identifier and display name. | +| `webDir` | The builder updates this to the current web output directory; no manual switching between operating systems is needed. | +| `ios.path` / `android.path` | Set only when using custom native project locations. | +| Game-specific Capacitor/Cordova plugins | Keep them in the game's dependencies. Game hooks continue to work. | +| `@capacitor/core` | Add a compatible version as a direct game dependency only when the game's JavaScript imports it directly. | -If `xcode-select -p` returns `/Library/Developer/CommandLineTools`, the standalone Command Line Tools are selected. -Creating an iOS archive or IPA requires the full Xcode application and the iOS SDK. -Install Xcode and complete its first-launch setup. +See [Capacitor configuration](https://capacitorjs.com/docs/config) for other settings. -To use `/Applications/Xcode.app` for a single build, run this from the game root: +### STEP3: Open the native project and configure signing and icons + +Run the command for your target OS: ```sh -DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ - npx @next2d/builder --platform ios --env prd --build +npx @next2d/builder --platform ios --env prd --open +npx @next2d/builder --platform android --env prd --open ``` -To select Xcode as the system-wide command-line default: +The builder creates missing `ios/` / `android/` projects, synchronizes web assets and native dependencies, then opens the IDE. +Configure app icons, signing and distribution-specific settings in Xcode / Android Studio. +Keep these native projects in the game. Subsequent runs preserve existing settings and native code. + +### STEP4: Build and verify on the target environment ```sh -sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer -xcodebuild -version +npx @next2d/builder --platform ios --env prd --build +npx @next2d/builder --platform android --env prd --build ``` -Adjust the path if Xcode is installed elsewhere. The builder inherits the caller's `DEVELOPER_DIR`. -See [Apple's command-line tools settings](https://developer.apple.com/documentation/xcode/configuring-command-line-tools-settings) for details. +Web assets and native dependencies are synchronized before building. Verify launch, input and saved data on target devices or simulators/emulators. +Replace `--build` with `--preview` to use Capacitor's `cap run`. +If any command fails, the builder fails as well. -### Migrating an existing project +Template npm scripts `open:ios` / `open:android` / `build:ios` / `build:android` are also available. +Pass the environment as in `npm run build:ios -- --env prd`. -1. Use a published builder version that includes this feature through `npx`. -2. Remove direct dependencies on `@capacitor/cli`, `@capacitor/core`, `@capacitor/ios` and `@capacitor/android` that the game does not import directly. -3. Run the `open:ios` / `open:android` npm scripts to resynchronize native dependency paths. +### Reference: Existing project migration and SDK cache -When developing with a sibling builder repository, run `npm ci` or `npm install` in the builder, -then run `npm install` in the game. After changing builder source files, run `npm run build` in the builder. -If the npx cache is deleted, use the builder's `--open` / `--build` to resynchronize the SDK paths referenced by the IDE. +1. Remove direct dependencies on `@capacitor/cli`, `@capacitor/ios` and `@capacitor/android` that are no longer needed by the game. + Follow STEP2 for `@capacitor/core` and game-specific plugins. +2. Keep the configuration and `ios/` / `android/`, then use STEP3 to resynchronize native dependency paths. -[Capacitor development workflow](https://capacitorjs.com/docs/basics/workflow) / -[Capacitor configuration](https://capacitorjs.com/docs/config) +The builder runs the Capacitor CLI from the game root and adds acquired SDK paths to the child process's `NODE_PATH`. +Regular and nested npm dependency layouts and `file:../builder` are supported; no game-side helper scripts or SDK symlinks are needed. +Keep the SDK cache after export because Gradle / CocoaPods reference it. +If you delete the npx cache, use STEP3 or STEP4 to resynchronize those paths. diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..c10d9c0 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,91 @@ +# 共通準備 / Common setup + +[日本語](#日本語) | [English](#english) + +## 日本語 + +各プラットフォームの手順に進む前に、ゲームのビルド環境を準備する。 + +### STEP1:Node.js・npm・npxを用意する + +利用するbuilderとゲームの依存パッケージが要求するNode.jsをインストールし、次のコマンドを使える状態にする。 + +```sh +node --version +npm --version +npx --version +``` + +初回のツール取得にはネットワーク接続が必要。取得後はnpmキャッシュを再利用する。 +builderはElectron PackagerやCapacitor SDKを必要な操作時に取得するが、 +Xcode・Android SDK・GDKなどのネイティブ開発環境はユーザーが別途用意する。 + +### STEP2:ゲームの依存をインストールする + +ゲームの `package.json` があるディレクトリで実行する。 + +```sh +npm ci +``` + +`package-lock.json` がない新規プロジェクトは `npm install` を使う。 +以降の各ガイドのコマンドも、特記がなければゲームルートで実行する。 +`@next2d/builder` は `npx` で起動できるため、ゲームの依存へ追加する必要はない。 + +### STEP3:対象プラットフォームの準備へ進む + +| ガイド | ユーザーが用意するもの | +|---|---| +| [Electron / Steam](steam.md#日本語) | アイコン・対象OSの検証環境。Steam配布時はApp/Depot・ブランチ・SteamCMD、macOS配布時は署名・公証の認証。 | +| [iOS / Android](capacitor.md#日本語) | 対象OSのIDE・SDK、アプリ設定、配布時の署名。 | +| [Xbox(試作)](xbox.md#日本語) | WindowsのC++ / GDK開発環境。コンソール検証時は対応する開発環境・開発機。 | + +### 補足:隣接リポジトリのbuilderを開発中に使う場合 + +builderリポジトリで `npm ci` を実行し、ソース変更後は `npm run build` で `dist/` を更新する。 +ゲームルートでは各ガイドの `npx @next2d/builder` を `node ../builder/dist/index.js` に置き換える。 +ゲーム側の `package.json` を変更せずに開発版を使える。 + +## English + +Prepare the game build environment before following a platform guide. + +### STEP1: Install Node.js, npm and npx + +Install a Node.js version supported by the builder and the game's dependencies, and verify these commands work: + +```sh +node --version +npm --version +npx --version +``` + +Initial tool downloads require network access; later runs reuse npm's cache. +The builder acquires Electron Packager and Capacitor SDKs when needed, but you must separately install +native development tools such as Xcode, the Android SDK or the GDK. + +### STEP2: Install the game's dependencies + +Run in the directory containing the game's `package.json`: + +```sh +npm ci +``` + +For a new project without `package-lock.json`, use `npm install`. +Run subsequent guide commands from the game root unless stated otherwise. +`@next2d/builder` runs through `npx`, so it does not need to be added to the game's dependencies. + +### STEP3: Continue with the target platform + +| Guide | What you need to prepare | +|---|---| +| [Electron / Steam](steam.md#english) | Icons and environments for testing each OS. Steam distribution needs an app/depots, a branch and SteamCMD; macOS distribution needs signing/notarization credentials. | +| [iOS / Android](capacitor.md#english) | Platform IDEs/SDKs, app configuration and distribution signing. | +| [Xbox (prototype)](xbox.md#english) | A Windows C++ / GDK environment; console testing also needs the corresponding development environment and hardware. | + +### Reference: Using a sibling builder repository during development + +Run `npm ci` in the builder repository, then `npm run build` after source changes to update `dist/`. +From the game root, replace `npx @next2d/builder` in each guide with `node ../builder/dist/index.js`. +This uses the development build without changing the game's `package.json`. diff --git a/docs/steam.md b/docs/steam.md index af62d96..b2cc6f3 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -4,9 +4,54 @@ ## 日本語 -### 設定 +[共通準備](setup.md#日本語)を済ませてから、以下を順に進める。 +ローカル書き出しだけなら、Steamworks・SteamCMD・配布用の署名認証は不要。 + +### STEP1:対象OSの実行環境とアイコンを用意する + +- macOSのUniversalアプリと配布用の署名・公証はmacOS上で行う。 +- 配布する各OSで起動・入力・保存を確認できる実行環境を用意する。 +- ゲームの `src/assets/icons/` にWindows用ICO、macOS用ICNS、Linux用PNGを用意する。 + テンプレートの仮アイコンは配布前に差し替える。設定の省略時の扱いはSTEP3の表を参照。 + +#### 対応OS・CPU + +| OS | 既定値 | `architectures` / `--arch` の対応値 | +|---|---|---| +| Windows | `x64` | `x64`, `arm64` | +| macOS | `universal` | `x64`, `arm64`, `universal` | +| Linux | `x64` | `x64`, `arm64` | + +32bit版Windows(`ia32`)には対応しない。`win32` はElectron内部のWindowsのOS名で、 +32bitを意味しない。例えば `My Game-win32-x64/` は64bit版Windows向けの成果物。 +`--platform` には `windows` または `steam:windows` を指定する。 + +macOSの `universal` はx64とarm64を含む1つの `.app`。 + +### STEP2:SteamworksのApp・Depot・テスト用ブランチを用意する(Steam配布時) + +1. 対象アプリのSteamworks管理画面で、発行済みのApp IDを確認する。 +2. SteamPipe > DepotsでDepotを作成し、対象OSを設定する。言語共通ならAll languagesを選び、 + 開発用・販売用Packageにも対象Depotを含める。**Depot IDはApp IDから推測せず、実際の値を使う。** +3. SteamPipe > Buildsで `internal` ブランチを作成し、**パスワードを設定して関係者だけに共有する。** + 名前だけでは非公開にならず、builderはブランチ作成・パスワード設定や確認を行わない。 +4. テスターにゲームと対象Depotの利用権を用意する。未発売ゲームの外部テスターには + Release State Override(beta)キー等を使う。ブランチのパスワードだけでは利用権は付与されない。 + パスワードを第三者に共有しないよう運用し、JSONやリポジトリにも保存しない。 + +OS別Depotと共有Depotのどちらも使用できる。共有するOSには同じDepot IDを設定し、 +Steamworks側の対象OSをAll OSesなど共有構成に合わせる。 +共有Depotは全OSのファイルを配布するため容量が増える。ValveはOS固有ファイルには別Depotを推奨する。 + +[Depotの設定](https://partner.steamgames.com/doc/store/application/depots) / +[betaブランチ](https://partner.steamgames.com/doc/store/application/branches) / +[Steamでのテスト](https://partner.steamgames.com/doc/store/testing) / +[Release State Overrideキー](https://partner.steamgames.com/doc/features/keys) + +### STEP3:electron.config.jsonを設定する プロジェクトルートの `electron.config.json` でゲーム固有設定を管理する。 +JavaScript / TypeScriptの両テンプレートに含まれるので、初期値を自分のゲームの値に変更する。 `appId` は macOS bundle ID・保存先識別子、`steam.appId` は Valve の数値 App ID。 これらは別のID。バージョンはゲームルートの `package.json` を使う。 @@ -38,152 +83,60 @@ | `appName` / `executableName` | `appName` はウィンドウ・アプリケーション名。`executableName` は実行ファイル名。 | 表示名と実行ファイル名を個別に設定できる。 | | `description` | 任意の説明文。生成するElectronホストの `package.json` とWindowsのファイル説明に反映する。 | JSONの値を優先。未指定または `null` ならルートの `package.json` の `description` を使う。両方未指定なら空文字。明示的な `""` も空文字として扱う。 | | `icons` | アイコン画像のパス。プロジェクトルートからの相対パス、または絶対パスを指定する。WindowsはICO、macOSはICNS、LinuxはPNG。 | 各キーは省略可。省略したOSにはbuilder同梱の仮のNext2Dアイコンを使う。`"icons": {}` なら全OSで仮アイコンを使える。指定したファイルがなければ失敗する。 | -| `architectures` | OSごとのCPU設定。対応する値と指定例は後述の「対応OS・CPU」を参照。 | 省略時はWindows/Linuxが `x64`、macOSが `universal`。`--arch` が優先する。 | +| `architectures` | OSごとのCPU設定。対応値はSTEP1の「対応OS・CPU」を参照。 | 省略時はWindows/Linuxが `x64`、macOSが `universal`。`--arch` が優先する。 | | `steam.appId` | Valveの数値App ID。未発行なら `null` を指定する。 | 未発行でもローカル書き出しは可能。SteamPipe用VDFは生成しない。 | | `steam.branch` | `--steam-upload` の反映先betaブランチ。 | 省略時は `internal`。`--steam-branch` が優先する。ブランチの作成・パスワード設定はSteamworks側で行う。 | | `steam.depots` | Steamworksで作成した実際のDepot ID。複数OSに同じIDを指定すると、同一Depotへまとめて配布する。 | App IDから推測しない。`null` / 未指定ならアプリと起動情報だけを生成し、アップロード用VDFは生成しない。 | | `macos` | macOSアプリの署名・公証設定。 | ローカル検証用は `sign` / `notarize` ともに `false`。本番配布では両方を有効化する。 | -#### テンプレートとApp作成時の初期値 +### STEP4:署名・アップロードの認証を用意する(配布時) -JavaScript / TypeScriptの両テンプレートは `electron.config.json` と -`src/assets/icons/` の仮のNext2Dアイコン(ICO / ICNS / PNG)を含む。 -配布前にアイコンとゲーム固有の値を差し替える。Steam App ID / Depot IDの初期値は `null`。 +#### macOSの署名・公証 -`create-next2d-app` はプロジェクト名から `appId`、`appName`、`executableName`、 -`companyName` を設定する。例えば `my-game` なら `appId` は `app.example.my-game`、 -他の3項目は `my-game` になる。配布前にbundle IDと会社名を自分の値へ変更する。 -CPU設定が省略されている場合はOSごとの既定値を補い、テンプレートに指定済みの値は保持する。 -アイコンパスやSteam IDなどの設定も保持する。 - -#### 対応OS・CPU - -| OS | 既定値 | `architectures` / `--arch` の対応値 | -|---|---|---| -| Windows | `x64` | `x64`, `arm64` | -| macOS | `universal` | `x64`, `arm64`, `universal` | -| Linux | `x64` | `x64`, `arm64` | - -32bit版Windows(`ia32`)には対応しない。`win32` はElectron内部のWindowsのOS名で、 -32bitを意味しない。例えば `My Game-win32-x64/` は64bit版Windows向けの成果物。 -`--platform` には `windows` または `steam:windows` を指定する。 - -macOSの `universal` はx64とarm64を含む1つの `.app` で、macOS上で生成する。 -Windows/Linuxの既定値はx64。ARM64版は `architectures.windows` / `architectures.linux` -に `arm64` を設定するか、書き出し時に `--arch arm64` を指定する。 +macOSのキーチェーンにDeveloper ID Application証明書を登録し、 +`xcrun notarytool store-credentials` で公証用プロファイルを保存する。認証情報はJSONへ書かない。 ```sh -npx @next2d/builder --platform steam:windows --env prd --arch arm64 -``` - -#### 書き出しコマンド - -```sh -npx @next2d/builder --platform steam:windows --env prd -npx @next2d/builder --platform steam:macos --env prd -npx @next2d/builder --platform steam:linux --env prd -npx @next2d/builder --platform macos --env prd --preview -``` - -更新済みのテンプレートと `create-next2d-app` が用意するnpmコマンドでも実行できる。 - -```sh -npm run build:steam:windows -npm run build:steam:macos -npm run build:steam:linux -npm run build:steam:windows -- --arch arm64 -npm run build:steam:windows -- --env dev +export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' +export APPLE_NOTARY_PROFILE='your-notary-profile' ``` -これらのnpmコマンドは `--env prd` を指定済み。末尾の `-- --env dev` などで上書きできる。 -builderを直接呼ぶ場合は `--env` を指定する。 -書き出しコマンドはSteamへのアップロードや公開を自動では行わない。 - -#### builderが管理するElectronホスト - -Electron関連でゲーム側に必要な設定ファイルは `electron.config.json` だけ。 -ゲーム名・バージョン・説明はルートの `package.json` と合わせてホストへ反映する。 -説明は `electron.config.json` の `description` を優先する。 -アイコン画像はゲームの共通アセットを参照でき、未指定ならbuilderの仮アイコンを使う。 - -builderはOSの一時ディレクトリにホスト・runtime設定・Web資産を配置し、書き出し後に削除する。 -ゲームルートへ `electron/` やElectron用の `node_modules` / lockfileを作らない。 -失敗時も一時ホストを削除する。Electronバージョンはbuilderの -`templates/electron/package.json` で固定し、Packagerが実行ファイルをダウンロード・キャッシュする。 -ゲーム側でElectronをインストールする処理は不要。 - -`@electron/packager` もbuilderのnpm依存には含めず、Electronの書き出し時だけ `npx` で取得する。 -バージョンは `src/tool-packages.ts` で固定する。npm/npxを利用できるNode.js環境が必要で、 -初回取得にはネットワーク接続が必要。取得済みツールはnpmキャッシュを再利用する。 -Web / Xboxビルドや `--steam-manifest` だけの実行ではPackagerを取得しない。 -PackagerはOS別パッケージの生成、アイコンの適用、署名・公証、Electron実行ファイルの取得を担当する。 -生成するホストにはnpm依存やネイティブアドオンがないため、ABIに合わせた再ビルドは不要。 - -プレビューも実行ホストのOS/CPU向けアプリを書き出して起動する。 -一時ホストの削除後、`dist//build//` のアプリを実行するので、 -本番書き出しと同じホスト・資産読み込みを確認できる。 - -旧 `electron/` のホストコード・`config.forge`・独自npm依存は参照しない。 -移行時はアイコンを共通アセットへ移し、設定パスを更新してから `electron/` を削除する。 -独自のmain/preloadやネイティブアドオンを持つプロジェクトは、移行前にbuilder側への対応が必要。 - -builder自体を開発中なら、ゲームのdevDependencyに `"@next2d/builder": "file:../builder"` を指定し、 -builder→ゲームの順に `npm ci` する。builder側のprepareでコンパイルされる。 -builderのソース変更後はbuilderで `npm run build` を実行する。 -公開後はこの依存を公開バージョンへ変更でき、ゲームのコマンドはそのまま使える。 - -### 成果物と起動 - -`dist/steam//build//` に以下を生成する(デフォルトoutDirの場合)。 -下表のExecutableはOS別の独立したDepotを使う場合。共有Depotの場合は後述のOS名プレフィックスが付く。 - -| OS | 配布するフォルダ全体 | SteamworksのExecutable | -|---|---|---| -| Windows | `My Game-win32-x64/` | `my-game.exe` | -| macOS | `My Game-darwin-universal/` | `My Game.app` | -| Linux | `My Game-linux-x64/` | `my-game` | +配布用は `macos.sign` / `macos.notarize` を両方 `true` にする。 +またはSTEP5のmacOSコマンドに `NEXT2D_STEAM_RELEASE=1` を付けると、JSONのfalse指定に関わらず両方を必須にできる。 +認証情報不足・署名失敗・公証失敗はビルド失敗となる。 -`*-steampipe/launch.json` に実際の起動パス・CPU・ContentRootが記録される。 -実行ファイルだけを取り出さず、Electronのライブラリ、locales、ライセンス、resourcesを含む -フォルダ全体を配布する。Steamにはインストーラ(DMG/DEB/MSI)は不要。 -macOSの `.app` 内のシンボリックリンクとLinuxの実行権限を保持する。 -CIの転送は `tar.gz` にしてからartifactへ格納する。 +Steam用のターゲットは `darwin`。Valveは新規macOSアプリに64bitとAppleの公証を要求する。 +テンプレートのentitlementsはJITとSteam Overlay用のlibrary validation / DYLD設定を含み、 +`com.apple.security.app-sandbox` は付けない(Chromiumのrenderer sandboxとは別)。 +[Valveのプラットフォーム要件](https://partner.steamgames.com/doc/store/application/platforms) / +[Electron Packagerの署名・公証設定](https://electron.github.io/packager/main/interfaces/Options.html) -テンプレートのホストは絶対パスと固定origin `next2d://game` で資産を読む。 -起動時の作業ディレクトリに依存せず、ローカルfetch・Worker・localStorageが使える。 -F11/Alt+Enterで全画面、Escapeで解除。閉じるとmacOSでもプロセスを終了する。 -Node integration無効・context isolation/sandbox有効、外部ページ遷移と新規ウィンドウは禁止。 -Next2D用CSPを付与する。外部APIを使うゲームでは接続先を明示的に追加すること。 -[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / -[protocol](https://www.electronjs.org/docs/latest/api/protocol) +#### SteamCMDとビルドアカウント -保存先はElectronのappData配下の `appId` ディレクトリ。 -表示名を変えても保存先は変わらない。旧file-originの開発版セーブは自動移行しない。 -Steam Cloudは未統合。Chromiumプロファイル全体をCloud対象にせず、導入時は -ゲーム用のセーブファイルとアカウント単位の保存方式を別途設計する。 -[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) +[Steamworks SDKのContentBuilder](https://partner.steamgames.com/doc/sdk/uploading)にある実行OS用のSteamCMDを用意する。 +Windowsは `steamcmd.exe`、macOS / Linuxは `steamcmd.sh` を使用できる。 +PATH上の `steamcmd`(Windowsは `steamcmd.exe`)を使うか、環境変数 `STEAMCMD` に実行ファイルのパスを指定する。 +引数やシェルコマンドを含めず、パスだけを指定する。`.sh` は実行権限が必要。 +SteamCMD自体の実行に必要なOSライブラリはSteamCMDの手順に従って用意する。 -### 同じDepotを複数OSで使う場合 +アップロード用アカウントには対象アプリの `Edit App Metadata` と `Publish App Changes To Steam` の権限を付与する。 +macOS / Linuxの設定例(配置先とアカウント名を置き換える): -`steam.depots` の同じIDを共有するOSを1グループとして扱う。例えば全OSに同じDepot IDを -指定すると、Steamのインストール先を次のように分け、Electronの同名ファイルの衝突を避ける。 +```sh +export STEAMCMD="/absolute/path/to/steamcmd.sh" +export STEAM_USERNAME="your_build_account" +"$STEAMCMD" +login "$STEAM_USERNAME" +quit +``` -| OS | SteamworksのExecutable(例) | -|---|---| -| Windows | `windows/my-game.exe` | -| macOS | `macos/My Game.app` | -| Linux | `linux/my-game` | +初回ログインではSteamCMDにパスワードとSteam Guardコードを対話入力する。 +その後builderは同じSteamCMDの保存済みログインを使い、パスワードをコマンド引数へ渡さない。 +認証が失効した場合は同じSteamCMDで再ログインする。builderからの実行は対話入力を待たず失敗させる。 +CIではSteamCMDの `config/config.vdf` をSecretとして復元・管理し、ビルド成果物に含めない。 +Windows PowerShellでは `$env:STEAMCMD` と `$env:STEAM_USERNAME` を設定する。 -Steamworks側のDepot対象OSを共有するOSまたはAll OSesに設定し、Launch Optionには -生成された `launch.json` のExecutableを指定する。OS別の起動条件も設定する。 -同一Depot内の全OS分のファイルが配布されるため、OS別Depotよりダウンロード容量は増える。 -ValveはOS固有ファイルには別Depotを推奨しているが、OS共有自体は禁止していない。 -[公式DepotのOS設定](https://partner.steamgames.com/doc/store/application/depots) +### STEP5:各OSを書き出し、成果物を揃える -各OSの書き出しは単独で成功する。共有対象の全OSの成果物が揃うと自動で -`dist/steam/shared//depot-/` に `app_build.vdf`、`app_preview.vdf`、 -`depot_build.vdf`、`launch.json` を生成する。未完了なら `launch.json` に不足OSを記録し、 -アップロード用VDFは生成しない。共有DepotのOS単独VDFも生成しない。 +ゲームルートで必要なOSのコマンドを実行する。書き出しはアップロード・公開を行わない。 ```sh npx @next2d/builder --platform steam:windows --env prd @@ -191,184 +144,150 @@ npx @next2d/builder --platform steam:macos --env prd npx @next2d/builder --platform steam:linux --env prd ``` -統合VDFは各OSのパッケージを `FileMapping` で `windows/` / `macos/` / `linux/` へ配置する。 -資産の再コピーは行わず、元パッケージ内のmacOSシンボリックリンク・Linux実行権限を保持する。 -VDFだけではなく `dist/steam/` 全体のディレクトリ構成を保持して移動すること。 -[公式FileMapping仕様](https://partner.steamgames.com/doc/sdk/uploading) - -CI等で別のマシン上で書き出した場合は、各OSの `build//` を -`steam-package.json` と `*-steampipe/` を含めて同じ `dist/steam//build//` に集め、次を実行する。 - -```sh -npx @next2d/builder --platform steam:macos --env prd --steam-manifest -``` - -更新済みのテンプレートと `create-next2d-app` では、同じ処理を次のnpmコマンドで実行できる。 - -```sh -npm run build:steam:manifest -# prd以外の成果物を統合する場合 -npm run build:steam:manifest -- --env dev -``` - -このコマンドはWebビルドやElectron書き出しを行わず、共有DepotのVDFだけを再生成する。 -どのOS上でも実行できる。`--platform` はVite設定を読むためのSteamターゲットで、統合対象は -設定内のすべての共有Depot。共有対象のパッケージが不足していれば終了コード1になる。 -`build.outDir` と `--env` を使い、各OSの最後に成功した書き出しを選ぶ(`--arch` 指定も記録される)。 -同じOSのx64版とarm64版を順に書き出した場合、統合対象になるのは最後に成功したCPUの成果物。 -1つの共有Depotへ同じOSの複数CPU版を同時にまとめる処理は行わない。 -`--steam-manifest` は `--arch` / `--preview` / `--build` / `--open` と併用できない。 -App ID・Depot ID・ゲーム名・実行ファイル名・バージョンが現設定と一致する必要がある。 -同じバージョン番号で更新する場合も、配布する全OSを意図したリビジョンで揃えること。 +CPUを変える場合は `--arch arm64` などを追加する。 +テンプレートの `npm run build:steam:windows` / `build:steam:macos` / `build:steam:linux` も同じ処理を行い、 +`--env prd` を指定済み。npm経由の引数は `-- --arch arm64` や `-- --env dev` で渡す。 -一部OSだけIDを共有する構成にも対応する。共有しないOSは従来どおり単独VDFを生成する。 -設定やバージョンを変更した場合は対象OSを再度書き出す。古い統合VDFは再生成時に無効化する。 +既定の出力先は `dist/steam//build//`。 -### macOSの配布用署名・公証 +| OS | 配布するフォルダ全体 | OS別DepotのExecutable | 共有DepotのExecutable | +|---|---|---|---| +| Windows | `My Game-win32-x64/` | `my-game.exe` | `windows/my-game.exe` | +| macOS | `My Game-darwin-universal/` | `My Game.app` | `macos/My Game.app` | +| Linux | `My Game-linux-x64/` | `my-game` | `linux/my-game` | -Steam用は `darwin` であり、Mac App Store向けの `mas` ではない。 -Valveは新規macOSアプリに64bitとAppleの公証を要求している。 -テンプレートのentitlementsはJITとSteam Overlayに必要なlibrary validation / DYLD設定を含み、 -`com.apple.security.app-sandbox` は付けない(Chromiumのrenderer sandboxとは別)。 -[Valveのプラットフォーム要件](https://partner.steamgames.com/doc/store/application/platforms) +実際の起動パス・CPU・ContentRootは `*-steampipe/launch.json` を参照する。 +実行ファイルだけでなく、Electronのライブラリ・locales・ライセンス・resourcesを含むフォルダ全体が配布対象。 +Steam用のDMG/DEB/MSIは不要で、書き出し後に追加のアプリビルドは行わない。 -macOS上でDeveloper ID Application証明書をキーチェーンへ登録し、Appleの -`xcrun notarytool store-credentials` で公証用プロファイルを保存してから実行する。 -認証情報はJSONへ書かない。 +別マシンやCIの成果物は、`steam-package.json` と `*-steampipe/` を含む各OSの `build//` を +同じ `dist/steam//build//` 構成に集める。macOSのシンボリックリンクとLinuxの実行権限を保つため、 +CI artifactは `tar.gz` にして転送する。カスタム出力先はSTEP6の `--steam-root` で指定する。 -```sh -export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' -export APPLE_NOTARY_PROFILE='your-notary-profile' -NEXT2D_STEAM_RELEASE=1 npx @next2d/builder --platform steam:macos --env prd -``` +設定・バージョンを変えたら対象OSを再書き出しする。同じバージョン番号でも全OSを意図したリビジョンで揃える。 +同じOSのx64とarm64を順に書き出すと、最後に成功したCPUの成果物を使う。 +同じOSの複数CPUを1つの共有Depotへ同時に統合する処理は行わない。 -`NEXT2D_STEAM_RELEASE=1` はJSONのfalse指定に関わらず署名と公証を必須にする。 -またはJSONの `macos.sign` / `macos.notarize` を両方trueにする。 -認証情報不足・署名失敗・公証失敗はビルド失敗となる。 -署名、公証、staple後にSteamPipeへ渡す。確認例: +配布用macOSアプリは署名・公証・staple完了を確認する(パスは成果物に合わせる): ```sh codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' ``` -[Electron Packagerの署名・公証設定](https://electron.github.io/packager/main/interfaces/Options.html) +### STEP6:起動設定を反映し、アップロード・テストする -### Steamworks / SteamPipe - -1. Steamworks > SteamPipe > Depotsで配布構成に合わせたDepotを作成し、対象OSを指定する。 - OS別Depotと、複数OS向けの同一Depotのどちらも使用できる。 - 言語共通ならAll languages。開発用・販売用Packageにも各Depotを含める。 -2. Installation > General InstallationにOS別Launch Optionを追加する。 - Executableには生成された `launch.json` の値を指定。Arguments/Working Directoryは空欄。 - x64版は64bit条件を指定し、macOS UniversalはIntel/Apple Silicon両方でテストする。 -3. 変更をSteamworksでPublishする。配布したいOS・最低OS要件は、同梱するElectronの - 対応バージョンと実機テストに合わせる(古いSteamページの最低OS表だけで決めない)。 -4. JSONへApp ID/Depot IDを設定して再ビルド。単独Depotは `*-steampipe/`、 - 共有Depotは全対象OSが揃った時点で `shared//depot-/` に次のVDFが出る。 - `app_preview.vdf`: ファイルマッピング検証。`app_build.vdf`: アップロード。 - `depot_build.vdf`: フォルダ内容をインストールルートへ再帰マッピングする。 - `steam_appid.txt` は配布対象から除外する。 -5. Steamworks SDKのContentBuilder/SteamCMDを取得し、権限のあるビルドアカウントでログインする。 - パスワードはコマンド・リポジトリへ保存しない。 +SteamworksのInstallation > General InstallationにOS別Launch Optionを追加する。 +ExecutableはSTEP5の `launch.json` に合わせ、Arguments/Working Directoryは空欄にする。 +x64版は64bit条件を指定する。対応OS・最低OS要件は同梱Electronと実機検証に合わせ、Steamworksで変更をPublishする。 -```text -steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit -steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit -``` +ゲームルートで次を実行する。共有Depot・OS別Depotをまとめて1回のAppBuildでアップロードする。 +Web/Electronの再ビルドや事前の `--steam-manifest` 実行は不要。 -ContentRootはVDFからの相対パス。単独Depotはアプリと `*-steampipe/` をセットで、 -共有Depotは `dist/steam/` 全体の構成を保持して移動する。 -通常の書き出し・`--steam-manifest` が生成するVDFにはSetLiveを入れていない。アップロード後にSteamworksでパスワード付きbetaへ設定し、 -各OSのSteamクライアントからインストールして検証する。 -OSごとのVDFは1つのDepotを更新するため、最終BuildのDepot manifestが全OSで意図した版になっているか確認する。 -[SteamPipe公式手順・VDF仕様](https://partner.steamgames.com/doc/sdk/uploading) / -[Depots](https://partner.steamgames.com/doc/store/application/depots) +```sh +# ローカル検証のみ。SteamCMD・認証・Steamへの接続は不要 +npx @next2d/builder --steam-upload --env prd --dry-run -### builderからアップロードしてinternalでテストする +# 検証後、設定したブランチへアップロード +npx @next2d/builder --steam-upload --env prd +``` -`--steam-upload` は書き出し済みの成果物をSteamCMDで一括アップロードし、 -`SetLive` で指定したbetaブランチへの反映を要求する。Web/Electronの再ビルドは行わない。 -`--platform` は不要で、`--build` / `--preview` / `--open` / `--steam-manifest` / `--arch` とは併用しない。 -builderのnpm依存は追加していない。SteamCMDは別途用意する。 +| 引数・設定 | 用途 | +|---|---| +| `--steam-branch internal` | `steam.branch` の反映先を上書き。`SetLive` で反映を要求する。`default` は指定不可で、一般公開用ビルドの切り替えはSteamworksで行う。 | +| `--steam-root dist/steam` | 成果物の収集先。既定は `dist/steam`。ゲームルートからの相対パスまたは絶対パス。アップロード時はVite設定を読まないため、カスタム出力先では明示する。 | +| `--dry-run` | パッケージと設定の整合性を検証する。Steam側の権限・ブランチ・パスワードの確認は行わない。 | +| 併用しない引数 | `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`。`--platform` も不要。 | -#### Steamworksで関係者向けの配布先を用意する +対応するテンプレートのnpmスクリプトは `upload:steam:check` / `upload:steam`。 -1. Steamworksのアプリ管理画面 > SteamPipe > Buildsで `internal` ブランチを作成する。 -2. **ブランチにパスワードを設定し、関係者だけに共有する。** `internal` という名前だけでは非公開にならない。 - builderはブランチの作成やパスワード設定・確認を行わない。パスワードはJSONやリポジトリに保存しない。 -3. テスターがゲームと対象Depotを利用できるPackageのライセンスを持っていることを確認する。 - 未発売ゲームの外部テスターにはRelease State Override(beta)キー等を使用する。 - ブランチのパスワードだけではゲームの利用権は付与されない。 -4. 関係者のSteamクライアントで、ゲームのプロパティ > ゲームバージョンとベータから - パスワードを入力して `internal` を選ぶ。 +builderは `steam.depots` に設定された全OSの `steam-package.json` と実行ファイルを検証し、 +App ID・Depot ID・バージョン・ゲーム名・CPUに不足や不整合があればSteamCMD起動前に停止する。 +VDFは検証結果から再生成する。通常の書き出し用VDFは変更しない。 -`default` ブランチはこのコマンドでは指定できない。一般公開用ビルドの切り替えはSteamworksで行う。 -betaのパスワードは共有秘密なので、第三者に渡ればアクセスできる。個々のテスターの利用権も管理する。 +| 保存先(`--steam-root` 配下) | 内容 | +|---|---| +| `uploads//-<識別子>/` | VDF、`upload-plan.json`。dry-runは `Preview=1`、`SetLive`なし。アップロード成功時はBuildIDを `upload-result.json` に記録。 | +| `uploads//cache//` | SteamPipeのログ・差分キャッシュ。 | -[公式betaブランチ設定](https://partner.steamgames.com/doc/store/application/branches) / -[Steamでのテスト](https://partner.steamgames.com/doc/store/testing) / -[Release State Overrideキー](https://partner.steamgames.com/doc/features/keys) +SteamworksのBuilds画面で、記録されたBuildIDが対象ブランチの現在のビルドになっていることを確認する。 +アカウントやアプリの状態によってSteam側の追加確認が必要になる場合がある。 +テスターはSteamクライアントのプロパティ > ゲームバージョンとベータでパスワードを入力し、`internal` を選ぶ。 +各OSでインストール・起動を確認し、macOS UniversalはIntel / Apple Siliconの両方でテストする。 -#### SteamCMDの準備と認証 +### 参照:VDFの生成と手動アップロード -[Steamworks SDKのContentBuilder](https://partner.steamgames.com/doc/sdk/uploading)にある実行OS用のSteamCMDを用意する。 -Windowsは `steamcmd.exe`、macOS / Linuxは `steamcmd.sh` を使用できる。 -PATH上の `steamcmd`(Windowsは `steamcmd.exe`)を使うか、環境変数 `STEAMCMD` に実行ファイルのパスを指定する。 -引数やシェルコマンドを含めず、パスだけを指定する。`.sh` は実行権限が必要。 -SteamCMD自体の実行に必要なOSライブラリはSteamCMDの手順に従って用意する。 +builderのSTEP6を使う場合、この節の操作は不要。SteamCMDを直接操作する場合に使う。 -アップロード用アカウントには対象アプリの `Edit App Metadata` と `Publish App Changes To Steam` の権限を付与する。 -macOS / Linuxの設定例(配置先とアカウント名を置き換える): +| 構成 | 通常の書き出し時のVDF生成先・条件 | +|---|---| +| OS別Depot | 各OSの `*-steampipe/`。 | +| 共有Depot | 全対象OSが揃うと `dist/steam/shared//depot-/`。不足OSは `launch.json` に記録し、アップロード用VDF・OS単独VDFは生成しない。 | + +一部OSだけDepot IDを共有する構成にも対応する。`FileMapping` でOS別サブディレクトリへ配置し、アセットは再コピーしない。 +別マシンの成果物を集めた後に共有DepotのVDFだけを再生成する場合: ```sh -export STEAMCMD="/absolute/path/to/steamcmd.sh" -export STEAM_USERNAME="your_build_account" -"$STEAMCMD" +login "$STEAM_USERNAME" +quit +npx @next2d/builder --platform steam:macos --env prd --steam-manifest ``` -初回ログインではSteamCMDにパスワードとSteam Guardコードを対話入力する。 -その後builderは同じSteamCMDの保存済みログインを使い、パスワードをコマンド引数へ渡さない。 -認証が失効した場合は同じSteamCMDで再ログインする。builderからの実行は対話入力を待たず失敗させる。 -CIではSteamCMDの `config/config.vdf` をSecretとして復元・管理し、ビルド成果物に含めない。 -Windows PowerShellでは `$env:STEAMCMD` と `$env:STEAM_USERNAME` を設定する。 +npmの別名は `build:steam:manifest`(別環境なら `-- --env dev`)。このコマンドはどのOSでも実行でき、 +`--platform` はVite設定を読むための指定で、設定内のすべての共有Depotを統合する。 +`build.outDir` と `--env` から最新の成功した書き出しを選び、App ID・Depot ID・ゲーム名・実行ファイル名・バージョンを検証する。 +不足パッケージは終了コード1となり、古い統合VDFは再生成時に無効化する。 +`--arch` / `--preview` / `--build` / `--open` とは併用できない。 -#### 検証とアップロード +`app_preview.vdf` はファイルマッピング検証、`app_build.vdf` はアップロード、 +`depot_build.vdf` はフォルダ内容の再帰マッピング用。`steam_appid.txt` は配布対象から除外する。 +ContentRootはVDFからの相対パスなので、単独Depotはアプリと `*-steampipe/` をセットで、共有Depotは `dist/steam/` 全体の構成を保って移動する。 -`electron.config.json` の `steam.depots` に設定された全OSの成果物を揃え、ゲームのルートで実行する。 -同じDepot IDを使うOSはOS別サブディレクトリへまとめ、異なるDepotも1回のAppBuildでアップロードする。 +```text +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit +``` -```sh -# ローカル検証だけ。SteamCMDや認証は不要、Steamへの接続・送信も行わない -npx @next2d/builder --steam-upload --env prd --dry-run +通常の書き出し・`--steam-manifest` のVDFには `SetLive` がないため、アップロード後にSteamworksで対象ブランチへ設定する。 +OS別VDFは1つのDepotを更新するので、最終BuildのDepot manifestが全OSで意図した版になっているか確認する。 +[SteamPipe公式手順・VDF仕様](https://partner.steamgames.com/doc/sdk/uploading) -# アップロードし、steam.branch(省略時internal)へ反映 -npx @next2d/builder --steam-upload --env prd +### 参照:テンプレート・ホスト・移行 -# ブランチや成果物の収集先を明示する場合 -npx @next2d/builder --steam-upload --env prd --steam-branch internal --steam-root dist/steam -``` +`create-next2d-app` はプロジェクト名から `appId`、`appName`、`executableName`、 +`companyName` を設定する。例えば `my-game` なら `appId` は `app.example.my-game`、 +他の3項目は `my-game` になる。配布前にbundle IDと会社名を自分の値へ変更する。 +CPU設定が省略されている場合はOSごとの既定値を補い、テンプレートに指定済みの値は保持する。 +アイコンパスやSteam IDなどの設定も保持する。 -ローカル開発中は `npx @next2d/builder` を `node ../builder/dist/index.js` に置き換える。 -この機能を含むbuilderの公開後は、ゲーム側の `npm run upload:steam:check` / `npm run upload:steam` も使用できる。 +Electron用コードはbuilderが管理する。OSの一時ディレクトリにホスト・runtime設定・Web資産を配置し、失敗時を含め書き出し後に削除する。 +ゲーム側へ `electron/`、Electron用 `node_modules` / lockfileは作らない。 +Electronは `templates/electron/package.json`、Packagerは `src/tool-packages.ts` の固定バージョンを取得・キャッシュする。 +PackagerはElectron書き出し時のみ取得し、OS別パッケージ・アイコン・署名・公証を処理する。 +ホストにはnpm依存やネイティブアドオンがないため、ABIに合わせた再ビルドは不要。 +プレビューは実行ホストのOS/CPUで書き出した `dist//build//` のアプリを起動する。 -`--steam-root` はゲームルートからの相対パス、または絶対パス。既定は `dist/steam`。 -アップロード処理ではVite設定を読み込まないため、独自の出力先を使う場合はこの引数を指定する。 -成果物の `steam-package.json` にあるApp ID・Depot ID・バージョン・ゲーム名・CPUと実行ファイルを検証し、 -不足や不整合があればSteamCMDを起動する前に停止する。VDFは既存ファイルを流用せず検証結果から再生成する。 +旧 `electron/` のホストコード・`config.forge`・独自npm依存は参照しない。 +移行時はアイコンを共通アセットへ移し、JSONのパスを更新してから旧ディレクトリを削除する。 +独自main/preloadやネイティブアドオンがある場合は、先にbuilder側への対応が必要。 -各実行のVDFと `upload-plan.json` は `dist/steam/uploads//-<識別子>/` に保存する。 -dry-runでは `Preview=1` のVDFを生成して `SetLive` を付けない。これはローカル検証であり、 -SteamCMDの認証・サーバー側の権限・ブランチ・パスワード設定が正しいことを保証するものではない。 -通常の書き出し用VDFは変更しない。SteamPipeのログ・差分キャッシュは `uploads//cache//` に置く。 +テンプレートのホストは絶対パスと固定origin `next2d://game` で資産を読む。 +起動時の作業ディレクトリに依存せず、ローカルfetch・Worker・localStorageが使える。 +F11/Alt+Enterで全画面、Escapeで解除。閉じるとmacOSでもプロセスを終了する。 +Node integration無効・context isolation/sandbox有効、外部ページ遷移と新規ウィンドウは禁止。 +Next2D用CSPを付与する。外部APIを使うゲームでは接続先を明示的に追加すること。 +[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / +[protocol](https://www.electronjs.org/docs/latest/api/protocol) -アップロード時は終了コードとSteamCMDの成功メッセージを確認し、BuildIDを `upload-result.json` に記録する。 -SteamworksのBuilds画面で、記録されたBuildIDが `internal` の現在のビルドになったことを確認する。 -アカウントやアプリの状態によって、Steam側で追加の確認が必要になる場合がある。 +保存先はElectronのappData配下の `appId` ディレクトリ。 +表示名を変えても保存先は変わらない。旧file-originの開発版セーブは自動移行しない。 +Steam Cloudは未統合。Chromiumプロファイル全体をCloud対象にせず、導入時は +ゲーム用のセーブファイルとアカウント単位の保存方式を別途設計する。 +[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) ### リリース前の確認範囲 -- 対象OSのSteamからインストール・起動・終了、オフライン起動、更新後の保存データ。 +STEP6のインストール・起動確認に加えて、以下を検証する。 + +- 終了動作、オフライン起動、更新後の保存データ。 - マウス/キーボード/コントローラ、解像度・全画面切替、音声、スリープ復帰。 - Shift+Tab Overlay。Electronは複数プロセスなので、OS/GPUごとに実動作を確認する。 この書き出しはSteamworks SDK・実績・DRM・Overlay APIを統合しない。 @@ -387,9 +306,54 @@ SteamworksのBuilds画面で、記録されたBuildIDが `internal` の現在の ## English -### Configuration +Complete the [common setup](setup.md#english), then follow these steps. +Local exports do not require Steamworks, SteamCMD or distribution signing credentials. + +### STEP1: Prepare target environments and icons + +- Build macOS Universal applications and perform macOS signing/notarization on macOS. +- Prepare an environment for each distributed OS to verify launch, input and saved data. +- Place Windows ICO, macOS ICNS and Linux PNG icons in the game's `src/assets/icons/`. + Replace template placeholders before distribution. STEP3 documents omitted icon settings. + +#### Supported operating systems and architectures + +| OS | Default | Accepted `architectures` / `--arch` values | +|---|---|---| +| Windows | `x64` | `x64`, `arm64` | +| macOS | `universal` | `x64`, `arm64`, `universal` | +| Linux | `x64` | `x64`, `arm64` | + +32-bit Windows (`ia32`) is not supported. `win32` is Electron's internal OS name for Windows, not a CPU bitness indicator. +For example, `My Game-win32-x64/` is a 64-bit Windows package. +Use `windows` or `steam:windows` for `--platform`. + +macOS `universal` produces one `.app` containing both x64 and arm64. + +### STEP2: Prepare the Steamworks app, depots and testing branch (Steam distribution) + +1. Find the issued App ID in the app's Steamworks administration page. +2. Create depots in SteamPipe > Depots and select their operating systems. Use All languages for shared language content, + and include the depots in development and retail Packages. **Use actual Depot IDs; do not infer them from the App ID.** +3. Create an `internal` branch in SteamPipe > Builds and **set a password shared only with the intended testers.** + The name alone does not make it private. The builder does not create branches, configure passwords or verify protection. +4. Give testers access to the game and its depots. For external testers of an unreleased game, use Release State Override + (beta) keys or another appropriate method. A branch password alone does not grant game access. + Keep the password out of JSON and the repository, and ask testers not to share it with others. + +Separate OS depots and shared depots are supported. Assign the same Depot ID to operating systems that share a depot, +and set its Steamworks OS selection accordingly, such as All OSes. +Shared depots distribute every OS's files and increase download size. Valve recommends separate depots for OS-specific files. + +[Depot configuration](https://partner.steamgames.com/doc/store/application/depots) / +[Beta branches](https://partner.steamgames.com/doc/store/application/branches) / +[Testing on Steam](https://partner.steamgames.com/doc/store/testing) / +[Release State Override keys](https://partner.steamgames.com/doc/features/keys) + +### STEP3: Configure electron.config.json Manage game-specific settings in `electron.config.json` at the project root. +Both JavaScript and TypeScript templates include it; replace the defaults with your game's values. `appId` is the macOS bundle ID and the identifier used for the save location; `steam.appId` is Valve's numeric App ID. These are separate identifiers. The game version comes from the root `package.json`. @@ -421,152 +385,60 @@ These are separate identifiers. The game version comes from the root `package.js | `appName` / `executableName` | `appName` is the window and application name. `executableName` is the executable file name. | The display name and executable name can be set independently. | | `description` | Optional description used in the generated Electron host's `package.json` and the Windows file description. | The JSON value takes priority. If omitted or `null`, uses `description` from the root `package.json`. If both are absent, uses an empty string. An explicit `""` also remains empty. | | `icons` | Icon paths, relative to the project root or absolute. Use ICO for Windows, ICNS for macOS and PNG for Linux. | Each key is optional. Omitted platforms use the builder's placeholder Next2D icons; `"icons": {}` uses placeholders for every OS. A specified file that does not exist causes an error. | -| `architectures` | CPU architecture per OS. See “Supported operating systems and architectures” below for accepted values and examples. | Defaults to `x64` for Windows/Linux and `universal` for macOS. `--arch` takes priority. | +| `architectures` | CPU architecture per OS. See STEP1 for supported operating systems and architectures. | Defaults to `x64` for Windows/Linux and `universal` for macOS. `--arch` takes priority. | | `steam.appId` | Valve's numeric App ID. Set to `null` if it has not been issued. | Local exports still work without an App ID, but SteamPipe VDFs are not generated. | | `steam.branch` | Target beta branch for `--steam-upload`. | Defaults to `internal`. `--steam-branch` takes priority. Create the branch and configure its password in Steamworks. | | `steam.depots` | Actual Depot IDs created in Steamworks. Assigning the same ID to multiple operating systems combines them in one depot. | Do not infer Depot IDs from the App ID. If `null` or omitted, only the application and launch metadata are generated, without upload VDFs. | | `macos` | macOS signing and notarization settings. | Use `false` for both `sign` / `notarize` during local testing. Enable both for production distribution. | -#### Templates and defaults for new apps +### STEP4: Prepare signing and upload credentials (distribution) -Both the JavaScript and TypeScript templates include `electron.config.json` and placeholder Next2D icons -(ICO / ICNS / PNG) in `src/assets/icons/`. Replace the icons and game-specific values before distribution. -Steam App ID / Depot IDs initially use `null`. - -`create-next2d-app` initializes `appId`, `appName`, `executableName` and `companyName` from the project name. -For example, `my-game` produces `app.example.my-game` for `appId` and `my-game` for the other three fields. -Replace the bundle ID and company name with your own values before distribution. -Missing CPU settings receive the defaults for each OS; values already provided by the template are preserved. -Other settings, such as icon paths and Steam IDs, are also preserved. - -#### Supported operating systems and architectures - -| OS | Default | Accepted `architectures` / `--arch` values | -|---|---|---| -| Windows | `x64` | `x64`, `arm64` | -| macOS | `universal` | `x64`, `arm64`, `universal` | -| Linux | `x64` | `x64`, `arm64` | - -32-bit Windows (`ia32`) is not supported. `win32` is Electron's internal OS name for Windows, not a CPU bitness indicator. -For example, `My Game-win32-x64/` is a 64-bit Windows package. -Use `windows` or `steam:windows` for `--platform`. +#### macOS signing and notarization -macOS `universal` produces one `.app` containing both x64 and arm64, and must be built on macOS. -Windows/Linux default to x64. For ARM64, set `architectures.windows` / `architectures.linux` to `arm64` -or pass `--arch arm64` when exporting. +Install a Developer ID Application certificate in the macOS keychain and save a notarization profile using +`xcrun notarytool store-credentials`. Do not put credentials in JSON. ```sh -npx @next2d/builder --platform steam:windows --env prd --arch arm64 -``` - -#### Export commands - -```sh -npx @next2d/builder --platform steam:windows --env prd -npx @next2d/builder --platform steam:macos --env prd -npx @next2d/builder --platform steam:linux --env prd -npx @next2d/builder --platform macos --env prd --preview -``` - -The updated templates and `create-next2d-app` also provide these npm scripts: - -```sh -npm run build:steam:windows -npm run build:steam:macos -npm run build:steam:linux -npm run build:steam:windows -- --arch arm64 -npm run build:steam:windows -- --env dev +export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' +export APPLE_NOTARY_PROFILE='your-notary-profile' ``` -These npm scripts already specify `--env prd`; append an override such as `-- --env dev` when needed. -Specify `--env` when invoking the builder directly. -Export commands do not automatically upload to or publish on Steam. - -#### The Electron host managed by the builder - -The only Electron-specific configuration file needed in the game is `electron.config.json`. -The builder combines it with the root `package.json` to populate the host's game name, version and description. -For the description, `description` in `electron.config.json` takes priority. -Icons may reference shared game assets; unspecified icons use the builder's placeholders. - -The builder stages the host, runtime configuration and web assets in an OS temporary directory and deletes it after export. -It does not create an `electron/` directory or Electron-specific `node_modules` / lockfile in the game root. -The temporary host is also removed on failure. The Electron version is pinned in the builder's -`templates/electron/package.json`, and Packager downloads and caches the runtime. -The game does not need a separate Electron installation step. - -`@electron/packager` is not an npm dependency of the builder either; it is acquired through `npx` only for Electron exports. -Its version is pinned in `src/tool-packages.ts`. Node.js with npm/npx is required, and the initial download needs network access. -Downloaded tools are reused from npm's cache. Web / Xbox builds and standalone `--steam-manifest` runs do not acquire Packager. -Packager handles OS-specific packages, icons, signing/notarization and Electron runtime downloads. -The generated host has no npm dependencies or native addons, so rebuilding for a specific ABI is unnecessary. - -Preview also exports an application for the host OS/CPU and launches it. -After the temporary host has been removed, it runs the application in `dist//build//`, -allowing you to test the same host and asset loading used by production exports. - -Legacy host code, `config.forge` and custom npm dependencies under `electron/` are not used. -When migrating, move icons into shared assets and update their paths before deleting `electron/`. -Projects with a custom main/preload or native addons need support in the builder before migration. - -When developing the builder itself, you can set `"@next2d/builder": "file:../builder"` in the game's devDependencies -and run `npm ci` in the builder, then in the game. The builder's prepare script compiles its source. -After changing builder source files, run `npm run build` in the builder. -After publication, this dependency can point to a published version while keeping the same game commands. - -### Output and launching +For distribution, set both `macos.sign` / `macos.notarize` to `true`. +Alternatively, prefix the STEP5 macOS command with `NEXT2D_STEAM_RELEASE=1` to require both even when JSON specifies false. +Missing credentials, signing failures or notarization failures fail the build. -With the default outDir, packages are generated under `dist/steam//build//`. -The Executable values below apply to separate depots for each OS. Shared depots add the OS prefix described later. - -| OS | Entire folder to distribute | Steamworks Executable | -|---|---|---| -| Windows | `My Game-win32-x64/` | `my-game.exe` | -| macOS | `My Game-darwin-universal/` | `My Game.app` | -| Linux | `My Game-linux-x64/` | `my-game` | - -`*-steampipe/launch.json` records the actual launch path, CPU architecture and ContentRoot. -Distribute the entire folder, including Electron libraries, locales, licenses and resources, rather than extracting only the executable. -Steam does not require an installer (DMG/DEB/MSI). -Preserve symlinks inside the macOS `.app` and executable permissions on Linux. -For CI transfers, create a `tar.gz` before storing the output as an artifact. +Steam uses the `darwin` target. Valve requires 64-bit support and Apple notarization for new macOS applications. +The template entitlements include library validation / DYLD settings for JIT and Steam Overlay, +without `com.apple.security.app-sandbox` (which is separate from Chromium's renderer sandbox). +[Valve platform requirements](https://partner.steamgames.com/doc/store/application/platforms) / +[Electron Packager signing and notarization options](https://electron.github.io/packager/main/interfaces/Options.html) -The template host loads assets using absolute paths and the fixed origin `next2d://game`. -Local fetch, Workers and localStorage work independently of the startup working directory. -F11/Alt+Enter enters fullscreen; Escape exits it. Closing the window also terminates the process on macOS. -Node integration is disabled, context isolation and sandboxing are enabled, and external navigation and new windows are blocked. -A Next2D CSP is applied. Games using external APIs must explicitly add their destinations. -[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / -[protocol](https://www.electronjs.org/docs/latest/api/protocol) +#### SteamCMD and the build account -Save data is stored in the `appId` directory under Electron's appData location. -Changing the display name does not change that location. Saves from older file-origin development builds are not migrated automatically. -Steam Cloud is not integrated. When adding it, design game-specific save files and per-account storage separately, -rather than syncing the entire Chromium profile. -[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) +Obtain SteamCMD for your host OS from the [Steamworks SDK ContentBuilder](https://partner.steamgames.com/doc/sdk/uploading). +Use `steamcmd.exe` on Windows or `steamcmd.sh` on macOS / Linux. +The builder uses `steamcmd` on PATH (`steamcmd.exe` on Windows), or the executable path set in `STEAMCMD`. +Specify only the path, without arguments or a shell command. A `.sh` file needs executable permission. +Install any OS libraries required by SteamCMD according to its setup instructions. -### Sharing one depot across multiple operating systems +Grant the upload account `Edit App Metadata` and `Publish App Changes To Steam` permissions for the target app. +Example for macOS / Linux; replace the path and account name: -Operating systems assigned the same ID in `steam.depots` form a group. -For example, assigning the same Depot ID to all operating systems separates their Steam installation paths as follows, -avoiding collisions between Electron files with identical names. +```sh +export STEAMCMD="/absolute/path/to/steamcmd.sh" +export STEAM_USERNAME="your_build_account" +"$STEAMCMD" +login "$STEAM_USERNAME" +quit +``` -| OS | Example Steamworks Executable | -|---|---| -| Windows | `windows/my-game.exe` | -| macOS | `macos/My Game.app` | -| Linux | `linux/my-game` | +During the first login, enter the password and Steam Guard code interactively in SteamCMD. +The builder then reuses that SteamCMD installation's saved login without passing a password as a command-line argument. +If authentication expires, log in again with the same SteamCMD. Builder uploads fail instead of waiting for interactive input. +In CI, restore and manage SteamCMD's `config/config.vdf` as a Secret and exclude it from build artifacts. +In Windows PowerShell, set `$env:STEAMCMD` and `$env:STEAM_USERNAME`. -In Steamworks, configure the depot's OS setting for the shared operating systems or All OSes. -Use the Executable values from the generated `launch.json` for the Launch Options and set OS-specific launch conditions. -Every OS's files in the shared depot are distributed, so downloads are larger than with separate depots. -Valve recommends separate depots for OS-specific files, but does not prohibit sharing a depot across operating systems. -[Official depot OS settings](https://partner.steamgames.com/doc/store/application/depots) +### STEP5: Export each OS and collect the packages -Each OS export can succeed independently. Once every OS in a shared group has been exported, the builder automatically generates -`app_build.vdf`, `app_preview.vdf`, `depot_build.vdf` and `launch.json` in `dist/steam/shared//depot-/`. -If the group is incomplete, `launch.json` lists the missing operating systems and no upload VDFs are generated. -Standalone per-OS VDFs are not generated for a shared depot either. +Run the required OS commands from the game root. Exporting does not upload or publish anything. ```sh npx @next2d/builder --platform steam:windows --env prd @@ -574,181 +446,151 @@ npx @next2d/builder --platform steam:macos --env prd npx @next2d/builder --platform steam:linux --env prd ``` -The combined VDF uses `FileMapping` to place the OS packages under `windows/` / `macos/` / `linux/`. -Assets are not copied again, preserving the original packages' macOS symlinks and Linux executable permissions. -When moving the output, preserve the entire `dist/steam/` directory structure, not just the VDF files. -[Official FileMapping specification](https://partner.steamgames.com/doc/sdk/uploading) - -For exports made on separate machines, such as in CI, collect each OS's `build//`, including `steam-package.json` -and `*-steampipe/`, under the same `dist/steam//build//` structure, then run: - -```sh -npx @next2d/builder --platform steam:macos --env prd --steam-manifest -``` - -The updated templates and `create-next2d-app` provide an npm script for the same operation: - -```sh -npm run build:steam:manifest -# Combine packages from an environment other than prd -npm run build:steam:manifest -- --env dev -``` - -This command only regenerates shared depot VDFs; it does not build the web application or export Electron packages. -It can run on any OS. `--platform` supplies a Steam target for loading the Vite configuration; all shared depots in the configuration are combined. -Missing packages in a shared group cause exit code 1. -The command uses `build.outDir` and `--env` to select the most recent successful export for each OS, including its recorded `--arch`. -If x64 and arm64 are exported successively for the same OS, only the last successfully exported architecture is included. -It does not combine multiple CPU variants of the same OS into one shared depot. -`--steam-manifest` cannot be combined with `--arch` / `--preview` / `--build` / `--open`. -The App ID, Depot ID, game name, executable name and version must match the current configuration. -Even when retaining the same version number, make sure all distributed OS packages come from the intended revision. +Append an option such as `--arch arm64` to change the architecture. +Template scripts `npm run build:steam:windows` / `build:steam:macos` / `build:steam:linux` perform the same operations +and already specify `--env prd`. Pass npm arguments with `-- --arch arm64` or `-- --env dev`. -Configurations where only some operating systems share an ID are supported. Other operating systems still get standalone VDFs. -Re-export the affected operating systems after changing the configuration or version. Old combined VDFs are invalidated during regeneration. +The default output location is `dist/steam//build//`. -### Signing and notarizing macOS distribution builds +| OS | Entire folder to distribute | Separate depot Executable | Shared depot Executable | +|---|---|---|---| +| Windows | `My Game-win32-x64/` | `my-game.exe` | `windows/my-game.exe` | +| macOS | `My Game-darwin-universal/` | `My Game.app` | `macos/My Game.app` | +| Linux | `My Game-linux-x64/` | `my-game` | `linux/my-game` | -Steam uses the `darwin` target, not the Mac App Store target `mas`. -Valve requires 64-bit support and Apple notarization for new macOS applications. -The template entitlements include the library validation / DYLD settings needed for JIT and Steam Overlay, -but do not include `com.apple.security.app-sandbox`, which is separate from Chromium's renderer sandbox. -[Valve platform requirements](https://partner.steamgames.com/doc/store/application/platforms) +Read the actual launch path, architecture and ContentRoot from `*-steampipe/launch.json`. +Distribute the entire folder, including Electron libraries, locales, licenses and resources. +Steam needs no DMG/DEB/MSI installer, and no additional application build is required after export. -On macOS, add a Developer ID Application certificate to the keychain and save a notarization profile with -Apple's `xcrun notarytool store-credentials` before running the build. Do not store credentials in JSON. +For separate machines or CI, collect each OS's `build//`, including `steam-package.json` and `*-steampipe/`, +under the same `dist/steam//build//` layout. Transfer CI artifacts as `tar.gz` to preserve macOS symlinks +and Linux executable permissions. Use STEP6's `--steam-root` for a custom output location. -```sh -export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' -export APPLE_NOTARY_PROFILE='your-notary-profile' -NEXT2D_STEAM_RELEASE=1 npx @next2d/builder --platform steam:macos --env prd -``` +Re-export affected operating systems after changing configuration or version. Even with an unchanged version number, +keep all OS packages on the intended revision. When exporting x64 and arm64 successively for one OS, the last successful +architecture is used. Combining multiple architectures of one OS into a shared depot is not supported. -`NEXT2D_STEAM_RELEASE=1` requires signing and notarization even when the JSON values are false. -Alternatively, set both `macos.sign` / `macos.notarize` to true in JSON. -Missing credentials, signing failures or notarization failures cause the build to fail. -Submit the package to SteamPipe after signing, notarization and stapling. Example checks: +Verify signing, notarization and stapling for macOS distribution packages (adjust the paths): ```sh codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' ``` -[Electron Packager signing and notarization options](https://electron.github.io/packager/main/interfaces/Options.html) +### STEP6: Configure launching, upload and test -### Steamworks / SteamPipe - -1. In Steamworks > SteamPipe > Depots, create depots matching your distribution layout and select their operating systems. - Both separate OS depots and shared depots are supported. Use All languages for language-independent content. - Include each depot in the development and retail Packages as well. -2. Add OS-specific Launch Options under Installation > General Installation. - Set Executable to the generated `launch.json` value; leave Arguments/Working Directory empty. - Select the 64-bit condition for x64 builds and test macOS Universal on both Intel and Apple Silicon. -3. Publish the changes in Steamworks. Match the supported operating systems and minimum OS requirements to - the bundled Electron version and hardware testing, rather than relying only on older Steam minimum-OS tables. -4. Set the App ID/Depot IDs in JSON and rebuild. Standalone depot VDFs appear in `*-steampipe/`; - shared depot VDFs appear in `shared//depot-/` once every required OS package exists. - `app_preview.vdf` validates file mappings; `app_build.vdf` uploads the build. - `depot_build.vdf` recursively maps the folder contents into the install root. `steam_appid.txt` is excluded from distribution. -5. Obtain ContentBuilder/SteamCMD from the Steamworks SDK and log in with an authorized build account. - Do not store passwords in commands or the repository. +Add OS-specific Launch Options in Steamworks under Installation > General Installation. +Use the Executable from STEP5's `launch.json`, leaving Arguments/Working Directory empty. +Set the 64-bit condition for x64. Match supported OS versions and minimum requirements to the bundled Electron and hardware testing, +then Publish the Steamworks changes. -```text -steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit -steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit -``` +Run these commands from the game root. Shared and separate depots are uploaded together in a single AppBuild. +No Web/Electron rebuild or prior `--steam-manifest` command is needed. -ContentRoot is relative to the VDF. Move standalone applications together with their `*-steampipe/` directories; -for shared depots, preserve the entire `dist/steam/` layout. -VDFs produced by ordinary exports and `--steam-manifest` do not include SetLive. -After uploading, assign the build to a password-protected beta in Steamworks and test installation using the Steam client on each OS. -Each per-OS VDF updates one depot, so verify that the final build references the intended depot manifest versions for every OS. -[Official SteamPipe workflow and VDF specification](https://partner.steamgames.com/doc/sdk/uploading) / -[Depots](https://partner.steamgames.com/doc/store/application/depots) +```sh +# Local validation only: no SteamCMD, credentials or Steam connection required +npx @next2d/builder --steam-upload --env prd --dry-run -### Uploading with the builder and testing on internal +# After validation, upload to the configured branch +npx @next2d/builder --steam-upload --env prd +``` -`--steam-upload` uploads existing exported packages together through SteamCMD and uses `SetLive` to request activation on the selected beta branch. -It does not rebuild the web application or Electron packages. -`--platform` is unnecessary; do not combine this command with `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`. -This feature adds no npm dependencies to the builder. Install SteamCMD separately. +| Option / setting | Purpose | +|---|---| +| `--steam-branch internal` | Overrides `steam.branch`; requests activation through `SetLive`. `default` is not allowed. Switch public release builds through Steamworks. | +| `--steam-root dist/steam` | Collected package location; defaults to `dist/steam`. Accepts an absolute path or a path relative to the game root. Specify custom output locations because uploading does not read the Vite configuration. | +| `--dry-run` | Validates packages against configuration. Does not check Steam permissions, branches or passwords. | +| Incompatible options | `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`. `--platform` is also unnecessary. | -#### Preparing a private testing branch in Steamworks +The corresponding template npm scripts are `upload:steam:check` / `upload:steam`. -1. Create an `internal` branch in Steamworks App Admin > SteamPipe > Builds. -2. **Set a branch password and share it only with the intended testers.** Naming a branch `internal` does not make it private. - The builder does not create branches, set passwords or verify password protection. Do not store the password in JSON or the repository. -3. Make sure testers hold a Package license that grants access to the game and its depots. - For external testers of an unreleased game, use Release State Override (beta) keys or another appropriate access method. - A branch password alone does not grant ownership of the game. -4. In the Steam client, testers open the game's Properties > Game Versions & Betas, enter the password and select `internal`. +The builder checks `steam-package.json` and executables for every OS configured in `steam.depots`. +Missing or inconsistent App IDs, Depot IDs, versions, game names or architectures stop the operation before SteamCMD starts. +VDFs are regenerated from validated packages; ordinary export VDFs remain unchanged. -This command does not allow the `default` branch. Switch the public release build through Steamworks. -A beta password is a shared secret; another person who receives it can access the branch. Also manage each tester's game access rights. +| Location (under `--steam-root`) | Contents | +|---|---| +| `uploads//-/` | VDFs and `upload-plan.json`. Dry runs use `Preview=1` without `SetLive`. Successful uploads record the BuildID in `upload-result.json`. | +| `uploads//cache//` | SteamPipe logs and incremental upload cache. | -[Official beta branch settings](https://partner.steamgames.com/doc/store/application/branches) / -[Testing on Steam](https://partner.steamgames.com/doc/store/testing) / -[Release State Override keys](https://partner.steamgames.com/doc/features/keys) +On the Steamworks Builds page, verify that the recorded BuildID is the target branch's current build. +Steam may request additional confirmation depending on the account or app state. +Testers enter the password and select `internal` in the Steam client's Properties > Game Versions & Betas. +Verify installation and launch on each OS, including both Intel and Apple Silicon for macOS Universal. -#### SteamCMD setup and authentication +### Reference: VDF generation and manual uploading -Obtain SteamCMD for your host OS from the [Steamworks SDK ContentBuilder](https://partner.steamgames.com/doc/sdk/uploading). -Use `steamcmd.exe` on Windows or `steamcmd.sh` on macOS / Linux. -The builder uses `steamcmd` on PATH (`steamcmd.exe` on Windows), or the executable path set in `STEAMCMD`. -Specify only the path, without arguments or a shell command. A `.sh` file needs executable permission. -Install any OS libraries required by SteamCMD according to its setup instructions. +Skip these operations when using the builder in STEP6. This section is for direct SteamCMD use. -Grant the upload account `Edit App Metadata` and `Publish App Changes To Steam` permissions for the target app. -Example for macOS / Linux; replace the path and account name: +| Layout | VDF output and conditions during ordinary export | +|---|---| +| Separate OS depots | Each OS's `*-steampipe/` directory. | +| Shared depots | `dist/steam/shared//depot-/` once all required OS packages exist. Missing operating systems are listed in `launch.json`; neither upload VDFs nor standalone per-OS VDFs are generated for an incomplete group. | + +Some operating systems may share a Depot ID while others use separate depots. `FileMapping` assigns OS subdirectories without copying assets again. +To regenerate only shared depot VDFs after collecting packages from separate machines: ```sh -export STEAMCMD="/absolute/path/to/steamcmd.sh" -export STEAM_USERNAME="your_build_account" -"$STEAMCMD" +login "$STEAM_USERNAME" +quit +npx @next2d/builder --platform steam:macos --env prd --steam-manifest ``` -During the first login, enter the password and Steam Guard code interactively in SteamCMD. -The builder then reuses that SteamCMD installation's saved login without passing a password as a command-line argument. -If authentication expires, log in again with the same SteamCMD. Builder uploads fail instead of waiting for interactive input. -In CI, restore and manage SteamCMD's `config/config.vdf` as a Secret and exclude it from build artifacts. -In Windows PowerShell, set `$env:STEAMCMD` and `$env:STEAM_USERNAME`. +The npm alias is `build:steam:manifest` (append `-- --env dev` for another environment). This command runs on any OS. +`--platform` selects the Vite configuration; all configured shared depots are combined. +It selects the last successful exports using `build.outDir` and `--env`, validating the App ID, Depot IDs, game name, executable name and version. +Missing packages cause exit code 1. Old combined VDFs are invalidated during regeneration. +Do not combine this command with `--arch` / `--preview` / `--build` / `--open`. -#### Validation and upload +`app_preview.vdf` validates file mappings, `app_build.vdf` uploads, and `depot_build.vdf` recursively maps folder contents. +`steam_appid.txt` is excluded from distribution. ContentRoot is relative to the VDF, so move a standalone depot's application +and `*-steampipe/` together; preserve the whole `dist/steam/` layout for shared depots. -Collect the packages for every OS configured in `electron.config.json` under `steam.depots`, then run from the game root. -Operating systems sharing a Depot ID are placed in separate OS subdirectories; distinct depots are also uploaded in a single AppBuild. +```text +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_preview.vdf" +quit +steamcmd +login BUILD_ACCOUNT +run_app_build "/absolute/path/to/app_build.vdf" +quit +``` -```sh -# Local validation only: no SteamCMD, credentials, Steam connection or upload -npx @next2d/builder --steam-upload --env prd --dry-run +Ordinary exports and `--steam-manifest` omit `SetLive`; assign the uploaded build to its branch through Steamworks. +Each per-OS VDF updates one depot, so verify the final Build references the intended manifest version for every OS. +[Official SteamPipe workflow and VDF specification](https://partner.steamgames.com/doc/sdk/uploading) -# Upload and request activation on steam.branch (internal by default) -npx @next2d/builder --steam-upload --env prd +### Reference: Templates, host and migration -# Specify a branch or the directory containing collected packages -npx @next2d/builder --steam-upload --env prd --steam-branch internal --steam-root dist/steam -``` +`create-next2d-app` initializes `appId`, `appName`, `executableName` and `companyName` from the project name. +For example, `my-game` produces `app.example.my-game` for `appId` and `my-game` for the other three fields. +Replace the bundle ID and company name with your own values before distribution. +Missing CPU settings receive the defaults for each OS; values already provided by the template are preserved. +Other settings, such as icon paths and Steam IDs, are also preserved. -During local development, replace `npx @next2d/builder` with `node ../builder/dist/index.js`. -After a builder version containing this feature is published, the game's `npm run upload:steam:check` / `npm run upload:steam` scripts can also be used. +The builder manages Electron host code. It stages the host, runtime configuration and web assets in an OS temporary directory, +then removes it after export, including failures. No `electron/`, Electron-specific `node_modules` or lockfile is created in the game. +Electron is pinned in `templates/electron/package.json`; Packager is pinned in `src/tool-packages.ts`. Both are downloaded and cached. +Packager is acquired only for Electron exports and handles OS packages, icons, signing and notarization. +The host has no npm dependencies or native addons, so ABI-specific rebuilding is unnecessary. +Preview exports for the host OS/CPU and launches the application under `dist//build//`. -`--steam-root` accepts a path relative to the game root or an absolute path; it defaults to `dist/steam`. -The upload operation does not read the Vite configuration, so specify this argument for a custom output directory. -It validates the App ID, Depot ID, version, game name and CPU recorded in `steam-package.json`, along with the executable. -Missing or inconsistent packages stop the operation before SteamCMD starts. VDFs are regenerated from validated packages instead of reusing existing files. +Legacy `electron/` host code, `config.forge` and custom npm dependencies are not used. +For migration, move icons into shared assets and update their JSON paths before deleting the old directory. +Custom main/preload code or native addons require builder support before migration. -Each run saves its VDFs and `upload-plan.json` in `dist/steam/uploads//-/`. -A dry run generates a VDF with `Preview=1` and no `SetLive`. This is local validation only; -it does not verify SteamCMD authentication, server-side permissions, branch existence or password settings. -Ordinary export VDFs are left unchanged. SteamPipe logs and incremental upload caches go in `uploads//cache//`. +The template host loads assets using absolute paths and the fixed origin `next2d://game`. +Local fetch, Workers and localStorage work independently of the startup working directory. +F11/Alt+Enter enters fullscreen; Escape exits it. Closing the window also terminates the process on macOS. +Node integration is disabled, context isolation and sandboxing are enabled, and external navigation and new windows are blocked. +A Next2D CSP is applied. Games using external APIs must explicitly add their destinations. +[Electron security](https://www.electronjs.org/docs/latest/tutorial/security) / +[protocol](https://www.electronjs.org/docs/latest/api/protocol) -Uploads check the exit code and SteamCMD's success message, then record the BuildID in `upload-result.json`. -On the Steamworks Builds page, verify that the recorded BuildID is the current build for `internal`. -Steam may require additional confirmation depending on the account or app state. +Save data is stored in the `appId` directory under Electron's appData location. +Changing the display name does not change that location. Saves from older file-origin development builds are not migrated automatically. +Steam Cloud is not integrated. When adding it, design game-specific save files and per-account storage separately, +rather than syncing the entire Chromium profile. +[Steam Cloud](https://partner.steamgames.com/doc/features/cloud) ### Pre-release validation -- Installation, launch and exit through Steam on each target OS, offline launch and save data after updates. +In addition to STEP6's installation and launch checks, verify: + +- Exit behavior, offline launch and saved data after updates. - Mouse/keyboard/controller input, resolution and fullscreen switching, audio and sleep/resume. - Shift+Tab Overlay. Electron uses multiple processes, so verify actual behavior on each OS/GPU. This export does not integrate the Steamworks SDK, achievements, DRM or Overlay APIs. diff --git a/docs/xbox.md b/docs/xbox.md index 5e887e6..2760eb1 100644 --- a/docs/xbox.md +++ b/docs/xbox.md @@ -4,53 +4,24 @@ ## 日本語 -Xbox対応は現在、GDK向けネイティブホストを試作・検証している開発フェーズ。 -ホスト生成やビルド処理は実装されているが、Xbox実機での動作確認・配布対応は完了していない。 -現時点では、開発環境での検証を目的として使用する。 +Xbox対応はGDK向けネイティブホストの試作・開発段階。Xbox実機での動作確認・配布対応は完了していない。 +以下は開発環境での検証手順。[共通準備](setup.md#日本語)を先に済ませる。 -### 構成と現在の対応範囲 +### STEP1:検証範囲に必要な環境を用意する -Next2DのJavaScriptをV8で実行し、Dawn(WebGPU → D3D12)で描画するC++ホストを生成する。 -ElectronやWebViewは使用しない。 - -| 項目 | 現在の状態 | -|---|---| -| ホスト生成 | builder同梱のC++ / CMakeテンプレートから、ゲーム側の `xbox/` を生成・更新する。 | -| ゲーム設定 | ゲームルートの `MicrosoftGame.config` をホストに反映する。未配置の場合はテンプレートから初期ファイルを用意する。 | -| アセット | Webビルド結果とホストスクリプトを `assets.pak` / `assets.rc` にまとめ、実行ファイルへの埋め込み用に配置する。 | -| Windows上のビルド | CMakeでVisual Studio / GDK向けの構成・ビルドを実行する処理を実装している。対象環境での検証が必要。 | -| macOS / Linux上の実行 | ホスト生成とアセットの準備まで。GDK向け実行ファイルのビルドは行わない。 | -| Xbox実機・配布 | 開発機での動作、性能、保存処理、配布設定などの検証・調整が残っている。 | - -### 検証に使う環境 - -以下は現行テンプレートが想定する構成。 - -| 項目 | 用途 | +| 検証範囲 | ユーザーが用意するもの | |---|---| -| Windows + Visual Studio 2022 | C++ホストのビルド。現行builderはVisual Studio 2022ジェネレーターを指定する。 | -| Microsoft GDK | GDK向けビルド。PC向けの検証とXboxコンソール向けの環境を区別して用意する。 | -| CMake 3.26以降 | ホストの構成・ビルド。 | -| V8 | JavaScript実行エンジン。ビルド済みライブラリの取得、または自前ビルドを利用する。 | -| Dawn | WebGPU実装。CMakeのFetchContentで取得するため、初回取得にはネットワーク接続が必要。 | -| Xbox開発機(devkit) | Xbox実機上での動作・性能検証。 | - -### 試作用コマンド - -ゲームのルートで実行する。 - -```sh -npx @next2d/builder --platform xbox --env prd -``` +| ホスト生成・アセット準備のみ | 共通準備の環境。macOS / Linuxでもここまで実行できる。 | +| C++ホストのビルド | Windows、Visual Studio 2022、Microsoft GDK、CMake 3.26以降。現行builderはVisual Studio 2022ジェネレーターを指定する。 | +| Xbox実機での検証 | コンソール向けGDK開発環境とXbox開発機(devkit)。PC向けのGDK検証環境とは区別して用意する。 | -Windowsではホストとアセットを準備した後、V8の解決とCMakeビルドへ進む。 -macOS / Linuxではホスト生成とアセットの準備を終えた時点で終了する。 -コマンドの終了だけでXbox実機向けの書き出しが完了したとは判断せず、生成物とログを確認する。 +### STEP2:ゲーム設定とV8を用意する -Windowsで `--open` または `--preview` を付けると、CMakeによる構成後にVisual Studioのソリューションを開く。 -実行・デバッグはVisual Studio側で行う。 +ゲームルートの `MicrosoftGame.config` にゲーム固有の値を設定する。 +未配置の場合は初回のホスト生成でテンプレートから作られるため、生成後に内容を確認・変更する。 -### V8の取得 +V8はJavaScript実行エンジン。通常はbuilderがビルド済みライブラリを取得し、自前ビルドを使う場合だけパスを指定する。 +描画用のDawn(WebGPU → D3D12)はCMakeのFetchContentが取得するため、初回取得にはネットワーク接続が必要。 Windowsでのビルド時は、次の優先順でV8を解決する。 @@ -70,9 +41,29 @@ npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8 公開しているWindows x64用V8の利用と、Xboxコンソール向けの互換性検証は別の作業。 コンソール向けにはGDKツールチェーンでの調整・再ビルドを含む検証が残っている。 -### 検証範囲と残っている作業 +### STEP3:ホストを生成・ビルドする -`xbox-host-ci` ワークフローには、ラスタライザーの単体テスト、Windows APIを使う一部機能のテスト、 +ゲームルートで実行する。 + +```sh +npx @next2d/builder --platform xbox --env prd +``` + +builder同梱のC++ / CMakeテンプレートから `xbox/` を生成・更新し、`MicrosoftGame.config` を反映する。 +Webビルド結果とホストスクリプトを `assets.pak` / `assets.rc` にまとめ、実行ファイルへの埋め込み用に配置する。 +このホストはV8とDawnを使い、ElectronやWebViewは使用しない。 + +| 実行環境・引数 | コマンドの到達点 | +|---|---| +| Windows | V8を解決し、CMakeでGDK向けの構成・ビルドを実行する。対象環境での検証が必要。 | +| Windows + `--open` / `--preview` | CMakeで構成した後、Visual Studioのソリューションを開く。実行・デバッグはVisual Studio側で行う。 | +| macOS / Linux | ホスト生成とアセット準備までで終了する。GDK向け実行ファイルは生成しない。 | + +### STEP4:生成物と対象環境での動作を確認する + +コマンドの終了だけでXbox向けの配布物が完成したとは判断せず、生成物とログを確認する。 + +`xbox-host-ci` ワークフローには、ラスタライザーとstbの回帰テスト、Windows APIを使う一部機能のテスト、 V8依存ソースのコンパイル確認、Game Core APIの確認を用意している。 これらはホスト全体のリンクやXbox実機での動作を保証するものではない。 Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必要。 @@ -84,53 +75,24 @@ Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必 ## English -Xbox support is currently in the development phase, focused on prototyping and testing a native GDK host. -Host generation and build operations are implemented, but validation on Xbox hardware and distribution support are not complete. -Use this feature for testing in a development environment at this stage. - -### Architecture and current scope +Xbox support is a prototype GDK native host under development. Xbox hardware validation and distribution support are not complete. +The following steps are for development testing. Complete the [common setup](setup.md#english) first. -The builder generates a C++ host that runs Next2D JavaScript on V8 and renders with Dawn (WebGPU → D3D12). -It does not use Electron or WebView. +### STEP1: Prepare the environment for your validation scope -| Item | Current status | +| Validation scope | What you need to prepare | |---|---| -| Host generation | Creates or updates the game's `xbox/` directory from the C++ / CMake templates bundled with the builder. | -| Game configuration | Applies `MicrosoftGame.config` from the game root to the host. If it is missing, an initial file is created from the template. | -| Assets | Packs the web build output and host scripts into `assets.pak` / `assets.rc` for embedding into the executable. | -| Building on Windows | CMake configuration and build operations for Visual Studio / GDK are implemented. Validation in the target environment is still needed. | -| Running on macOS / Linux | Generates the host and prepares assets only. Does not build a GDK executable. | -| Xbox hardware and distribution | Device behavior, performance, save handling and distribution settings still need validation and adjustment. | +| Host generation and asset preparation only | The common setup environment; this also works on macOS / Linux. | +| Building the C++ host | Windows, Visual Studio 2022, Microsoft GDK and CMake 3.26 or later. The current builder selects the Visual Studio 2022 generator. | +| Testing on Xbox hardware | A console GDK development environment and Xbox development hardware (devkit), distinct from a PC GDK testing environment. | -### Development environment +### STEP2: Prepare game configuration and V8 -The current template targets the following setup. +Set game-specific values in `MicrosoftGame.config` at the game root. +If absent, it is created from the template during initial host generation; review and edit it afterward. -| Component | Purpose | -|---|---| -| Windows + Visual Studio 2022 | Builds the C++ host. The current builder selects the Visual Studio 2022 generator. | -| Microsoft GDK | Builds the GDK target. Prepare the appropriate environment for PC testing or Xbox console development. | -| CMake 3.26 or later | Configures and builds the host. | -| V8 | JavaScript engine. Use the downloaded prebuilt library or your own build. | -| Dawn | WebGPU implementation. CMake FetchContent downloads it, so the first download needs network access. | -| Xbox development hardware (devkit) | Validates behavior and performance on Xbox hardware. | - -### Prototype commands - -Run from the game root: - -```sh -npx @next2d/builder --platform xbox --env prd -``` - -On Windows, the builder prepares the host and assets, then resolves V8 and starts the CMake build. -On macOS / Linux, it stops after generating the host and preparing assets. -Command completion alone does not mean an Xbox hardware export is ready; inspect the output files and logs. - -On Windows, `--open` or `--preview` opens the Visual Studio solution after CMake configuration. -Run and debug the application from Visual Studio. - -### Acquiring V8 +V8 is the JavaScript engine. The builder normally downloads a prebuilt library; specify a path only when using your own build. +CMake FetchContent acquires Dawn (WebGPU → D3D12) for rendering, so the first download requires network access. For Windows builds, V8 is resolved in this order: @@ -150,9 +112,29 @@ npx @next2d/builder --platform xbox --env prd --v8-root C:\path\to\v8 Using the published Windows x64 V8 library and validating Xbox console compatibility are separate tasks. Console support still needs validation, including adjustments or rebuilds with the GDK toolchain. -### Validation scope and remaining work +### STEP3: Generate and build the host + +Run from the game root: + +```sh +npx @next2d/builder --platform xbox --env prd +``` + +The builder creates or updates `xbox/` from its bundled C++ / CMake templates and applies `MicrosoftGame.config`. +It packs the web build output and host scripts into `assets.pak` / `assets.rc` for embedding in the executable. +The host uses V8 and Dawn, without Electron or WebView. + +| Environment / options | What the command completes | +|---|---| +| Windows | Resolves V8, then configures and builds the GDK target with CMake. Target-environment validation is still needed. | +| Windows + `--open` / `--preview` | Opens the Visual Studio solution after CMake configuration. Run and debug from Visual Studio. | +| macOS / Linux | Stops after host generation and asset preparation; no GDK executable is built. | + +### STEP4: Check the output and target-environment behavior + +Command completion alone does not mean an Xbox distribution package is ready; inspect the output and logs. -The `xbox-host-ci` workflow includes rasterizer unit tests, tests for selected Windows API functionality, +The `xbox-host-ci` workflow includes rasterizer and stb regression tests, tests for selected Windows API functionality, compilation checks for V8-dependent sources and Game Core API checks. These checks do not guarantee that the complete host links or runs on Xbox hardware. An integrated build with Dawn / GDK and testing with a game on the target hardware are still required. From fa0e60bb6e975dfc2983ecacf2f8f7079de55d67 Mon Sep 17 00:00:00 2001 From: ienaga Date: Wed, 23 Sep 2026 21:37:44 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/capacitor.md | 4 --- docs/setup.md | 91 ----------------------------------------------- docs/steam.md | 2 -- docs/xbox.md | 8 ++--- 4 files changed, 4 insertions(+), 101 deletions(-) delete mode 100644 docs/setup.md diff --git a/docs/capacitor.md b/docs/capacitor.md index f8f69f4..f463ecb 100644 --- a/docs/capacitor.md +++ b/docs/capacitor.md @@ -4,8 +4,6 @@ ## 日本語 -[共通準備](setup.md#日本語)を済ませてから、対象OSの手順を進める。 - ### STEP1:対象OSのIDE・SDKを用意する | 対象 | ユーザーが用意するもの | @@ -105,8 +103,6 @@ npxキャッシュを削除した場合は、STEP3またはSTEP4で参照先を ## English -Complete the [common setup](setup.md#english), then follow the steps for your target OS. - ### STEP1: Prepare the platform IDE and SDK | Target | What you need to prepare | diff --git a/docs/setup.md b/docs/setup.md deleted file mode 100644 index c10d9c0..0000000 --- a/docs/setup.md +++ /dev/null @@ -1,91 +0,0 @@ -# 共通準備 / Common setup - -[日本語](#日本語) | [English](#english) - -## 日本語 - -各プラットフォームの手順に進む前に、ゲームのビルド環境を準備する。 - -### STEP1:Node.js・npm・npxを用意する - -利用するbuilderとゲームの依存パッケージが要求するNode.jsをインストールし、次のコマンドを使える状態にする。 - -```sh -node --version -npm --version -npx --version -``` - -初回のツール取得にはネットワーク接続が必要。取得後はnpmキャッシュを再利用する。 -builderはElectron PackagerやCapacitor SDKを必要な操作時に取得するが、 -Xcode・Android SDK・GDKなどのネイティブ開発環境はユーザーが別途用意する。 - -### STEP2:ゲームの依存をインストールする - -ゲームの `package.json` があるディレクトリで実行する。 - -```sh -npm ci -``` - -`package-lock.json` がない新規プロジェクトは `npm install` を使う。 -以降の各ガイドのコマンドも、特記がなければゲームルートで実行する。 -`@next2d/builder` は `npx` で起動できるため、ゲームの依存へ追加する必要はない。 - -### STEP3:対象プラットフォームの準備へ進む - -| ガイド | ユーザーが用意するもの | -|---|---| -| [Electron / Steam](steam.md#日本語) | アイコン・対象OSの検証環境。Steam配布時はApp/Depot・ブランチ・SteamCMD、macOS配布時は署名・公証の認証。 | -| [iOS / Android](capacitor.md#日本語) | 対象OSのIDE・SDK、アプリ設定、配布時の署名。 | -| [Xbox(試作)](xbox.md#日本語) | WindowsのC++ / GDK開発環境。コンソール検証時は対応する開発環境・開発機。 | - -### 補足:隣接リポジトリのbuilderを開発中に使う場合 - -builderリポジトリで `npm ci` を実行し、ソース変更後は `npm run build` で `dist/` を更新する。 -ゲームルートでは各ガイドの `npx @next2d/builder` を `node ../builder/dist/index.js` に置き換える。 -ゲーム側の `package.json` を変更せずに開発版を使える。 - -## English - -Prepare the game build environment before following a platform guide. - -### STEP1: Install Node.js, npm and npx - -Install a Node.js version supported by the builder and the game's dependencies, and verify these commands work: - -```sh -node --version -npm --version -npx --version -``` - -Initial tool downloads require network access; later runs reuse npm's cache. -The builder acquires Electron Packager and Capacitor SDKs when needed, but you must separately install -native development tools such as Xcode, the Android SDK or the GDK. - -### STEP2: Install the game's dependencies - -Run in the directory containing the game's `package.json`: - -```sh -npm ci -``` - -For a new project without `package-lock.json`, use `npm install`. -Run subsequent guide commands from the game root unless stated otherwise. -`@next2d/builder` runs through `npx`, so it does not need to be added to the game's dependencies. - -### STEP3: Continue with the target platform - -| Guide | What you need to prepare | -|---|---| -| [Electron / Steam](steam.md#english) | Icons and environments for testing each OS. Steam distribution needs an app/depots, a branch and SteamCMD; macOS distribution needs signing/notarization credentials. | -| [iOS / Android](capacitor.md#english) | Platform IDEs/SDKs, app configuration and distribution signing. | -| [Xbox (prototype)](xbox.md#english) | A Windows C++ / GDK environment; console testing also needs the corresponding development environment and hardware. | - -### Reference: Using a sibling builder repository during development - -Run `npm ci` in the builder repository, then `npm run build` after source changes to update `dist/`. -From the game root, replace `npx @next2d/builder` in each guide with `node ../builder/dist/index.js`. -This uses the development build without changing the game's `package.json`. diff --git a/docs/steam.md b/docs/steam.md index b2cc6f3..e9df759 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -4,7 +4,6 @@ ## 日本語 -[共通準備](setup.md#日本語)を済ませてから、以下を順に進める。 ローカル書き出しだけなら、Steamworks・SteamCMD・配布用の署名認証は不要。 ### STEP1:対象OSの実行環境とアイコンを用意する @@ -306,7 +305,6 @@ STEP6のインストール・起動確認に加えて、以下を検証する。 ## English -Complete the [common setup](setup.md#english), then follow these steps. Local exports do not require Steamworks, SteamCMD or distribution signing credentials. ### STEP1: Prepare target environments and icons diff --git a/docs/xbox.md b/docs/xbox.md index 2760eb1..5b9c16a 100644 --- a/docs/xbox.md +++ b/docs/xbox.md @@ -5,13 +5,13 @@ ## 日本語 Xbox対応はGDK向けネイティブホストの試作・開発段階。Xbox実機での動作確認・配布対応は完了していない。 -以下は開発環境での検証手順。[共通準備](setup.md#日本語)を先に済ませる。 +以下は開発環境での検証手順。 ### STEP1:検証範囲に必要な環境を用意する | 検証範囲 | ユーザーが用意するもの | |---|---| -| ホスト生成・アセット準備のみ | 共通準備の環境。macOS / Linuxでもここまで実行できる。 | +| ホスト生成・アセット準備のみ | macOS / Linuxでも実行できる。 | | C++ホストのビルド | Windows、Visual Studio 2022、Microsoft GDK、CMake 3.26以降。現行builderはVisual Studio 2022ジェネレーターを指定する。 | | Xbox実機での検証 | コンソール向けGDK開発環境とXbox開発機(devkit)。PC向けのGDK検証環境とは区別して用意する。 | @@ -76,13 +76,13 @@ Dawn / GDKを含む統合ビルドと、ゲームを使った実機検証が必 ## English Xbox support is a prototype GDK native host under development. Xbox hardware validation and distribution support are not complete. -The following steps are for development testing. Complete the [common setup](setup.md#english) first. +The following steps are for development testing. ### STEP1: Prepare the environment for your validation scope | Validation scope | What you need to prepare | |---|---| -| Host generation and asset preparation only | The common setup environment; this also works on macOS / Linux. | +| Host generation and asset preparation only | Also works on macOS / Linux. | | Building the C++ host | Windows, Visual Studio 2022, Microsoft GDK and CMake 3.26 or later. The current builder selects the Visual Studio 2022 generator. | | Testing on Xbox hardware | A console GDK development environment and Xbox development hardware (devkit), distinct from a PC GDK testing environment. | From a431d16048302f3361cac978f6dff175f3db01bd Mon Sep 17 00:00:00 2001 From: ienaga Date: Thu, 24 Sep 2026 18:47:26 +0900 Subject: [PATCH 7/9] =?UTF-8?q?steam=E3=81=AE=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E5=87=A6=E7=90=86=E3=82=92=E6=94=B9?= =?UTF-8?q?=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/steam.md | 14 ++++++++++++-- src/electron-config.ts | 2 ++ src/steam-upload.ts | 4 +++- tests/electron.test.ts | 23 +++++++++++++++++++++++ tests/steam-upload.test.ts | 15 ++++++++++++++- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/docs/steam.md b/docs/steam.md index e9df759..46f8d66 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -92,9 +92,12 @@ JavaScript / TypeScriptの両テンプレートに含まれるので、初期値 #### macOSの署名・公証 -macOSのキーチェーンにDeveloper ID Application証明書を登録し、 +macOSのキーチェーンにDeveloper ID Application証明書と対応する秘密鍵を登録し、 `xcrun notarytool store-credentials` で公証用プロファイルを保存する。認証情報はJSONへ書かない。 +`security find-identity -v -p codesigning` に有効な `Developer ID Application: ...` が表示されることを確認する。 +`APPLE_SIGNING_IDENTITY` にはその実際の名前を指定する。環境変数を設定するだけでは証明書は作成されない。 + ```sh export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' export APPLE_NOTARY_PROFILE='your-notary-profile' @@ -103,6 +106,8 @@ export APPLE_NOTARY_PROFILE='your-notary-profile' 配布用は `macos.sign` / `macos.notarize` を両方 `true` にする。 またはSTEP5のmacOSコマンドに `NEXT2D_STEAM_RELEASE=1` を付けると、JSONのfalse指定に関わらず両方を必須にできる。 認証情報不足・署名失敗・公証失敗はビルド失敗となる。 +署名エラーは公証へ進まずに停止する。`Signature=adhoc` / `TeamIdentifier=not set` が出る場合は、 +Developer ID署名が付いていないため、先に表示された署名エラーとキーチェーンの証明書・秘密鍵を確認する。 Steam用のターゲットは `darwin`。Valveは新規macOSアプリに64bitとAppleの公証を要求する。 テンプレートのentitlementsはJITとSteam Overlay用のlibrary validation / DYLD設定を含み、 @@ -393,9 +398,12 @@ These are separate identifiers. The game version comes from the root `package.js #### macOS signing and notarization -Install a Developer ID Application certificate in the macOS keychain and save a notarization profile using +Install a Developer ID Application certificate and its matching private key in the macOS keychain, and save a notarization profile using `xcrun notarytool store-credentials`. Do not put credentials in JSON. +Check that `security find-identity -v -p codesigning` lists a valid `Developer ID Application: ...` identity. +Use its actual name for `APPLE_SIGNING_IDENTITY`. Setting the environment variable does not create a certificate. + ```sh export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' export APPLE_NOTARY_PROFILE='your-notary-profile' @@ -404,6 +412,8 @@ export APPLE_NOTARY_PROFILE='your-notary-profile' For distribution, set both `macos.sign` / `macos.notarize` to `true`. Alternatively, prefix the STEP5 macOS command with `NEXT2D_STEAM_RELEASE=1` to require both even when JSON specifies false. Missing credentials, signing failures or notarization failures fail the build. +Signing failures stop before notarization. If the output shows `Signature=adhoc` / `TeamIdentifier=not set`, +the app lacks a Developer ID signature; check the preceding signing error and the certificate/private key in the keychain. Steam uses the `darwin` target. Valve requires 64-bit support and Apple notarization for new macOS applications. The template entitlements include library validation / DYLD settings for JIT and Steam Overlay, diff --git a/src/electron-config.ts b/src/electron-config.ts index 820c3ee..b7331cc 100644 --- a/src/electron-config.ts +++ b/src/electron-config.ts @@ -122,6 +122,8 @@ export const createElectronPackagerOptions = ( ...os === "macos" && sign ? { "osxSign": { "identity": process.env.APPLE_SIGNING_IDENTITY, + // Packager otherwise swallows signing failures and attempts notarization. + "continueOnError": false, "optionsForFile": () => ({ "hardenedRuntime": true, "entitlements": path.join(getTemplateDir("electron"), "entitlements.plist") diff --git a/src/steam-upload.ts b/src/steam-upload.ts index 8376a85..ea54d62 100644 --- a/src/steam-upload.ts +++ b/src/steam-upload.ts @@ -121,7 +121,9 @@ export const uploadSteam = async (options: ISteamUploadOptions): Promise = reject(new Error(`SteamCMD failed (${signal || code}). Inspect its output and SteamPipe logs; check login, app permissions and beta branch settings.`)); return; } - const success = output.match(new RegExp(`Success! App '${plan.appId}' fully built \\(BuildID ([0-9]+)\\)`, "i")); + const success = output.match(new RegExp( + `(?:Success! App '${plan.appId}' fully built|Successfully finished AppID ${plan.appId} build) \\(BuildID ([0-9]+)\\)`, "i" + )); if (reportedError || !success) { reject(new Error("SteamCMD did not report a successful app build, or reported an error. Check SteamPipe logs and the branch's active BuildID in Steamworks before retrying.")); return; diff --git a/tests/electron.test.ts b/tests/electron.test.ts index 429635f..f5eb4b0 100644 --- a/tests/electron.test.ts +++ b/tests/electron.test.ts @@ -217,5 +217,28 @@ test("release mode requires signing and notarization credentials", (t) => { process.env.APPLE_NOTARY_PROFILE = "test profile"; const options = createElectronPackagerOptions(root, config, "macos", path.join(root, "resources")); assert.ok(options.osxSign); + assert.equal(options.osxSign.continueOnError, false); assert.deepEqual(options.osxNotarize, { keychainProfile: "test profile" }); }); + +test("configured macOS signing must fail the build with or without notarization", (t) => { + const root = fixture(t); + const previous = { ...process.env }; + t.after(() => { process.env = previous; }); + delete process.env.NEXT2D_STEAM_RELEASE; + process.env.APPLE_SIGNING_IDENTITY = "test identity"; + process.env.APPLE_NOTARY_PROFILE = "test profile"; + for (const notarize of [false, true]) { + fs.writeFileSync(path.join(root, "electron.config.json"), JSON.stringify({ macos: { sign: true, notarize } })); + const config = readElectronConfig(root); + const options = createElectronPackagerOptions(root, config, "macos", path.join(root, "resources")); + assert.equal(options.osxSign?.continueOnError, false); + assert.equal(Boolean(options.osxNotarize), notarize); + // Signing settings must not affect other target operating systems. + for (const os of ["windows", "linux"] as const) { + const other = createElectronPackagerOptions(root, config, os, path.join(root, "resources")); + assert.equal(other.osxSign, undefined); + assert.equal(other.osxNotarize, undefined); + } + } +}); diff --git a/tests/steam-upload.test.ts b/tests/steam-upload.test.ts index e28cb0b..e804a18 100644 --- a/tests/steam-upload.test.ts +++ b/tests/steam-upload.test.ts @@ -151,8 +151,21 @@ process.exit(Number(process.env.TEST_CODE || 0)); assert.equal(args[8], "+quit"); assert.ok(fs.existsSync(args[7])); assert.equal(JSON.parse(fs.readFileSync(path.join(path.dirname(args[7]), "upload-result.json"), "utf8")).buildId, "123456"); + // Actual SteamCMD output includes a timestamp and uses a different completion message. + const currentOutput = "[2026-09-24 08:02:22]: Successfully finished AppID 2409460 build (BuildID 25493101).\r\nUnloading Steam API...OK"; + const current = cli(f, [], { ...env, TEST_OUTPUT: currentOutput }); + assert.equal(current.status, 0, current.stdout + current.stderr); + const currentArgs = JSON.parse(fs.readFileSync(calls, "utf8")); + const currentRecord = JSON.parse(fs.readFileSync(path.join(path.dirname(currentArgs[7]), "upload-result.json"), "utf8")); + assert.equal(currentRecord.buildId, "25493101"); + assert.equal(currentRecord.requestedBranch, "internal"); for (const failure of [{ TEST_CODE: "7" }, { TEST_OUTPUT: "ERROR! Failed to build App." }, - { TEST_OUTPUT: "Logged in but no build happened" }, { TEST_OUTPUT: env.TEST_OUTPUT + "\nFailed to set build live" }]) { + { TEST_OUTPUT: "Logged in but no build happened" }, { TEST_OUTPUT: env.TEST_OUTPUT + "\nFailed to set build live" }, + { TEST_OUTPUT: currentOutput, TEST_CODE: "7" }, + { TEST_OUTPUT: currentOutput.replace("AppID 2409460", "AppID 480") }, + { TEST_OUTPUT: currentOutput.replace("BuildID 25493101", "BuildID unknown") }, + { TEST_OUTPUT: currentOutput + "\nERROR! Failed to set build live" }, + { TEST_OUTPUT: "ERROR! Failed to build depot.\n" + currentOutput }]) { const failed = cli(f, [], { ...env, ...failure }); assert.notEqual(failed.status, 0, failed.stdout + failed.stderr); const failedArgs = JSON.parse(fs.readFileSync(calls, "utf8")); From 05b9c8260b664ced69cbc2361e7a18436840b59b Mon Sep 17 00:00:00 2001 From: ienaga Date: Thu, 24 Sep 2026 20:40:27 +0900 Subject: [PATCH 8/9] =?UTF-8?q?Steam=E3=81=AE=E3=83=93=E3=83=AB=E3=83=89?= =?UTF-8?q?=E3=80=81=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC=E3=83=89?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E6=94=B9=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/steam.md | 98 ++++++++++++++++++++++++++++++++------ src/cli.ts | 14 ++++-- src/context.ts | 2 + src/index.ts | 2 +- src/steam-upload.ts | 12 ++++- tests/steam-upload.test.ts | 35 +++++++++++++- 6 files changed, 140 insertions(+), 23 deletions(-) diff --git a/docs/steam.md b/docs/steam.md index 46f8d66..8704a17 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -95,8 +95,15 @@ JavaScript / TypeScriptの両テンプレートに含まれるので、初期値 macOSのキーチェーンにDeveloper ID Application証明書と対応する秘密鍵を登録し、 `xcrun notarytool store-credentials` で公証用プロファイルを保存する。認証情報はJSONへ書かない。 -`security find-identity -v -p codesigning` に有効な `Developer ID Application: ...` が表示されることを確認する。 -`APPLE_SIGNING_IDENTITY` にはその実際の名前を指定する。環境変数を設定するだけでは証明書は作成されない。 +`APPLE_SIGNING_IDENTITY` に指定する署名IDは、次のコマンドで確認できる。 + +```sh +security find-identity -v -p codesigning +``` + +表示された有効な署名IDから `Developer ID Application: ...` を選び、ダブルクォート内の名前全体を +`APPLE_SIGNING_IDENTITY` に指定する。以下の `YOUR NAME (TEAMID)` は実際の表示に置き換える。 +環境変数を設定するだけでは証明書は作成されない。 ```sh export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' @@ -132,9 +139,15 @@ export STEAM_USERNAME="your_build_account" "$STEAMCMD" +login "$STEAM_USERNAME" +quit ``` -初回ログインではSteamCMDにパスワードとSteam Guardコードを対話入力する。 -その後builderは同じSteamCMDの保存済みログインを使い、パスワードをコマンド引数へ渡さない。 -認証が失効した場合は同じSteamCMDで再ログインする。builderからの実行は対話入力を待たず失敗させる。 +初回ログインではSteamCMDにパスワードと、要求された場合はSteam Guardコードを対話入力する。 +ログイン成功後も、`+quit` による正常終了まで待ち、通常のターミナルのプロンプトに戻ったことを確認する。 +`Steam>` で手動ログインした場合も、`quit` を入力して終了してからアップロードへ進む。 +その後builderは同じSteamCMD・同じアカウントの保存済みログインを使い、パスワードをコマンド引数へ渡さない。 +Steamアプリへのログインだけでは、このSteamCMDの認証準備を完了したことにはならない。 + +アップロード時に `Cached credentials not found` / `No cached credentials and @NoPromptForPassword is set` が出た場合や、 +認証が失効した場合は、上の `"$STEAMCMD" +login "$STEAM_USERNAME" +quit` を再実行し、正常終了後にアップロードを再実行する。 +builderからの実行は対話入力を待たず失敗させるため、builderの再実行だけではログイン情報を登録できない。 CIではSteamCMDの `config/config.vdf` をSecretとして復元・管理し、ビルド成果物に含めない。 Windows PowerShellでは `$env:STEAMCMD` と `$env:STEAM_USERNAME` を設定する。 @@ -175,10 +188,24 @@ CI artifactは `tar.gz` にして転送する。カスタム出力先はSTEP6の 配布用macOSアプリは署名・公証・staple完了を確認する(パスは成果物に合わせる): ```sh -codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +codesign --verify --deep --strict --verbose=2 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +echo $? xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +echo $? ``` +| 検証 | 正常時の出力 | 直後の `echo $?` | +| --- | --- | --- | +| `codesign --verify --deep --strict --verbose=2` | アプリのパスに続いて `valid on disk` と `satisfies its Designated Requirement` が表示される。内部コードの検証結果が追加で表示される場合もある。 | `0` | +| `xcrun stapler validate` | 最後に `The validate action worked!` が表示される。 | `0` | + +`codesign` は `--verbose=2` を付けない場合、成功しても通常は何も表示しない。終了コードが `0` なら検証成功と判断できる。 +`echo $?` は確認したいコマンドの直後に実行する。`0` 以外なら検証失敗なので、出力されたエラーを解消して再確認する。 +これらは署名と添付済み公証チケットを検証するコマンドであり、署名・公証を実施する操作ではない。 +両方が成功した後も、STEP6でSteamからのインストール・起動を確認する。 + +参考:[Appleの署名検証手順](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html)。 + ### STEP6:起動設定を反映し、アップロード・テストする SteamworksのInstallation > General InstallationにOS別Launch Optionを追加する。 @@ -192,13 +219,21 @@ Web/Electronの再ビルドや事前の `--steam-manifest` 実行は不要。 # ローカル検証のみ。SteamCMD・認証・Steamへの接続は不要 npx @next2d/builder --steam-upload --env prd --dry-run -# 検証後、設定したブランチへアップロード +# STEP4のSteamCMDログイン・正常終了後、同じターミナルで設定したブランチへアップロード npx @next2d/builder --steam-upload --env prd ``` +任意のアップロードコメントを付ける場合は `--steam-comment` を指定する。 +Steamworksの「あなたのビルド」の説明(SteamPipeの `Desc`)に反映される。 + +```sh +npx @next2d/builder --steam-upload --env prd --steam-comment "内部テスト:ゲームパッド操作を修正" +``` + | 引数・設定 | 用途 | |---|---| | `--steam-branch internal` | `steam.branch` の反映先を上書き。`SetLive` で反映を要求する。`default` は指定不可で、一般公開用ビルドの切り替えはSteamworksで行う。 | +| `--steam-comment "コメント"` | アップロードコメントを指定する。未指定時は従来どおり ` (, )`。空白のみ・改行・制御文字・ダブルクォート・バックスラッシュは使用不可。`--dry-run` の出力と `upload-plan.json` でも確認できる。 | | `--steam-root dist/steam` | 成果物の収集先。既定は `dist/steam`。ゲームルートからの相対パスまたは絶対パス。アップロード時はVite設定を読まないため、カスタム出力先では明示する。 | | `--dry-run` | パッケージと設定の整合性を検証する。Steam側の権限・ブランチ・パスワードの確認は行わない。 | | 併用しない引数 | `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`。`--platform` も不要。 | @@ -401,8 +436,15 @@ These are separate identifiers. The game version comes from the root `package.js Install a Developer ID Application certificate and its matching private key in the macOS keychain, and save a notarization profile using `xcrun notarytool store-credentials`. Do not put credentials in JSON. -Check that `security find-identity -v -p codesigning` lists a valid `Developer ID Application: ...` identity. -Use its actual name for `APPLE_SIGNING_IDENTITY`. Setting the environment variable does not create a certificate. +Use the following command to find the signing identity to set as `APPLE_SIGNING_IDENTITY`: + +```sh +security find-identity -v -p codesigning +``` + +Choose a valid `Developer ID Application: ...` identity from the output and use the entire name inside the double quotes +for `APPLE_SIGNING_IDENTITY`. Replace `YOUR NAME (TEAMID)` below with the actual value shown. +Setting the environment variable does not create a certificate. ```sh export APPLE_SIGNING_IDENTITY='Developer ID Application: YOUR NAME (TEAMID)' @@ -438,9 +480,15 @@ export STEAM_USERNAME="your_build_account" "$STEAMCMD" +login "$STEAM_USERNAME" +quit ``` -During the first login, enter the password and Steam Guard code interactively in SteamCMD. -The builder then reuses that SteamCMD installation's saved login without passing a password as a command-line argument. -If authentication expires, log in again with the same SteamCMD. Builder uploads fail instead of waiting for interactive input. +During the first login, enter the password and, if requested, the Steam Guard code interactively in SteamCMD. +After login succeeds, wait for `+quit` to exit normally and confirm that the regular terminal prompt has returned. +If you log in manually at the `Steam>` prompt, also enter `quit` before proceeding to upload. +The builder then reuses the saved login for the same SteamCMD installation and account without passing a password as a command-line argument. +Logging into the Steam desktop app alone does not confirm that this SteamCMD authentication setup is complete. + +If uploading reports `Cached credentials not found` / `No cached credentials and @NoPromptForPassword is set`, or authentication expires, +rerun `"$STEAMCMD" +login "$STEAM_USERNAME" +quit` above, wait for it to exit normally, then retry the upload. +Builder uploads fail instead of waiting for interactive input, so retrying the builder alone cannot register login credentials. In CI, restore and manage SteamCMD's `config/config.vdf` as a Secret and exclude it from build artifacts. In Windows PowerShell, set `$env:STEAMCMD` and `$env:STEAM_USERNAME`. @@ -481,10 +529,24 @@ architecture is used. Combining multiple architectures of one OS into a shared d Verify signing, notarization and stapling for macOS distribution packages (adjust the paths): ```sh -codesign --verify --deep --strict 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +codesign --verify --deep --strict --verbose=2 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +echo $? xcrun stapler validate 'dist/steam/macos/build/prd/My Game-darwin-universal/My Game.app' +echo $? ``` +| Check | Successful output | Immediately following `echo $?` | +| --- | --- | --- | +| `codesign --verify --deep --strict --verbose=2` | The app path followed by `valid on disk` and `satisfies its Designated Requirement`. Additional results for nested code may also appear. | `0` | +| `xcrun stapler validate` | Ends with `The validate action worked!`. | `0` | + +Without `--verbose=2`, `codesign` normally prints nothing on success. An exit code of `0` indicates that verification passed. +Run `echo $?` immediately after each command you want to check. A nonzero exit code means verification failed; resolve the reported error and check again. +These commands verify the signature and the attached notarization ticket; they do not sign or notarize the app. +After both checks pass, still verify installation and launching through Steam in STEP6. + +Reference: [Apple's signature verification instructions](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html). + ### STEP6: Configure launching, upload and test Add OS-specific Launch Options in Steamworks under Installation > General Installation. @@ -499,13 +561,21 @@ No Web/Electron rebuild or prior `--steam-manifest` command is needed. # Local validation only: no SteamCMD, credentials or Steam connection required npx @next2d/builder --steam-upload --env prd --dry-run -# After validation, upload to the configured branch +# After STEP4's SteamCMD login exits normally, upload to the configured branch in the same terminal npx @next2d/builder --steam-upload --env prd ``` +Use `--steam-comment` to set an optional upload comment. +It appears as the description under "Your Builds" in Steamworks (SteamPipe's `Desc`). + +```sh +npx @next2d/builder --steam-upload --env prd --steam-comment "Internal test: fix gamepad controls" +``` + | Option / setting | Purpose | |---|---| | `--steam-branch internal` | Overrides `steam.branch`; requests activation through `SetLive`. `default` is not allowed. Switch public release builds through Steamworks. | +| `--steam-comment "Comment"` | Sets the upload comment. If omitted, keeps the existing ` (, )` format. Whitespace-only text, newlines, control characters, double quotes and backslashes are not allowed. Also shown in `--dry-run` output and `upload-plan.json`. | | `--steam-root dist/steam` | Collected package location; defaults to `dist/steam`. Accepts an absolute path or a path relative to the game root. Specify custom output locations because uploading does not read the Vite configuration. | | `--dry-run` | Validates packages against configuration. Does not check Steam permissions, branches or passwords. | | Incompatible options | `--build` / `--preview` / `--open` / `--steam-manifest` / `--arch`. `--platform` is also unnecessary. | diff --git a/src/cli.ts b/src/cli.ts index 1877b20..c856af8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -29,7 +29,7 @@ export const echoHelp = (): void => console.log("Steam: --platform steam:windows|steam:macos|steam:linux --env prd"); console.log("Electron: --arch x64|arm64|universal (default: macOS universal, others x64)"); console.log("Combine existing Steam packages only: --platform steam:macos --env prd --steam-manifest"); - console.log("Upload existing packages: --steam-upload --env prd [--steam-branch internal] [--steam-root dist/steam] [--dry-run]"); + console.log("Upload existing packages: --steam-upload --env prd [--steam-branch internal] [--steam-root dist/steam] [--steam-comment \"Build comment\"] [--dry-run]"); console.log("Steam upload uses STEAM_USERNAME and optional STEAMCMD (executable path); no --platform is needed."); console.log(); process.exit(1); @@ -50,6 +50,7 @@ export interface ParsedArgs { steamUpload: boolean; steamBranch: string; steamRoot: string; + steamComment: string; dryRun: boolean; hasHelp: boolean; } @@ -88,6 +89,7 @@ export const parseArgv = (argv: string[]): ParsedArgs => "steamUpload": false, "steamBranch": "", "steamRoot": "", + "steamComment": "", "dryRun": false, "hasHelp": false }; @@ -105,7 +107,8 @@ export const parseArgv = (argv: string[]): ParsedArgs => break; case "--steam-branch": - case "--steam-root": { + case "--steam-root": + case "--steam-comment": { const flag = argv[idx]; const value = argv[++idx] || ""; if (!value || value.startsWith("--")) { @@ -113,8 +116,10 @@ export const parseArgv = (argv: string[]): ParsedArgs => } if (flag === "--steam-branch") { result.steamBranch = value; - } else { + } else if (flag === "--steam-root") { result.steamRoot = value; + } else { + result.steamComment = value; } break; } @@ -182,7 +187,7 @@ export const parseArgv = (argv: string[]): ParsedArgs => if (result.steamUpload && (result.platform || result.steamManifest || result.preview || result.open || result.build || result.arch || result.v8Root)) { result.hasHelp = true; } - if (!result.steamUpload && (result.steamBranch || result.steamRoot || result.dryRun)) { + if (!result.steamUpload && (result.steamBranch || result.steamRoot || result.steamComment || result.dryRun)) { result.hasHelp = true; } @@ -237,6 +242,7 @@ Please update your version of Node.`); ctx.steamUpload = parsed.steamUpload; ctx.steamBranch = parsed.steamBranch; ctx.steamRoot = parsed.steamRoot; + ctx.steamComment = parsed.steamComment; ctx.dryRun = parsed.dryRun; if (parsed.hasHelp || !ctx.steamUpload && !SUPPORTED_PLATFORMS.has(ctx.platform)) { diff --git a/src/context.ts b/src/context.ts index 03f836e..99a69f5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -26,6 +26,7 @@ export interface BuildContext { steamUpload: boolean; steamBranch: string; steamRoot: string; + steamComment: string; dryRun: boolean; /** vite の loadConfigFromFile 結果 */ configObject: Awaited>; @@ -52,6 +53,7 @@ export const ctx: BuildContext = { "steamUpload": false, "steamBranch": "", "steamRoot": "", + "steamComment": "", "dryRun": false, "configObject": null, "buildDir": "", diff --git a/src/index.ts b/src/index.ts index 71b9398..54d630e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -90,7 +90,7 @@ const execute = async (): Promise => if (ctx.steamUpload) { await uploadSteam({ "root": process.cwd(), "steamRoot": ctx.steamRoot || "dist/steam", "environment": ctx.environment, - "branch": ctx.steamBranch || undefined, "dryRun": ctx.dryRun + "branch": ctx.steamBranch || undefined, "comment": ctx.steamComment || undefined, "dryRun": ctx.dryRun }); return; } diff --git a/src/steam-upload.ts b/src/steam-upload.ts index ea54d62..75ea205 100644 --- a/src/steam-upload.ts +++ b/src/steam-upload.ts @@ -9,6 +9,7 @@ export interface ISteamUploadOptions { steamRoot: string; environment: string; branch?: string; + comment?: string; dryRun?: boolean; } @@ -36,6 +37,12 @@ export const prepareSteamUpload = (options: ISteamUploadOptions) => { if (typeof version !== "string" || !version) { throw new Error("Set package.json version before uploading."); } + if (options.comment !== undefined && (typeof options.comment !== "string" || !options.comment.trim() + || /["\\\u0000-\u001f\u007f]/.test(options.comment))) { + throw new Error("Steam upload --steam-comment must be nonempty single-line text without double quotes, backslashes or control characters."); + } + const comment = options.comment ?? `${config.appName} ${version} (${options.environment}, ${branch})`; + const quotedComment = quote(comment); const groups = new Map(); for (const os of ["windows", "macos", "linux"] as const) { const id = config.steam.depots[os]; @@ -74,11 +81,11 @@ export const prepareSteamUpload = (options: ISteamUploadOptions) => { // A stable cache directory allows SteamPipe to reuse chunks across uploads to a branch. const cache = path.join(uploads, "cache", branch); fs.writeFileSync(manifest, - `"AppBuild"\n{\n "AppID" "${config.steam.appId}"\n "Desc" ${quote(`${config.appName} ${version} (${options.environment}, ${branch})`)}\n` + `"AppBuild"\n{\n "AppID" "${config.steam.appId}"\n "Desc" ${quotedComment}\n` + ` "ContentRoot" ${quote(steamRoot)}\n "BuildOutput" ${quote(cache)}\n "Preview" "${options.dryRun ? "1" : "0"}"\n` + (options.dryRun ? "" : ` "SetLive" ${quote(branch)}\n`) + ` "Depots"\n {\n${depotFiles.join("\n")}\n }\n}\n`); - const plan = { "appId": config.steam.appId, branch, version, "environment": options.environment, "dryRun": !!options.dryRun, manifest, launches }; + const plan = { "appId": config.steam.appId, branch, version, comment, "environment": options.environment, "dryRun": !!options.dryRun, manifest, launches }; fs.writeFileSync(path.join(scripts, "upload-plan.json"), `${JSON.stringify(plan, null, 2)}\n`); return plan; }; @@ -87,6 +94,7 @@ export const prepareSteamUpload = (options: ISteamUploadOptions) => { export const uploadSteam = async (options: ISteamUploadOptions): Promise => { const plan = prepareSteamUpload(options); console.log(`Steam App ${plan.appId} -> beta branch ${plan.branch}`); + console.log(`Comment: ${plan.comment}`); console.log(`Manifest: ${plan.manifest}`); for (const launch of plan.launches) { console.log(` Depot ${launch.depotId}: ${launch.platform}/${launch.arch} -> ${launch.executable}`); diff --git a/tests/steam-upload.test.ts b/tests/steam-upload.test.ts index e804a18..a4e4209 100644 --- a/tests/steam-upload.test.ts +++ b/tests/steam-upload.test.ts @@ -45,18 +45,23 @@ test("Steam upload CLI is platform independent and rejects mixed actions, missin assert.equal(parseArgv(args).hasHelp, false); assert.equal(parseArgv([...args, "--dry-run"]).dryRun, true); assert.equal(parseArgv([...args, "--steam-branch", "internal"]).steamBranch, "internal"); + assert.equal(parseArgv([...args, "--steam-comment", "内部テスト用"]).steamComment, "内部テスト用"); for (const extra of [["--platform", "steam:macos"], ["--steam-manifest"], ["--preview"], ["--build"], ["--open"], - ["--arch", "x64"], ["--steam-branch"], ["--steam-root"], ["--dryrun"]]) { + ["--arch", "x64"], ["--steam-branch"], ["--steam-root"], ["--steam-comment"], + ["--steam-comment", ""], ["--steam-comment", "--dry-run"], ["--dryrun"]]) { assert.equal(parseArgv([...args, ...extra]).hasHelp, true, extra.join(" ")); } assert.equal(parseArgv(["--steam-upload"]).hasHelp, true); assert.equal(parseArgv(["--platform", "web", "--env", "prd", "--dry-run"]).hasHelp, true); + assert.equal(parseArgv(["--platform", "web", "--env", "prd", "--steam-comment", "test"]).hasHelp, true); }); test("upload combines a shared depot once, sets internal, and preserves ordinary build manifests", (t) => { const f = fixture(t); const plan = prepareSteamUpload(f); const manifest = fs.readFileSync(plan.manifest, "utf8"); + assert.match(manifest, /"Desc" "Test Game 1\.0\.0 \(prd, internal\)"/); + assert.equal(plan.comment, "Test Game 1.0.0 (prd, internal)"); assert.match(manifest, /"SetLive" "internal"/); assert.match(manifest, /"Preview" "0"/); assert.equal((manifest.match(/"2409461" "depot_2409461.vdf"/g) || []).length, 1); @@ -124,6 +129,30 @@ test("dry-run CLI needs no SteamCMD, credentials, Vite or tool downloads", (t) = assert.equal(fs.existsSync(path.join(f.root, "node_modules")), false); }); +test("dry-run CLI preserves a custom build comment in its manifest, plan and output", (t) => { + const f = fixture(t); + const comment = "内部テスト:プレイヤー's 入力を修正 (revision abc123)"; + const result = cli(f, ["--dry-run", "--steam-comment", comment]); + assert.equal(result.status, 0, result.stdout + result.stderr); + assert.ok(result.stdout.includes(`Comment: ${comment}`)); + const manifest = result.stdout.match(/^Manifest: (.+)$/m)[1].trim(); + assert.ok(fs.readFileSync(manifest, "utf8").includes(`"Desc" "${comment}"`)); + const plan = JSON.parse(fs.readFileSync(path.join(path.dirname(manifest), "upload-plan.json"), "utf8")); + assert.equal(plan.comment, comment); +}); + +test("invalid comments fail before writing manifests or starting SteamCMD", (t) => { + const f = fixture(t); + for (const comment of ["", " ", "bad\"comment", "bad\\comment", "bad\ncomment", "bad\rcomment", "bad\tcomment", "bad\0comment", null]) { + assert.throws(() => prepareSteamUpload({ ...f, comment }), /--steam-comment/); + } + const result = cli(f, ["--steam-comment", " "], { STEAM_USERNAME: "test_builder" }); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /--steam-comment/); + assert.doesNotMatch(result.stderr, /Cannot start SteamCMD/); + assert.equal(fs.existsSync(path.join(f.steamRoot, "uploads")), false); +}); + test("upload requires a cached-login account and propagates missing executable errors", (t) => { const f = fixture(t); assert.match(cli(f).stderr, /Set STEAM_USERNAME/); @@ -153,9 +182,11 @@ process.exit(Number(process.env.TEST_CODE || 0)); assert.equal(JSON.parse(fs.readFileSync(path.join(path.dirname(args[7]), "upload-result.json"), "utf8")).buildId, "123456"); // Actual SteamCMD output includes a timestamp and uses a different completion message. const currentOutput = "[2026-09-24 08:02:22]: Successfully finished AppID 2409460 build (BuildID 25493101).\r\nUnloading Steam API...OK"; - const current = cli(f, [], { ...env, TEST_OUTPUT: currentOutput }); + const comment = "内部テスト用ビルド"; + const current = cli(f, ["--steam-comment", comment], { ...env, TEST_OUTPUT: currentOutput }); assert.equal(current.status, 0, current.stdout + current.stderr); const currentArgs = JSON.parse(fs.readFileSync(calls, "utf8")); + assert.ok(fs.readFileSync(currentArgs[7], "utf8").includes(`"Desc" "${comment}"`)); const currentRecord = JSON.parse(fs.readFileSync(path.join(path.dirname(currentArgs[7]), "upload-result.json"), "utf8")); assert.equal(currentRecord.buildId, "25493101"); assert.equal(currentRecord.requestedBranch, "internal"); From 3bdcdba12c23c6257a077c5b60a47697dbd376ce Mon Sep 17 00:00:00 2001 From: ienaga Date: Thu, 24 Sep 2026 21:35:39 +0900 Subject: [PATCH 9/9] =?UTF-8?q?steam=E3=81=AE=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/steam.md | 80 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/docs/steam.md b/docs/steam.md index 8704a17..90bb47f 100644 --- a/docs/steam.md +++ b/docs/steam.md @@ -1,16 +1,15 @@ -# Electron / Steam 書き出し / Export +# Steam 書き出し / アップロード / Export / Upload [日本語](#日本語) | [English](#english) ## 日本語 -ローカル書き出しだけなら、Steamworks・SteamCMD・配布用の署名認証は不要。 +ローカルへの書き出しだけなら、Steamworks・SteamCMD・配布用の署名認証は不要。 ### STEP1:対象OSの実行環境とアイコンを用意する - macOSのUniversalアプリと配布用の署名・公証はmacOS上で行う。 -- 配布する各OSで起動・入力・保存を確認できる実行環境を用意する。 -- ゲームの `src/assets/icons/` にWindows用ICO、macOS用ICNS、Linux用PNGを用意する。 +- Next2D Frameworkのテンプレートの `src/assets/icons/` にWindows用ICO、macOS用ICNS、Linux用PNGを用意する。 テンプレートの仮アイコンは配布前に差し替える。設定の省略時の扱いはSTEP3の表を参照。 #### 対応OS・CPU @@ -21,28 +20,43 @@ | macOS | `universal` | `x64`, `arm64`, `universal` | | Linux | `x64` | `x64`, `arm64` | -32bit版Windows(`ia32`)には対応しない。`win32` はElectron内部のWindowsのOS名で、 -32bitを意味しない。例えば `My Game-win32-x64/` は64bit版Windows向けの成果物。 -`--platform` には `windows` または `steam:windows` を指定する。 - -macOSの `universal` はx64とarm64を含む1つの `.app`。 - ### STEP2:SteamworksのApp・Depot・テスト用ブランチを用意する(Steam配布時) 1. 対象アプリのSteamworks管理画面で、発行済みのApp IDを確認する。 2. SteamPipe > DepotsでDepotを作成し、対象OSを設定する。言語共通ならAll languagesを選び、 - 開発用・販売用Packageにも対象Depotを含める。**Depot IDはApp IDから推測せず、実際の値を使う。** + Save Changesで保存後、公開(Publish)タブで変更を反映する。 + 開発用・テスト用・販売用Packageにも必要なDepotを含める。**Depot IDはApp IDから推測せず、実際の値を使う。** 3. SteamPipe > Buildsで `internal` ブランチを作成し、**パスワードを設定して関係者だけに共有する。** 名前だけでは非公開にならず、builderはブランチ作成・パスワード設定や確認を行わない。 4. テスターにゲームと対象Depotの利用権を用意する。未発売ゲームの外部テスターには Release State Override(beta)キー等を使う。ブランチのパスワードだけでは利用権は付与されない。 パスワードを第三者に共有しないよう運用し、JSONやリポジトリにも保存しない。 -OS別Depotと共有Depotのどちらも使用できる。共有するOSには同じDepot IDを設定し、 -Steamworks側の対象OSをAll OSesなど共有構成に合わせる。 -共有Depotは全OSのファイルを配布するため容量が増える。ValveはOS固有ファイルには別Depotを推奨する。 +#### 複数Depotの構成とbuilderの対応範囲 + +**Steamでは1つのApp IDに複数Depotを設定できる。** OS固有ファイルはOS別Depotに分ける構成をValveが推奨している。 + +| 構成 | Steam | 現在のbuilder | +|---|---|---| +| Windows / macOS / Linuxを別Depotにする | 対応。各Depotの対象OSを指定する。 | 対応。`steam.depots` の各OSに異なる発行済みIDを設定する。 | +| 複数OSを1つのDepotにまとめる | 対応。対象OSをAll OSesなど構成に合わせる。 | 対応。対象OSに同じIDを設定する。各OSのファイルをサブディレクトリに分ける。全対象OSのファイルを配布するため容量が増える。 | +| 一部OSだけ同じDepotにする | 対応。 | 対応。共有するOSだけ同じIDにする。 | +| 同じOS向けに本体・共通アセット・言語などを複数Depotに分ける | 対応。複数Depotの内容をインストール時に組み合わせる。 | 未対応。各OSの値は単一のDepot IDで、配列や追加アセット用キーには対応しない。 | + +builderの設定可能数は各OSに1 ID、全体で最大3つの異なるID。この上限はbuilderの設定形式によるもので、Steamの上限ではない。 +OSごとの成果物を揃える手順と起動パスはSTEP5、複数Depotを1回でアップロードする手順はSTEP6を参照する。 + +Steam側のダウンロード対象はPackageの利用権とDepotのOS・言語などの条件で決まる。 +同時にインストールするDepotで同じパスが重なると、Depot一覧の後にあるものが優先されるため、ファイルの配置を確認する。 +これらの選択条件はSteamworks側で設定し、builderのJSONからは反映しない。 + +`internal` / `beta` はAppのビルドを選ぶブランチで、ブランチごとに別Depotを作る必要はない。 +また、この文書の「共有Depot」は同一App内で複数OSをまとめる意味。 +Steamworksの「Add Shared Depot」は別AppのDepotを参照する機能であり、builderの同一ID指定とは別の操作。 [Depotの設定](https://partner.steamgames.com/doc/store/application/depots) / +[Packageと利用権](https://partner.steamgames.com/doc/store/application/packages) / +[複数Depotのアップロード](https://partner.steamgames.com/doc/sdk/uploading) / [betaブランチ](https://partner.steamgames.com/doc/store/application/branches) / [Steamでのテスト](https://partner.steamgames.com/doc/store/testing) / [Release State Overrideキー](https://partner.steamgames.com/doc/features/keys) @@ -350,8 +364,7 @@ Local exports do not require Steamworks, SteamCMD or distribution signing creden ### STEP1: Prepare target environments and icons - Build macOS Universal applications and perform macOS signing/notarization on macOS. -- Prepare an environment for each distributed OS to verify launch, input and saved data. -- Place Windows ICO, macOS ICNS and Linux PNG icons in the game's `src/assets/icons/`. +- Place Windows ICO, macOS ICNS and Linux PNG icons in `src/assets/icons/` in the Next2D Framework template. Replace template placeholders before distribution. STEP3 documents omitted icon settings. #### Supported operating systems and architectures @@ -362,28 +375,43 @@ Local exports do not require Steamworks, SteamCMD or distribution signing creden | macOS | `universal` | `x64`, `arm64`, `universal` | | Linux | `x64` | `x64`, `arm64` | -32-bit Windows (`ia32`) is not supported. `win32` is Electron's internal OS name for Windows, not a CPU bitness indicator. -For example, `My Game-win32-x64/` is a 64-bit Windows package. -Use `windows` or `steam:windows` for `--platform`. - -macOS `universal` produces one `.app` containing both x64 and arm64. - ### STEP2: Prepare the Steamworks app, depots and testing branch (Steam distribution) 1. Find the issued App ID in the app's Steamworks administration page. 2. Create depots in SteamPipe > Depots and select their operating systems. Use All languages for shared language content, - and include the depots in development and retail Packages. **Use actual Depot IDs; do not infer them from the App ID.** + save with Save Changes, then apply the changes on the Publish tab. + Include the required depots in development, testing and retail Packages. **Use actual Depot IDs; do not infer them from the App ID.** 3. Create an `internal` branch in SteamPipe > Builds and **set a password shared only with the intended testers.** The name alone does not make it private. The builder does not create branches, configure passwords or verify protection. 4. Give testers access to the game and its depots. For external testers of an unreleased game, use Release State Override (beta) keys or another appropriate method. A branch password alone does not grant game access. Keep the password out of JSON and the repository, and ask testers not to share it with others. -Separate OS depots and shared depots are supported. Assign the same Depot ID to operating systems that share a depot, -and set its Steamworks OS selection accordingly, such as All OSes. -Shared depots distribute every OS's files and increase download size. Valve recommends separate depots for OS-specific files. +#### Multiple depots and builder support + +**Steam supports multiple depots under one App ID.** Valve recommends separate depots for OS-specific files. + +| Configuration | Steam | Current builder | +|---|---|---| +| Separate depots for Windows / macOS / Linux | Supported. Set the target OS for each depot. | Supported. Assign different issued IDs to the OS entries in `steam.depots`. | +| One depot containing multiple operating systems | Supported. Set its OS selection accordingly, such as All OSes. | Supported. Assign the same ID to those operating systems. Files are placed in OS subdirectories. Distributing every included OS's files increases download size. | +| Only some operating systems share a depot | Supported. | Supported. Assign the same ID only to those operating systems. | +| Multiple depots for one OS, such as the application, common assets and languages | Supported. Their contents are combined during installation. | Not supported. Each OS accepts a single Depot ID; arrays and extra asset keys are not supported. | + +The builder accepts one ID per OS, for a maximum of three distinct IDs overall. This is a limit of the builder's configuration format, not a Steam limit. +See STEP5 for collecting OS packages and launch paths, and STEP6 for uploading multiple depots in one operation. + +Steam selects downloads based on Package ownership and depot conditions such as OS and language. +If simultaneously installed depots contain the same path, the depot later in the list takes priority, so check file placement. +Configure these selection conditions in Steamworks; the builder's JSON does not apply them. + +`internal` / `beta` are branches that select an app build; separate depots are not required for each branch. +In this document, a "shared depot" groups multiple operating systems within the same app. +Steamworks' "Add Shared Depot" references a depot from another app; it is a separate operation from assigning the same ID in the builder. [Depot configuration](https://partner.steamgames.com/doc/store/application/depots) / +[Packages and access](https://partner.steamgames.com/doc/store/application/packages) / +[Uploading multiple depots](https://partner.steamgames.com/doc/sdk/uploading) / [Beta branches](https://partner.steamgames.com/doc/store/application/branches) / [Testing on Steam](https://partner.steamgames.com/doc/store/testing) / [Release State Override keys](https://partner.steamgames.com/doc/features/keys)