diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7c0726c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: + - develop + pull_request: + branches: + - main + - develop + workflow_call: + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: "https://registry.npmjs.org" + - run: npm install -g npm@latest + - run: npm ci + # TypeScript のビルド (dist/*.js を生成) + - run: npm run build + # 分割モジュールの純関数ユニットテスト (node:test) + - run: npm test + # ESLint (src 全体) + - run: npm run lint diff --git a/.github/workflows/xbox-host-ci.yml b/.github/workflows/xbox-host-ci.yml index e9c03af..b57fe29 100644 --- a/.github/workflows/xbox-host-ci.yml +++ b/.github/workflows/xbox-host-ci.yml @@ -8,6 +8,9 @@ # 3. v8-syntax-check : V8 依存ソースを V8 ヘッダ + MSVC でコンパイル検証 # (リンクなし。Dawn 依存の WebGPU.cpp / DawnContext.cpp / # main.cpp と GDK 依存は対象外 → 実機セットアップ時に検証) +# 4. gamecore-api-audit : WINAPI_FAMILY=WINAPI_FAMILY_GAMES でコンパイルし、 +# ホスト/V8 platform 層が使う Win32 API がコンソール +# (Game Core) パーティションに存在するかを公開 SDK で監査 name: xbox-host-ci on: @@ -30,7 +33,7 @@ jobs: name: RasterCore tests + V8 syntax check (Linux) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Fetch V8 headers (${{ env.V8_TAG }}) run: | git clone --depth 1 --branch "$V8_TAG" --filter=blob:none --sparse \ @@ -45,8 +48,24 @@ jobs: # VS2022 (v143) 世代に固定 — GDK ビルドの実ターゲットと同じ toolset で検証する runs-on: windows-2022 steps: - - uses: actions/checkout@v4 - - uses: ilammy/msvc-dev-cmd@v1 + - uses: actions/checkout@v6 + # MSVC 環境 (vcvars64) をサードパーティ action なしで設定する。 + # ilammy/msvc-dev-cmd は Node20 非推奨警告が出るため、実体である + # 「vcvars64.bat を実行して環境変数を GITHUB_ENV へ書き出す」処理を直接行う。 + - name: Setup MSVC environment (vcvars64) + shell: pwsh + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vs = & $vswhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + if (-not $vs) { Write-Error "Visual Studio (VC tools) not found"; exit 1 } + cmd /s /c "`"$vs\VC\Auxiliary\Build\vcvars64.bat`" > NUL && set" | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { + Add-Content $env:GITHUB_ENV "$($Matches[1])=$($Matches[2])" + } + } + Write-Host "MSVC env exported from: $vs" - name: Build & run RasterCore tests shell: cmd @@ -56,28 +75,103 @@ jobs: if errorlevel 1 exit /b 1 raster_test.exe - - name: Build & run Windows platform tests (WIC/DirectWrite/MF/XAudio2) + - name: Build & run Windows platform tests (WIC/DirectWrite/MF/XAudio2/stb/dr) shell: cmd working-directory: templates/xbox run: | - cl /nologo /std:c++17 /EHsc /W3 /DNOMINMAX /Isrc ^ + rem /I. は third_party/ (stb_image, dr_mp3 等) の include 用。 + rem ImageDecoder/AudioDecoder はポータブルな第一経路 (AudioEngine::Decode が参照)。 + cl /nologo /std:c++17 /EHsc /W3 /DNOMINMAX /Isrc /I. ^ tests\windows_platform_test.cpp ^ src\platform\WicDecoder.cpp ^ src\platform\TextRasterizer.cpp ^ src\platform\AudioEngine.cpp ^ + src\platform\ImageDecoder.cpp ^ + src\platform\AudioDecoder.cpp ^ + src\platform\StbTextRasterizer.cpp ^ /Fe:platform_test.exe ^ ole32.lib uuid.lib windowscodecs.lib d2d1.lib dwrite.lib ^ mfplat.lib mfreadwrite.lib mfuuid.lib xaudio2.lib shlwapi.lib if errorlevel 1 exit /b 1 platform_test.exe + gamecore-api-audit: + name: Game Core API partition audit (WINAPI_FAMILY_GAMES) + runs-on: windows-2022 + steps: + - uses: actions/checkout@v6 + # MSVC 環境 (vcvars64) をサードパーティ action なしで設定する。 + # ilammy/msvc-dev-cmd は Node20 非推奨警告が出るため、実体である + # 「vcvars64.bat を実行して環境変数を GITHUB_ENV へ書き出す」処理を直接行う。 + - name: Setup MSVC environment (vcvars64) + shell: pwsh + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vs = & $vswhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + if (-not $vs) { Write-Error "Visual Studio (VC tools) not found"; exit 1 } + cmd /s /c "`"$vs\VC\Auxiliary\Build\vcvars64.bat`" > NUL && set" | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { + Add-Content $env:GITHUB_ENV "$($Matches[1])=$($Matches[2])" + } + } + Write-Host "MSVC env exported from: $vs" + + # ホストがコンソールでも使う API: 全て GAMES パーティションに必要 (欠落は fail)。 + # 事前のローカル監査 (SDK 22621/26100/28000 のヘッダ解析) では全て GAMES 可視。 + # NOTE: cmd の複数行 if ブロックは括弧/errorlevel の罠が多い (2 回誤爆) ため、 + # v8-syntax-check と同じ pwsh + $LASTEXITCODE 方式に統一。 + - name: Host APIs must exist in GAMES partition + shell: pwsh + working-directory: templates/xbox + run: | + Write-Host "SDK: $env:WindowsSDKVersion" + cl /nologo /std:c++17 /W3 /c /DWINAPI_FAMILY=WINAPI_FAMILY_GAMES ` + tests/gamecore_api_probe.cpp + if ($LASTEXITCODE -ne 0) { + Write-Host "::error::Game Core host API probe failed (cl errors above)" + exit 1 + } + Write-Host "OK: host APIs are all present in the GAMES partition" + + # V8 platform 層の API: 欠落 = 移植時の置換 TODO リスト (情報提供、fail しない) + - name: V8 platform-layer APIs (informational TODO list) + shell: pwsh + working-directory: templates/xbox + run: | + cl /nologo /std:c++17 /W3 /c /DWINAPI_FAMILY=WINAPI_FAMILY_GAMES /DPROBE_V8 ` + tests/gamecore_api_probe.cpp + if ($LASTEXITCODE -ne 0) { + Write-Host "=== The cl errors above = V8 porting TODO (APIs not in GAMES partition) ===" + } else { + Write-Host "OK: V8 platform APIs are all present in the GAMES partition" + } + exit 0 + v8-syntax-check: name: V8-dependent sources compile check (MSVC) # VS2022 (v143) 世代に固定 — GDK ビルドの実ターゲットと同じ toolset で検証する runs-on: windows-2022 steps: - - uses: actions/checkout@v4 - - uses: ilammy/msvc-dev-cmd@v1 + - uses: actions/checkout@v6 + # MSVC 環境 (vcvars64) をサードパーティ action なしで設定する。 + # ilammy/msvc-dev-cmd は Node20 非推奨警告が出るため、実体である + # 「vcvars64.bat を実行して環境変数を GITHUB_ENV へ書き出す」処理を直接行う。 + - name: Setup MSVC environment (vcvars64) + shell: pwsh + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vs = & $vswhere -latest -products * ` + -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` + -property installationPath + if (-not $vs) { Write-Error "Visual Studio (VC tools) not found"; exit 1 } + cmd /s /c "`"$vs\VC\Auxiliary\Build\vcvars64.bat`" > NUL && set" | ForEach-Object { + if ($_ -match '^([^=]+)=(.*)$') { + Add-Content $env:GITHUB_ENV "$($Matches[1])=$($Matches[2])" + } + } + Write-Host "MSVC env exported from: $vs" - name: Fetch V8 headers (${{ env.V8_TAG }}) shell: pwsh diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..6c56142 --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ +# npm 12 は git 依存の取得を既定で無効化する (EALLOWGIT)。 +# 依存ツリー内の @electron/node-gyp (electron の推移的依存) が git 参照のため、 +# プロジェクト単位でオプトインする。npm 11 以前はこの設定を無視する (無害)。 +allow-git=all diff --git a/package-lock.json b/package-lock.json index 4cba83a..d34e0af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,30 +1,30 @@ { "name": "@next2d/builder", - "version": "0.1.8", + "version": "0.1.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@next2d/builder", - "version": "0.1.8", + "version": "0.1.10", "license": "MIT", "dependencies": { "@electron-forge/core": "^7.11.2", - "@types/node": "^26.1.0", + "@types/node": "^26.1.2", "picocolors": "^1.1.1", - "vite": "^8.1.3" + "vite": "^8.2.0" }, "bin": { "builder": "dist/index.js" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.5", + "@eslint/eslintrc": "^3.3.6", "@eslint/js": "^10.0.1", - "@typescript-eslint/eslint-plugin": "^8.62.1", - "@typescript-eslint/parser": "^8.62.1", - "eslint": "^10.6.0", + "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^10.8.0", "eslint-plugin-unused-imports": "^4.4.1", - "globals": "^17.7.0", + "globals": "^17.8.0", "typescript": "^6.0.3" } }, @@ -358,9 +358,9 @@ } }, "node_modules/@electron/node-gyp/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -547,9 +547,9 @@ } }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -618,20 +618,20 @@ } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", + "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", "license": "MIT", "optional": true, "dependencies": { @@ -639,9 +639,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", "license": "MIT", "optional": true, "dependencies": { @@ -732,9 +732,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -758,9 +758,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -770,7 +770,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -979,21 +979,24 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@nodelib/fs.scandir": { @@ -1059,18 +1062,18 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.138.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", - "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", - "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -1084,9 +1087,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", - "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -1100,9 +1103,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", - "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -1116,9 +1119,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", - "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -1132,9 +1135,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", - "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -1148,9 +1151,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", - "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], @@ -1167,9 +1170,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", - "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], @@ -1186,9 +1189,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", - "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], @@ -1205,9 +1208,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", - "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], @@ -1224,9 +1227,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", - "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], @@ -1243,9 +1246,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", - "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], @@ -1262,9 +1265,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", - "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -1278,27 +1281,24 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", - "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", - "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -1312,9 +1312,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", - "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -1423,9 +1423,9 @@ } }, "node_modules/@types/node": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", - "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "version": "26.1.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", + "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -1451,17 +1451,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", - "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/type-utils": "8.62.1", - "@typescript-eslint/utils": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1474,15 +1474,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.62.1", + "@typescript-eslint/parser": "^8.65.0", "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.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -1490,16 +1490,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", - "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "engines": { @@ -1515,14 +1515,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", - "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.1", - "@typescript-eslint/types": "^8.62.1", + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "engines": { @@ -1537,14 +1537,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", - "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1555,9 +1555,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", - "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", "dev": true, "license": "MIT", "engines": { @@ -1572,15 +1572,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", - "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1", - "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1597,9 +1597,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", - "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", "dev": true, "license": "MIT", "engines": { @@ -1611,16 +1611,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", - "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.62.1", - "@typescript-eslint/tsconfig-utils": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/visitor-keys": "8.62.1", + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1649,26 +1649,26 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "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": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "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.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -1678,16 +1678,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", - "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.1", - "@typescript-eslint/types": "8.62.1", - "@typescript-eslint/typescript-estree": "8.62.1" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1702,13 +1702,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", - "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2132,9 +2132,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.41", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", - "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -2169,9 +2169,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2191,9 +2191,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "funding": [ { "type": "opencollective", @@ -2210,10 +2210,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -2292,9 +2292,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2370,9 +2370,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", "funding": [ { "type": "opencollective", @@ -2786,9 +2786,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.386", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.386.tgz", - "integrity": "sha512-f9yJE+9dJy+B4QC1iTu+mVP/KWLZviG5u/qtRwdBF0zPt3etWa1HSY1lMuWw0Nt8/KpLCY6ok+XlKHOs6ZhxSA==", + "version": "1.5.389", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", + "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2817,9 +2817,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", - "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2873,9 +2873,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "license": "MIT" }, "node_modules/es6-error": { @@ -2908,9 +2908,9 @@ } }, "node_modules/eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -2920,7 +2920,7 @@ "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", + "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", @@ -2944,7 +2944,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -3683,9 +3683,9 @@ } }, "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "version": "17.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz", + "integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==", "dev": true, "license": "MIT", "engines": { @@ -4229,9 +4229,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "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" @@ -4244,23 +4244,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "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.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -4278,9 +4278,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "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" ], @@ -4298,9 +4298,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "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" ], @@ -4318,9 +4318,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "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" ], @@ -4338,9 +4338,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "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" ], @@ -4358,9 +4358,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "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" ], @@ -4381,9 +4381,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "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" ], @@ -4404,9 +4404,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "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" ], @@ -4427,9 +4427,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "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" ], @@ -4450,9 +4450,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "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" ], @@ -4470,9 +4470,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "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" ], @@ -4920,9 +4920,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -5007,9 +5007,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "license": "MIT", "engines": { "node": ">=18" @@ -5439,9 +5439,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "funding": [ { "type": "opencollective", @@ -5458,7 +5458,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5501,9 +5501,9 @@ } }, "node_modules/prettier": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", - "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -5887,12 +5887,12 @@ } }, "node_modules/rolldown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", - "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.138.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -5902,21 +5902,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.4", - "@rolldown/binding-darwin-arm64": "1.1.4", - "@rolldown/binding-darwin-x64": "1.1.4", - "@rolldown/binding-freebsd-x64": "1.1.4", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", - "@rolldown/binding-linux-arm64-gnu": "1.1.4", - "@rolldown/binding-linux-arm64-musl": "1.1.4", - "@rolldown/binding-linux-ppc64-gnu": "1.1.4", - "@rolldown/binding-linux-s390x-gnu": "1.1.4", - "@rolldown/binding-linux-x64-gnu": "1.1.4", - "@rolldown/binding-linux-x64-musl": "1.1.4", - "@rolldown/binding-openharmony-arm64": "1.1.4", - "@rolldown/binding-wasm32-wasi": "1.1.4", - "@rolldown/binding-win32-arm64-msvc": "1.1.4", - "@rolldown/binding-win32-x64-msvc": "1.1.4" + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" } }, "node_modules/run-parallel": { @@ -6412,9 +6412,9 @@ } }, "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -6687,15 +6687,15 @@ } }, "node_modules/vite": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", - "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.16", - "rolldown": "~1.1.3", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -6712,7 +6712,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -6803,9 +6803,9 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.108.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.3.tgz", - "integrity": "sha512-hOpaCHmQVVY66IVTjofnH14IgSdmod2aquSGHGuYig/OIdWge01Hk2Wt988DZcwXumFUT4+FvJY5N+ikl8o/ww==", + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.8", @@ -6848,9 +6848,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", - "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "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" diff --git a/package.json b/package.json index 491b49c..4a131f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@next2d/builder", - "version": "0.1.8", + "version": "0.1.10", "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", @@ -21,7 +21,9 @@ "Multi-platform builder" ], "scripts": { - "lint": "eslint src/index.ts", + "lint": "eslint src", + "build": "tsc", + "test": "tsc && node --test \"tests/**/*.test.ts\"", "prepare": "tsc", "release": "tsc" }, @@ -38,18 +40,18 @@ }, "dependencies": { "@electron-forge/core": "^7.11.2", - "@types/node": "^26.1.0", + "@types/node": "^26.1.2", "picocolors": "^1.1.1", - "vite": "^8.1.3" + "vite": "^8.2.0" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.5", + "@eslint/eslintrc": "^3.3.6", "@eslint/js": "^10.0.1", - "@typescript-eslint/eslint-plugin": "^8.62.1", - "@typescript-eslint/parser": "^8.62.1", - "eslint": "^10.6.0", + "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/parser": "^8.65.0", + "eslint": "^10.8.0", "eslint-plugin-unused-imports": "^4.4.1", - "globals": "^17.7.0", + "globals": "^17.8.0", "typescript": "^6.0.3" } } diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..c7d5c07 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,182 @@ +// CLI 引数の解析とヘルプ表示。process.argv を読み取り ctx を初期化する。 +import pc from "picocolors"; +import { ctx } from "./context.js"; +import { RECOMMENDED_NODE_VERSION } from "./constants.js"; + +/** + * @description ヘルプを出力して終了 + * Output help and exit + * + * @return {void} + * @method + * @public + */ +export const echoHelp = (): void => +{ + console.log(); + console.log(pc.green("`--platform` can be specified for macOS, Windows, iOS, Android, Xbox, and Web")); + console.log(pc.green("It is not case sensitive.")); + console.log(); + console.log("For build example:"); + console.log("npx @next2d/builder --platform web --env prd"); + console.log(); + console.log("For preview example:"); + console.log("npx @next2d/builder --preview --platform web --env prd"); + console.log(); + 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(); + process.exit(1); +}; + +/** + * @typedef {object} ParsedArgs + */ +export interface ParsedArgs { + platform: string; + environment: string; + preview: boolean; + open: boolean; + build: boolean; + v8Root: string; + hasHelp: boolean; +} + +/** + * @description ビルド対象として受理するプラットフォーム名の集合。 + * @type {Set} + * @constant + */ +export const SUPPORTED_PLATFORMS: Set = new Set([ + "windows", "macos", "linux", + "steam:windows", "steam:macos", "steam:linux", + "ios", "android", "xbox", "web" +]); + +/** + * @description process.argv 等のトークン列を解析する純関数 (副作用なし)。 + * Parse an argv token list into structured flags (pure, no side effects). + * + * @param {string[]} argv + * @return {ParsedArgs} + * @method + * @public + */ +export const parseArgv = (argv: string[]): ParsedArgs => +{ + const result: ParsedArgs = { + "platform": "", + "environment": "", + "preview": false, + "open": false, + "build": false, + "v8Root": "", + "hasHelp": false + }; + + for (let idx: number = 0; idx < argv.length; ++idx) { + + switch (argv[idx]) { + + case "--preview": + result.preview = true; + break; + + case "--open": + result.open = true; + break; + + case "--build": + result.build = true; + break; + + case "--help": + case "--h": + result.hasHelp = true; + break; + + case "--platform": + result.platform = (argv[++idx] || "").toLowerCase(); + break; + + case "--env": + result.environment = argv[++idx] || ""; + break; + + case "--v8-root": + // Xbox ビルドで使う prebuilt V8 monolith のパス (環境変数 V8_ROOT より優先) + result.v8Root = argv[++idx] || ""; + break; + + default: + break; + + } + + if (result.hasHelp) { + break; + } + } + + if (!result.platform || !result.environment) { + result.hasHelp = true; + } + + return result; +}; + +/** + * @description プラットフォーム名をパス表現へ変換する ("steam:windows" -> "steam/windows")。 + * Convert a platform name to its path form. + * + * @param {string} platform + * @return {string} + * @method + * @public + */ +export const derivePlatformDir = (platform: string): string => +{ + return platform.indexOf(":") ? platform.split(":").join("/") : platform; +}; + +/** + * @description Node バージョン検証 → process.argv 解析 → ctx 初期化 → + * プラットフォーム妥当性検証 → platformDir 算出 → 環境変数設定。 + * 不正/ヘルプ要求時は echoHelp() で終了する。 + * Validate Node version, parse argv into ctx, validate platform, + * derive platformDir and export env vars. Exits via echoHelp on error. + * + * @return {void} + * @method + * @public + */ +export const initCli = (): void => +{ + const version: string = process.versions.node; + if (RECOMMENDED_NODE_VERSION > parseInt(version.split(".")[0])) { + pc.red(`You are running Node Version:${version}. +View Generator requires Node ${RECOMMENDED_NODE_VERSION} or higher. +Please update your version of Node.`); + process.exit(1); + } + + const parsed: ParsedArgs = parseArgv(process.argv); + + ctx.platform = parsed.platform; + ctx.environment = parsed.environment; + ctx.preview = parsed.preview; + ctx.open = parsed.open; + ctx.build = parsed.build; + ctx.v8Root = parsed.v8Root; + + if (parsed.hasHelp || !SUPPORTED_PLATFORMS.has(ctx.platform)) { + echoHelp(); + } + + ctx.platformDir = derivePlatformDir(ctx.platform); + + // update env variables + process.env.NEXT2D_EBUILD_ENVIRONMENT = ctx.environment; + process.env.NEXT2D_TARGET_PLATFORM = ctx.platform; +}; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..2480517 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,71 @@ +// ビルダー全体で使う定数群。 +// Constants shared across the builder. + +/** + * @description 推奨 Node.js メジャーバージョン。 + * @type {number} + * @constant + */ +export const RECOMMENDED_NODE_VERSION: number = 22; + +/** + * @type {string} + * @constant + */ +export const CAPACITOR_CONFIG_NAME: string = "capacitor.config.json"; + +/** + * @type {string} + * @constant + */ +export const XBOX_DIR_NAME: string = "xbox"; + +/** + * @type {string} + * @constant + */ +export const XBOX_CONFIG_NAME: string = "MicrosoftGame.config"; + +/** + * @description 自動ダウンロードする prebuilt V8 のバージョン。 + * `.github/workflows/build-v8.yml` で発行した Releases のタグと、 + * `xbox-host-ci.yml` の V8_TAG に一致させること。 + * The prebuilt V8 version to auto-download. Must match the release tag + * published by `build-v8.yml` and V8_TAG in `xbox-host-ci.yml`. + * + * @type {string} + * @constant + */ +export const XBOX_V8_VERSION: string = "13.7.152.19"; + +/** + * @description prebuilt V8 のアセットリビジョン。同一 V8 バージョンでも + * gn フラグ変更でバイナリが変わる場合に上げ、Release タグと + * ローカルキャッシュを分離する。 + * Asset revision. Bump when gn flags change for the same V8 version. + * + * @type {string} + * @constant + */ +export const XBOX_V8_REVISION: string = "r3"; + +/** + * @description ゲーム側の `xbox/` へスキャフォールドしないテンプレート内ファイル。 + * - MicrosoftGame.config : ゲーム固有設定 (injectGameConfig が注入) + * - tests / .v8_headers : builder リポジトリ側の開発用テスト・キャッシュ + * - build 系 : テンプレート内でビルドした場合の生成物 + * Template entries excluded from the per-game `xbox/` scaffold. + * + * @type {Set} + * @constant + */ +export const XBOX_SCAFFOLD_EXCLUDES: Set = new Set([ + XBOX_CONFIG_NAME, + "tests", + ".v8_headers", + "build", + "out", + "_deps", + "CMakeCache.txt", + "CMakeFiles" +]); diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..f721a15 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,46 @@ +// ビルド全体で共有する可変状態。CLI 解析結果 (platform/environment/フラグ) と +// loadConfig が設定するビルドディレクトリ等を保持し、各モジュールが参照/更新する。 +// 単一のオブジェクトにまとめることで、ESM 間でも生きた状態を共有できる。 +// Mutable build-wide state shared across modules via a single object. + +/** + * @typedef {object} BuildContext + */ +export interface BuildContext { + /** ビルド対象プラットフォーム (例 "web" / "xbox" / "steam:windows") */ + platform: string; + /** 環境識別子 (例 "prd" / "dev") */ + environment: string; + /** --preview */ + preview: boolean; + /** --open */ + open: boolean; + /** --build */ + build: boolean; + /** --v8-root (Xbox) */ + v8Root: string; + /** vite の loadConfigFromFile 結果 */ + configObject: any | null; + /** ビルド出力ディレクトリ (dist//) */ + buildDir: string; + /** vite build.outDir (既定 "dist") */ + outDir: string; + /** プラットフォームのパス表現 ("steam:windows" -> "steam/windows") */ + platformDir: string; +} + +/** + * @type {BuildContext} + */ +export const ctx: BuildContext = { + "platform": "", + "environment": "", + "preview": false, + "open": false, + "build": false, + "v8Root": "", + "configObject": null, + "buildDir": "", + "outDir": "dist", + "platformDir": "" +}; diff --git a/src/electron.ts b/src/electron.ts new file mode 100644 index 0000000..bce08e7 --- /dev/null +++ b/src/electron.ts @@ -0,0 +1,178 @@ +// Electron ベースのデスクトップ配布 (Steam / Epic Games Store など)。 +import pc from "picocolors"; +import fs from "fs"; +import cp from "child_process"; +import { api } from "@electron-forge/core"; +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(); + }); + }); +}; + +/** + * @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 => + { + if (code !== 0) { + reject("Failed to remove built resources."); + } + + 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(); + }); + }); +}; + +/** + * @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); + + } + + 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)); + }); + } +}; diff --git a/src/index.ts b/src/index.ts index 2cfc8a5..1d83416 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,981 +2,29 @@ "use strict"; -import pc from "picocolors"; -import fs from "fs"; -import os from "os"; -import path from "path"; -import { fileURLToPath } from "url"; -import cp from "child_process"; -import { loadConfigFromFile } from "vite"; -import { api } from "@electron-forge/core"; +// エントリポイント。CLI を初期化し、Web ビルド → 各プラットフォーム書き出しを実行する。 +// 各プラットフォームの実装は web/steam/native/xbox の各モジュールへ分割している。 +import { ctx } from "./context.js"; +import { initCli, echoHelp } from "./cli.js"; +import { loadConfig, buildWeb } from "./web.js"; +import { buildElectron } from "./electron.js"; +import { runNative, openNative, buildNative } from "./native.js"; +import { buildXbox } from "./xbox.js"; -const recommendeVersion: number = 22; -const version: string = process.versions.node; -if (recommendeVersion > parseInt(version.split(".")[0])) { - pc.red(`You are running Node Version:${version}. -View Generator requires Node ${recommendeVersion} or higher. -Please update your version of Node.`); - process.exit(1); -} - -let platform: string = ""; -let environment: string = ""; -let hasHelp: boolean = false; -let preview: boolean = false; -let open: boolean = false; -let build: boolean = false; -let v8Root: string = ""; - -for (let idx: number = 0; idx < process.argv.length; ++idx) { - - switch (process.argv[idx]) { - - case "--preview": - preview = true; - break; - - case "--open": - open = true; - break; - - case "--build": - build = true; - break; - - case "--help": - case "--h": - hasHelp = true; - break; - - case "--platform": - platform = process.argv[++idx].toLowerCase(); - break; - - case "--env": - environment = process.argv[++idx]; - break; - - case "--v8-root": - // Xbox ビルドで使う prebuilt V8 monolith のパス (環境変数 V8_ROOT より優先) - v8Root = process.argv[++idx] || ""; - break; - - default: - break; - - } - - if (hasHelp) { - break; - } -} - -if (!platform || !environment) { - hasHelp = true; -} - -/** - * @description ヘルプを出力して終了 - * Output help and exit - * - * @return {void} - * @method - * @public - */ -const echoHelp = (): void => -{ - console.log(); - console.log(pc.green("`--platform` can be specified for macOS, Windows, iOS, Android, Xbox, and Web")); - console.log(pc.green("It is not case sensitive.")); - console.log(); - console.log("For build example:"); - console.log("npx @next2d/builder --platform web --env prd"); - console.log(); - console.log("For preview example:"); - console.log("npx @next2d/builder --preview --platform web --env prd"); - console.log(); - 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(); - process.exit(1); -}; - -if (hasHelp) { - echoHelp(); -} - -if (!platform || !environment) { - echoHelp(); -} - -switch (platform) { - - case "windows": - case "macos": - case "linux": - case "steam:windows": - case "steam:macos": - case "steam:linux": - case "ios": - case "android": - case "xbox": - case "web": - break; - - default: - echoHelp(); - break; - -} - -/** - * @type {string} - * @private - */ -const platformDir: string = platform.indexOf(":") ? platform.split(":").join("/") : platform; - -/** - * @type {object} - * @private - */ -let $configObject: any | null = null; - -/** - * @type {string} - * @private - */ -let $buildDir: string = ""; - -/** - * @type {string} - * @private - */ -let $outDir: string = "dist"; - -// update env variables -process.env.NEXT2D_EBUILD_ENVIRONMENT = environment; -process.env.NEXT2D_TARGET_PLATFORM = platform; - -/** - * @type {string} - * @constant - */ -const CAPACITOR_CONFIG_NAME: string = "capacitor.config.json"; - -/** - * @return {Promise} - * @method - * @public - */ -const loadConfig = async (): Promise => -{ - const packageJson = JSON.parse( - fs.readFileSync(`${process.cwd()}/package.json`, { "encoding": "utf8" }) - ); - - if (packageJson.type !== "module") { - packageJson.type = "module"; - - // overwride - fs.writeFileSync( - `${process.cwd()}/package.json`, - JSON.stringify(packageJson, null, 2) - ); - } - - const ext: string = fs.existsSync(`${process.cwd()}/vite.config.ts`) ? "ts" : "js"; - - const config = await loadConfigFromFile( - { - "command": "build", - "mode": "build" - }, - `${process.cwd()}/vite.config.${ext}` - ); - - // update config - $configObject = config; - $outDir = $configObject.config?.build?.outDir || "dist"; - $buildDir = `${process.cwd()}/${$outDir}/${platformDir}/${environment}`; - - if (!fs.existsSync(`${$buildDir}`)) { - fs.mkdirSync(`${$buildDir}`, { "recursive": true }); - console.log(pc.green(`Create build directory: ${$buildDir}`)); - console.log(); - } -}; - -/** - * @description Web用アプリの書き出し関数 - * Export function for web applications - * - * @return {Promise} - * @method - * @public - */ -const buildWeb = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const stream = $spawn("npx", [ - "@next2d/vite-plugin-next2d-auto-loader" - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - reject("vite plugin command failed."); - } - - const stream = $spawn("npx", [ - "vite", - "--outDir", - $buildDir, - "build" - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - reject("Export of `HTML` and `JavaScript` failed."); - } - - console.log(); - console.log(pc.green("`HTML` and `JavaScript` files are written out.")); - resolve(); - }); - }); - }); -}; - -/** - * @description electronがインストールされてなければインストールを実行 - * If electron is not installed, run install. - * - * @return {Promise} - * @method - * @public - */ -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(); - }); - }); -}; - -/** - * @description electronに含むリソースを初期化 - * Initialize resources included in electron - * - * @return {Promise} - * @method - * @public - */ -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 => - { - if (code !== 0) { - reject("Failed to remove built resources."); - } - - console.log(pc.green("Successfully remove built resources.")); - resolve(); - }); - }); -}; - -/** - * @description ビルドしたリソースをコピー - * Copy built resources - * - * @return {Promise} - * @method - * @public - */ -const copyResources = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const stream = cp.spawn("cp", [ - "-r", - `${$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(); - }); - }); -}; - -/** - * @description Windows用アプリの書き出し関数 - * Export function for Windows apps - * - * @return {Promise} - * @method - * @public - */ -const buildSteam = async (): Promise => -{ - // reset - await removeResources(); - - // copy HTML, JavaScript - await copyResources(); - - if (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": `${$outDir}/${platformDir}/build`, - "platform": "", - "arch": "all" - }; - - switch (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); - - } - - api - .package(packageOptions) - .then((): void => - { - console.log(pc.green(`Finished building \`Electron\` for ${platform}.`)); - console.log(); - }) - .catch((error: any): void => - { - console.log(pc.red("Export of Electron failed.")); - console.log(pc.red(error)); - }); - } -}; +// Node バージョン検証・引数解析・ctx 初期化 (不正時はここで終了)。 +initCli(); /** - * @description iOSのプロジェクトを生成 - * Generate iOS project + * @description プラットフォーム別のビルド実行関数 + * Build execution dispatcher per platform * * @return {Promise} * @method * @public */ -const generateNativeProject = (): Promise => -{ - return new Promise((resolve, reject): void => - { - if (fs.existsSync(`${process.cwd()}/${platform}`)) { - return resolve(); - } - - const stream = $spawn("npx", [ - "cap", - "add", - platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - reject(`Failed generated ${platform} project.`); - } - - console.log(pc.green(`Successfully generated ${platform} project.`)); - console.log(); - - resolve(); - }); - }); -}; - -/** - * @description iOS用アプリの書き出し関数 - * Export function for iOS apps - * - * @return {Promise} - * @method - * @public - */ -const runNative = async (): Promise => -{ - await generateNativeProject(); - - /** - * Capacitorの書き出しに必要な設定を生成 - * Generate settings necessary for exporting Capacitor - */ - const config = JSON.parse( - fs.readFileSync(`${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, { "encoding": "utf8" }) - ); - - config.webDir = `${$outDir}/${platformDir}/${environment}/`; - - fs.writeFileSync( - `${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, - JSON.stringify(config, null, 2) - ); - - $spawn("npx", [ - "cap", - "run", - platform - ], { "stdio": "inherit" }); -}; - -/** - * @description iOS/Androidアプリのオープン関数 - * Open function for iOS/Android apps - * - * @return {Promise} - * @method - * @public - */ -const openNative = async (): Promise => -{ - await generateNativeProject(); - - /** - * Capacitorの書き出しに必要な設定を生成 - * Generate settings necessary for exporting Capacitor - */ - const config = JSON.parse( - fs.readFileSync(`${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, { "encoding": "utf8" }) - ); - - config.webDir = `${$outDir}/${platformDir}/${environment}/`; - - fs.writeFileSync( - `${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, - JSON.stringify(config, null, 2) - ); - - const stream = $spawn("npx", [ - "cap", - "sync", - platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - console.log(pc.red(`Failed to sync ${platform} project.`)); - return; - } - - $spawn("npx", [ - "cap", - "open", - platform - ], { "stdio": "inherit" }); - }); -}; - -/** - * @description iOS/Androidアプリのビルド関数 - * Build function for iOS/Android apps - * - * @return {Promise} - * @method - * @public - */ -const buildNative = async (): Promise => -{ - await generateNativeProject(); - - /** - * Capacitorの書き出しに必要な設定を生成 - * Generate settings necessary for exporting Capacitor - */ - const config = JSON.parse( - fs.readFileSync(`${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, { "encoding": "utf8" }) - ); - - config.webDir = `${$outDir}/${platformDir}/${environment}/`; - - fs.writeFileSync( - `${process.cwd()}/${CAPACITOR_CONFIG_NAME}`, - JSON.stringify(config, null, 2) - ); - - const stream = $spawn("npx", [ - "cap", - "sync", - platform - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - console.log(pc.red(`Failed to sync ${platform} project.`)); - return; - } - - $spawn("npx", [ - "cap", - "build", - platform - ], { "stdio": "inherit" }); - }); -}; - -/** - * @type {string} - * @constant - */ -const XBOX_DIR_NAME: string = "xbox"; - -/** - * @description builder パッケージに同梱したテンプレートの絶対パスを取得 - * Get the absolute path of a template shipped with the builder package - * - * @param {string} name - * @return {string} - * @method - * @public - */ -const getTemplateDir = (name: string): string => -{ - // dist/index.js から見た templates/ (パッケージルート直下) - const currentDir: string = path.dirname(fileURLToPath(import.meta.url)); - return path.resolve(currentDir, "..", "templates", name); -}; - -/** - * @type {string} - * @constant - */ -const XBOX_CONFIG_NAME: string = "MicrosoftGame.config"; - -/** - * @description npx / npm を子プロセスとして起動する (クロスプラットフォーム)。 - * Windows では npx/npm が .cmd (バッチファイル) のため、shell 経由で - * ないと起動できない (spawn ENOENT / Node の CVE-2024-27980 対応により - * .cmd の直接 spawn も不可)。スペースを含む引数は二重引用符で保護する。 - * Spawn npx/npm cross-platform. On Windows they are .cmd batch files, - * which require a shell to spawn. - * - * @param {string} command - * @param {array} args - * @param {object} options - * @return {object} - * @method - * @public - */ -const $spawn = (command: string, args: string[], options: object = {}): cp.ChildProcess => -{ - if (process.platform === "win32" && (command === "npx" || command === "npm")) { - const quoted: string[] = args.map((a: string): string => /\s/.test(a) ? `"${a}"` : a); - return cp.spawn(command, quoted, { ...options, "shell": true }); - } - return cp.spawn(command, args, options); -}; - -/** - * @description 自動ダウンロードする prebuilt V8 のバージョン。 - * `.github/workflows/build-v8.yml` で発行した Releases のタグと、 - * `xbox-host-ci.yml` の V8_TAG に一致させること。 - * The prebuilt V8 version to auto-download. Must match the release tag - * published by `build-v8.yml` and V8_TAG in `xbox-host-ci.yml`. - * - * @type {string} - * @constant - */ -const XBOX_V8_VERSION: string = "13.7.152.19"; - -/** - * @description prebuilt V8 のアセットリビジョン。同一 V8 バージョンでも - * gn フラグ変更 (例: r2 = WebAssembly + DrumBrake 有効化) で - * バイナリが変わる場合に上げ、Release タグとローカルキャッシュを分離する。 - * Asset revision. Bump when gn flags change for the same V8 version - * (r2 = WebAssembly + DrumBrake enabled). - * - * @type {string} - * @constant - */ -const XBOX_V8_REVISION: string = "r3"; - -/** - * @description ゲーム側の `xbox/` へスキャフォールドしないテンプレート内ファイル。 - * - MicrosoftGame.config : ゲーム固有設定 (injectGameConfig が注入) - * - tests / .v8_headers : builder リポジトリ側の開発用テスト・キャッシュ - * - build 系 : テンプレート内でビルドした場合の生成物 - * Template entries excluded from the per-game `xbox/` scaffold. - * - * @type {Set} - * @constant - */ -const XBOX_SCAFFOLD_EXCLUDES: Set = new Set([ - XBOX_CONFIG_NAME, - "tests", - ".v8_headers", - "build", - "out", - "_deps", - "CMakeCache.txt", - "CMakeFiles" -]); - -/** - * @description XboxホストのC++/CMake一式をbuilder同梱テンプレートから毎回更新する。 - * ホストは「エンジン相当」でユーザーは編集しない前提のため、常に最新へ上書きする。 - * ただし各ゲーム固有の `MicrosoftGame.config` は対象外(injectGameConfigが扱う)。 - * Refresh the Xbox host (C++/CMake) from the builder's bundled template on every build. - * The host is engine-like and not user-edited, so it is always overwritten, - * excluding the per-game `MicrosoftGame.config` (handled by injectGameConfig). - * - * @return {Promise} - * @method - * @public - */ -const refreshXboxHost = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const projectDir: string = `${process.cwd()}/${XBOX_DIR_NAME}`; - const templateDir: string = getTemplateDir("xbox"); - if (!fs.existsSync(templateDir)) { - return reject(`Xbox template not found: ${templateDir}`); - } - - try { - fs.cpSync(templateDir, projectDir, { - "recursive": true, - // ゲーム固有設定と builder 側の開発用ファイルはスキャフォールドしない - "filter": (src: string): boolean => !XBOX_SCAFFOLD_EXCLUDES.has(path.basename(src)) - }); - console.log(pc.green(`Successfully refreshed \`${XBOX_DIR_NAME}\` host.`)); - resolve(); - } catch (error) { - reject(`Failed to refresh ${XBOX_DIR_NAME} host. ${error}`); - } - }); -}; - -/** - * @description ゲームルートの `MicrosoftGame.config` をXboxホストへ注入する。 - * capacitor.config.json と同様、各ゲーム固有の設定(TitleId/StoreId/名称/ロゴ)は - * プロジェクトルートで管理し、それをホストのビルド対象へ配置する。 - * 未配置ならbuilder同梱の既定をフォールバックとして使い、作成を促す。 - * Inject the game-root `MicrosoftGame.config` into the Xbox host. - * - * @return {Promise} - * @method - * @public - */ -const injectGameConfig = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const rootConfig: string = `${process.cwd()}/${XBOX_CONFIG_NAME}`; - const hostConfig: string = `${process.cwd()}/${XBOX_DIR_NAME}/${XBOX_CONFIG_NAME}`; - - try { - if (fs.existsSync(rootConfig)) { - fs.cpSync(rootConfig, hostConfig); - console.log(pc.green(`Applied project \`${XBOX_CONFIG_NAME}\`.`)); - } else { - // フォールバック: builder同梱の既定を配置し、ルートへの作成を促す - fs.cpSync(`${getTemplateDir("xbox")}/${XBOX_CONFIG_NAME}`, hostConfig); - console.log(pc.yellow(`\`${XBOX_CONFIG_NAME}\` not found at project root. Using the default template.`)); - console.log(pc.yellow(`Create \`${XBOX_CONFIG_NAME}\` at your project root (like capacitor.config.json) to set TitleId/StoreId/logos.`)); - } - resolve(); - } catch (error) { - reject(`Failed to apply ${XBOX_CONFIG_NAME}. ${error}`); - } - }); -}; - -/** - * @description ビルド済みWeb資材(JS/HTML/アセット)をXboxホストのassetsへ配置 - * Deploy built web resources (JS/HTML/assets) into the Xbox host assets - * - * @return {Promise} - * @method - * @public - */ -const copyXboxResources = (): Promise => -{ - return new Promise((resolve, reject): void => - { - const assetsDir: string = `${process.cwd()}/${XBOX_DIR_NAME}/assets/app`; - - try { - // reset - fs.rmSync(assetsDir, { "recursive": true, "force": true }); - fs.mkdirSync(assetsDir, { "recursive": true }); - - // copy built web resources (JS/HTML/assets) - fs.cpSync(`${$buildDir}/`, assetsDir, { "recursive": true }); - - console.log(pc.green("Successfully copy built resources to Xbox host.")); - resolve(); - } catch (error) { - reject(`Failed to copy built resources to Xbox host. ${error}`); - } - }); -}; - -/** - * @description Xbox ビルドに使う V8 のパスを解決する。優先順: - * 1. `--v8-root` 引数 - * 2. 環境変数 `V8_ROOT` - * 3. キャッシュ済みの prebuilt (%LOCALAPPDATA%/next2d/v8/) - * 4. GitHub Releases (build-v8.yml が発行) から自動ダウンロード - * 3・4 により、通常は何も指定せずに `--platform xbox` だけでビルドできる。 - * Resolve the V8 path for the Xbox build. With the cached/auto-downloaded - * prebuilt, no flag or env var is required in the common case. - * - * @return {Promise} - * @method - * @public - */ -const resolveXboxV8Root = async (): Promise => -{ - // 1. --v8-root 引数 - if (v8Root) { - return path.resolve(process.cwd(), v8Root); - } - - // 2. 環境変数 V8_ROOT - if (process.env.V8_ROOT) { - return process.env.V8_ROOT; - } - - // 3. キャッシュ済み prebuilt - const cacheBase: string = process.env.LOCALAPPDATA - ? `${process.env.LOCALAPPDATA}/next2d` - : `${os.homedir()}/.cache/next2d`; - const cacheDir: string = `${cacheBase}/v8/${XBOX_V8_VERSION}-${XBOX_V8_REVISION}`; - if (fs.existsSync(`${cacheDir}/include/v8.h`)) { - return cacheDir; - } - - // 4. GitHub Releases から自動ダウンロード (マシンごとに初回のみ) - const assetName: string = `v8-monolith-${XBOX_V8_VERSION}-windows-x64.zip`; - const url: string = `https://github.com/Next2D/builder/releases/download/v8-${XBOX_V8_VERSION}-windows-x64-${XBOX_V8_REVISION}/${assetName}`; - - console.log(pc.green(`Downloading prebuilt V8 ${XBOX_V8_VERSION} (first time only) ...`)); - console.log(url); - - const response = await fetch(url); - if (!response.ok) { - throw new Error(`download failed: HTTP ${response.status}`); - } - - fs.mkdirSync(cacheDir, { "recursive": true }); - const zipPath: string = `${cacheDir}/${assetName}.tmp`; - fs.writeFileSync(zipPath, Buffer.from(await response.arrayBuffer())); - - // Windows 10+ 標準の tar (bsdtar) は zip も展開できる - const extract = cp.spawnSync("tar", ["-xf", zipPath, "-C", cacheDir], { "stdio": "inherit" }); - fs.rmSync(zipPath, { "force": true }); - if (extract.status !== 0 || !fs.existsSync(`${cacheDir}/include/v8.h`)) { - fs.rmSync(cacheDir, { "recursive": true, "force": true }); - throw new Error("failed to extract the prebuilt V8 archive"); - } - - console.log(pc.green(`Prebuilt V8 cached at: ${cacheDir}`)); - return cacheDir; -}; - -/** - * @description Xbox(GDKネイティブ)用アプリの書き出し関数。 - * V8にNext2DのJSを載せ、Dawn(WebGPU/D3D12)で描画するC++ホストをビルドする。 - * Export function for Xbox (GDK native) apps. - * Builds a C++ host that runs Next2D's JS on V8 and renders via Dawn (WebGPU/D3D12). - * - * @return {Promise} - * @method - * @public - */ -const buildXbox = async (): Promise => -{ - // C++/CMake ホストを最新へ更新 (ゲーム固有設定は除外) - await refreshXboxHost(); - // ゲームルートの MicrosoftGame.config を注入 - await injectGameConfig(); - // ビルド済みWeb資材を配置 - await copyXboxResources(); - - /** - * GDK ビルドは Windows + Visual Studio + Microsoft GDK が必須。 - * それ以外の環境ではスキャフォールドと資材配置のみを行い、手順を案内する。 - * The GDK build requires Windows + Visual Studio + Microsoft GDK. - * On other platforms we only scaffold and copy assets, then guide the next steps. - */ - if (process.platform !== "win32") { - console.log(); - console.log(pc.yellow("Xbox host project has been generated and assets were copied.")); - console.log(pc.yellow("The GDK build must run on Windows with Visual Studio and the Microsoft GDK installed.")); - console.log(pc.yellow(`See ${XBOX_DIR_NAME}/README.md for the build steps.`)); - console.log(); - return; - } - - /** - * @type {string} - * GDK の対象コンソール世代。既定は Xbox Series X|S (Scarlett)。 - * 必要に応じて `Gaming.Xbox.XboxOne.x64` / `Gaming.Desktop.x64` を選択。 - */ - const gdkArch: string = process.env.NEXT2D_XBOX_ARCH || "Gaming.Xbox.Scarlett.x64"; - const cmakeBuildDir: string = `${process.cwd()}/${$outDir}/${platformDir}/build`; - - /** - * prebuilt V8 のパスを解決する (--v8-root > V8_ROOT > キャッシュ > 自動ダウンロード)。 - * 通常は何も指定せずにビルドできる。 - */ - let resolvedV8Root: string = ""; - try { - resolvedV8Root = await resolveXboxV8Root(); - } catch (error) { - console.log(pc.red(`Failed to prepare the prebuilt V8: ${error}`)); - console.log(pc.red("Fallbacks:")); - console.log(pc.red(" 1) Retry later (the download may have failed temporarily).")); - console.log(pc.red(" 2) Build V8 yourself and pass `--v8-root `:")); - console.log(pc.red(" npx @next2d/builder --platform xbox --env prd --v8-root C:\\path\\to\\v8")); - console.log(pc.red(` See ${XBOX_DIR_NAME}/README.md ("V8 の用意") for the build steps.`)); - return; - } - if (!fs.existsSync(`${resolvedV8Root}/include/v8.h`)) { - console.log(pc.red(`\`include/v8.h\` not found under: ${resolvedV8Root}`)); - console.log(pc.red(`Check the \`--v8-root\` path. See ${XBOX_DIR_NAME}/README.md for how to prepare the prebuilt V8.`)); - return; - } - - const configure = (): Promise => - { - return new Promise((resolve, reject): void => - { - const stream = cp.spawn("cmake", [ - "-S", `${process.cwd()}/${XBOX_DIR_NAME}`, - "-B", cmakeBuildDir, - "-G", "Visual Studio 17 2022", - "-A", gdkArch, - "-D", `V8_ROOT=${resolvedV8Root}` - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - return reject("Failed to configure the Xbox (CMake/GDK) project."); - } - resolve(); - }); - }); - }; - - await configure(); - - if (open || preview) { - // Visual Studio ソリューションを開く (実機/エミュレータでの実行はVS側で行う) - cp.spawn("cmake", [ - "--open", cmakeBuildDir - ], { "stdio": "inherit" }); - return; - } - - // フルビルド (Release パッケージまで)。失敗時は reject して非ゼロ終了させる - // (以前は表示のみで exit code 0 のまま成功扱いになっていた) - await new Promise((resolve, reject): void => - { - const stream = cp.spawn("cmake", [ - "--build", cmakeBuildDir, - "--config", "Release" - ], { "stdio": "inherit" }); - - stream.on("close", (code: number): void => - { - if (code !== 0) { - return reject("Export of the Xbox (GDK) package failed."); - } - console.log(); - console.log(pc.green(`Finished building the Xbox (GDK) package for ${gdkArch}.`)); - console.log(); - resolve(); - }); - }); -}; - -/** - * @description ビルドの実行関数 - * Build Execution Functions - * - * @return {void} - * @method - * @public - */ const multiBuild = async (): Promise => { - switch (platform) { + switch (ctx.platform) { case "windows": case "macos": @@ -984,22 +32,22 @@ const multiBuild = async (): Promise => case "steam:windows": case "steam:macos": case "steam:linux": - await buildSteam(); + await buildElectron(); break; case "ios": case "android": switch (true) { - case open: + case ctx.open: await openNative(); break; - case build: + case ctx.build: await buildNative(); break; - case preview: + case ctx.preview: await runNative(); break; @@ -1043,4 +91,4 @@ const execute = async (): Promise => } }; -execute(); \ No newline at end of file +execute(); diff --git a/src/native.ts b/src/native.ts new file mode 100644 index 0000000..07f468a --- /dev/null +++ b/src/native.ts @@ -0,0 +1,158 @@ +// iOS / Android (Capacitor) ビルド。 +import pc from "picocolors"; +import fs from "fs"; +import { ctx } from "./context.js"; +import { $spawn } from "./utils.js"; +import { CAPACITOR_CONFIG_NAME } from "./constants.js"; + +/** + * @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 => + { + if (code !== 0) { + reject(`Failed generated ${ctx.platform} project.`); + } + + 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) + ); +}; + +/** + * @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" }); +}; + +/** + * @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" }); + }); +}; + +/** + * @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; + } + + $spawn("npx", [ + "cap", + "build", + ctx.platform + ], { "stdio": "inherit" }); + }); +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..1bef2fa --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,44 @@ +// 汎用ユーティリティ (テンプレート解決 / 子プロセス起動)。 +import path from "path"; +import cp from "child_process"; +import { fileURLToPath } from "url"; + +/** + * @description builder パッケージに同梱したテンプレートの絶対パスを取得 + * Get the absolute path of a template shipped with the builder package + * + * @param {string} name + * @return {string} + * @method + * @public + */ +export const getTemplateDir = (name: string): string => +{ + // dist/*.js から見た templates/ (パッケージルート直下) + const currentDir: string = path.dirname(fileURLToPath(import.meta.url)); + return path.resolve(currentDir, "..", "templates", name); +}; + +/** + * @description npx / npm を子プロセスとして起動する (クロスプラットフォーム)。 + * Windows では npx/npm が .cmd (バッチファイル) のため、shell 経由で + * ないと起動できない (spawn ENOENT / Node の CVE-2024-27980 対応により + * .cmd の直接 spawn も不可)。スペースを含む引数は二重引用符で保護する。 + * Spawn npx/npm cross-platform. On Windows they are .cmd batch files, + * which require a shell to spawn. + * + * @param {string} command + * @param {array} args + * @param {object} options + * @return {object} + * @method + * @public + */ +export const $spawn = (command: string, args: string[], options: object = {}): cp.ChildProcess => +{ + if (process.platform === "win32" && (command === "npx" || command === "npm")) { + const quoted: string[] = args.map((a: string): string => /\s/.test(a) ? `"${a}"` : a); + return cp.spawn(command, quoted, { ...options, "shell": true }); + } + return cp.spawn(command, args, options); +}; diff --git a/src/web.ts b/src/web.ts new file mode 100644 index 0000000..504768d --- /dev/null +++ b/src/web.ts @@ -0,0 +1,95 @@ +// Web ビルド: vite 設定の読み込みと HTML/JavaScript の書き出し。 +import pc from "picocolors"; +import fs from "fs"; +import { loadConfigFromFile } from "vite"; +import { ctx } from "./context.js"; +import { $spawn } from "./utils.js"; + +/** + * @description package.json / vite.config を読み取り、ビルドディレクトリ等を ctx へ設定 + * Load vite config and populate build directories into ctx + * + * @return {Promise} + * @method + * @public + */ +export const loadConfig = async (): Promise => +{ + const packageJson = JSON.parse( + fs.readFileSync(`${process.cwd()}/package.json`, { "encoding": "utf8" }) + ); + + if (packageJson.type !== "module") { + packageJson.type = "module"; + + // overwride + fs.writeFileSync( + `${process.cwd()}/package.json`, + JSON.stringify(packageJson, null, 2) + ); + } + + const ext: string = fs.existsSync(`${process.cwd()}/vite.config.ts`) ? "ts" : "js"; + + const config = await loadConfigFromFile( + { + "command": "build", + "mode": "build" + }, + `${process.cwd()}/vite.config.${ext}` + ); + + // update config + ctx.configObject = config; + ctx.outDir = ctx.configObject.config?.build?.outDir || "dist"; + ctx.buildDir = `${process.cwd()}/${ctx.outDir}/${ctx.platformDir}/${ctx.environment}`; + + if (!fs.existsSync(`${ctx.buildDir}`)) { + fs.mkdirSync(`${ctx.buildDir}`, { "recursive": true }); + console.log(pc.green(`Create build directory: ${ctx.buildDir}`)); + console.log(); + } +}; + +/** + * @description Web用アプリの書き出し関数 + * Export function for web applications + * + * @return {Promise} + * @method + * @public + */ +export const buildWeb = (): Promise => +{ + return new Promise((resolve, reject): void => + { + const stream = $spawn("npx", [ + "@next2d/vite-plugin-next2d-auto-loader" + ], { "stdio": "inherit" }); + + stream.on("close", (code: number): void => + { + if (code !== 0) { + reject("vite plugin command failed."); + } + + const stream = $spawn("npx", [ + "vite", + "--outDir", + ctx.buildDir, + "build" + ], { "stdio": "inherit" }); + + stream.on("close", (code: number): void => + { + if (code !== 0) { + reject("Export of `HTML` and `JavaScript` failed."); + } + + console.log(); + console.log(pc.green("`HTML` and `JavaScript` files are written out.")); + resolve(); + }); + }); + }); +}; diff --git a/src/xbox.ts b/src/xbox.ts new file mode 100644 index 0000000..b338148 --- /dev/null +++ b/src/xbox.ts @@ -0,0 +1,488 @@ +// Xbox (GDK ネイティブ) ビルド。V8 に Next2D の JS を載せ、Dawn(WebGPU/D3D12)で +// 描画する C++ ホストを、テンプレートのスキャフォールド → 資材配置 → pak 埋め込み → +// CMake/GDK ビルドの順で書き出す。 +import pc from "picocolors"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import cp from "child_process"; +import { minifySync } from "vite"; +import { ctx } from "./context.js"; +import { getTemplateDir } from "./utils.js"; +import { + XBOX_DIR_NAME, + XBOX_CONFIG_NAME, + XBOX_V8_VERSION, + XBOX_V8_REVISION, + XBOX_SCAFFOLD_EXCLUDES +} from "./constants.js"; + +/** + * @description XboxホストのC++/CMake一式をbuilder同梱テンプレートから毎回更新する。 + * ホストは「エンジン相当」でユーザーは編集しない前提のため、常に最新へ上書きする。 + * ただし各ゲーム固有の `MicrosoftGame.config` は対象外(injectGameConfigが扱う)。 + * Refresh the Xbox host (C++/CMake) from the builder's bundled template. + * + * @return {Promise} + * @method + * @private + */ +const refreshXboxHost = (): Promise => +{ + return new Promise((resolve, reject): void => + { + const projectDir: string = `${process.cwd()}/${XBOX_DIR_NAME}`; + const templateDir: string = getTemplateDir("xbox"); + if (!fs.existsSync(templateDir)) { + return reject(`Xbox template not found: ${templateDir}`); + } + + try { + fs.cpSync(templateDir, projectDir, { + "recursive": true, + // ゲーム固有設定と builder 側の開発用ファイルはスキャフォールドしない + "filter": (src: string): boolean => !XBOX_SCAFFOLD_EXCLUDES.has(path.basename(src)) + }); + console.log(pc.green(`Successfully refreshed \`${XBOX_DIR_NAME}\` host.`)); + resolve(); + } catch (error) { + reject(`Failed to refresh ${XBOX_DIR_NAME} host. ${error}`); + } + }); +}; + +/** + * @description ゲームルートの `MicrosoftGame.config` をXboxホストへ注入する。 + * 未配置ならbuilder同梱の既定をフォールバックとして使い、作成を促す。 + * Inject the game-root `MicrosoftGame.config` into the Xbox host. + * + * @return {Promise} + * @method + * @private + */ +const injectGameConfig = (): Promise => +{ + return new Promise((resolve, reject): void => + { + const rootConfig: string = `${process.cwd()}/${XBOX_CONFIG_NAME}`; + const hostConfig: string = `${process.cwd()}/${XBOX_DIR_NAME}/${XBOX_CONFIG_NAME}`; + + try { + if (fs.existsSync(rootConfig)) { + fs.cpSync(rootConfig, hostConfig); + console.log(pc.green(`Applied project \`${XBOX_CONFIG_NAME}\`.`)); + } else { + // フォールバック: builder同梱の既定を配置し、ルートへの作成を促す + fs.cpSync(`${getTemplateDir("xbox")}/${XBOX_CONFIG_NAME}`, hostConfig); + console.log(pc.yellow(`\`${XBOX_CONFIG_NAME}\` not found at project root. Using the default template.`)); + console.log(pc.yellow(`Create \`${XBOX_CONFIG_NAME}\` at your project root (like capacitor.config.json) to set TitleId/StoreId/logos.`)); + } + resolve(); + } catch (error) { + reject(`Failed to apply ${XBOX_CONFIG_NAME}. ${error}`); + } + }); +}; + +/** + * @description ビルド済みWeb資材(JS/HTML/アセット)をXboxホストのassetsへ配置 + * Deploy built web resources (JS/HTML/assets) into the Xbox host assets + * + * @return {Promise} + * @method + * @private + */ +const copyXboxResources = (): Promise => +{ + return new Promise((resolve, reject): void => + { + const assetsDir: string = `${process.cwd()}/${XBOX_DIR_NAME}/assets/app`; + + try { + // reset + fs.rmSync(assetsDir, { "recursive": true, "force": true }); + fs.mkdirSync(assetsDir, { "recursive": true }); + + // copy built web resources (JS/HTML/assets) + fs.cpSync(`${ctx.buildDir}/`, assetsDir, { "recursive": true }); + + console.log(pc.green("Successfully copy built resources to Xbox host.")); + resolve(); + } catch (error) { + reject(`Failed to copy built resources to Xbox host. ${error}`); + } + }); +}; + +/** + * @description ディレクトリを再帰的に走査し、各ファイルの + * [posix 相対キー, 絶対パス] を集める。 + * Walk a directory recursively, collecting [posixRelKey, absPath] pairs. + * + * @param {string} rootDir 走査ルート + * @param {string} [prefix] キーへ付与する接頭辞 (例 "" / "assets/") + * @return {Array<[string, string]>} + * @method + * @public + */ +export const walkFiles = (rootDir: string, prefix: string = ""): [string, string][] => +{ + const out: [string, string][] = []; + if (!fs.existsSync(rootDir)) { + return out; + } + for (const entry of fs.readdirSync(rootDir, { "withFileTypes": true })) { + const abs: string = path.join(rootDir, entry.name); + const key: string = prefix ? `${prefix}${entry.name}` : entry.name; + if (entry.isDirectory()) { + out.push(...walkFiles(abs, `${key}/`)); + } else if (entry.isFile()) { + out.push([key, abs]); + } + } + return out; +}; + +/** + * @description (key, データ) の並びを exe 埋め込み用 pak バイナリへ直列化する純関数。 + * host 側 EmbeddedAssets.cpp の ParseEmbeddedPak と対になる。 + * フォーマット (リトルエンディアン uint32): + * magic "N2DA" / version(=1) / count / [keyLen,key,dataLen,data]... + * Serialize (key, data) entries into the embedded pak binary. + * + * @param {Array<[string, Buffer]>} entries + * @return {Buffer} + * @method + * @public + */ +export const buildPak = (entries: [string, Buffer][]): Buffer => +{ + const chunks: Buffer[] = []; + const header: Buffer = Buffer.alloc(12); + header.write("N2DA", 0, "ascii"); + header.writeUInt32LE(1, 4); // version + header.writeUInt32LE(entries.length, 8); // count + chunks.push(header); + + for (const [key, data] of entries) { + const keyBuf: Buffer = Buffer.from(key, "utf8"); + const meta: Buffer = Buffer.alloc(4); + meta.writeUInt32LE(keyBuf.length, 0); + chunks.push(meta, keyBuf); + const meta2: Buffer = Buffer.alloc(4); + meta2.writeUInt32LE(data.length, 0); + chunks.push(meta2, data); + } + + return Buffer.concat(chunks); +}; + +/** + * @description assets.pak を RCDATA "N2DASSETS" として取り込む .rc の内容を生成する純関数。 + * 相対パスだと rc.exe の CWD (build ディレクトリ) 基準で探して見つからない + * (RC2135) ため絶対パスを埋める。RC はフォワードスラッシュを受理する。 + * 文字列は必ずダブルクォート (RC 仕様。シングルクォートは不可)。 + * Render the .rc content that embeds assets.pak as RCDATA. + * + * @param {string} pakAbsPath assets.pak の絶対パス + * @return {string} + * @method + * @public + */ +export const renderAssetsRc = (pakAbsPath: string): string => +{ + const forward: string = pakAbsPath.replace(/\\/g, "/"); + return `N2DASSETS RCDATA "${forward}"\n`; +}; + +/** + * @description ホストスクリプト(js/bootstrap.js 等)を minify + 難読化する純関数。 + * Oxc(vite 同梱)でコメント除去・空白圧縮・ローカル識別子マングルを行う。 + * `globalThis.*` 等のグローバル名はゲームが参照するため保持される。 + * 失敗時は元コードをそのまま返す(埋め込み自体は継続)。 + * Minify/obfuscate a host script; returns the original on failure. + * + * @param {string} name ファイル名 (拡張子で構文判定) + * @param {string} code 元の JavaScript + * @return {string} + * @method + * @public + */ +export const minifyJs = (name: string, code: string): string => +{ + try { + const result = minifySync(name, code); + if (result.errors && result.errors.length > 0) { + const msg = (result.errors[0] as any)?.message ?? String(result.errors[0]); + console.log(pc.yellow(`Xbox: minify skipped for ${name} (${msg}); embedding as-is.`)); + return code; + } + return result.code; + } catch (error) { + console.log(pc.yellow(`Xbox: minify failed for ${name}; embedding as-is. ${error}`)); + return code; + } +}; + +/** + * @description Xbox ホストの assets/app とホストスクリプト(js/bootstrap.js 等)を + * 単一の pak バイナリへまとめ、`assets.pak` と RCDATA 参照用の + * `assets.rc` を xbox/ 直下へ生成する。CMake が assets.rc を検出して + * これらを exe 内リソースへ**必ず**埋め込み、平文 JS/HTML を配布物に残さない。 + * 埋め込みは必須(非埋め込みの選択肢は無い)。資材が無ければハードエラー。 + * + * pak フォーマット (リトルエンディアン uint32): + * magic "N2DA" / version(=1) / count / [keyLen,key,dataLen,data]... + * キーは assets/app 基準の posix 相対パス、または "js/bootstrap.js"。 + * host 側 EmbeddedAssets.cpp の ParseEmbeddedPak と対になる。 + * + * @return {Promise} + * @method + * @private + */ +const embedXboxAssets = (): 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`; + + 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)."); + } + + // (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")]; + } + return [key, fs.readFileSync(abs)]; + } + ); + 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"); + + 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).")); + + resolve(); + } catch (error) { + reject(`Failed to embed Xbox assets. ${error}`); + } + }); +}; + +/** + * @description Xbox ビルドに使う V8 のパスを解決する。優先順: + * 1. `--v8-root` 引数 + * 2. 環境変数 `V8_ROOT` + * 3. キャッシュ済みの prebuilt (%LOCALAPPDATA%/next2d/v8/) + * 4. GitHub Releases (build-v8.yml が発行) から自動ダウンロード + * 3・4 により、通常は何も指定せずに `--platform xbox` だけでビルドできる。 + * Resolve the V8 path for the Xbox build. + * + * @return {Promise} + * @method + * @private + */ +const resolveXboxV8Root = async (): Promise => +{ + // 1. --v8-root 引数 + if (ctx.v8Root) { + return path.resolve(process.cwd(), ctx.v8Root); + } + + // 2. 環境変数 V8_ROOT + if (process.env.V8_ROOT) { + return process.env.V8_ROOT; + } + + // 3. キャッシュ済み prebuilt + const cacheBase: string = process.env.LOCALAPPDATA + ? `${process.env.LOCALAPPDATA}/next2d` + : `${os.homedir()}/.cache/next2d`; + const cacheDir: string = `${cacheBase}/v8/${XBOX_V8_VERSION}-${XBOX_V8_REVISION}`; + if (fs.existsSync(`${cacheDir}/include/v8.h`)) { + return cacheDir; + } + + // 4. GitHub Releases から自動ダウンロード (マシンごとに初回のみ) + const assetName: string = `v8-monolith-${XBOX_V8_VERSION}-windows-x64.zip`; + const url: string = `https://github.com/Next2D/builder/releases/download/v8-${XBOX_V8_VERSION}-windows-x64-${XBOX_V8_REVISION}/${assetName}`; + + console.log(pc.green(`Downloading prebuilt V8 ${XBOX_V8_VERSION} (first time only) ...`)); + console.log(url); + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`download failed: HTTP ${response.status}`); + } + + fs.mkdirSync(cacheDir, { "recursive": true }); + const zipPath: string = `${cacheDir}/${assetName}.tmp`; + fs.writeFileSync(zipPath, Buffer.from(await response.arrayBuffer())); + + // Windows 10+ 標準の tar (bsdtar) は zip も展開できる + const extract = cp.spawnSync("tar", ["-xf", zipPath, "-C", cacheDir], { "stdio": "inherit" }); + fs.rmSync(zipPath, { "force": true }); + if (extract.status !== 0 || !fs.existsSync(`${cacheDir}/include/v8.h`)) { + fs.rmSync(cacheDir, { "recursive": true, "force": true }); + throw new Error("failed to extract the prebuilt V8 archive"); + } + + console.log(pc.green(`Prebuilt V8 cached at: ${cacheDir}`)); + return cacheDir; +}; + +/** + * @description Xbox(GDKネイティブ)用アプリの書き出し関数。 + * V8にNext2DのJSを載せ、Dawn(WebGPU/D3D12)で描画するC++ホストをビルドする。 + * Export function for Xbox (GDK native) apps. + * + * @return {Promise} + * @method + * @public + */ +export const buildXbox = async (): Promise => +{ + // C++/CMake ホストを最新へ更新 (ゲーム固有設定は除外) + await refreshXboxHost(); + // ゲームルートの MicrosoftGame.config を注入 + await injectGameConfig(); + // ビルド済みWeb資材を配置 + await copyXboxResources(); + // assets/app + host スクリプトを exe 埋め込み用 pak (+ rc) へまとめる + await embedXboxAssets(); + + /** + * GDK ビルドは Windows + Visual Studio + Microsoft GDK が必須。 + * それ以外の環境ではスキャフォールドと資材配置のみを行い、手順を案内する。 + * The GDK build requires Windows + Visual Studio + Microsoft GDK. + */ + if (process.platform !== "win32") { + console.log(); + console.log(pc.yellow("Xbox host project has been generated and assets were copied.")); + console.log(pc.yellow("The GDK build must run on Windows with Visual Studio and the Microsoft GDK installed.")); + console.log(pc.yellow(`See ${XBOX_DIR_NAME}/README.md for the build steps.`)); + console.log(); + return; + } + + /** + * @type {string} + * GDK の対象コンソール世代。既定は Xbox Series X|S (Scarlett)。 + */ + const gdkArch: string = process.env.NEXT2D_XBOX_ARCH || "Gaming.Xbox.Scarlett.x64"; + const cmakeBuildDir: string = `${process.cwd()}/${ctx.outDir}/${ctx.platformDir}/build`; + + /** + * prebuilt V8 のパスを解決する (--v8-root > V8_ROOT > キャッシュ > 自動ダウンロード)。 + */ + let resolvedV8Root: string = ""; + try { + resolvedV8Root = await resolveXboxV8Root(); + } catch (error) { + console.log(pc.red(`Failed to prepare the prebuilt V8: ${error}`)); + console.log(pc.red("Fallbacks:")); + console.log(pc.red(" 1) Retry later (the download may have failed temporarily).")); + console.log(pc.red(" 2) Build V8 yourself and pass `--v8-root `:")); + console.log(pc.red(" npx @next2d/builder --platform xbox --env prd --v8-root C:\\path\\to\\v8")); + console.log(pc.red(` See ${XBOX_DIR_NAME}/README.md ("V8 の用意") for the build steps.`)); + return; + } + if (!fs.existsSync(`${resolvedV8Root}/include/v8.h`)) { + console.log(pc.red(`\`include/v8.h\` not found under: ${resolvedV8Root}`)); + console.log(pc.red(`Check the \`--v8-root\` path. See ${XBOX_DIR_NAME}/README.md for how to prepare the prebuilt V8.`)); + return; + } + + const configure = (): Promise => + { + return new Promise((resolve, reject): void => + { + const stream = cp.spawn("cmake", [ + "-S", `${process.cwd()}/${XBOX_DIR_NAME}`, + "-B", cmakeBuildDir, + "-G", "Visual Studio 17 2022", + "-A", gdkArch, + "-D", `V8_ROOT=${resolvedV8Root}` + ], { "stdio": "inherit" }); + + stream.on("close", (code: number): void => + { + if (code !== 0) { + return reject("Failed to configure the Xbox (CMake/GDK) project."); + } + resolve(); + }); + }); + }; + + await configure(); + + if (ctx.open || ctx.preview) { + // Visual Studio ソリューションを開く (実機/エミュレータでの実行はVS側で行う) + cp.spawn("cmake", [ + "--open", cmakeBuildDir + ], { "stdio": "inherit" }); + return; + } + + // フルビルド (Release パッケージまで)。失敗時は reject して非ゼロ終了させる + await new Promise((resolve, reject): void => + { + const stream = cp.spawn("cmake", [ + "--build", cmakeBuildDir, + "--config", "Release" + ], { "stdio": "inherit" }); + + stream.on("close", (code: number): void => + { + if (code !== 0) { + return reject("Export of the Xbox (GDK) package failed."); + } + console.log(); + console.log(pc.green(`Finished building the Xbox (GDK) package for ${gdkArch}.`)); + console.log(); + resolve(); + }); + }); +}; diff --git a/templates/xbox/CMakeLists.txt b/templates/xbox/CMakeLists.txt index d7aff88..c003855 100644 --- a/templates/xbox/CMakeLists.txt +++ b/templates/xbox/CMakeLists.txt @@ -89,10 +89,24 @@ file(GLOB_RECURSE NEXT2D_HOST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.h" ) +# 埋め込みアセット: builder の buildXbox が assets.pak と assets.rc を必ず生成し、 +# RCDATA "N2DASSETS" として exe にリンクする (ゲーム JS/HTML は平文で隣に置かない)。 +# 埋め込みは必須。assets.rc が無いのは builder を経由せず cmake を直接叩いた +# ホスト単体ビルド (ゲーム資材なし) のみで、その場合はゲームは起動しない。 +set(NEXT2D_ASSETS_RC "${CMAKE_CURRENT_SOURCE_DIR}/assets.rc") +if(EXISTS "${NEXT2D_ASSETS_RC}") + enable_language(RC) + list(APPEND NEXT2D_HOST_SOURCES "${NEXT2D_ASSETS_RC}") + message(STATUS "Next2D: embedding assets from ${NEXT2D_ASSETS_RC}") +else() + message(WARNING "Next2D: assets.rc not found - host-only build (no game assets). Run @next2d/builder to embed.") +endif() + add_executable(Next2DXboxHost WIN32 ${NEXT2D_HOST_SOURCES}) target_include_directories(Next2DXboxHost PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}" # third_party/ (stb_image, dr_mp3 等) ) # 展開レイアウト (GDK_ROOT) 使用時: include/lib を明示指定し、実行時 DLL を並べる @@ -124,6 +138,20 @@ if(MSVC) # Release でも PDB を生成する (クラッシュダンプ解析でスタックを名前解決するため) target_compile_options(Next2DXboxHost PRIVATE /Zi) target_link_options(Next2DXboxHost PRIVATE /DEBUG /OPT:REF /OPT:ICF) + + # Release: リンク時コード生成 (LTCG)。翻訳単位を跨いだインライン化が効き、 + # 毎フレーム通る V8 バインディング層 (小さな関数の呼び出し連鎖) に有効。 + # (VS ジェネレータでは COMPILE_OPTIONS は C++ (ClCompile) にのみ渡り、 + # RC (assets.rc) には影響しない。/Zi と同じ流儀) + target_compile_options(Next2DXboxHost PRIVATE $<$:/GL>) + target_link_options(Next2DXboxHost PRIVATE $<$:/LTCG>) + + # /arch:AVX2: Xbox Series X|S (Zen 2) と Haswell (2013) 以降の PC で動作。 + # ソフトラスタ (RasterCore) や pak 解析などホスト C++ の自動ベクトル化が進む。 + # Xbox One (Jaguar) は AVX2 非対応のため除外する。 + if(NOT CMAKE_GENERATOR_PLATFORM MATCHES "XboxOne") + target_compile_options(Next2DXboxHost PRIVATE /arch:AVX2) + endif() endif() # webgpu_dawn は SHARED (DLL)。実行に必要なため exe の隣へステージする @@ -135,14 +163,25 @@ if(TARGET webgpu_dawn) COMMENT "Staging webgpu_dawn.dll next to the executable") endif() -# WebGPU / D3D12 / GameInput / WIC / XAudio2 / GDK +# WebGPU / D3D12 / GameInput / XAudio2 / GDK target_link_libraries(Next2DXboxHost PRIVATE d3d12 dxgi dxguid - windowscodecs # WIC: 画像デコード ) +if(NOT NEXT2D_IS_CONSOLE) + # デスクトップ専用 (Game Core OS に存在しない): + # windowscodecs = WIC (stb_image で扱えない形式の画像デコードフォールバック) + # dbghelp = クラッシュハンドラのスタックトレース (StackWalk64/SymFromAddr) + # MF (mfplat 等)/d2d1/dwrite/shlwapi/winmm はソース側の #pragma comment(lib) が + # 同じ NEXT2D_XBOX_CONSOLE ガード内でリンク指示する。 + target_link_libraries(Next2DXboxHost PRIVATE + windowscodecs + dbghelp + ) +endif() + if(NEXT2D_IS_CONSOLE) # コンソール用ライブラリ (GDK が提供) target_link_libraries(Next2DXboxHost PRIVATE @@ -176,18 +215,15 @@ target_compile_definitions(Next2DXboxHost PRIVATE # ----------------------------------------------------------------------------- # ランタイム資材のステージング -# builder が assets/app に JS/HTML/アセットを配置する。 -# js/bootstrap.js はブラウザ相当グローバルを整える起動スクリプト。 +# ゲーム JS/HTML/アセット (assets/app) とホストスクリプト (js/bootstrap.js) は +# builder が assets.pak にまとめて exe 内 RCDATA へ**必ず**埋め込む +# (非埋め込みの選択肢は無い。平文 JS/HTML を配布物に残さないのが目的)。 +# したがって exe 隣へは assets/ も js/ もコピーしない。 +# MicrosoftGame.config (GDK タイトル設定) だけを exe の隣へステージングする。 # ----------------------------------------------------------------------------- add_custom_command(TARGET Next2DXboxHost POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/assets" - "$/assets" - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_CURRENT_SOURCE_DIR}/js" - "$/js" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/MicrosoftGame.config" "$/MicrosoftGame.config" - COMMENT "Staging Next2D assets, bootstrap JS and MicrosoftGame.config" + COMMENT "Staging MicrosoftGame.config (assets/JS embedded in exe)" ) diff --git a/templates/xbox/README.md b/templates/xbox/README.md index 0be7b33..e668e65 100644 --- a/templates/xbox/README.md +++ b/templates/xbox/README.md @@ -378,3 +378,21 @@ xbox/ ├─ Gamepad.cpp DomShims.cpp └─ webgpu/{WebGPU.cpp,WebGPUCommon.h,WebGPUEnums.h} ``` + +--- + +## コンソール (Game Core OS) とテキスト描画・フォント + +コンソールには DirectWrite / システムフォントが存在しないため、`fillText`/`measureText` +は同梱の stb_truetype 実装 (`platform/StbTextRasterizer`) が担う。フォントは実行時登録: + +- **ゲーム資材に TTF/OTF を含める**(推奨): Web ビルドの資材 (`assets/app`) に + `*.ttf` / `*.otf` があれば起動時に自動登録される (vite の `public/` に置く等)。 + 日本語テキストを使う場合は Noto Sans JP (OFL) などの同梱を推奨。 +- 開発ビルド: exe 隣接の `fonts/` ディレクトリからも読み込む。 +- フォント未登録の場合、テキストは近似メトリクスのみ (グリフ描画なし) になる。 + +デスクトップ (PC/GDK) は従来どおり DirectWrite が主経路のため、フォント同梱は不要。 +同様に、画像は stb_image、音声は dr_mp3/dr_wav/stb_vorbis が全プラットフォーム共通の +第一経路で、WIC / Media Foundation はデスクトップ限定のフォールバック +(AAC/TIFF 等の非標準形式向け)。コンソールでの動画 (`