diff --git a/.gitea/workflows/project-ci.yml b/.gitea/workflows/project-ci.yml index d9212897f..3db0f637b 100644 --- a/.gitea/workflows/project-ci.yml +++ b/.gitea/workflows/project-ci.yml @@ -78,9 +78,6 @@ jobs: - name: Install npm dependencies run: bash scripts/ci-npm-ci-with-retry.sh - - name: Install AI game creator dependencies - run: bash scripts/ci-npm-ci-with-retry.sh --prefix apps/ai-game-creator-shell - - name: Run repository checks run: npm run check:repository-ci @@ -100,9 +97,6 @@ jobs: - name: Install npm dependencies run: bash scripts/ci-npm-ci-with-retry.sh - - name: Install AI game creator dependencies - run: bash scripts/ci-npm-ci-with-retry.sh --prefix apps/ai-game-creator-shell - - name: Run frontend and script tests run: npm run test @@ -213,9 +207,6 @@ jobs: - name: Install npm dependencies run: bash scripts/ci-npm-ci-with-retry.sh - - name: Install AI game creator dependencies - run: bash scripts/ci-npm-ci-with-retry.sh --prefix apps/ai-game-creator-shell - - name: Prepare native Rust dependencies shell: bash run: | diff --git a/README.md b/README.md index f84bee3fb..29bd49c26 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ npm install ``` +该命令会按根 `package.json` 的 npm workspaces 一次安装主站、Admin、Mobile、Desktop、AGC、Preview Deployer、内部 packages 与工具依赖;仓库只使用根 `package-lock.json`,不要在子目录单独执行 `npm install` / `npm ci` 或提交嵌套 lockfile。 + 准备环境变量: - 复制 `.env.example` 为 `.env.local` diff --git a/apps/admin-web/package.json b/apps/admin-web/package.json index 883c0688e..6e16afab7 100644 --- a/apps/admin-web/package.json +++ b/apps/admin-web/package.json @@ -10,6 +10,7 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { + "@genarrative/shared": "0.1.0", "@vitejs/plugin-react": "^5.0.4", "lucide-react": "^0.546.0", "react": "^19.0.0", @@ -17,8 +18,11 @@ "vite": "^6.2.0" }, "devDependencies": { + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "typescript": "~5.8.2" + "typescript": "~5.8.2", + "vitest": "^0.34.6" } } diff --git a/apps/ai-game-creator-shell/package-lock.json b/apps/ai-game-creator-shell/package-lock.json deleted file mode 100644 index f694b7dd0..000000000 --- a/apps/ai-game-creator-shell/package-lock.json +++ /dev/null @@ -1,5214 +0,0 @@ -{ - "name": "@genarrative/ai-game-creator-shell", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@genarrative/ai-game-creator-shell", - "version": "0.1.0", - "dependencies": { - "@cubone/react-file-manager": "^1.35.0", - "@lexical/react": "^0.47.0", - "@lexical/utils": "^0.47.0", - "@tauri-apps/api": "^2.11.1", - "@tauri-apps/plugin-clipboard-manager": "2.3.2", - "@tauri-apps/plugin-dialog": "^2.7.2", - "@tauri-apps/plugin-http": "^2.5.9", - "@tauri-apps/plugin-opener": "~2", - "@vitejs/plugin-react": "^5.0.4", - "focus-trap-react": "^12.0.3", - "lexical": "^0.47.0", - "lucide-react": "^0.546.0", - "react": "^19.0.0", - "react-arborist": "^3.16.0", - "react-colorful": "^5.8.0", - "react-dom": "^19.0.0", - "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1", - "vite": "^6.2.0", - "zustand": "^5.0.14" - }, - "devDependencies": { - "@openai/codex": "0.147.0", - "@tailwindcss/vite": "^4.1.14", - "@tauri-apps/cli": "^2.11.2", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "tailwindcss": "^4.1.14", - "typescript": "~5.8.2" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cubone/react-file-manager": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/@cubone/react-file-manager/-/react-file-manager-1.35.0.tgz", - "integrity": "sha512-yvMDY0DXw3JMPhJeFUahJI8NsmSStPU8cOOr6v3i9aYeIioQYVfm0jkVyHAKoW3wKlPgIOc0lL4BI2PV6FejQw==", - "license": "MIT", - "dependencies": { - "@fontsource/nunito-sans": "^5.2.7", - "i18next": "^25.0.0", - "react-collapsed": "^4.2.0", - "react-icons": "^5.4.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", - "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", - "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.8.0", - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.27.20", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.20.tgz", - "integrity": "sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.9", - "@floating-ui/utils": "^0.2.12", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", - "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.8.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", - "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", - "license": "MIT" - }, - "node_modules/@fontsource/nunito-sans": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@fontsource/nunito-sans/-/nunito-sans-5.3.0.tgz", - "integrity": "sha512-CctNhebQ2YYJUwVMCAkHexQg7yedfz9s8JzaETALyc9QIQuDFRlNTWXux9H5PsNLgptPkrzRDomAdLJt1sDFpQ==", - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lexical/a11y": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/a11y/-/a11y-0.47.0.tgz", - "integrity": "sha512-MlAL25PxThTYQuFn2aOwyfY9+/wKloAd8fbf4a1GeFATVgvirC8xACBmERpqpf4A06OdLIoIZ5J/qtgJXsdn1g==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/clipboard": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.47.0.tgz", - "integrity": "sha512-OIB+NxHjfpRJJoTtU/mqeb7vPVMks5l97QgwoZthQLR/GIc5zrOtvoLftxl5hKAG5lwOe9BzhashKgVcsSPB3g==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/list": "0.47.0", - "@lexical/selection": "0.47.0", - "@lexical/utils": "0.47.0", - "@types/trusted-types": "^2.0.7", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/code-core": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/code-core/-/code-core-0.47.0.tgz", - "integrity": "sha512-L0hg+57yzQDhYRV5NFyTwSz2+N3O/iLBgRuMNccE3ieltPPSq7lCBTKOuXF3G0ot3NVudu8//ewHjw7OCMqI5g==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/devtools-core": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.47.0.tgz", - "integrity": "sha512-u/+WHfs9p6Jiz83OdWzM2W7xc/BXUqN/RN/o97qFP+H7XX/D5fD3Sn0q/0ymyDgacN7sI4HNxQttkrKXKn1PCA==", - "license": "MIT", - "dependencies": { - "@lexical/html": "0.47.0", - "@lexical/link": "0.47.0", - "@lexical/mark": "0.47.0", - "@lexical/table": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "react": ">=18.x", - "react-dom": ">=18.x", - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/dragon": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.47.0.tgz", - "integrity": "sha512-qqkro6sTLddOh2aYSqoXtm1QOQ/DY71AtWj3XmHPIPtxN/PkVKbvJBVZiP/a9kgFoPs+2a2Tlq9cCIDf6xSHuw==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/extension": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/extension/-/extension-0.47.0.tgz", - "integrity": "sha512-F9WyXB0eDsCbeSuShzgjBS3ugxNnTvHQa+P+VZiVSWilbDNGjJyysco2JCwn5ruLmEP4MXCQn5IKeW7A9FG8KQ==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0", - "@lexical/utils": "0.47.0", - "@preact/signals-core": "^1.14.1", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/hashtag": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.47.0.tgz", - "integrity": "sha512-i5ML0v5d52h8hen4cdS/tnqfiteWbvpwihJgZR9hLIk9w6vQJISXUWzg4jRPQIezWwU1xikbBLkT5rD+Qth7tw==", - "license": "MIT", - "dependencies": { - "@lexical/text": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/history": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.47.0.tgz", - "integrity": "sha512-CqAR6G4x6WewKS2uDgf2N1/rszIlXiDCW4dwu0IiQV9oVbhOklvAmPBJX6319myJxB+ArtjuuMgDU3gi/iTqXA==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/html": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.47.0.tgz", - "integrity": "sha512-VrJ9tSRuoUP5tTQb7TB+6PTW0Gh5aC3Hnne4GuXDwKUIzoWFXWkxlB0oIlfL5HVljrzO/Ra65v5M3RnR4TX1PA==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/selection": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/internal": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/internal/-/internal-0.47.0.tgz", - "integrity": "sha512-VKDTHajXvjCkjYHwWra4Qjz8AI6PWeNPW7l6Q9AIlAwUiolAXrlkyfrBAFzdAP360IyDbC2gO9fwTb9ZvZM85Q==", - "license": "MIT", - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/link": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.47.0.tgz", - "integrity": "sha512-lDAbTyJwufKs3yUn1D4AY0IoBU9O4JXzTDqSRa8qVCK7LC3+cF0u+rR5o9RJfxtHUzmALk+s2I0IqlxGCyIocg==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/list": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.47.0.tgz", - "integrity": "sha512-g8AApVZNqqcCDXfJtgxEfAv7ylK0HmblRaJYMxB0OI7nlLipUjutIUy8cRX5GKI0woa+oGPYi68cZcMQnBysGw==", - "license": "MIT", - "dependencies": { - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/mark": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.47.0.tgz", - "integrity": "sha512-/9ZS9NSjrqJ/sTZ7l2KFiBxZbbXEmPp/JxtN5sTlM+F4a/hTRreROWlcXgTLyf9g8GTnRyZScvBqFYKWd6HR1g==", - "license": "MIT", - "dependencies": { - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/markdown": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.47.0.tgz", - "integrity": "sha512-mtfcomzk/ZT2wtImEzYeFB6VLsOYM6RqBmwnvEMp5alvJISXFhIHDKAT4BkUMMtWGM6AnrlytZyMNPb4QwnZIA==", - "license": "MIT", - "dependencies": { - "@lexical/code-core": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/link": "0.47.0", - "@lexical/list": "0.47.0", - "@lexical/rich-text": "0.47.0", - "@lexical/selection": "0.47.0", - "@lexical/text": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/overflow": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.47.0.tgz", - "integrity": "sha512-XR+F2mni27TgjmE2/PsFYXkAq0AIMV8JJh5VSeHiSg0mccKSfEqsfTxYlwV+um2eGT+6XggtpwFMDy6ZjC+uMw==", - "license": "MIT", - "dependencies": { - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/plain-text": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.47.0.tgz", - "integrity": "sha512-MyRjCpEnDJuEeoT/ndYI/i2605QM2bAgFEi6sX/NObYDJSuupC2iY6tOEbCFZ1s0Y1SX6arNrrAPhwygcemz7g==", - "license": "MIT", - "dependencies": { - "@lexical/clipboard": "0.47.0", - "@lexical/dragon": "0.47.0", - "@lexical/extension": "0.47.0", - "@lexical/selection": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/react": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.47.0.tgz", - "integrity": "sha512-4y2iEKghKcYcJ8+GoO8pqyvwjJFVDWR71Ezm37lLQGmSTFKY50miTJmgKI12GeL4hLWQjePpB3eVdmSQHG1b7g==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.27.19", - "@lexical/a11y": "0.47.0", - "@lexical/devtools-core": "0.47.0", - "@lexical/dragon": "0.47.0", - "@lexical/extension": "0.47.0", - "@lexical/hashtag": "0.47.0", - "@lexical/history": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/link": "0.47.0", - "@lexical/list": "0.47.0", - "@lexical/mark": "0.47.0", - "@lexical/markdown": "0.47.0", - "@lexical/overflow": "0.47.0", - "@lexical/plain-text": "0.47.0", - "@lexical/rich-text": "0.47.0", - "@lexical/table": "0.47.0", - "@lexical/text": "0.47.0", - "@lexical/utils": "0.47.0", - "@lexical/yjs": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "react": ">=18.x", - "react-dom": ">=18.x", - "typescript": ">=5.2", - "yjs": ">=13.5.22" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "yjs": { - "optional": true - } - } - }, - "node_modules/@lexical/rich-text": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.47.0.tgz", - "integrity": "sha512-GtRH7KNW7fVJzd3Xftdr/EPXaMqHt2xCIO/eJtf17Yrs7vlVOljM0xMcDoj4QOY2Gp4p3CheLlwBbcO96YYV0A==", - "license": "MIT", - "dependencies": { - "@lexical/clipboard": "0.47.0", - "@lexical/dragon": "0.47.0", - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/selection": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/selection": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.47.0.tgz", - "integrity": "sha512-/q+eXnryZxCeqeWAODhTRlJL+jGa6/vIhE/bh+KvHmLbZJM8qfwa0qzt4rb3g+L1/CbjcowS/Xwv2ha1OmjBFQ==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/table": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.47.0.tgz", - "integrity": "sha512-T0sOB1i0I05f2U+tTVXDitYq4ACtFob42gULwwqqKfQ/SEPq26Kfd5CljI1rSS4+N+vQz/VTDYF/BN7ZMgHpjA==", - "license": "MIT", - "dependencies": { - "@lexical/clipboard": "0.47.0", - "@lexical/extension": "0.47.0", - "@lexical/html": "0.47.0", - "@lexical/internal": "0.47.0", - "@lexical/utils": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/text": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.47.0.tgz", - "integrity": "sha512-Rj1xa/MMhgOckjOJ+ifDUO8BUyi7o+a/vpYcsftMTCpiKmeteO+qSAKoyhvZke496nLh/WFCWKw/iD43bst/Ig==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/utils": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.47.0.tgz", - "integrity": "sha512-uVAqNr5qiE4t9GSj+L4rv06WaIGADzblxEKCn134sPzvsVouDv0VTT0pBKjQYUgshT4z9iW5cFtO11+iPacBYg==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0", - "@lexical/selection": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@lexical/yjs": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.47.0.tgz", - "integrity": "sha512-EKw1df2cmUTQrfSp1EnXqsHtNjwgxS973CRor0W4GWmIQJyKdmf6cmA7cct3flkyH5tE/xDnpr8sy38U4R2hlQ==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0", - "@lexical/selection": "0.47.0", - "lexical": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2", - "yjs": ">=13.5.22" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@openai/codex": { - "version": "0.147.0", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0.tgz", - "integrity": "sha512-EQLEXecAG2ptxI7UpBMo2TR/ga5596/c/OsYF/0LoUDh5JANZ7IoGqlzBEWbuEVQ76JePIbtTW/ihCkp1a7Z3w==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "codex": "bin/codex.js" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@openai/codex-darwin-arm64": "npm:@openai/codex@0.147.0-darwin-arm64", - "@openai/codex-darwin-x64": "npm:@openai/codex@0.147.0-darwin-x64", - "@openai/codex-linux-arm64": "npm:@openai/codex@0.147.0-linux-arm64", - "@openai/codex-linux-x64": "npm:@openai/codex@0.147.0-linux-x64", - "@openai/codex-win32-arm64": "npm:@openai/codex@0.147.0-win32-arm64", - "@openai/codex-win32-x64": "npm:@openai/codex@0.147.0-win32-x64" - } - }, - "node_modules/@openai/codex-darwin-arm64": { - "name": "@openai/codex", - "version": "0.147.0-darwin-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-darwin-arm64.tgz", - "integrity": "sha512-BEUVkiOW7kLcRyrMLfAr/h9wF8sRVJyZDy6OHtVn6QGDXiv3BvAZVTY1Pu9xF7KdIdkYXbp4uayN0aDQQaAUJw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-darwin-x64": { - "name": "@openai/codex", - "version": "0.147.0-darwin-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-darwin-x64.tgz", - "integrity": "sha512-Tb8McE5SvJIH0Vs5R6sq7u+quiC931yan2KOOl6km1OdZ82+Wi7eF5XrSFPs5CF7xCgoIK4Vs+byMbT5hN+ZUw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-linux-arm64": { - "name": "@openai/codex", - "version": "0.147.0-linux-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-linux-arm64.tgz", - "integrity": "sha512-SLC1JXw2TYfr/c3HhrJubyyLelq7vTOLWVmiThFA+z0+WgzCPmaseJ/kzDD3Gge/TO7fCnnj7UcPmC0d2c8XAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-linux-x64": { - "name": "@openai/codex", - "version": "0.147.0-linux-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-linux-x64.tgz", - "integrity": "sha512-0W9MBxPpWW0cSkNqrTDN2jR7rzzT7oNMhQY5446lT2Lw5cz5yhDTck4Va9rjkQEm+HlFzP/dmEMSZbXfJsINmw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-win32-arm64": { - "name": "@openai/codex", - "version": "0.147.0-win32-arm64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-win32-arm64.tgz", - "integrity": "sha512-e2ZstJ8zT8Rm1nvR7CUVO+Gr3cTChE41+VfOzGhynzDXEoW0wfbjUQbc2bWbh1arG94LMm4y3dqBtUIbSrfeGA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@openai/codex-win32-x64": { - "name": "@openai/codex", - "version": "0.147.0-win32-x64", - "resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.147.0-win32-x64.tgz", - "integrity": "sha512-oT7Ss5fAPf2fiWE9QNURqZcQGAAawSVxmIUdgPzckq4KFZAM+pRz9JbM4Rr498CjtbNgTOjWvDJ+DXvIBSfOPA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@preact/signals-core": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.4.tgz", - "integrity": "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/@react-dnd/asap": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", - "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==", - "license": "MIT" - }, - "node_modules/@react-dnd/invariant": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", - "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==", - "license": "MIT" - }, - "node_modules/@react-dnd/shallowequal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", - "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", - "license": "MIT" - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", - "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", - "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", - "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", - "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", - "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", - "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", - "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", - "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", - "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", - "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", - "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", - "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", - "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", - "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", - "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", - "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", - "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", - "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", - "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", - "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", - "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", - "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", - "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", - "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", - "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", - "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tailwindcss/node": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", - "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.24.1", - "jiti": "^2.7.0", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.3.3" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", - "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-arm64": "4.3.3", - "@tailwindcss/oxide-darwin-x64": "4.3.3", - "@tailwindcss/oxide-freebsd-x64": "4.3.3", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", - "@tailwindcss/oxide-linux-x64-musl": "4.3.3", - "@tailwindcss/oxide-wasm32-wasi": "4.3.3", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", - "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", - "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", - "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", - "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", - "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", - "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", - "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", - "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", - "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", - "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.11.1", - "@emnapi/runtime": "^1.11.1", - "@emnapi/wasi-threads": "^1.2.2", - "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "inBundle": true, - "license": "0BSD", - "optional": true - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", - "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", - "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", - "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.3", - "@tailwindcss/oxide": "4.3.3", - "tailwindcss": "4.3.3" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", - "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", - "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.11.4", - "@tauri-apps/cli-darwin-x64": "2.11.4", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", - "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", - "@tauri-apps/cli-linux-arm64-musl": "2.11.4", - "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", - "@tauri-apps/cli-linux-x64-gnu": "2.11.4", - "@tauri-apps/cli-linux-x64-musl": "2.11.4", - "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", - "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", - "@tauri-apps/cli-win32-x64-msvc": "2.11.4" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", - "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", - "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", - "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", - "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", - "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", - "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", - "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", - "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", - "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", - "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", - "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/plugin-clipboard-manager": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.2.tgz", - "integrity": "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.2.tgz", - "integrity": "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.11.0" - } - }, - "node_modules/@tauri-apps/plugin-http": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.5.9.tgz", - "integrity": "sha512-lCiY0+vs4HvIUSvZrBs8TC3TiCB0MOPRmiUjTq4prW7SlcJE2jdLeT6KBsJrT9Tlplufl7W1pY6SFAO3gCWxDA==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.11.0" - } - }, - "node_modules/@tauri-apps/plugin-opener": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.4.tgz", - "integrity": "sha512-1HnPkb+AmgO29HBazm4uPLKB+r7zzcTBW1d0fyYp1uP+jwtpoiNDGKMMzz58SFp49nOIrxdE3aUJtT57lfO9CQ==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.11.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "license": "MIT" - }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", - "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/hast": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", - "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", - "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", - "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.44", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.44.tgz", - "integrity": "sha512-T3ghW+sl/ZJ8w1v/yQx3qvJ9040DWoLBz8JT/CILbAKcFyG9b2MRe75v6W5uXjv6uH1lumK2Kv46y2zSkcej0Q==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", - "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "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": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001806", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", - "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dnd-core": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-14.0.1.tgz", - "integrity": "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==", - "license": "MIT", - "dependencies": { - "@react-dnd/asap": "^4.0.0", - "@react-dnd/invariant": "^2.0.0", - "redux": "^4.1.1" - } - }, - "node_modules/dnd-core/node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.394", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.394.tgz", - "integrity": "sha512-Wmt2Gm0o8JWBuGgmc4XZ0u9s1RaCRqhxP47phplmfg04+qypTUurpeJGP45A7Fhv7jdrrVH44PLlR9qXo37cVQ==", - "license": "ISC" - }, - "node_modules/enhanced-resolve": { - "version": "5.24.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz", - "integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-util-is-identifier-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", - "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/focus-trap": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-8.2.2.tgz", - "integrity": "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==", - "license": "MIT", - "dependencies": { - "tabbable": "^6.5.0" - } - }, - "node_modules/focus-trap-react": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-12.0.3.tgz", - "integrity": "sha512-4eXtzhRTtFrxle9Tkl0Wkj8SFJnWz3i3yb5e53Mdn4E8XZQ/Lzqom9U4uUAJ8wd7yDmyGVP96dxwQZXLXd4sbg==", - "license": "MIT", - "dependencies": { - "focus-trap": "^8.2.2", - "tabbable": "^6.5.0" - }, - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", - "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/html-url-attributes": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", - "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/i18next": { - "version": "25.10.10", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.10.10.tgz", - "integrity": "sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==", - "funding": [ - { - "type": "individual", - "url": "https://www.locize.com/i18next" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - }, - { - "type": "individual", - "url": "https://www.locize.com" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2" - }, - "peerDependencies": { - "typescript": "^5 || ^6" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/inline-style-parser": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", - "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "license": "MIT" - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isomorphic.js": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", - "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", - "license": "MIT", - "peer": true, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "devOptional": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lexical": { - "version": "0.47.0", - "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.47.0.tgz", - "integrity": "sha512-ZKsxsk3jUpXsRtG20EBq42z2bq8A20UHtjqvVT/kIxfsaiXwaRFBBcLSFxPa77j+hXkBF5w96C3/imwtmLoRdg==", - "license": "MIT", - "dependencies": { - "@lexical/internal": "0.47.0" - }, - "peerDependencies": { - "typescript": ">=5.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/lib0": { - "version": "0.2.117", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", - "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", - "license": "MIT", - "peer": true, - "dependencies": { - "isomorphic.js": "^0.2.4" - }, - "bin": { - "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", - "0gentesthtml": "bin/gentesthtml.js", - "0serve": "bin/0serve.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "devOptional": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.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" - } - }, - "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==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.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==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lucide-react": { - "version": "0.546.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.546.0.tgz", - "integrity": "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-jsx": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", - "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdxjs-esm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", - "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "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" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.21.tgz", - "integrity": "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/property-information": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", - "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-arborist": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/react-arborist/-/react-arborist-3.16.0.tgz", - "integrity": "sha512-Pp1TQXETKMeg/0yAMGBFIwze7IfZaghDScX4vekJ4Ge8/t7uYifbDBhEGUSOYaDuahE1f8fyBIrlN2pLD4ysTA==", - "license": "MIT", - "dependencies": { - "react-dnd": "^14.0.3", - "react-dnd-html5-backend": "^14.0.3", - "react-window": "^1.8.11", - "redux": "^5.0.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": ">= 16.14", - "react-dom": ">= 16.14" - } - }, - "node_modules/react-collapsed": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-collapsed/-/react-collapsed-4.2.0.tgz", - "integrity": "sha512-8rQuV3wGTjBI4M1WzD5DpqCBRliHNDKEEFeC6tAQEVrRDic46xBOtySgMGjVVacIobEaMf7AiYlI5APCZIZtkg==", - "license": "MIT", - "dependencies": { - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17 || ^18 || ^19", - "react-dom": "^16.9.0 || ^17 || ^18 || ^19" - } - }, - "node_modules/react-colorful": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.8.0.tgz", - "integrity": "sha512-Wy9OzPfjSN9bF12OB8N7UQvlsZ0I+7wHxpN+bV5BjNQGxOj6IiwkRjevJK9yOBjJWGQvAaf1OXtn8rUeEatAng==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/react-dnd": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.5.tgz", - "integrity": "sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A==", - "license": "MIT", - "dependencies": { - "@react-dnd/invariant": "^2.0.0", - "@react-dnd/shallowequal": "^2.0.0", - "dnd-core": "14.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - }, - "peerDependencies": { - "@types/hoist-non-react-statics": ">= 3.3.1", - "@types/node": ">= 12", - "@types/react": ">= 16", - "react": ">= 16.14" - }, - "peerDependenciesMeta": { - "@types/hoist-non-react-statics": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-dnd-html5-backend": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz", - "integrity": "sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==", - "license": "MIT", - "dependencies": { - "dnd-core": "14.0.1" - } - }, - "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.7" - } - }, - "node_modules/react-icons": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz", - "integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-markdown": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", - "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "html-url-attributes": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=18", - "react": ">=18" - } - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-window": { - "version": "1.8.11", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", - "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.0.0", - "memoize-one": ">=3.1.1 <6" - }, - "engines": { - "node": ">8.0.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rollup": { - "version": "4.62.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", - "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.9" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.62.2", - "@rollup/rollup-android-arm64": "4.62.2", - "@rollup/rollup-darwin-arm64": "4.62.2", - "@rollup/rollup-darwin-x64": "4.62.2", - "@rollup/rollup-freebsd-arm64": "4.62.2", - "@rollup/rollup-freebsd-x64": "4.62.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", - "@rollup/rollup-linux-arm-musleabihf": "4.62.2", - "@rollup/rollup-linux-arm64-gnu": "4.62.2", - "@rollup/rollup-linux-arm64-musl": "4.62.2", - "@rollup/rollup-linux-loong64-gnu": "4.62.2", - "@rollup/rollup-linux-loong64-musl": "4.62.2", - "@rollup/rollup-linux-ppc64-gnu": "4.62.2", - "@rollup/rollup-linux-ppc64-musl": "4.62.2", - "@rollup/rollup-linux-riscv64-gnu": "4.62.2", - "@rollup/rollup-linux-riscv64-musl": "4.62.2", - "@rollup/rollup-linux-s390x-gnu": "4.62.2", - "@rollup/rollup-linux-x64-gnu": "4.62.2", - "@rollup/rollup-linux-x64-musl": "4.62.2", - "@rollup/rollup-openbsd-x64": "4.62.2", - "@rollup/rollup-openharmony-arm64": "4.62.2", - "@rollup/rollup-win32-arm64-msvc": "4.62.2", - "@rollup/rollup-win32-ia32-msvc": "4.62.2", - "@rollup/rollup-win32-x64-gnu": "4.62.2", - "@rollup/rollup-win32-x64-msvc": "4.62.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/style-to-js": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", - "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", - "license": "MIT", - "dependencies": { - "style-to-object": "1.0.14" - } - }, - "node_modules/style-to-object": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", - "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "license": "MIT", - "dependencies": { - "inline-style-parser": "0.2.7" - } - }, - "node_modules/tabbable": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", - "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", - "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yjs": { - "version": "13.6.31", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.31.tgz", - "integrity": "sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==", - "license": "MIT", - "peer": true, - "dependencies": { - "lib0": "^0.2.99" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "GitHub Sponsors ❤", - "url": "https://github.com/sponsors/dmonad" - } - }, - "node_modules/zustand": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", - "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index 2932477af..5e0546d77 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -32,10 +32,13 @@ "agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill", "agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs", "agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill", - "typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs" + "typecheck": "tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs" }, "dependencies": { "@cubone/react-file-manager": "^1.35.0", + "@genarrative/image-canvas-core": "0.1.0", + "@genarrative/image-canvas-react": "0.1.0", + "@genarrative/shared": "0.1.0", "@lexical/react": "^0.47.0", "@lexical/utils": "^0.47.0", "@tauri-apps/api": "^2.11.1", @@ -60,9 +63,12 @@ "@openai/codex": "0.147.0", "@tailwindcss/vite": "^4.1.14", "@tauri-apps/cli": "^2.11.2", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "tailwindcss": "^4.1.14", - "typescript": "~5.8.2" + "typescript": "~5.8.2", + "vitest": "^0.34.6" } } diff --git a/apps/ai-game-creator-shell/src-tauri/build.rs b/apps/ai-game-creator-shell/src-tauri/build.rs index 4faf2aaba..d63faf051 100644 --- a/apps/ai-game-creator-shell/src-tauri/build.rs +++ b/apps/ai-game-creator-shell/src-tauri/build.rs @@ -47,6 +47,10 @@ fn stage_bundled_codex_cli(manifest_dir: &std::path::Path) { let app_root = manifest_dir .parent() .expect("AI 游戏创作 Tauri manifest 必须位于应用目录下"); + let repo_root = app_root + .parent() + .and_then(|apps_dir| apps_dir.parent()) + .expect("AI 游戏创作应用必须位于仓库 apps 目录下"); let source_candidates = [ app_root.join( "node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc", @@ -54,6 +58,12 @@ fn stage_bundled_codex_cli(manifest_dir: &std::path::Path) { app_root.join( "node_modules/@openai/codex/node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc", ), + repo_root.join( + "node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc", + ), + repo_root.join( + "node_modules/@openai/codex/node_modules/@openai/codex-win32-x64/vendor/x86_64-pc-windows-msvc", + ), ]; let source = source_candidates .iter() @@ -65,12 +75,12 @@ fn stage_bundled_codex_cli(manifest_dir: &std::path::Path) { .cloned() .unwrap_or_else(|| { panic!( - "内置 Codex CLI 缺失;请先在 apps/ai-game-creator-shell 执行 npm ci(已检查:{})", + "内置 Codex CLI 缺失;请先在仓库根目录执行 npm ci(已检查:{})", source_candidates .iter() - .map(|path| path.display().to_string()) - .collect::>() - .join(";") + .map(|path| path.display().to_string()) + .collect::>() + .join(";") ) }); let target_dir = manifest_dir.join("resources/codex/win-x64"); diff --git a/apps/ai-game-creator-shell/vite.config.ts b/apps/ai-game-creator-shell/vite.config.ts index f616247b6..8f784f2a0 100644 --- a/apps/ai-game-creator-shell/vite.config.ts +++ b/apps/ai-game-creator-shell/vite.config.ts @@ -1,4 +1,5 @@ import { existsSync, readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -8,6 +9,10 @@ import { defineConfig } from 'vite'; const appRoot = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(appRoot, '../..'); +const appRequire = createRequire(resolve(appRoot, 'package.json')); +const reactFileManagerPackageRoot = dirname( + appRequire.resolve('@cubone/react-file-manager/package.json'), +); function resolveDevApiTarget() { const statePath = resolve(repoRoot, '.app/dev-stack.json'); @@ -75,8 +80,8 @@ export default defineConfig({ { find: /^@cubone\/react-file-manager$/, replacement: resolve( - appRoot, - 'node_modules/@cubone/react-file-manager/dist/react-file-manager.es.js', + reactFileManagerPackageRoot, + 'dist/react-file-manager.es.js', ), }, { diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 0765ae9cc..993d07933 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -7,8 +7,13 @@ const packagePath = new URL('../package.json', import.meta.url); const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8')); const rootPackagePath = new URL('../../../package.json', import.meta.url); const rootPackageConfig = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8')); -const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url); -const rootPackageLock = JSON.parse(fs.readFileSync(rootPackageLockPath, 'utf8')); +const rootPackageLockPath = new URL( + '../../../package-lock.json', + import.meta.url, +); +const rootPackageLock = JSON.parse( + fs.readFileSync(rootPackageLockPath, 'utf8'), +); const nativeShellCheckPath = new URL( '../../../scripts/check-native-shells.mjs', import.meta.url, @@ -41,7 +46,10 @@ const nativeAppHostBridgePath = new URL( '../../../src/services/host-bridge/nativeAppHostBridge.ts', import.meta.url, ); -const nativeAppHostBridgeSource = fs.readFileSync(nativeAppHostBridgePath, 'utf8'); +const nativeAppHostBridgeSource = fs.readFileSync( + nativeAppHostBridgePath, + 'utf8', +); const h5HostBridgePath = new URL( '../../../src/services/host-bridge/hostBridge.ts', import.meta.url, @@ -56,7 +64,9 @@ if ( '#![cfg_attr(all(not(dev), target_os = "windows"), windows_subsystem = "windows")]', ) ) { - throw new Error('desktop shell release builds on Windows must use the GUI subsystem'); + throw new Error( + 'desktop shell release builds on Windows must use the GUI subsystem', + ); } const rustSourceDir = new URL('../src-tauri/src/', import.meta.url); const desktopHostBridgeAppearancePath = new URL( @@ -212,7 +222,10 @@ const stageReleaseBinaryPath = new URL( '../scripts/stage-release-binary.mjs', import.meta.url, ); -const stageReleaseBinarySource = fs.readFileSync(stageReleaseBinaryPath, 'utf8'); +const stageReleaseBinarySource = fs.readFileSync( + stageReleaseBinaryPath, + 'utf8', +); const productionSourceRoots = [ new URL('../package.json', import.meta.url), new URL('../scripts/', import.meta.url), @@ -223,10 +236,14 @@ const productionSourceRoots = [ new URL('../src-tauri/src/', import.meta.url), new URL('../src-tauri/tauri.conf.json', import.meta.url), ]; -const productionFileExtensions = new Set(['.json', '.mjs', '.plist', '.rs', '.toml']); -const productionSourceExcludedDirectories = new Set([ - 'target', +const productionFileExtensions = new Set([ + '.json', + '.mjs', + '.plist', + '.rs', + '.toml', ]); +const productionSourceExcludedDirectories = new Set(['target']); const productionSourceExcludedRelativePaths = new Set([ 'src-tauri/gen', 'src-tauri/permissions/autogenerated', @@ -382,9 +399,13 @@ function extractCargoLockPackages(source) { .map((block) => { const name = block.match(/^name = "([^"]+)"/m)?.[1]; const version = block.match(/^version = "([^"]+)"/m)?.[1]; - const dependenciesMatch = block.match(/^dependencies = \[\n([\s\S]*?)\n\]/m); + const dependenciesMatch = block.match( + /^dependencies = \[\n([\s\S]*?)\n\]/m, + ); const dependencies = dependenciesMatch - ? [...dependenciesMatch[1].matchAll(/ "([^"]+)"/g)].map((entry) => entry[1]) + ? [...dependenciesMatch[1].matchAll(/ "([^"]+)"/g)].map( + (entry) => entry[1], + ) : []; return { block, dependencies, name, version }; }); @@ -394,49 +415,6 @@ function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -function assertNoBlockedNpmDependencies() { - const dependencySections = [ - 'dependencies', - 'devDependencies', - 'optionalDependencies', - 'peerDependencies', - ]; - - for (const dependency of blockedDesktopNpmDependencies) { - for (const section of dependencySections) { - if (packageConfig[section]?.[dependency]) { - throw new Error( - `desktop shell must not depend on ${dependency} before real observability/channel contracts exist`, - ); - } - } - } -} - -function assertNoBlockedNpmLockPackages() { - const packageNames = Object.keys(rootPackageLock.packages ?? {}) - .filter((packagePath) => packagePath.startsWith('node_modules/')) - .map((packagePath) => packagePath.replace(/^node_modules\//, '')); - const dependencyNames = Object.keys(rootPackageLock.dependencies ?? {}); - const lockedPackageNames = new Set([...packageNames, ...dependencyNames]); - - for (const dependency of blockedDesktopNpmDependencies) { - if (lockedPackageNames.has(dependency)) { - throw new Error( - `root package-lock must not resolve ${dependency} before real observability/channel contracts exist`, - ); - } - } - - for (const dependency of lockedPackageNames) { - if (dependency === '@tauri-apps/api' || dependency.startsWith('@tauri-apps/plugin-')) { - throw new Error( - `root package-lock must not resolve ${dependency}; desktop H5 must use HostBridge through the injected Tauri global only`, - ); - } - } -} - function collectNpmDependencyNames(packageJson) { const dependencySections = [ 'dependencies', @@ -450,9 +428,46 @@ function collectNpmDependencyNames(packageJson) { ); } +function assertNoBlockedNpmDependencies(packageJson, packageLabel) { + const dependencyNames = new Set(collectNpmDependencyNames(packageJson)); + + for (const dependency of blockedDesktopNpmDependencies) { + if (dependencyNames.has(dependency)) { + throw new Error( + `${packageLabel} must not depend on ${dependency} before real observability/channel contracts exist`, + ); + } + } +} + +function readWorkspaceLockManifest(packagePath, packageLabel) { + const lockManifest = rootPackageLock.packages?.[packagePath]; + if (!lockManifest) { + throw new Error( + `root package-lock is missing ${packageLabel} manifest at ${packagePath || ''}`, + ); + } + return lockManifest; +} + +function assertLockManifestMatchesDependencyBoundary( + packagePath, + packageLabel, +) { + const lockManifest = readWorkspaceLockManifest(packagePath, packageLabel); + assertNoBlockedNpmDependencies(lockManifest, `${packageLabel} lock manifest`); + assertNoTauriGuestNpmDependencies( + lockManifest, + `${packageLabel} lock manifest`, + ); +} + function assertNoTauriGuestNpmDependencies(packageJson, packageLabel) { for (const dependency of collectNpmDependencyNames(packageJson)) { - if (dependency === '@tauri-apps/api' || dependency.startsWith('@tauri-apps/plugin-')) { + if ( + dependency === '@tauri-apps/api' || + dependency.startsWith('@tauri-apps/plugin-') + ) { throw new Error( `${packageLabel} must not depend on ${dependency}; desktop H5 must use HostBridge through the injected Tauri global only`, ); @@ -529,11 +544,19 @@ function assertPackageDependencyVersion( } } -function assertPackageLockVersion(dependency, expected) { - const actual = rootPackageLock.packages?.[`node_modules/${dependency}`]?.version; +function assertPackageLockVersion(packagePath, dependency, expected) { + const pathParts = packagePath ? packagePath.split('/') : []; + const candidates = []; + for (let depth = pathParts.length; depth >= 0; depth -= 1) { + const prefix = pathParts.slice(0, depth).join('/'); + candidates.push(`${prefix ? `${prefix}/` : ''}node_modules/${dependency}`); + } + const actual = candidates + .map((candidate) => rootPackageLock.packages?.[candidate]) + .find(Boolean)?.version; if (actual !== expected) { throw new Error( - `root package-lock ${dependency} resolved version drifted: expected ${expected} but got ${actual}`, + `root package-lock ${packagePath} ${dependency} resolved version drifted: expected ${expected} but got ${actual}`, ); } } @@ -605,12 +628,15 @@ function collectProductionSourceFiles(entry) { return []; } - const directory = entry.href.endsWith('/') ? entry : new URL(`${entry.href}/`); + const directory = entry.href.endsWith('/') + ? entry + : new URL(`${entry.href}/`); return fs .readdirSync(entry, { withFileTypes: true }) .filter( (child) => - !child.isDirectory() || !productionSourceExcludedDirectories.has(child.name), + !child.isDirectory() || + !productionSourceExcludedDirectories.has(child.name), ) .flatMap((child) => collectProductionSourceFiles( @@ -637,10 +663,7 @@ function pathRelativeToDesktopShell(entry) { } function pathRelative(fromPath, toPath) { - return toPath - .slice(fromPath.length) - .replace(/^\/+/, '') - .replace(/\/$/, ''); + return toPath.slice(fromPath.length).replace(/^\/+/, '').replace(/\/$/, ''); } function cspDirectiveValues(source, directiveName) { @@ -652,10 +675,7 @@ function cspDirectiveValues(source, directiveName) { return []; } - return directive - .split(/\s+/) - .slice(1) - .filter(Boolean); + return directive.split(/\s+/).slice(1).filter(Boolean); } function assertExactCspDirective(source, directiveName, expectedValues, label) { @@ -702,17 +722,22 @@ function assertNoDevScaffoldTerms(files) { } assertNoDevScaffoldTerms(productionSourceFiles); -assertNoBlockedNpmDependencies(); +assertNoBlockedNpmDependencies(packageConfig, 'desktop shell package'); +assertNoBlockedNpmDependencies(rootPackageConfig, 'root H5 package'); assertNoTauriGuestNpmDependencies(packageConfig, 'desktop shell package'); assertNoTauriGuestNpmDependencies(rootPackageConfig, 'root H5 package'); -assertNoBlockedNpmLockPackages(); +assertLockManifestMatchesDependencyBoundary('', 'root H5 package'); +assertLockManifestMatchesDependencyBoundary( + 'apps/desktop-shell', + 'desktop shell workspace', +); assertNoBlockedCargoDependencies(); assertNoBlockedCargoLockPackages(); assertNoBlockedDesktopSdkSnippets(); assertDesktopSourceLayout(); for (const snippet of [ - 'label: \'desktop-shell-release-build-smoke\'', + "label: 'desktop-shell-release-build-smoke'", "args: ['run', 'desktop-shell:build', '--', '--no-bundle']", "label: 'desktop-shell-stage-release-binary'", "args: ['run', 'desktop-shell:stage-release-binary']", @@ -747,7 +772,9 @@ for (const snippet of [ 'assertDesktopReleaseBinaryArtifact();', ]) { if (!nativeShellCheckSource.includes(snippet)) { - throw new Error(`root native shell gate must keep desktop release artifact check ${snippet}`); + throw new Error( + `root native shell gate must keep desktop release artifact check ${snippet}`, + ); } } @@ -771,7 +798,7 @@ for (const snippet of [ 'machMagic === 0xcefaedfe', 'machMagic === 0xfeedfacf', 'machMagic === 0xcffaedfe', - "console.log(`[desktop-shell:stage-release-binary] ${stagedPath}`)", + 'console.log(`[desktop-shell:stage-release-binary] ${stagedPath}`)', ]) { if (!stageReleaseBinarySource.includes(snippet)) { throw new Error(`desktop shell release staging script missing ${snippet}`); @@ -784,15 +811,22 @@ for (const [scriptName, expected] of Object.entries({ 'stage-release-binary': 'node scripts/stage-release-binary.mjs', typecheck: 'node scripts/check-config.mjs', })) { - assertPackageScript(packageConfig, 'desktop shell package', scriptName, expected); + assertPackageScript( + packageConfig, + 'desktop shell package', + scriptName, + expected, + ); } for (const [scriptName, expected] of Object.entries({ 'desktop-shell:dev': 'npm --prefix apps/desktop-shell run dev', 'desktop-shell:build': 'npm --prefix apps/desktop-shell run build --', - 'desktop-shell:stage-release-binary': 'npm --prefix apps/desktop-shell run stage-release-binary', + 'desktop-shell:stage-release-binary': + 'npm --prefix apps/desktop-shell run stage-release-binary', 'desktop-shell:typecheck': 'npm --prefix apps/desktop-shell run typecheck', - 'desktop-shell:test': 'cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml', + 'desktop-shell:test': + 'cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml', })) { assertPackageScript(rootPackageConfig, 'root package', scriptName, expected); } @@ -808,38 +842,35 @@ for (const [dependency, expected] of Object.entries({ dependency, expected, ); - assertPackageDependencyVersion( - rootPackageConfig, - 'root package', - 'devDependencies', - dependency, - expected, - ); } for (const [dependency, expected] of Object.entries({ '@tauri-apps/cli': '2.11.2', typescript: '5.8.3', })) { - assertPackageLockVersion(dependency, expected); + assertPackageLockVersion('apps/desktop-shell', dependency, expected); } for (const [sectionName, dependencyName, expectedLine] of [ - ['build-dependencies', 'tauri-build', 'tauri-build = { version = "2.6.2", features = [] }'], + [ + 'build-dependencies', + 'tauri-build', + 'tauri-build = { version = "2.6.2", features = [] }', + ], ['dependencies', 'base64', 'base64 = "0.22"'], ['dependencies', 'serde', 'serde = { version = "1", features = ["derive"] }'], ['dependencies', 'serde_json', 'serde_json = "1"'], - ['dependencies', 'tauri', 'tauri = { version = "2.11.2", features = ["tray-icon"] }'], + [ + 'dependencies', + 'tauri', + 'tauri = { version = "2.11.2", features = ["tray-icon"] }', + ], [ 'dependencies', 'tauri-plugin-clipboard-manager', 'tauri-plugin-clipboard-manager = "2.3.2"', ], - [ - 'dependencies', - 'tauri-plugin-dialog', - 'tauri-plugin-dialog = "2.7.1"', - ], + ['dependencies', 'tauri-plugin-dialog', 'tauri-plugin-dialog = "2.7.1"'], [ 'dependencies', 'tauri-plugin-deep-link', @@ -850,11 +881,7 @@ for (const [sectionName, dependencyName, expectedLine] of [ 'tauri-plugin-notification', 'tauri-plugin-notification = "2.3.3"', ], - [ - 'dependencies', - 'tauri-plugin-opener', - 'tauri-plugin-opener = "2.5.4"', - ], + ['dependencies', 'tauri-plugin-opener', 'tauri-plugin-opener = "2.5.4"'], [ 'dependencies', 'tauri-plugin-single-instance', @@ -943,10 +970,14 @@ function assertDesktopIconSet() { for (const [fileName, expectedSize] of requiredPngIconSizes) { const icon = readPngSize(new URL(fileName, iconDirPath)); if (icon.width !== expectedSize || icon.height !== expectedSize) { - throw new Error(`desktop shell icon ${fileName} must be ${expectedSize}x${expectedSize}`); + throw new Error( + `desktop shell icon ${fileName} must be ${expectedSize}x${expectedSize}`, + ); } if (!icon.hasTransparency || icon.dataStart === -1) { - throw new Error(`desktop shell icon ${fileName} must use a real transparent brand asset`); + throw new Error( + `desktop shell icon ${fileName} must use a real transparent brand asset`, + ); } } @@ -1009,7 +1040,9 @@ function extractRustStringArrayConst(source, constName) { function extractRustStringConst(source, constName) { const match = source.match( - new RegExp(`(?:pub\\(crate\\)\\s+)?const ${constName}\\s*:\\s*&str\\s*=\\s*"([^"]+)";`), + new RegExp( + `(?:pub\\(crate\\)\\s+)?const ${constName}\\s*:\\s*&str\\s*=\\s*"([^"]+)";`, + ), ); if (!match) { throw new Error(`unable to read Rust const ${constName}`); @@ -1044,7 +1077,9 @@ function extractTsStringConst(source, constName) { function extractTsStringObjectConst(source, constName) { const match = source.match( - new RegExp(`export const ${constName}\\s*=\\s*\\{([\\s\\S]*?)\\}\\s*as const;`), + new RegExp( + `export const ${constName}\\s*=\\s*\\{([\\s\\S]*?)\\}\\s*as const;`, + ), ); if (!match) { throw new Error(`unable to read TypeScript const ${constName}`); @@ -1074,10 +1109,7 @@ function evaluateNumberExpression(expression) { .split('*') .map((token) => token.trim()) .filter(Boolean); - if ( - tokens.length === 0 || - tokens.some((token) => !/^\d+$/.test(token)) - ) { + if (tokens.length === 0 || tokens.some((token) => !/^\d+$/.test(token))) { throw new Error(`unsupported numeric expression ${expression}`); } @@ -1099,7 +1131,9 @@ function extractRustStringMatchArms(source, functionName) { function extractRustSomeStringValues(source, functionName) { const match = source.match( - new RegExp(`(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{([\\s\\S]*?)\\n\\}`), + new RegExp( + `(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{([\\s\\S]*?)\\n\\}`, + ), ); if (!match) { throw new Error(`unable to read Rust function ${functionName}`); @@ -1112,7 +1146,9 @@ function extractRustSomeStringValues(source, functionName) { function extractRustGuardedMimeValues(source, functionName) { const match = source.match( - new RegExp(`(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{([\\s\\S]*?)\\n\\}`), + new RegExp( + `(?:pub\\(crate\\)\\s+)?fn ${functionName}[^\\{]*\\{([\\s\\S]*?)\\n\\}`, + ), ); if (!match) { throw new Error(`unable to read Rust function ${functionName}`); @@ -1124,7 +1160,9 @@ function extractRustGuardedMimeValues(source, functionName) { } function extractDesktopCapabilities(source) { - const match = source.match(/fn capabilities\(\)[^{]*\{[\s\S]*?vec!\[([\s\S]*?)\]\s*\}/); + const match = source.match( + /fn capabilities\(\)[^{]*\{[\s\S]*?vec!\[([\s\S]*?)\]\s*\}/, + ); if (!match) { throw new Error('unable to read desktop shell capabilities'); } @@ -1133,9 +1171,11 @@ function extractDesktopCapabilities(source) { } function extractDesktopHandledMethods(source) { - const matchBodies = [...source.matchAll(/match request\.method\.as_str\(\) \{([\s\S]*?)\n {4}\}/g)].map( - (match) => match[1], - ); + const matchBodies = [ + ...source.matchAll( + /match request\.method\.as_str\(\) \{([\s\S]*?)\n {4}\}/g, + ), + ].map((match) => match[1]); if (matchBodies.length === 0) { throw new Error('unable to read desktop shell HostBridge handler methods'); } @@ -1180,7 +1220,9 @@ function extractDesktopHostBridgeMethodBody(source, method) { function extractFunctionBody(source, functionName) { const functionStart = source.search( - new RegExp(`(?:pub\\(crate\\)\\s+)?(?:async\\s+)?fn\\s+${functionName}\\s*\\(`), + new RegExp( + `(?:pub\\(crate\\)\\s+)?(?:async\\s+)?fn\\s+${functionName}\\s*\\(`, + ), ); if (functionStart < 0) { throw new Error(`unable to read Rust function ${functionName}`); @@ -1211,14 +1253,18 @@ function assertSnippetOrder(source, firstSnippet, secondSnippet, label) { const firstIndex = source.indexOf(firstSnippet); const secondIndex = source.indexOf(secondSnippet); if (firstIndex < 0 || secondIndex < 0 || firstIndex > secondIndex) { - throw new Error(`${label} must call ${firstSnippet} before ${secondSnippet}`); + throw new Error( + `${label} must call ${firstSnippet} before ${secondSnippet}`, + ); } } function extractDesktopDialogFilter(source, ownerName, filterLabel) { const ownerBody = extractFunctionBody(source, ownerName); const match = ownerBody.match( - new RegExp(`\\.add_filter\\(\\s*"${filterLabel}",\\s*&\\[([^\\]]*)\\]\\s*,?\\s*\\)`), + new RegExp( + `\\.add_filter\\(\\s*"${filterLabel}",\\s*&\\[([^\\]]*)\\]\\s*,?\\s*\\)`, + ), ); if (!match) { throw new Error( @@ -1229,7 +1275,13 @@ function extractDesktopDialogFilter(source, ownerName, filterLabel) { return [...match[1].matchAll(/"([^"]+)"/g)].map((entry) => entry[1]); } -function assertDesktopDialogBoundary(method, functionName, filterLabel, expected, action) { +function assertDesktopDialogBoundary( + method, + functionName, + filterLabel, + expected, + action, +) { const methodBody = extractDesktopHostBridgeMethodBody( desktopHostBridgeDispatchSource, method, @@ -1259,32 +1311,44 @@ function assertDesktopDialogBoundary(method, functionName, filterLabel, expected throw new Error(`desktop shell ${method} must use ${requiredDialogAction}`); } if (fileFunctionBody.includes(blockedDialogAction)) { - throw new Error(`desktop shell ${method} must not use ${blockedDialogAction}`); + throw new Error( + `desktop shell ${method} must not use ${blockedDialogAction}`, + ); } const unavailableResponse = action === 'save' ? 'return file_export_unavailable_response(request)' : 'return file_import_unavailable_response(request)'; if (!fileFunctionBody.includes(unavailableResponse)) { - throw new Error(`desktop shell ${method} must hide native path conversion errors`); + throw new Error( + `desktop shell ${method} must hide native path conversion errors`, + ); } const failureLogger = action === 'save' ? 'log_desktop_file_export_failure("path.convert")' : 'log_desktop_file_import_failure("path.convert")'; if (!fileFunctionBody.includes(failureLogger)) { - throw new Error(`desktop shell ${method} must log native path conversion errors`); + throw new Error( + `desktop shell ${method} must log native path conversion errors`, + ); } if ( fileFunctionBody.includes( 'return failed(request.id.clone(), "host_error", error.to_string())', ) ) { - throw new Error(`desktop shell ${method} must not expose native path errors`); + throw new Error( + `desktop shell ${method} must not expose native path errors`, + ); } } -function assertDesktopFileExportPayloadOrder(method, functionName, payloadFunction) { +function assertDesktopFileExportPayloadOrder( + method, + functionName, + payloadFunction, +) { const fileFunctionBody = extractFunctionBody( desktopHostBridgeFilesSource, functionName, @@ -1339,7 +1403,9 @@ function readDirectoryEntryList(directory, label) { if (entry.isDirectory()) { return `dir:${entry.name}`; } - throw new Error(`${label} must not contain special entries: ${entry.name}`); + throw new Error( + `${label} must not contain special entries: ${entry.name}`, + ); }) .sort(); if (entries.length === 0) { @@ -1352,7 +1418,9 @@ function readDirectoryFileList(directory, label) { const files = []; for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { if (!entry.isFile()) { - throw new Error(`${label} must not contain nested entries: ${entry.name}`); + throw new Error( + `${label} must not contain nested entries: ${entry.name}`, + ); } files.push(entry.name); } @@ -1361,7 +1429,10 @@ function readDirectoryFileList(directory, label) { function assertDesktopSourceLayout() { assertSameList( - readDirectoryFileList(new URL('../scripts/', import.meta.url), 'desktop shell scripts'), + readDirectoryFileList( + new URL('../scripts/', import.meta.url), + 'desktop shell scripts', + ), expectedDesktopShellScripts, 'desktop shell scripts', ); @@ -1431,16 +1502,12 @@ function collectRustSourceRelativePaths(files) { ? rustSourceDir.pathname : `${rustSourceDir.pathname}/`; - return files - .map((file) => file.pathname.replace(rootPath, '')) - .sort(); + return files.map((file) => file.pathname.replace(rootPath, '')).sort(); } function extractNativeAppTauriInvokeCommands(source) { return [ - ...source.matchAll( - /\btauriInvoke(?:<[^)]*>)?\(\s*([A-Z_][A-Z0-9_]*)\s*,/g, - ), + ...source.matchAll(/\btauriInvoke(?:<[^)]*>)?\(\s*([A-Z_][A-Z0-9_]*)\s*,/g), ] .map((match) => match[1].trim()) .filter(Boolean); @@ -1454,13 +1521,15 @@ function assertGeneratedPermissions(commandNames) { const expectedPermissionFiles = commandNames .map((command) => `${command}.toml`) .sort(); - const permissionEntries = fs.readdirSync(generatedPermissionDir, { - withFileTypes: true, - }).sort((left, right) => left.name.localeCompare(right.name)); + const permissionEntries = fs + .readdirSync(generatedPermissionDir, { + withFileTypes: true, + }) + .sort((left, right) => left.name.localeCompare(right.name)); assertSameList( - permissionEntries.map((entry) => - `${entry.isDirectory() ? 'dir' : 'file'}:${entry.name}`, + permissionEntries.map( + (entry) => `${entry.isDirectory() ? 'dir' : 'file'}:${entry.name}`, ), expectedPermissionFiles.map((file) => `file:${file}`), 'desktop shell generated permission files', @@ -1708,8 +1777,14 @@ const desktopHostBridgePayloadLimits = { 'IMPORT_AUDIO_MAX_BYTES', ), }; -const desktopMethods = extractRustStringArrayConst(rustHostSource, 'HOST_BRIDGE_METHODS'); -const desktopEvents = extractRustStringArrayConst(rustHostSource, 'HOST_BRIDGE_EVENTS'); +const desktopMethods = extractRustStringArrayConst( + rustHostSource, + 'HOST_BRIDGE_METHODS', +); +const desktopEvents = extractRustStringArrayConst( + rustHostSource, + 'HOST_BRIDGE_EVENTS', +); const desktopErrorCodes = extractRustStringArrayConst( rustHostSource, 'HOST_BRIDGE_ERROR_CODES', @@ -1732,33 +1807,47 @@ const desktopCapabilities = extractDesktopCapabilities( const desktopHandledMethods = extractDesktopHandledMethods( desktopHostBridgeDispatchSource, ); -const desktopUnsupportedHandledMethods = extractDesktopUnsupportedHandledMethods( - desktopHostBridgeDispatchSource, - desktopHandledMethods, -); +const desktopUnsupportedHandledMethods = + extractDesktopUnsupportedHandledMethods( + desktopHostBridgeDispatchSource, + desktopHandledMethods, + ); const hostBridgeAcceptedUnsupportedMethods = sharedMethods.filter( (method) => !desktopCapabilities.includes(method), ); const desktopCapabilityDelegationContracts = { 'host.getRuntime': 'desktop_host_bridge_runtime_response(&request)', - 'appearance.getColorScheme': 'desktop_appearance_color_scheme(&app, &request)', + 'appearance.getColorScheme': + 'desktop_appearance_color_scheme(&app, &request)', 'share.open': 'open_desktop_host_bridge_share(&app, &request)', 'share.setTarget': 'set_desktop_host_bridge_share_target(&app, &request)', - 'navigation.openNativePage': 'open_desktop_host_bridge_native_page(&app, &request)', + 'navigation.openNativePage': + 'open_desktop_host_bridge_native_page(&app, &request)', 'app.reloadWebView': 'reload_desktop_host_bridge_webview(&app, &request)', - 'app.openExternalUrl': 'open_desktop_host_bridge_external_url(&app, &request)', + 'app.openExternalUrl': + 'open_desktop_host_bridge_external_url(&app, &request)', 'app.setTitle': 'set_desktop_host_bridge_window_title(&app, &request)', 'app.setBadgeCount': 'set_desktop_app_badge_count(&app, &request)', - 'network.status': 'resolve_desktop_host_bridge_network_status(&request).await', - 'clipboard.writeText': 'write_desktop_host_bridge_clipboard_text(&app, &request)', - 'clipboard.readText': 'read_desktop_host_bridge_clipboard_text(&app, &request)', - 'file.exportText': 'export_desktop_host_bridge_text_file(&app, &request).await', - 'file.importText': 'import_desktop_host_bridge_text_file(&app, &request).await', - 'file.importDocument': 'import_desktop_host_bridge_document_file(&app, &request).await', - 'file.exportImage': 'export_desktop_host_bridge_image_file(&app, &request).await', - 'file.importImage': 'import_desktop_host_bridge_image_file(&app, &request).await', - 'file.importAudio': 'import_desktop_host_bridge_audio_file(&app, &request).await', - 'file.exportAudio': 'export_desktop_host_bridge_audio_file(&app, &request).await', + 'network.status': + 'resolve_desktop_host_bridge_network_status(&request).await', + 'clipboard.writeText': + 'write_desktop_host_bridge_clipboard_text(&app, &request)', + 'clipboard.readText': + 'read_desktop_host_bridge_clipboard_text(&app, &request)', + 'file.exportText': + 'export_desktop_host_bridge_text_file(&app, &request).await', + 'file.importText': + 'import_desktop_host_bridge_text_file(&app, &request).await', + 'file.importDocument': + 'import_desktop_host_bridge_document_file(&app, &request).await', + 'file.exportImage': + 'export_desktop_host_bridge_image_file(&app, &request).await', + 'file.importImage': + 'import_desktop_host_bridge_image_file(&app, &request).await', + 'file.importAudio': + 'import_desktop_host_bridge_audio_file(&app, &request).await', + 'file.exportAudio': + 'export_desktop_host_bridge_audio_file(&app, &request).await', 'notification.showLocal': 'show_desktop_local_notification(&app, &request)', }; if (desktopHostBridgeProtocol !== sharedHostBridgeProtocol) { @@ -1789,7 +1878,9 @@ if ( !rustHostSource.includes('HOST_BRIDGE_ERROR_CODES.contains(&code)') || !rustHostSource.includes('desktop host bridge request failed') ) { - throw new Error('desktop shell protocol must normalize non-contract host errors'); + throw new Error( + 'desktop shell protocol must normalize non-contract host errors', + ); } if (desktopPublicWebOrigin !== sharedPublicWebOrigin) { @@ -1804,8 +1895,14 @@ assertSameList( 'desktop shell host-context query keys', ); -if (!desktopShellUrlSource.includes('fn append_desktop_host_context(url: &mut Url)')) { - throw new Error('desktop shell host-context query appending must stay centralized'); +if ( + !desktopShellUrlSource.includes( + 'fn append_desktop_host_context(url: &mut Url)', + ) +) { + throw new Error( + 'desktop shell host-context query appending must stay centralized', + ); } for (const [key, value, label] of [ @@ -1814,7 +1911,9 @@ for (const [key, value, label] of [ ['hostShell', sharedNativeAppQuery.hostShellTauriDesktop, 'host shell'], ]) { if (!desktopShellUrlSource.includes(`.append_pair("${key}", "${value}")`)) { - throw new Error(`desktop shell host-context ${label} drifted from shared contract`); + throw new Error( + `desktop shell host-context ${label} drifted from shared contract`, + ); } } @@ -1832,13 +1931,15 @@ for (const expectedNetworkSnippet of [ } if (desktopShellNetworkSource.includes('"www.genarrative.world"')) { - throw new Error('desktop shell network probe must not duplicate the public web host'); + throw new Error( + 'desktop shell network probe must not duplicate the public web host', + ); } for (const blockedDesktopNetworkEventSnippet of [ 'register_desktop_network_events', 'navigator.onLine', - 'window.addEventListener(\'online\'', - 'window.addEventListener(\'offline\'', + "window.addEventListener('online'", + "window.addEventListener('offline'", ]) { if (desktopShellNetworkSource.includes(blockedDesktopNetworkEventSnippet)) { throw new Error( @@ -1877,11 +1978,21 @@ for (const expectedShareSnippet of [ } if ( - desktopHostBridgeShareSource.includes('failed(request.id.clone(), "host_error", "share target lock poisoned")') || - desktopHostBridgeShareSource.includes('failed(request.id.clone(), "host_error", error.to_string())') || - desktopHostBridgeShareSource.includes('desktop share failed for {label}: {error}') || - desktopHostBridgeShareSource.includes('log_desktop_share_failure("target.lock", "share target lock poisoned")') || - desktopHostBridgeShareSource.includes('log_desktop_share_failure("target.store", "share target lock poisoned")') + desktopHostBridgeShareSource.includes( + 'failed(request.id.clone(), "host_error", "share target lock poisoned")', + ) || + desktopHostBridgeShareSource.includes( + 'failed(request.id.clone(), "host_error", error.to_string())', + ) || + desktopHostBridgeShareSource.includes( + 'desktop share failed for {label}: {error}', + ) || + desktopHostBridgeShareSource.includes( + 'log_desktop_share_failure("target.lock", "share target lock poisoned")', + ) || + desktopHostBridgeShareSource.includes( + 'log_desktop_share_failure("target.store", "share target lock poisoned")', + ) ) { throw new Error('desktop shell share module must hide native share errors'); } @@ -1890,12 +2001,16 @@ if ( !desktopHostBridgeDispatchSource.includes( 'set_desktop_host_bridge_share_target(&app, &request)', ) || - !desktopHostBridgeDispatchSource.includes('open_desktop_host_bridge_share(&app, &request)') || + !desktopHostBridgeDispatchSource.includes( + 'open_desktop_host_bridge_share(&app, &request)', + ) || desktopHostBridgeDispatchSource.includes('DesktopShareState') || desktopHostBridgeDispatchSource.includes('share_text_from_request') || desktopHostBridgeDispatchSource.includes('"copied_to_clipboard"') ) { - throw new Error('desktop shell share HostBridge methods must delegate to share module'); + throw new Error( + 'desktop shell share HostBridge methods must delegate to share module', + ); } for (const [limitName, sharedLimit] of Object.entries( @@ -1913,7 +2028,9 @@ if ( extractRustStringConst(rustHostSource, 'EXPORT_FILE_NAME_FALLBACK') !== extractTsStringConst(sharedContractSource, 'HOST_BRIDGE_FILE_NAME_FALLBACK') ) { - throw new Error('desktop shell file name fallback drifted from shared HostBridge contract'); + throw new Error( + 'desktop shell file name fallback drifted from shared HostBridge contract', + ); } const sharedTextMimeTypes = extractStringArrayExport( @@ -1934,7 +2051,10 @@ assertSameList( 'desktop shell text MIME types', ); assertSameList( - extractRustGuardedMimeValues(rustHostSource, 'normalize_export_text_mime_type'), + extractRustGuardedMimeValues( + rustHostSource, + 'normalize_export_text_mime_type', + ), sharedTextMimeTypes, 'desktop shell export text MIME types', ); @@ -1959,7 +2079,11 @@ assertSameList( 'desktop shell import audio MIME types', ); -assertSameList(desktopMethods, sharedMethods, 'desktop shell HostBridge method whitelist'); +assertSameList( + desktopMethods, + sharedMethods, + 'desktop shell HostBridge method whitelist', +); assertSameList( desktopEvents, sharedDesktopEvents, @@ -1977,7 +2101,9 @@ if (desktopEvents.includes('network.statusChanged')) { } for (const eventName of sharedEvents) { if (!sharedCapabilities.includes(eventName)) { - throw new Error(`shared HostBridge event must also be a capability: ${eventName}`); + throw new Error( + `shared HostBridge event must also be a capability: ${eventName}`, + ); } } const unknownHandledDesktopMethods = desktopHandledMethods.filter( @@ -2044,7 +2170,9 @@ const desktopUnsupportedMethodTestMatch = desktopHostBridgeDispatchSource.match( /fn unsupported_method_is_explicit\(\)[\s\S]*?assert_eq!\(\s*unsupported_methods,\s*vec!\[([^\]]+)\]/, ); if (!desktopUnsupportedMethodTestMatch) { - throw new Error('desktop shell unsupported HostBridge method test is missing'); + throw new Error( + 'desktop shell unsupported HostBridge method test is missing', + ); } for (const snippet of [ 'use crate::host_bridge::capabilities::capabilities;', @@ -2135,19 +2263,30 @@ if (config.version !== '0.1.0') { } if (packageConfig.version !== config.version) { - throw new Error('desktop shell package version must match tauri.conf.json version'); + throw new Error( + 'desktop shell package version must match tauri.conf.json version', + ); } -if (extractCargoPackageString(cargoManifest, 'name') !== 'genarrative-desktop-shell') { - throw new Error('desktop shell Cargo package name must be genarrative-desktop-shell'); +if ( + extractCargoPackageString(cargoManifest, 'name') !== + 'genarrative-desktop-shell' +) { + throw new Error( + 'desktop shell Cargo package name must be genarrative-desktop-shell', + ); } if (extractCargoPackageString(cargoManifest, 'version') !== config.version) { - throw new Error('desktop shell Cargo package version must match tauri.conf.json version'); + throw new Error( + 'desktop shell Cargo package version must match tauri.conf.json version', + ); } if (!rustHostSource.includes('host_version: env!("CARGO_PKG_VERSION")')) { - throw new Error('desktop shell runtime response must use the Cargo package version'); + throw new Error( + 'desktop shell runtime response must use the Cargo package version', + ); } for (const runtimeSnippet of [ @@ -2167,8 +2306,14 @@ for (const runtimeSnippet of [ } const desktopDispatchRuntimeDelegationSource = [ - extractFunctionBody(desktopHostBridgeDispatchSource, 'resolve_host_bridge_request'), - extractFunctionBody(desktopHostBridgeDispatchSource, 'execute_host_bridge_request'), + extractFunctionBody( + desktopHostBridgeDispatchSource, + 'resolve_host_bridge_request', + ), + extractFunctionBody( + desktopHostBridgeDispatchSource, + 'execute_host_bridge_request', + ), ].join('\n'); if ( !desktopDispatchRuntimeDelegationSource.includes( @@ -2182,15 +2327,21 @@ if ( desktopDispatchRuntimeDelegationSource.includes('desktop_platform()') || desktopDispatchRuntimeDelegationSource.includes('env!("CARGO_PKG_VERSION")') ) { - throw new Error('desktop shell dispatch must delegate host.getRuntime to runtime.rs'); + throw new Error( + 'desktop shell dispatch must delegate host.getRuntime to runtime.rs', + ); } -if (desktopDispatchRuntimeDelegationSource.includes('json!(HostBridgeRuntime {')) { +if ( + desktopDispatchRuntimeDelegationSource.includes('json!(HostBridgeRuntime {') +) { throw new Error('desktop shell host.getRuntime must be built by runtime.rs'); } if ( - !desktopShellNavigationSource.includes('pub(crate) fn open_normalized_desktop_external_url') || + !desktopShellNavigationSource.includes( + 'pub(crate) fn open_normalized_desktop_external_url', + ) || !desktopShellNavigationSource.includes( 'open_normalized_desktop_external_url(app, external_url)', ) @@ -2210,7 +2361,9 @@ if ( !desktopHostBridgeDispatchSource.includes( 'reload_desktop_host_bridge_webview(&app, &request)', ) || - desktopHostBridgeDispatchSource.includes('open_normalized_desktop_external_url(&app, url)') || + desktopHostBridgeDispatchSource.includes( + 'open_normalized_desktop_external_url(&app, url)', + ) || desktopHostBridgeDispatchSource.includes('normalize_external_url') || desktopHostBridgeDispatchSource.includes('normalize_native_page_url') || desktopHostBridgeDispatchSource.includes('app.opener().open_url(') @@ -2258,16 +2411,32 @@ for (const snippet of [ } } if ( - desktopHostBridgeNavigationSource.includes('failed(request.id.clone(), "host_error", error.to_string())') || + desktopHostBridgeNavigationSource.includes( + 'failed(request.id.clone(), "host_error", error.to_string())', + ) || desktopHostBridgeNavigationSource.includes('"main window not found"') || - desktopHostBridgeNavigationSource.includes('desktop navigation failed for {label}: {error}') || - desktopHostBridgeNavigationSource.includes('log_desktop_navigation_failure("external.open", &error.to_string())') || - desktopHostBridgeNavigationSource.includes('log_desktop_navigation_failure("native.navigate", &error.to_string())') || - desktopHostBridgeNavigationSource.includes('log_desktop_navigation_failure("webview.reload", &error.to_string())') || - desktopHostBridgeNavigationSource.includes('log_desktop_navigation_failure("native.window", "main window unavailable")') || - desktopHostBridgeNavigationSource.includes('log_desktop_navigation_failure("webview.window", "main window unavailable")') + desktopHostBridgeNavigationSource.includes( + 'desktop navigation failed for {label}: {error}', + ) || + desktopHostBridgeNavigationSource.includes( + 'log_desktop_navigation_failure("external.open", &error.to_string())', + ) || + desktopHostBridgeNavigationSource.includes( + 'log_desktop_navigation_failure("native.navigate", &error.to_string())', + ) || + desktopHostBridgeNavigationSource.includes( + 'log_desktop_navigation_failure("webview.reload", &error.to_string())', + ) || + desktopHostBridgeNavigationSource.includes( + 'log_desktop_navigation_failure("native.window", "main window unavailable")', + ) || + desktopHostBridgeNavigationSource.includes( + 'log_desktop_navigation_failure("webview.window", "main window unavailable")', + ) ) { - throw new Error('desktop shell navigation module must hide native navigation errors'); + throw new Error( + 'desktop shell navigation module must hide native navigation errors', + ); } if ( @@ -2275,10 +2444,16 @@ if ( 'resolve_desktop_host_bridge_network_status(&request).await', ) || desktopHostBridgeDispatchSource.includes('resolve_desktop_network_status') || - desktopHostBridgeDispatchSource.includes('spawn_blocking(resolve_desktop_network_status)') || - desktopHostBridgeDispatchSource.includes('Err(error) => failed(request.id, "host_error"') + desktopHostBridgeDispatchSource.includes( + 'spawn_blocking(resolve_desktop_network_status)', + ) || + desktopHostBridgeDispatchSource.includes( + 'Err(error) => failed(request.id, "host_error"', + ) ) { - throw new Error('desktop shell network HostBridge method must delegate to network module'); + throw new Error( + 'desktop shell network HostBridge method must delegate to network module', + ); } for (const snippet of [ 'resolve_desktop_host_bridge_network_status', @@ -2306,10 +2481,16 @@ for (const snippet of [ } } if ( - desktopHostBridgeNetworkSource.includes('desktop network failed for {label}: {error}') || - desktopHostBridgeNetworkSource.includes('log_desktop_network_failure("status.resolve", &error)') + desktopHostBridgeNetworkSource.includes( + 'desktop network failed for {label}: {error}', + ) || + desktopHostBridgeNetworkSource.includes( + 'log_desktop_network_failure("status.resolve", &error)', + ) ) { - throw new Error('desktop shell network module must hide native network errors'); + throw new Error( + 'desktop shell network module must hide native network errors', + ); } if ( @@ -2319,10 +2500,14 @@ if ( !desktopHostBridgeDispatchSource.includes( 'read_desktop_host_bridge_clipboard_text(&app, &request)', ) || - desktopHostBridgeDispatchSource.includes('required_string_payload(&request, "text")') || + desktopHostBridgeDispatchSource.includes( + 'required_string_payload(&request, "text")', + ) || desktopHostBridgeDispatchSource.includes('read_desktop_clipboard_text(&app)') ) { - throw new Error('desktop shell clipboard HostBridge methods must delegate to clipboard module'); + throw new Error( + 'desktop shell clipboard HostBridge methods must delegate to clipboard module', + ); } for (const snippet of [ 'write_desktop_clipboard_text(app, text)', @@ -2348,23 +2533,40 @@ for (const snippet of [ } } if ( - desktopHostBridgeClipboardSource.includes('desktop clipboard failed for {label}: {error}') || - desktopHostBridgeClipboardSource.includes('log_desktop_clipboard_failure("write.text", &error)') || - desktopHostBridgeClipboardSource.includes('log_desktop_clipboard_failure("read.text", &error)') + desktopHostBridgeClipboardSource.includes( + 'desktop clipboard failed for {label}: {error}', + ) || + desktopHostBridgeClipboardSource.includes( + 'log_desktop_clipboard_failure("write.text", &error)', + ) || + desktopHostBridgeClipboardSource.includes( + 'log_desktop_clipboard_failure("read.text", &error)', + ) ) { - throw new Error('desktop shell clipboard module must hide native clipboard errors'); + throw new Error( + 'desktop shell clipboard module must hide native clipboard errors', + ); } if (Object.hasOwn(config.build ?? {}, 'frontendDist')) { - throw new Error('desktop shell release must load the shared public H5 URL without packaging root dist'); + throw new Error( + 'desktop shell release must load the shared public H5 URL without packaging root dist', + ); } if (config.build?.beforeBuildCommand !== 'npm run typecheck') { - throw new Error('desktop shell build command must run from apps/desktop-shell'); + throw new Error( + 'desktop shell build command must run from apps/desktop-shell', + ); } -if (config.build?.beforeDevCommand !== 'npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port') { - throw new Error('desktop shell dev command must run the root H5 Vite dev server on the Tauri devUrl port'); +if ( + config.build?.beforeDevCommand !== + 'npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port' +) { + throw new Error( + 'desktop shell dev command must run the root H5 Vite dev server on the Tauri devUrl port', + ); } if ( @@ -2382,7 +2584,9 @@ if (!mainWindow || mainWindow.create !== false) { } if ((config.app?.windows ?? []).length !== 1) { - throw new Error('desktop shell must expose exactly one configured main window'); + throw new Error( + 'desktop shell must expose exactly one configured main window', + ); } if (mainWindow.label !== 'main') { @@ -2390,7 +2594,9 @@ if (mainWindow.label !== 'main') { } if (String(mainWindow.url ?? '') !== sharedPublicWebUrl) { - throw new Error('desktop shell release window must load the shared public H5 URL'); + throw new Error( + 'desktop shell release window must load the shared public H5 URL', + ); } if (String(config.build?.devUrl ?? '') !== 'http://127.0.0.1:3000/') { @@ -2401,17 +2607,23 @@ assertNoRawHostContextUrl(mainWindow.url, 'desktop shell main window URL'); assertNoRawHostContextUrl(config.build?.devUrl, 'desktop shell dev URL'); if (mainWindow.devtools !== false) { - throw new Error('desktop shell main WebView devtools must be explicitly disabled'); + throw new Error( + 'desktop shell main WebView devtools must be explicitly disabled', + ); } assertDesktopIconSet(); if (config.bundle?.active !== true || config.bundle?.targets !== 'all') { - throw new Error('desktop shell bundle targets must remain enabled for all platforms'); + throw new Error( + 'desktop shell bundle targets must remain enabled for all platforms', + ); } if (config.bundle?.macOS?.infoPlist !== 'Info.plist') { - throw new Error('desktop shell macOS bundle must merge the checked Info.plist'); + throw new Error( + 'desktop shell macOS bundle must merge the checked Info.plist', + ); } for (const [key, value] of [ @@ -2439,7 +2651,9 @@ for (const blockedCspToken of ["'unsafe-eval'", 'tauri:', 'file:']) { throw new Error(`desktop shell CSP must not include ${blockedCspToken}`); } if (devCsp.includes(blockedCspToken)) { - throw new Error(`desktop shell dev CSP must not include ${blockedCspToken}`); + throw new Error( + `desktop shell dev CSP must not include ${blockedCspToken}`, + ); } } @@ -2476,12 +2690,20 @@ for (const requiredDevCspToken of [ } } -assertExactCspDirective(csp, 'script-src', ["'self'"], 'desktop shell release CSP'); -assertExactCspDirective(devCsp, 'script-src', ["'self'"], 'desktop shell dev CSP'); +assertExactCspDirective( + csp, + 'script-src', + ["'self'"], + 'desktop shell release CSP', +); +assertExactCspDirective( + devCsp, + 'script-src', + ["'self'"], + 'desktop shell dev CSP', +); -const allowedPermissions = [ - 'allow-host-bridge-request', -]; +const allowedPermissions = ['allow-host-bridge-request']; const blockedCoreDefaultPermissions = [ 'core:default', 'core:app:default', @@ -2884,11 +3106,19 @@ if ( ) || desktopHostBridgeProtocolSource.includes('&error.to_string()') ) { - throw new Error('desktop HostBridge replay diagnostics must not log Rust error details'); + throw new Error( + 'desktop HostBridge replay diagnostics must not log Rust error details', + ); } -if (desktopShellNetworkSource.includes('desktop network reachability probe failed: {reason}')) { - throw new Error('desktop network probe diagnostics must not log resolver or connection details'); +if ( + desktopShellNetworkSource.includes( + 'desktop network reachability probe failed: {reason}', + ) +) { + throw new Error( + 'desktop network probe diagnostics must not log resolver or connection details', + ); } assertSameList( @@ -2907,7 +3137,9 @@ for (const snippet of [ 'invalid request must not reserve replay slot', ]) { if (!rustHostSource.includes(snippet)) { - throw new Error(`desktop shell host_bridge command facade test missing ${snippet}`); + throw new Error( + `desktop shell host_bridge command facade test missing ${snippet}`, + ); } } @@ -2918,7 +3150,9 @@ for (const snippet of [ 'expect("host bridge replay response")', ]) { if (desktopHostBridgeProtocolSource.includes(snippet)) { - throw new Error(`desktop shell HostBridge replay path must not panic on ${snippet}`); + throw new Error( + `desktop shell HostBridge replay path must not panic on ${snippet}`, + ); } } @@ -2957,7 +3191,9 @@ if ( 'bytes.is_empty() || bytes.len() > EXPORT_IMAGE_MAX_BYTES', ) ) { - throw new Error('desktop shell image export must reject empty and oversized bytes'); + throw new Error( + 'desktop shell image export must reject empty and oversized bytes', + ); } assertSameList( extractNativeAppTauriInvokeCommands(nativeAppHostBridgeSource), @@ -2965,7 +3201,9 @@ assertSameList( 'H5 native app Tauri invoke command source', ); if (nativeAppHostBridgeSource.includes("'host_bridge_request'")) { - throw new Error('H5 native app HostBridge must use HOST_BRIDGE_TAURI_COMMAND'); + throw new Error( + 'H5 native app HostBridge must use HOST_BRIDGE_TAURI_COMMAND', + ); } if ( !desktopHostBridgeDispatchSource.includes( @@ -2974,7 +3212,9 @@ if ( desktopHostBridgeDispatchSource.includes('window.theme()') || desktopHostBridgeDispatchSource.includes('color_scheme_from_theme') ) { - throw new Error('desktop shell appearance HostBridge method must delegate to appearance module'); + throw new Error( + 'desktop shell appearance HostBridge method must delegate to appearance module', + ); } for (const snippet of [ 'desktop_appearance_color_scheme', @@ -2996,23 +3236,33 @@ for (const snippet of [ } } if ( - desktopHostBridgeAppearanceSource.includes('failed(request.id.clone(), "host_error", error.to_string())') || + desktopHostBridgeAppearanceSource.includes( + 'failed(request.id.clone(), "host_error", error.to_string())', + ) || desktopHostBridgeAppearanceSource.includes('"main window not found"') || - desktopHostBridgeAppearanceSource.includes('desktop appearance failed for {label}: {error}') || + desktopHostBridgeAppearanceSource.includes( + 'desktop appearance failed for {label}: {error}', + ) || desktopHostBridgeAppearanceSource.includes('&error.to_string()') ) { - throw new Error('desktop shell appearance module must hide native window errors'); + throw new Error( + 'desktop shell appearance module must hide native window errors', + ); } if ( !desktopHostBridgeDispatchSource.includes( 'set_desktop_app_badge_count(&app, &request)', ) || desktopHostBridgeDispatchSource.includes('"app.setBadgeCount" => match') || - desktopHostBridgeDispatchSource.includes('Ok(()) => ok(request.id, json!(true))') || + desktopHostBridgeDispatchSource.includes( + 'Ok(()) => ok(request.id, json!(true))', + ) || desktopHostBridgeDispatchSource.includes('set_badge_count') || desktopHostBridgeDispatchSource.includes('BADGE_COUNT_MAX') ) { - throw new Error('desktop shell badge HostBridge method must delegate to badge module'); + throw new Error( + 'desktop shell badge HostBridge method must delegate to badge module', + ); } for (const snippet of [ 'BADGE_COUNT_MAX', @@ -3035,25 +3285,39 @@ for (const snippet of [ } } if ( - desktopHostBridgeBadgeSource.includes('failed(request.id.clone(), "host_error", error.to_string())') || + desktopHostBridgeBadgeSource.includes( + 'failed(request.id.clone(), "host_error", error.to_string())', + ) || desktopHostBridgeBadgeSource.includes('"main window not found"') || - desktopHostBridgeBadgeSource.includes('desktop app badge failed for {label}: {error}') || + desktopHostBridgeBadgeSource.includes( + 'desktop app badge failed for {label}: {error}', + ) || desktopHostBridgeBadgeSource.includes('&error.to_string()') ) { throw new Error('desktop shell badge module must hide native window errors'); } -if (!desktopHostBridgeBadgeSource.match(/window\s*\.\s*set_badge_count\s*\(\s*count\s*\)/)) { - throw new Error('desktop shell badge module must call window.set_badge_count(count)'); +if ( + !desktopHostBridgeBadgeSource.match( + /window\s*\.\s*set_badge_count\s*\(\s*count\s*\)/, + ) +) { + throw new Error( + 'desktop shell badge module must call window.set_badge_count(count)', + ); } if ( !desktopHostBridgeDispatchSource.includes( 'set_desktop_host_bridge_window_title(&app, &request)', ) || desktopHostBridgeDispatchSource.includes('WINDOW_TITLE_MAX_LENGTH') || - desktopHostBridgeDispatchSource.includes('required_string_payload(&request, "title")') || + desktopHostBridgeDispatchSource.includes( + 'required_string_payload(&request, "title")', + ) || desktopHostBridgeDispatchSource.includes('window.set_title') ) { - throw new Error('desktop shell title HostBridge method must delegate to title module'); + throw new Error( + 'desktop shell title HostBridge method must delegate to title module', + ); } for (const snippet of [ 'WINDOW_TITLE_MAX_LENGTH', @@ -3079,9 +3343,13 @@ for (const snippet of [ } } if ( - desktopHostBridgeTitleSource.includes('failed(request.id.clone(), "host_error", error.to_string())') || + desktopHostBridgeTitleSource.includes( + 'failed(request.id.clone(), "host_error", error.to_string())', + ) || desktopHostBridgeTitleSource.includes('"main window not found"') || - desktopHostBridgeTitleSource.includes('desktop window title failed for {label}: {error}') || + desktopHostBridgeTitleSource.includes( + 'desktop window title failed for {label}: {error}', + ) || desktopHostBridgeTitleSource.includes('&error.to_string()') ) { throw new Error('desktop shell title module must hide native window errors'); @@ -3090,8 +3358,12 @@ if ( !desktopHostBridgeDispatchSource.includes( 'show_desktop_local_notification(&app, &request)', ) || - desktopHostBridgeDispatchSource.includes('"notification.showLocal" => match') || - desktopHostBridgeDispatchSource.includes('Ok(()) => ok(request.id, json!(true))') || + desktopHostBridgeDispatchSource.includes( + '"notification.showLocal" => match', + ) || + desktopHostBridgeDispatchSource.includes( + 'Ok(()) => ok(request.id, json!(true))', + ) || desktopHostBridgeDispatchSource.includes('app.notification()') || desktopHostBridgeDispatchSource.includes('NotificationExt') || desktopHostBridgeDispatchSource.includes('PermissionState') @@ -3101,7 +3373,10 @@ if ( ); } assertSnippetOrder( - extractFunctionBody(desktopHostBridgeNotificationsSource, 'show_desktop_local_notification'), + extractFunctionBody( + desktopHostBridgeNotificationsSource, + 'show_desktop_local_notification', + ), 'local_notification_payload(request)', 'app.notification()', 'desktop shell notification boundary', @@ -3154,27 +3429,45 @@ if ( ); } if ( - desktopHostBridgeNotificationsSource.includes('desktop notification failed for {label}: {error}') || - desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("permission.state", &error.to_string())') || - desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("permission.request", &error.to_string())') || - desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("delivery.show", &error.to_string())') + desktopHostBridgeNotificationsSource.includes( + 'desktop notification failed for {label}: {error}', + ) || + desktopHostBridgeNotificationsSource.includes( + 'log_desktop_notification_failure("permission.state", &error.to_string())', + ) || + desktopHostBridgeNotificationsSource.includes( + 'log_desktop_notification_failure("permission.request", &error.to_string())', + ) || + desktopHostBridgeNotificationsSource.includes( + 'log_desktop_notification_failure("delivery.show", &error.to_string())', + ) ) { - throw new Error('desktop shell notification module must hide native notification errors'); + throw new Error( + 'desktop shell notification module must hide native notification errors', + ); } if (!h5HostBridgeSource.includes('HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,')) { - throw new Error('H5 HostBridge facade must import shared runtime refresh timeout'); + throw new Error( + 'H5 HostBridge facade must import shared runtime refresh timeout', + ); } if (!h5HostBridgeSource.includes('HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS,')) { - throw new Error('H5 HostBridge facade must import shared user interaction timeout'); + throw new Error( + 'H5 HostBridge facade must import shared user interaction timeout', + ); } if (!h5HostBridgeSource.includes('HOST_BRIDGE_SCANNER_TIMEOUT_MS,')) { throw new Error('H5 HostBridge facade must import shared scanner timeout'); } if (h5HostBridgeSource.includes('HOST_RUNTIME_REFRESH_TIMEOUT_MS')) { - throw new Error('H5 HostBridge facade must not redeclare runtime refresh timeout'); + throw new Error( + 'H5 HostBridge facade must not redeclare runtime refresh timeout', + ); } if (h5HostBridgeSource.includes('timeoutMs: 30000')) { - throw new Error('H5 HostBridge facade must not redeclare user interaction timeout'); + throw new Error( + 'H5 HostBridge facade must not redeclare user interaction timeout', + ); } if (h5HostBridgeSource.includes('timeoutMs: 60000')) { throw new Error('H5 HostBridge facade must not redeclare scanner timeout'); @@ -3283,7 +3576,9 @@ for (const snippet of [ 'write_export_bytes_file', ]) { if (desktopHostBridgeDispatchSource.includes(snippet)) { - throw new Error(`desktop shell dispatch must delegate file boundary instead of ${snippet}`); + throw new Error( + `desktop shell dispatch must delegate file boundary instead of ${snippet}`, + ); } } @@ -3309,14 +3604,22 @@ for (const permission of capability.permissions ?? []) { throw new Error(`desktop shell must not expose ${permission} to H5`); } if ( - blockedCorePermissionPrefixes.some((prefix) => permission.startsWith(prefix)) + blockedCorePermissionPrefixes.some((prefix) => + permission.startsWith(prefix), + ) ) { - throw new Error(`desktop shell must not expose Tauri core permission ${permission} to H5`); + throw new Error( + `desktop shell must not expose Tauri core permission ${permission} to H5`, + ); } if ( - blockedPluginPermissionPrefixes.some((prefix) => permission.startsWith(prefix)) + blockedPluginPermissionPrefixes.some((prefix) => + permission.startsWith(prefix), + ) ) { - throw new Error(`desktop shell must not expose plugin permission ${permission} to H5`); + throw new Error( + `desktop shell must not expose plugin permission ${permission} to H5`, + ); } } assertSameList( @@ -3333,7 +3636,9 @@ assertGeneratedPermissions(allowedTauriCommands); assertNoTrackedDesktopGeneratedTauriFiles(); if (buildScript.includes('resolve_desktop_shell_runtime')) { - throw new Error('desktop shell build manifest exposes an unused runtime command'); + throw new Error( + 'desktop shell build manifest exposes an unused runtime command', + ); } if (!cargoManifest.includes('features = ["tray-icon"]')) { @@ -3349,7 +3654,9 @@ if ( 'tauri-plugin-single-instance = { version = "2.4.2", features = ["deep-link"] }', ) ) { - throw new Error('desktop shell must depend on tauri-plugin-single-instance with deep-link feature'); + throw new Error( + 'desktop shell must depend on tauri-plugin-single-instance with deep-link feature', + ); } if (!cargoManifest.includes('tauri-plugin-deep-link = "2.4.9"')) { @@ -3366,14 +3673,18 @@ if ( JSON.stringify(config.plugins['deep-link']?.desktop?.schemes ?? []) !== JSON.stringify(['genarrative']) ) { - throw new Error('desktop shell deep-link plugin must register only the genarrative scheme'); + throw new Error( + 'desktop shell deep-link plugin must register only the genarrative scheme', + ); } if ( cargoManifest.includes('tauri-plugin-updater') || JSON.stringify(config).includes('updater') ) { - throw new Error('desktop shell must not configure updater without real signing and endpoint'); + throw new Error( + 'desktop shell must not configure updater without real signing and endpoint', + ); } const orderedDesktopPluginSnippets = [ @@ -3431,7 +3742,9 @@ for (const appSetupSnippet of [ 'log_desktop_host_event_result(', ]) { if (!app.includes(appSetupSnippet)) { - throw new Error(`desktop shell app.rs missing setup snippet ${appSetupSnippet}`); + throw new Error( + `desktop shell app.rs missing setup snippet ${appSetupSnippet}`, + ); } } @@ -3505,7 +3818,9 @@ for (const snippet of [ 'desktop_deep_link_window_missing_reports_failure', ]) { if (!desktopShellDeepLinkSource.includes(snippet)) { - throw new Error(`desktop shell deep-link registration logging missing ${snippet}`); + throw new Error( + `desktop shell deep-link registration logging missing ${snippet}`, + ); } } @@ -3517,11 +3832,15 @@ if ( 'desktop host event failed for deep_link.current: {error}', ) ) { - throw new Error('desktop deep-link diagnostics must not log plugin error details'); + throw new Error( + 'desktop deep-link diagnostics must not log plugin error details', + ); } if (app.includes('desktop tray registration failed: {error}')) { - throw new Error('desktop tray registration diagnostics must not log Tauri error details'); + throw new Error( + 'desktop tray registration diagnostics must not log Tauri error details', + ); } if ( @@ -3532,7 +3851,9 @@ if ( 'desktop host event failed for {label}: {error}', ) ) { - throw new Error('desktop lifecycle diagnostics must not log Tauri error details'); + throw new Error( + 'desktop lifecycle diagnostics must not log Tauri error details', + ); } if ( @@ -3541,5 +3862,7 @@ if ( ) || rustHostSource.includes('log_desktop_image_drop_payload_failure(&error)') ) { - throw new Error('desktop image drop diagnostics must not log payload error details'); + throw new Error( + 'desktop image drop diagnostics must not log payload error details', + ); } diff --git a/apps/mobile-shell/package.json b/apps/mobile-shell/package.json index 7a146bc03..29c2d64c2 100644 --- a/apps/mobile-shell/package.json +++ b/apps/mobile-shell/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@expo/metro-runtime": "^56.0.15", + "@genarrative/shared": "0.1.0", "expo": "^56.0.12", "expo-camera": "56.0.8", "expo-clipboard": "^56.0.4", @@ -37,7 +38,10 @@ "react-native-webview": "^13.16.1" }, "devDependencies": { + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.14", "eas-cli": "^20.3.0", + "pngjs": "^7.0.0", "typescript": "~5.8.2", "vitest": "^0.34.6" } diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 38ba50b7d..8b0ff415a 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -27,9 +27,15 @@ const nativeShellCheckPath = new URL( import.meta.url, ); const nativeShellCheckSource = fs.readFileSync(nativeShellCheckPath, 'utf8'); -const shellAppTestPath = new URL('../src/shell/ShellApp.test.tsx', import.meta.url); +const shellAppTestPath = new URL( + '../src/shell/ShellApp.test.tsx', + import.meta.url, +); const shellAppTestSource = fs.readFileSync(shellAppTestPath, 'utf8'); -const qrScannerOverlayPath = new URL('../src/shell/QrScannerOverlay.tsx', import.meta.url); +const qrScannerOverlayPath = new URL( + '../src/shell/QrScannerOverlay.tsx', + import.meta.url, +); const qrScannerOverlaySource = fs.readFileSync(qrScannerOverlayPath, 'utf8'); const qrScannerOverlayTestPath = new URL( '../src/shell/QrScannerOverlay.test.tsx', @@ -39,7 +45,10 @@ const qrScannerOverlayTestSource = fs.readFileSync( qrScannerOverlayTestPath, 'utf8', ); -const appearancePath = new URL('../src/host-bridge/appearance.ts', import.meta.url); +const appearancePath = new URL( + '../src/host-bridge/appearance.ts', + import.meta.url, +); const appearanceSource = fs.readFileSync(appearancePath, 'utf8'); const appearanceTestPath = new URL( '../src/host-bridge/appearance.test.ts', @@ -48,11 +57,17 @@ const appearanceTestPath = new URL( const appearanceTestSource = fs.readFileSync(appearanceTestPath, 'utf8'); const bridgePath = new URL('../src/host-bridge/bridge.ts', import.meta.url); const bridgeSource = fs.readFileSync(bridgePath, 'utf8'); -const bridgeTestPath = new URL('../src/host-bridge/bridge.test.ts', import.meta.url); +const bridgeTestPath = new URL( + '../src/host-bridge/bridge.test.ts', + import.meta.url, +); const bridgeTestSource = fs.readFileSync(bridgeTestPath, 'utf8'); const badgePath = new URL('../src/host-bridge/badge.ts', import.meta.url); const badgeSource = fs.readFileSync(badgePath, 'utf8'); -const badgeTestPath = new URL('../src/host-bridge/badge.test.ts', import.meta.url); +const badgeTestPath = new URL( + '../src/host-bridge/badge.test.ts', + import.meta.url, +); const badgeTestSource = fs.readFileSync(badgeTestPath, 'utf8'); const capabilitiesPath = new URL( '../src/host-bridge/capabilities.ts', @@ -64,7 +79,10 @@ const capabilitiesTestPath = new URL( import.meta.url, ); const capabilitiesTestSource = fs.readFileSync(capabilitiesTestPath, 'utf8'); -const clipboardPath = new URL('../src/host-bridge/clipboard.ts', import.meta.url); +const clipboardPath = new URL( + '../src/host-bridge/clipboard.ts', + import.meta.url, +); const clipboardSource = fs.readFileSync(clipboardPath, 'utf8'); const clipboardTestPath = new URL( '../src/host-bridge/clipboard.test.ts', @@ -80,7 +98,10 @@ const dispatchTestPath = new URL( const dispatchTestSource = fs.readFileSync(dispatchTestPath, 'utf8'); const filesPath = new URL('../src/host-bridge/files.ts', import.meta.url); const filesSource = fs.readFileSync(filesPath, 'utf8'); -const filesTestPath = new URL('../src/host-bridge/files.test.ts', import.meta.url); +const filesTestPath = new URL( + '../src/host-bridge/files.test.ts', + import.meta.url, +); const filesTestSource = fs.readFileSync(filesTestPath, 'utf8'); const hapticsPath = new URL('../src/host-bridge/haptics.ts', import.meta.url); const hapticsSource = fs.readFileSync(hapticsPath, 'utf8'); @@ -109,10 +130,7 @@ const hostBridgeNetworkPath = new URL( '../src/host-bridge/network.ts', import.meta.url, ); -const hostBridgeNetworkSource = fs.readFileSync( - hostBridgeNetworkPath, - 'utf8', -); +const hostBridgeNetworkSource = fs.readFileSync(hostBridgeNetworkPath, 'utf8'); const hostBridgeNetworkTestPath = new URL( '../src/host-bridge/network.test.ts', import.meta.url, @@ -121,7 +139,10 @@ const hostBridgeNetworkTestSource = fs.readFileSync( hostBridgeNetworkTestPath, 'utf8', ); -const notificationsPath = new URL('../src/host-bridge/notifications.ts', import.meta.url); +const notificationsPath = new URL( + '../src/host-bridge/notifications.ts', + import.meta.url, +); const notificationsSource = fs.readFileSync(notificationsPath, 'utf8'); const notificationsTestPath = new URL( '../src/host-bridge/notifications.test.ts', @@ -142,7 +163,10 @@ const scannerTestPath = new URL( import.meta.url, ); const scannerTestSource = fs.readFileSync(scannerTestPath, 'utf8'); -const hostBridgeRuntimePath = new URL('../src/host-bridge/runtime.ts', import.meta.url); +const hostBridgeRuntimePath = new URL( + '../src/host-bridge/runtime.ts', + import.meta.url, +); const hostBridgeRuntimeSource = fs.readFileSync(hostBridgeRuntimePath, 'utf8'); const hostBridgeRuntimeTestPath = new URL( '../src/host-bridge/runtime.test.ts', @@ -154,7 +178,10 @@ const hostBridgeRuntimeTestSource = fs.readFileSync( ); const sharePath = new URL('../src/host-bridge/share.ts', import.meta.url); const shareSource = fs.readFileSync(sharePath, 'utf8'); -const shareTestPath = new URL('../src/host-bridge/share.test.ts', import.meta.url); +const shareTestPath = new URL( + '../src/host-bridge/share.test.ts', + import.meta.url, +); const shareTestSource = fs.readFileSync(shareTestPath, 'utf8'); const bridgeDirPath = new URL('../src/host-bridge/', import.meta.url); const bridgeSourceFiles = fs @@ -190,7 +217,11 @@ for (const [source, label, message] of [ 'mobile HostBridge navigation failed for', ], [hostBridgeNetworkSource, 'mobile network', 'mobile network failed for'], - [notificationsSource, 'mobile notification', 'mobile notification failed for'], + [ + notificationsSource, + 'mobile notification', + 'mobile notification failed for', + ], [shareSource, 'mobile share', 'mobile share failed for'], ]) { assertLabelOnlyMobileHostBridgeDiagnostics(source, label, message); @@ -208,15 +239,27 @@ const urlTestPath = new URL('../src/shell/url.test.ts', import.meta.url); const urlTestSource = fs.readFileSync(urlTestPath, 'utf8'); const deepLinkPath = new URL('../src/shell/deepLink.ts', import.meta.url); const deepLinkSource = fs.readFileSync(deepLinkPath, 'utf8'); -const deepLinkTestPath = new URL('../src/shell/deepLink.test.ts', import.meta.url); +const deepLinkTestPath = new URL( + '../src/shell/deepLink.test.ts', + import.meta.url, +); const deepLinkTestSource = fs.readFileSync(deepLinkTestPath, 'utf8'); const navigationPath = new URL('../src/shell/navigation.ts', import.meta.url); const navigationSource = fs.readFileSync(navigationPath, 'utf8'); -const navigationTestPath = new URL('../src/shell/navigation.test.ts', import.meta.url); +const navigationTestPath = new URL( + '../src/shell/navigation.test.ts', + import.meta.url, +); const navigationTestSource = fs.readFileSync(navigationTestPath, 'utf8'); -const webViewPolicyPath = new URL('../src/shell/webViewPolicy.ts', import.meta.url); +const webViewPolicyPath = new URL( + '../src/shell/webViewPolicy.ts', + import.meta.url, +); const webViewPolicySource = fs.readFileSync(webViewPolicyPath, 'utf8'); -const webViewHistoryPath = new URL('../src/shell/webViewHistory.ts', import.meta.url); +const webViewHistoryPath = new URL( + '../src/shell/webViewHistory.ts', + import.meta.url, +); const webViewHistorySource = fs.readFileSync(webViewHistoryPath, 'utf8'); const loadFailurePath = new URL('../src/shell/loadFailure.ts', import.meta.url); const loadFailureSource = fs.readFileSync(loadFailurePath, 'utf8'); @@ -229,9 +272,15 @@ const runtimePath = new URL('../src/shell/runtime.ts', import.meta.url); const runtimeSource = fs.readFileSync(runtimePath, 'utf8'); const lifecyclePath = new URL('../src/shell/lifecycle.ts', import.meta.url); const lifecycleSource = fs.readFileSync(lifecyclePath, 'utf8'); -const lifecycleTestPath = new URL('../src/shell/lifecycle.test.ts', import.meta.url); +const lifecycleTestPath = new URL( + '../src/shell/lifecycle.test.ts', + import.meta.url, +); const lifecycleTestSource = fs.readFileSync(lifecycleTestPath, 'utf8'); -const safeAreaTestPath = new URL('../src/shell/safeArea.test.ts', import.meta.url); +const safeAreaTestPath = new URL( + '../src/shell/safeArea.test.ts', + import.meta.url, +); const safeAreaTestSource = fs.readFileSync(safeAreaTestPath, 'utf8'); for (const [source, snippet, message] of [ @@ -285,8 +334,13 @@ const easConfigPath = new URL('../eas.json', import.meta.url); const easConfig = JSON.parse(fs.readFileSync(easConfigPath, 'utf8')); const rootPackagePath = new URL('../../../package.json', import.meta.url); const rootPackageConfig = JSON.parse(fs.readFileSync(rootPackagePath, 'utf8')); -const rootPackageLockPath = new URL('../../../package-lock.json', import.meta.url); -const rootPackageLock = JSON.parse(fs.readFileSync(rootPackageLockPath, 'utf8')); +const rootPackageLockPath = new URL( + '../../../package-lock.json', + import.meta.url, +); +const rootPackageLock = JSON.parse( + fs.readFileSync(rootPackageLockPath, 'utf8'), +); const iconPath = new URL('../assets/icon.png', import.meta.url); const icon = PNG.sync.read(fs.readFileSync(iconPath)); const brandBackgroundColor = '#fffdf9'; @@ -370,9 +424,6 @@ const blockedMobileChannelDependencies = [ 'posthog-react-native', 'react-native-code-push', ]; -const blockedMobileChannelLockDependencies = blockedMobileChannelDependencies.filter( - (dependency) => dependency !== 'expo-application', -); const blockedMobileChannelSnippets = [ '@react-native-firebase/analytics', '@react-native-firebase/app', @@ -463,7 +514,9 @@ function extractStringConstExport(source, exportName) { function extractStringObjectConstExport(source, exportName) { const match = source.match( - new RegExp(`export const ${exportName}\\s*=\\s*\\{([\\s\\S]*?)\\}\\s*as const;`), + new RegExp( + `export const ${exportName}\\s*=\\s*\\{([\\s\\S]*?)\\}\\s*as const;`, + ), ); if (!match) { throw new Error(`unable to read ${exportName}`); @@ -504,7 +557,9 @@ function extractMobileBridgeUnsupportedMethods(source) { /async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n {2}\}/, ); if (!match) { - throw new Error('unable to read mobile shell HostBridge unsupported methods'); + throw new Error( + 'unable to read mobile shell HostBridge unsupported methods', + ); } const unsupportedMethods = new Set(); @@ -586,20 +641,24 @@ function assertNoBlockedMobileChannelDependencies(packageJson, packageLabel) { } } -function assertNoBlockedMobileChannelLockPackages() { - const packageNames = Object.keys(rootPackageLock.packages ?? {}) - .filter((packagePath) => packagePath.startsWith('node_modules/')) - .map((packagePath) => packagePath.replace(/^node_modules\//, '')); - const dependencyNames = Object.keys(rootPackageLock.dependencies ?? {}); - const lockedPackageNames = new Set([...packageNames, ...dependencyNames]); - - for (const dependency of blockedMobileChannelLockDependencies) { - if (lockedPackageNames.has(dependency)) { - throw new Error( - `root package-lock must not resolve ${dependency} before the real mobile channel contract exists`, - ); - } +function readWorkspaceLockManifest(packagePath, packageLabel) { + const lockManifest = rootPackageLock.packages?.[packagePath]; + if (!lockManifest) { + throw new Error( + `root package-lock is missing ${packageLabel} manifest at ${packagePath || ''}`, + ); } + return lockManifest; +} + +function assertLockManifestMatchesMobileChannelBoundary( + packagePath, + packageLabel, +) { + assertNoBlockedMobileChannelDependencies( + readWorkspaceLockManifest(packagePath, packageLabel), + `${packageLabel} lock manifest`, + ); } function assertNoBlockedMobileChannelSnippets() { @@ -647,11 +706,19 @@ function assertPackageDependencyVersion( } } -function assertPackageLockVersion(dependency, expected) { - const actual = rootPackageLock.packages?.[`node_modules/${dependency}`]?.version; +function assertPackageLockVersion(packagePath, dependency, expected) { + const pathParts = packagePath ? packagePath.split('/') : []; + const candidates = []; + for (let depth = pathParts.length; depth >= 0; depth -= 1) { + const prefix = pathParts.slice(0, depth).join('/'); + candidates.push(`${prefix ? `${prefix}/` : ''}node_modules/${dependency}`); + } + const actual = candidates + .map((candidate) => rootPackageLock.packages?.[candidate]) + .find(Boolean)?.version; if (actual !== expected) { throw new Error( - `root package-lock ${dependency} resolved version drifted: expected ${expected} but got ${actual}`, + `root package-lock ${packagePath} ${dependency} resolved version drifted: expected ${expected} but got ${actual}`, ); } } @@ -659,7 +726,9 @@ function assertPackageLockVersion(dependency, expected) { function collectProductionSourceFiles(entry) { const stats = fs.statSync(entry); if (stats.isDirectory()) { - const directory = entry.href.endsWith('/') ? entry : new URL(`${entry.href}/`); + const directory = entry.href.endsWith('/') + ? entry + : new URL(`${entry.href}/`); return fs .readdirSync(entry, { withFileTypes: true }) .filter( @@ -775,7 +844,9 @@ function collectMobileShellSourceRelativePaths(files) { } assertSameList( - collectMobileShellSourceRelativePaths(collectProductionSourceFiles(new URL('../src/', import.meta.url))), + collectMobileShellSourceRelativePaths( + collectProductionSourceFiles(new URL('../src/', import.meta.url)), + ), requiredMobileShellSourceModules, 'mobile shell source modules', ); @@ -807,7 +878,9 @@ for (const snippet of [ "!bundlePath.endsWith('.hbc')", ]) { if (!expoExportSmokeSource.includes(snippet)) { - throw new Error(`mobile shell Expo export smoke must verify host context token ${snippet}`); + throw new Error( + `mobile shell Expo export smoke must verify host context token ${snippet}`, + ); } } @@ -835,7 +908,9 @@ for (const snippet of [ "args: ['run', 'mobile-shell:export']", ]) { if (!nativeShellCheckSource.includes(snippet)) { - throw new Error(`root native shell gate must keep mobile distribution smoke ${snippet}`); + throw new Error( + `root native shell gate must keep mobile distribution smoke ${snippet}`, + ); } } @@ -853,7 +928,11 @@ assertNoDevScaffoldTerms( assertNoTrackedMobileGeneratedFiles(); assertNoBlockedMobileChannelDependencies(packageConfig, 'mobile shell package'); assertNoBlockedMobileChannelDependencies(rootPackageConfig, 'root H5 package'); -assertNoBlockedMobileChannelLockPackages(); +assertLockManifestMatchesMobileChannelBoundary('', 'root H5 package'); +assertLockManifestMatchesMobileChannelBoundary( + 'apps/mobile-shell', + 'mobile shell workspace', +); assertNoBlockedMobileChannelSnippets(); for (const [scriptName, expected] of Object.entries({ @@ -869,17 +948,25 @@ for (const [scriptName, expected] of Object.entries({ 'export:smoke': 'node scripts/check-expo-export.mjs', typecheck: 'tsc -p tsconfig.json --noEmit && node scripts/check-config.mjs', })) { - assertPackageScript(packageConfig, 'mobile shell package', scriptName, expected); + assertPackageScript( + packageConfig, + 'mobile shell package', + scriptName, + expected, + ); } for (const [scriptName, expected] of Object.entries({ 'mobile-shell:dev': 'npm --prefix apps/mobile-shell run dev', 'mobile-shell:typecheck': 'npm --prefix apps/mobile-shell run typecheck', 'mobile-shell:test': 'npm --prefix apps/mobile-shell run test', - 'mobile-shell:build-config': 'npm --prefix apps/mobile-shell run build-config:smoke', - 'mobile-shell:build:android': 'npm --prefix apps/mobile-shell run build:android', + 'mobile-shell:build-config': + 'npm --prefix apps/mobile-shell run build-config:smoke', + 'mobile-shell:build:android': + 'npm --prefix apps/mobile-shell run build:android', 'mobile-shell:build:ios': 'npm --prefix apps/mobile-shell run build:ios', - 'mobile-shell:build-artifacts': 'npm --prefix apps/mobile-shell run build-artifacts:smoke', + 'mobile-shell:build-artifacts': + 'npm --prefix apps/mobile-shell run build-artifacts:smoke', 'mobile-shell:config': 'npm --prefix apps/mobile-shell run config:smoke', 'mobile-shell:export': 'npm --prefix apps/mobile-shell run export:smoke', })) { @@ -912,13 +999,6 @@ for (const [dependency, expected] of Object.entries({ dependency, expected, ); - assertPackageDependencyVersion( - rootPackageConfig, - 'root package', - 'dependencies', - dependency, - expected, - ); } for (const [dependency, expected] of Object.entries({ @@ -940,7 +1020,7 @@ for (const [dependency, expected] of Object.entries({ 'react-native-safe-area-context': '5.8.0', 'react-native-webview': '13.16.1', })) { - assertPackageLockVersion(dependency, expected); + assertPackageLockVersion('apps/mobile-shell', dependency, expected); } for (const [dependency, expected] of Object.entries({ @@ -955,22 +1035,15 @@ for (const [dependency, expected] of Object.entries({ dependency, expected, ); - assertPackageDependencyVersion( - rootPackageConfig, - 'root package', - 'devDependencies', - dependency, - expected, - ); } -assertPackageLockVersion('eas-cli', '20.3.0'); +assertPackageLockVersion('apps/mobile-shell', 'eas-cli', '20.3.0'); for (const [dependency, expected] of Object.entries({ typescript: '5.8.3', vitest: '0.34.6', })) { - assertPackageLockVersion(dependency, expected); + assertPackageLockVersion('apps/mobile-shell', dependency, expected); } const sharedCapabilities = extractStringArrayExport( @@ -1027,12 +1100,15 @@ const sharedNativeAppQuery = extractStringObjectConstExport( ); const sharedPublicWebOriginUrl = new URL(sharedPublicWebOrigin); if (sharedPublicWebOriginUrl.protocol !== 'https:') { - throw new Error('shared HostBridge public web origin must use https for mobile app links'); + throw new Error( + 'shared HostBridge public web origin must use https for mobile app links', + ); } const sharedPublicWebHost = sharedPublicWebOriginUrl.hostname; const sharedPublicWebAssociatedDomain = `applinks:${sharedPublicWebHost}`; const handledMobileMethods = extractMobileBridgeHandledMethods(dispatchSource); -const unsupportedMobileMethods = extractMobileBridgeUnsupportedMethods(dispatchSource); +const unsupportedMobileMethods = + extractMobileBridgeUnsupportedMethods(dispatchSource); const mobileCapabilities = sharedMobileBaseCapabilities; const iosMobileCapabilities = sharedMobileIosCapabilities; const mobileCapabilitySet = new Set(mobileCapabilities); @@ -1109,10 +1185,14 @@ if (!hostBridgeSource.includes('function assertImportedFileSizeWithinLimit')) { } if (!bridgeSource.includes('HOST_BRIDGE_RESPONSE_CACHE_MAX,')) { - throw new Error('mobile shell bridge must import shared HostBridge response cache limit'); + throw new Error( + 'mobile shell bridge must import shared HostBridge response cache limit', + ); } if (/const HOST_BRIDGE_RESPONSE_CACHE_MAX\s*=/.test(protocolSource)) { - throw new Error('mobile shell protocol must not redeclare HostBridge response cache limit'); + throw new Error( + 'mobile shell protocol must not redeclare HostBridge response cache limit', + ); } for (const expectedErrorCode of [ 'invalid_request', @@ -1133,7 +1213,9 @@ if ( protocolSource.includes('? error.message') || protocolSource.includes('error.message\n :') ) { - throw new Error('mobile shell protocol must not expose unknown native Error.message values to H5'); + throw new Error( + 'mobile shell protocol must not expose unknown native Error.message values to H5', + ); } for (const snippet of [ 'parseRequest', @@ -1158,7 +1240,9 @@ if ( !protocolSource.includes('HOST_BRIDGE_ERROR_CODES.has(') || !protocolSource.includes('mobile host bridge request failed') ) { - throw new Error('mobile shell protocol must normalize non-contract host errors'); + throw new Error( + 'mobile shell protocol must normalize non-contract host errors', + ); } for (const [functionName, readCall] of [ @@ -1167,7 +1251,9 @@ for (const [functionName, readCall] of [ ['importAudioFile', 'readMobileBase64File(file,'], ]) { const functionBody = extractFunctionBody(hostBridgeSource, functionName); - const sizeCheckIndex = functionBody.indexOf('assertImportedFileSizeWithinLimit('); + const sizeCheckIndex = functionBody.indexOf( + 'assertImportedFileSizeWithinLimit(', + ); const readIndex = functionBody.indexOf(readCall); if (sizeCheckIndex === -1 || readIndex === -1 || sizeCheckIndex > readIndex) { throw new Error( @@ -1181,7 +1267,9 @@ for (const profileSource of [ 'HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES', ]) { if (!hostBridgeSource.includes(profileSource)) { - throw new Error(`mobile shell must use shared HostBridge profile ${profileSource}`); + throw new Error( + `mobile shell must use shared HostBridge profile ${profileSource}`, + ); } } @@ -1190,7 +1278,9 @@ for (const snippet of [ 'export const IOS_MOBILE_HOST_CAPABILITIES =\n HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES;', ]) { if (!capabilitiesSource.includes(snippet)) { - throw new Error(`mobile shell capability profile must stay directly shared: ${snippet}`); + throw new Error( + `mobile shell capability profile must stay directly shared: ${snippet}`, + ); } } @@ -1198,7 +1288,9 @@ if ( /export const MOBILE_HOST_CAPABILITIES[^=]*= \[/.test(hostBridgeSource) || /export const IOS_MOBILE_HOST_CAPABILITIES[^=]*= \[/.test(hostBridgeSource) ) { - throw new Error('mobile shell must not redeclare HostBridge capability profiles'); + throw new Error( + 'mobile shell must not redeclare HostBridge capability profiles', + ); } for (const snippet of [ @@ -1288,7 +1380,8 @@ if (unsupportedMobileCapabilities.length > 0) { const undeclaredMobileMethodHandlers = handledMobileMethods.filter( (method) => - !iosMobileCapabilitySet.has(method) && !sdkBackedCapabilities.includes(method), + !iosMobileCapabilitySet.has(method) && + !sdkBackedCapabilities.includes(method), ); if (undeclaredMobileMethodHandlers.length > 0) { throw new Error( @@ -1317,11 +1410,15 @@ const mobileHostVersion = extractStringConstExport( 'MOBILE_SHELL_HOST_VERSION_FALLBACK', ); if (mobileHostVersion !== appConfig.version) { - throw new Error('mobile shell HostBridge fallback host version must match app.json version'); + throw new Error( + 'mobile shell HostBridge fallback host version must match app.json version', + ); } if (appConfig.orientation !== 'default') { - throw new Error('mobile shell must allow device orientation for landscape-capable H5 games'); + throw new Error( + 'mobile shell must allow device orientation for landscape-capable H5 games', + ); } if (packageConfig.version !== appConfig.version) { @@ -1333,7 +1430,9 @@ if (appConfig.icon !== './assets/icon.png') { } if (appConfig.userInterfaceStyle !== 'automatic') { - throw new Error('mobile shell must follow the native system appearance setting'); + throw new Error( + 'mobile shell must follow the native system appearance setting', + ); } assertSameList( @@ -1348,7 +1447,9 @@ if (icon.width < 512 || icon.height < 512) { const iconAlpha = countAlphaPixels(icon); if (iconAlpha.transparent === 0 || iconAlpha.opaque === 0) { - throw new Error('mobile shell adaptive icon foreground must use the real transparent brand asset'); + throw new Error( + 'mobile shell adaptive icon foreground must use the real transparent brand asset', + ); } if ( @@ -1356,27 +1457,42 @@ if ( appConfig.splash?.resizeMode !== 'contain' || appConfig.splash?.backgroundColor !== brandBackgroundColor ) { - throw new Error('mobile shell splash must use the real brand icon and brand background'); + throw new Error( + 'mobile shell splash must use the real brand icon and brand background', + ); } if (appConfig.updates?.enabled !== false) { - throw new Error('mobile shell OTA updates must stay disabled until a real release channel exists'); + throw new Error( + 'mobile shell OTA updates must stay disabled until a real release channel exists', + ); } if (Object.keys(appConfig.updates ?? {}).some((key) => key !== 'enabled')) { - throw new Error('mobile shell must not configure OTA update metadata without a real release channel'); + throw new Error( + 'mobile shell must not configure OTA update metadata without a real release channel', + ); } if ('runtimeVersion' in appConfig) { - throw new Error('mobile shell must not configure runtimeVersion without a real OTA release channel'); + throw new Error( + 'mobile shell must not configure runtimeVersion without a real OTA release channel', + ); } if ('releaseChannel' in appConfig || 'channel' in appConfig) { - throw new Error('mobile shell must not configure an app release channel without a real release process'); + throw new Error( + 'mobile shell must not configure an app release channel without a real release process', + ); } -if (easConfig.cli?.version !== '>= 20.3.0' || easConfig.cli?.appVersionSource !== 'local') { - throw new Error('mobile shell EAS builds must pin CLI floor and use local app version fields'); +if ( + easConfig.cli?.version !== '>= 20.3.0' || + easConfig.cli?.appVersionSource !== 'local' +) { + throw new Error( + 'mobile shell EAS builds must pin CLI floor and use local app version fields', + ); } for (const [label, outputPath, extension] of [ @@ -1384,10 +1500,14 @@ for (const [label, outputPath, extension] of [ ['iOS simulator', iosSimulatorBuildOutputPath, '.tar.gz'], ]) { if (!outputPath.startsWith('../../build/native/mobile/')) { - throw new Error(`mobile shell ${label} local build output must stay under root build/native/mobile`); + throw new Error( + `mobile shell ${label} local build output must stay under root build/native/mobile`, + ); } if (!outputPath.endsWith(extension)) { - throw new Error(`mobile shell ${label} local build output must end with ${extension}`); + throw new Error( + `mobile shell ${label} local build output must end with ${extension}`, + ); } } @@ -1397,7 +1517,9 @@ if ( easConfig.build?.production?.android?.buildType !== 'apk' || easConfig.build?.production?.env?.EXPO_NO_DOTENV !== '1' ) { - throw new Error('mobile shell EAS Android production profile must build an internal APK without local dotenv'); + throw new Error( + 'mobile shell EAS Android production profile must build an internal APK without local dotenv', + ); } if ( @@ -1406,19 +1528,30 @@ if ( easConfig.build?.['production-simulator']?.ios?.simulator !== true || easConfig.build?.['production-simulator']?.env?.EXPO_NO_DOTENV !== '1' ) { - throw new Error('mobile shell EAS iOS production smoke profile must build an internal simulator package without local dotenv'); + throw new Error( + 'mobile shell EAS iOS production smoke profile must build an internal simulator package without local dotenv', + ); } for (const [profileName, profile] of Object.entries(easConfig.build ?? {})) { - for (const blockedKey of ['credentialsSource', 'autoIncrement', 'submit', 'releaseChannel']) { + for (const blockedKey of [ + 'credentialsSource', + 'autoIncrement', + 'submit', + 'releaseChannel', + ]) { if (blockedKey in profile) { - throw new Error(`mobile shell EAS ${profileName} profile must not configure ${blockedKey}`); + throw new Error( + `mobile shell EAS ${profileName} profile must not configure ${blockedKey}`, + ); } } } if ('submit' in easConfig) { - throw new Error('mobile shell EAS config must not include store submit profiles yet'); + throw new Error( + 'mobile shell EAS config must not include store submit profiles yet', + ); } assertSameList( @@ -1428,7 +1561,9 @@ assertSameList( ); if (appConfig.ios?.bundleIdentifier !== 'world.genarrative.mobile') { - throw new Error('mobile shell iOS bundle identifier must be world.genarrative.mobile'); + throw new Error( + 'mobile shell iOS bundle identifier must be world.genarrative.mobile', + ); } if (appConfig.ios?.buildNumber !== '1') { @@ -1440,16 +1575,21 @@ if (appConfig.ios?.infoPlist?.ITSAppUsesNonExemptEncryption !== false) { } if ( - appConfig.ios?.infoPlist?.NSAppTransportSecurity?.NSAllowsArbitraryLoads !== false + appConfig.ios?.infoPlist?.NSAppTransportSecurity?.NSAllowsArbitraryLoads !== + false ) { - throw new Error('mobile shell iOS ATS must not allow arbitrary network loads'); + throw new Error( + 'mobile shell iOS ATS must not allow arbitrary network loads', + ); } if ( appConfig.ios?.infoPlist?.NSMicrophoneUsageDescription !== '允许 Genarrative 使用麦克风运行需要实时声音输入的玩法。' ) { - throw new Error('mobile shell iOS microphone permission text must describe same-origin H5 gameplay input'); + throw new Error( + 'mobile shell iOS microphone permission text must describe same-origin H5 gameplay input', + ); } const iosPrivacyManifests = appConfig.ios?.privacyManifests; @@ -1473,10 +1613,7 @@ assertSameList( ); const requiredPrivacyAccessedApiTypes = new Map([ - [ - 'NSPrivacyAccessedAPICategoryFileTimestamp', - ['0A2A.1', '3B52.1', 'C617.1'], - ], + ['NSPrivacyAccessedAPICategoryFileTimestamp', ['0A2A.1', '3B52.1', 'C617.1']], ['NSPrivacyAccessedAPICategoryDiskSpace', ['85F4.1', 'E174.1']], ['NSPrivacyAccessedAPICategorySystemBootTime', ['35F9.1']], ['NSPrivacyAccessedAPICategoryUserDefaults', ['CA92.1']], @@ -1484,7 +1621,9 @@ const requiredPrivacyAccessedApiTypes = new Map([ const privacyAccessedApiTypes = iosPrivacyManifests.NSPrivacyAccessedAPITypes ?? []; if (privacyAccessedApiTypes.length !== requiredPrivacyAccessedApiTypes.size) { - throw new Error('mobile shell iOS privacy manifest accessed API type count drifted'); + throw new Error( + 'mobile shell iOS privacy manifest accessed API type count drifted', + ); } for (const [apiType, reasons] of requiredPrivacyAccessedApiTypes) { @@ -1502,7 +1641,9 @@ for (const [apiType, reasons] of requiredPrivacyAccessedApiTypes) { } if (appConfig.android?.package !== 'world.genarrative.mobile') { - throw new Error('mobile shell Android package must be world.genarrative.mobile'); + throw new Error( + 'mobile shell Android package must be world.genarrative.mobile', + ); } if (appConfig.android?.versionCode !== 1) { @@ -1510,7 +1651,9 @@ if (appConfig.android?.versionCode !== 1) { } if (appConfig.android?.usesCleartextTraffic !== false) { - throw new Error('mobile shell Android package must disable cleartext traffic'); + throw new Error( + 'mobile shell Android package must disable cleartext traffic', + ); } if (appConfig.android?.allowBackup !== false) { @@ -1518,19 +1661,26 @@ if (appConfig.android?.allowBackup !== false) { } if (appConfig.android?.softwareKeyboardLayoutMode !== 'resize') { - throw new Error('mobile shell Android keyboard layout must resize the WebView'); + throw new Error( + 'mobile shell Android keyboard layout must resize the WebView', + ); } if ( !Array.isArray(appConfig.android?.permissions) || appConfig.android.permissions.length !== 2 || - appConfig.android.permissions[0] !== 'android.permission.POST_NOTIFICATIONS' || + appConfig.android.permissions[0] !== + 'android.permission.POST_NOTIFICATIONS' || appConfig.android.permissions[1] !== 'android.permission.RECORD_AUDIO' ) { - throw new Error('mobile shell Android package must request only POST_NOTIFICATIONS and RECORD_AUDIO for real local notifications and same-origin H5 microphone gameplay'); + throw new Error( + 'mobile shell Android package must request only POST_NOTIFICATIONS and RECORD_AUDIO for real local notifications and same-origin H5 microphone gameplay', + ); } if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) { - throw new Error('mobile shell Android CAMERA permission must come from real camera plugins only'); + throw new Error( + 'mobile shell Android CAMERA permission must come from real camera plugins only', + ); } for (const permission of blockedAndroidPermissions) { @@ -1541,32 +1691,52 @@ for (const permission of blockedAndroidPermissions) { for (const permission of blockedAndroidPermissions) { if (appConfig.android?.permissions?.includes(permission)) { - throw new Error(`mobile shell Android package must not request ${permission}`); + throw new Error( + `mobile shell Android package must not request ${permission}`, + ); } } -if (appConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) { - throw new Error('mobile shell Android package must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay'); +if ( + appConfig.android?.blockedPermissions?.includes( + 'android.permission.RECORD_AUDIO', + ) +) { + throw new Error( + 'mobile shell Android package must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay', + ); } -if (appConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) { - throw new Error('mobile shell Android package must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery'); +if ( + appConfig.android?.blockedPermissions?.includes( + 'android.permission.POST_NOTIFICATIONS', + ) +) { + throw new Error( + 'mobile shell Android package must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery', + ); } if ( appConfig.android?.adaptiveIcon?.foregroundImage !== './assets/icon.png' || appConfig.android?.adaptiveIcon?.backgroundColor !== brandBackgroundColor ) { - throw new Error('mobile shell Android adaptive icon must use the real brand icon and brand background'); + throw new Error( + 'mobile shell Android adaptive icon must use the real brand icon and brand background', + ); } const androidFilters = appConfig.android?.intentFilters ?? []; if (androidFilters.length !== 1) { - throw new Error('mobile shell Android app link filter must be the only intent filter'); + throw new Error( + 'mobile shell Android app link filter must be the only intent filter', + ); } const [androidFilter] = androidFilters; if (androidFilter.action !== 'VIEW' || androidFilter.autoVerify !== true) { - throw new Error('mobile shell Android app link filter must be a verified VIEW filter'); + throw new Error( + 'mobile shell Android app link filter must be a verified VIEW filter', + ); } assertSameList( @@ -1590,7 +1760,9 @@ if ( } if (appConfig.extra?.genarrativeHostBridgeVersion !== sharedHostBridgeVersion) { - throw new Error('mobile shell extra HostBridge version must match shared HostBridge version'); + throw new Error( + 'mobile shell extra HostBridge version must match shared HostBridge version', + ); } for (const snippet of [ @@ -1599,7 +1771,7 @@ for (const snippet of [ 'resolveMobileShellUrlFromDeepLink', 'logMobileShellDeepLinkFailure', "'initial_url.read'", - "logMobileShellDeepLinkFailure(`${source}.rejected`, url)", + 'logMobileShellDeepLinkFailure(`${source}.rejected`, url)', 'configureMobileHostBridgeNavigation', 'HOST_BRIDGE_PROTOCOL', 'HOST_BRIDGE_VERSION', @@ -1689,19 +1861,37 @@ for (const snippet of [ } if (shellAppSource.includes('catch(() => undefined)')) { - throw new Error('mobile shell ShellApp must not hide async navigation failures'); + throw new Error( + 'mobile shell ShellApp must not hide async navigation failures', + ); } -if (shellAppSource.includes("console.warn('mobile WebView process failed', {")) { - throw new Error('mobile shell WebView process diagnostics must not log detail objects'); +if ( + shellAppSource.includes("console.warn('mobile WebView process failed', {") +) { + throw new Error( + 'mobile shell WebView process diagnostics must not log detail objects', + ); } -if (!shellAppTestSource.includes('blocked WebView file downloads are logged for host diagnostics')) { - throw new Error('mobile shell tests must cover blocked WebView file download diagnostics'); +if ( + !shellAppTestSource.includes( + 'blocked WebView file downloads are logged for host diagnostics', + ) +) { + throw new Error( + 'mobile shell tests must cover blocked WebView file download diagnostics', + ); } -if (!shellAppTestSource.includes('WebView origin whitelist is limited to the resolved H5 origin')) { - throw new Error('mobile shell tests must cover WebView origin whitelist boundaries'); +if ( + !shellAppTestSource.includes( + 'WebView origin whitelist is limited to the resolved H5 origin', + ) +) { + throw new Error( + 'mobile shell tests must cover WebView origin whitelist boundaries', + ); } for (const snippet of [ @@ -1725,7 +1915,7 @@ for (const snippet of [ "state === 'background'", "? 'background'", ": 'inactive'", - 'focused: state === \'active\'', + "focused: state === 'active'", 'nativeState: state', ]) { if (!lifecycleSource.includes(snippet)) { @@ -1767,7 +1957,9 @@ for (const snippet of [ 'injectHostBridgeMessage(response, logMobileHostBridgeMessageFailure)', ]) { if (!shellAppSource.includes(snippet)) { - throw new Error(`mobile shell HostBridge event injection missing ${snippet}`); + throw new Error( + `mobile shell HostBridge event injection missing ${snippet}`, + ); } } @@ -1828,7 +2020,7 @@ for (const snippet of [ "url: 'javascript:alert(1)'", "url: '/favicon.ico'", "url: 'https://www.genarrative.world/assets/main.js'", - "https://www.genarrative.world/creation/puzzle?sessionId=private#recover", + 'https://www.genarrative.world/creation/puzzle?sessionId=private#recover', "url: 'https://www.genarrative.world/works/detail'", ]) { if (!loadFailureTestSource.includes(snippet)) { @@ -1867,7 +2059,9 @@ if ( webViewPolicySource.includes('catch (error) {}') || webViewPolicySource.includes('catch (_error) {}') ) { - throw new Error('mobile shell WebView policy must not hide injected script failures'); + throw new Error( + 'mobile shell WebView policy must not hide injected script failures', + ); } if ( @@ -1882,7 +2076,7 @@ if ( for (const snippet of [ 'parseMobileWebViewHistoryStateMessage', 'genarrative.mobile.historyState', - 'typeof candidate.canGoBack !== \'boolean\'', + "typeof candidate.canGoBack !== 'boolean'", ]) { if (!webViewHistorySource.includes(snippet)) { throw new Error(`mobile shell WebView history parser missing ${snippet}`); @@ -1901,9 +2095,7 @@ if (appSource.includes('./src/host-bridge/')) { throw new Error('mobile shell App must not import HostBridge directly'); } -if ( - shellAppSource.includes('process.env.EXPO_PUBLIC_GENARRATIVE_WEB_URL ||') -) { +if (shellAppSource.includes('process.env.EXPO_PUBLIC_GENARRATIVE_WEB_URL ||')) { throw new Error( 'mobile shell ShellApp must normalize EXPO_PUBLIC_GENARRATIVE_WEB_URL', ); @@ -1930,16 +2122,22 @@ for (const localWebOriginDuplicate of [ `ALLOWED_PRODUCTION_WEB_ORIGIN = '${sharedPublicWebOrigin}'`, ]) { if (urlSource.includes(localWebOriginDuplicate)) { - throw new Error('mobile shell public web origin must come from shared HostBridge contract'); + throw new Error( + 'mobile shell public web origin must come from shared HostBridge contract', + ); } } if (!webViewPolicySource.includes('DEFAULT_MOBILE_SHELL_WEB_URL')) { - throw new Error('mobile shell WebView policy must use the shared default H5 URL'); + throw new Error( + 'mobile shell WebView policy must use the shared default H5 URL', + ); } if (webViewPolicySource.includes(`'${sharedPublicWebUrl}'`)) { - throw new Error('mobile shell WebView policy must not duplicate the public web URL'); + throw new Error( + 'mobile shell WebView policy must not duplicate the public web URL', + ); } for (const snippet of [ @@ -1973,9 +2171,13 @@ if ( dispatchSource.includes('ok(request, await openShare') || dispatchSource.includes('ok(request, setMobileHostBridgeShareTarget') || dispatchSource.includes('let currentShareTarget') || - dispatchSource.includes('normalizeHostBridgeShareOpenPayload(currentShareTarget)') + dispatchSource.includes( + 'normalizeHostBridgeShareOpenPayload(currentShareTarget)', + ) ) { - throw new Error('mobile shell share HostBridge methods must delegate to share module'); + throw new Error( + 'mobile shell share HostBridge methods must delegate to share module', + ); } for (const snippet of [ @@ -1998,8 +2200,12 @@ for (const snippet of [ } } -if (shareSource.includes("const WEB_APP_ORIGIN = 'https://www.genarrative.world'")) { - throw new Error('mobile shell share URL policy must reuse the shared web origin'); +if ( + shareSource.includes("const WEB_APP_ORIGIN = 'https://www.genarrative.world'") +) { + throw new Error( + 'mobile shell share URL policy must reuse the shared web origin', + ); } for (const snippet of [ @@ -2020,7 +2226,7 @@ for (const snippet of [ 'HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostVersion', 'HOST_BRIDGE_NATIVE_APP_QUERY_KEY.bridgeVersion', 'HOST_BRIDGE_NATIVE_APP_QUERY_KEY.hostCapabilities', - ]) { +]) { if (!urlSource.includes(snippet)) { throw new Error(`mobile shell host-context URL builder missing ${snippet}`); } @@ -2058,22 +2264,30 @@ for (const hardcodedHostContextSnippet of [ for (const snippet of [ '附加宿主上下文前会清理旧移动壳 query', 'clientRuntime=browser&hostShell=old_shell&hostCapabilities=old', - "expect(builtUrl.match(/clientRuntime=/g)).toHaveLength(1)", - "expect(builtUrl.match(/hostShell=/g)).toHaveLength(1)", - "expect(builtUrl.match(/hostCapabilities=/g)).toHaveLength(1)", + 'expect(builtUrl.match(/clientRuntime=/g)).toHaveLength(1)', + 'expect(builtUrl.match(/hostShell=/g)).toHaveLength(1)', + 'expect(builtUrl.match(/hostCapabilities=/g)).toHaveLength(1)', ]) { if (!urlTestSource.includes(snippet)) { - throw new Error(`mobile shell URL tests must cover stale host-context cleanup: ${snippet}`); + throw new Error( + `mobile shell URL tests must cover stale host-context cleanup: ${snippet}`, + ); } } if ( !shellAppSource.includes('allowLocalDevelopment: __DEV__') || !shellAppSource.includes('baseWebUrlOptions') || - !deepLinkSource.includes('baseWebUrlOptions: MobileShellBaseWebUrlOptions = {}') || - !deepLinkSource.includes('resolveMobileShellBaseWebUrl(\n baseWebUrl,\n baseWebUrlOptions,') + !deepLinkSource.includes( + 'baseWebUrlOptions: MobileShellBaseWebUrlOptions = {}', + ) || + !deepLinkSource.includes( + 'resolveMobileShellBaseWebUrl(\n baseWebUrl,\n baseWebUrlOptions,', + ) ) { - throw new Error('mobile shell must only allow local H5 URLs through explicit development mode'); + throw new Error( + 'mobile shell must only allow local H5 URLs through explicit development mode', + ); } for (const snippet of [ @@ -2107,7 +2321,9 @@ for (const snippet of [ 'buildMobileShellUrl(\n new URL(targetPath, webOrigin).toString(),\n options,\n baseWebUrlOptions,', ]) { if (!deepLinkSource.includes(snippet)) { - throw new Error(`mobile shell deep link host-context flow missing ${snippet}`); + throw new Error( + `mobile shell deep link host-context flow missing ${snippet}`, + ); } } @@ -2124,18 +2340,26 @@ for (const snippet of [ 'new URL(rawUrl, allowedOrigin).toString()', ]) { if (!navigationSource.includes(snippet)) { - throw new Error(`mobile shell native-page navigation policy missing ${snippet}`); + throw new Error( + `mobile shell native-page navigation policy missing ${snippet}`, + ); } } if ( - !navigationTestSource.includes('WebView 外链原生探测或打开失败时抛给壳层记录') || + !navigationTestSource.includes( + 'WebView 外链原生探测或打开失败时抛给壳层记录', + ) || !navigationTestSource.includes('native canOpenURL failed') || !navigationTestSource.includes('native openURL failed') || - !navigationTestSource.includes(").rejects.toThrow('native canOpenURL failed')") || + !navigationTestSource.includes( + ").rejects.toThrow('native canOpenURL failed')", + ) || !navigationTestSource.includes(").rejects.toThrow('native openURL failed')") ) { - throw new Error('mobile shell navigation tests must cover native external open failures'); + throw new Error( + 'mobile shell navigation tests must cover native external open failures', + ); } if ( @@ -2171,7 +2395,9 @@ for (const dependency of [ } const imagePickerPlugin = appConfig.plugins?.find((plugin) => - Array.isArray(plugin) ? plugin[0] === 'expo-image-picker' : plugin === 'expo-image-picker', + Array.isArray(plugin) + ? plugin[0] === 'expo-image-picker' + : plugin === 'expo-image-picker', ); if (!imagePickerPlugin) { throw new Error('mobile shell image picker plugin is missing'); @@ -2183,24 +2409,32 @@ if (Array.isArray(imagePickerPlugin)) { pluginOptions.photosPermission !== '允许 Genarrative 读取你选择的图片,用于导入创作素材和参考图。' ) { - throw new Error('mobile shell image picker photo permission text must describe selected creative image import'); + throw new Error( + 'mobile shell image picker photo permission text must describe selected creative image import', + ); } if ( pluginOptions.cameraPermission !== '允许 Genarrative 使用相机拍摄创作素材和参考图。' ) { - throw new Error('mobile shell image picker camera permission text must describe creative image capture'); + throw new Error( + 'mobile shell image picker camera permission text must describe creative image capture', + ); } if ( pluginOptions.microphonePermission !== '允许 Genarrative 使用麦克风运行需要实时声音输入的玩法。' ) { - throw new Error('mobile shell image picker microphone text must describe same-origin H5 gameplay input'); + throw new Error( + 'mobile shell image picker microphone text must describe same-origin H5 gameplay input', + ); } } const cameraPlugin = appConfig.plugins?.find((plugin) => - Array.isArray(plugin) ? plugin[0] === 'expo-camera' : plugin === 'expo-camera', + Array.isArray(plugin) + ? plugin[0] === 'expo-camera' + : plugin === 'expo-camera', ); if (!cameraPlugin) { throw new Error('mobile shell camera plugin is missing'); @@ -2209,24 +2443,31 @@ if (!cameraPlugin) { if (Array.isArray(cameraPlugin)) { const pluginOptions = cameraPlugin[1] ?? {}; if ( - pluginOptions.cameraPermission !== - '允许 Genarrative 使用相机扫描二维码。' + pluginOptions.cameraPermission !== '允许 Genarrative 使用相机扫描二维码。' ) { - throw new Error('mobile shell camera permission text must describe QR scanning'); + throw new Error( + 'mobile shell camera permission text must describe QR scanning', + ); } if ( pluginOptions.microphonePermission !== '允许 Genarrative 使用麦克风运行需要实时声音输入的玩法。' ) { - throw new Error('mobile shell camera microphone text must describe same-origin H5 gameplay input'); + throw new Error( + 'mobile shell camera microphone text must describe same-origin H5 gameplay input', + ); } if (pluginOptions.recordAudioAndroid !== true) { - throw new Error('mobile shell camera plugin must enable Android record audio for same-origin H5 microphone gameplay'); + throw new Error( + 'mobile shell camera plugin must enable Android record audio for same-origin H5 microphone gameplay', + ); } } const notificationsPlugin = appConfig.plugins?.find((plugin) => - Array.isArray(plugin) ? plugin[0] === 'expo-notifications' : plugin === 'expo-notifications', + Array.isArray(plugin) + ? plugin[0] === 'expo-notifications' + : plugin === 'expo-notifications', ); if (!notificationsPlugin) { throw new Error('mobile shell notifications plugin is missing'); @@ -2234,16 +2475,22 @@ if (!notificationsPlugin) { if ( appConfig.plugins?.some((plugin) => - Array.isArray(plugin) ? plugin[0] === 'expo-updates' : plugin === 'expo-updates', + Array.isArray(plugin) + ? plugin[0] === 'expo-updates' + : plugin === 'expo-updates', ) ) { - throw new Error('mobile shell must not install expo-updates without a real release channel'); + throw new Error( + 'mobile shell must not install expo-updates without a real release channel', + ); } if (Array.isArray(notificationsPlugin)) { const pluginOptions = notificationsPlugin[1] ?? {}; if (pluginOptions.enableBackgroundRemoteNotifications !== false) { - throw new Error('mobile shell must not enable background remote notifications'); + throw new Error( + 'mobile shell must not enable background remote notifications', + ); } } @@ -2283,8 +2530,14 @@ if (sharedMobileLocalNotificationChannelId !== 'genarrative-local') { throw new Error('shared mobile local notification channel id drifted'); } -if (!notificationsSource.includes('HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID')) { - throw new Error('mobile shell must use the shared local notification channel id'); +if ( + !notificationsSource.includes( + 'HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID', + ) +) { + throw new Error( + 'mobile shell must use the shared local notification channel id', + ); } if ( @@ -2292,12 +2545,16 @@ if ( notificationsSource, ) ) { - throw new Error('mobile shell local notifications must stay immediate and channel-only'); + throw new Error( + 'mobile shell local notifications must stay immediate and channel-only', + ); } if ( !dispatchSource.includes('showMobileHostBridgeLocalNotification(request)') || - dispatchSource.includes('ok(request, await showMobileHostBridgeLocalNotification') || + dispatchSource.includes( + 'ok(request, await showMobileHostBridgeLocalNotification', + ) || dispatchSource.includes('normalizeHostBridgeLocalNotification') || dispatchSource.includes("from 'expo-notifications'") || dispatchSource.includes('Notifications.scheduleNotificationAsync') || @@ -2326,7 +2583,9 @@ for (const notificationSnippet of [ 'HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT', ]) { if (!notificationsSource.includes(notificationSnippet)) { - throw new Error(`mobile shell notifications module is missing ${notificationSnippet}`); + throw new Error( + `mobile shell notifications module is missing ${notificationSnippet}`, + ); } } @@ -2468,7 +2727,9 @@ if ( 'bytes <= 0 || bytes > HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES', ) ) { - throw new Error('mobile shell image export must reject empty and oversized bytes'); + throw new Error( + 'mobile shell image export must reject empty and oversized bytes', + ); } for (const [functionName, mimeCheckSnippet, label] of [ @@ -2505,13 +2766,25 @@ for (const [functionName, mimeCheckSnippet, label] of [ } for (const [wrapperName, fileCall] of [ - ['exportMobileHostBridgeTextFile', 'ok(request, await exportTextFile(request.payload))'], + [ + 'exportMobileHostBridgeTextFile', + 'ok(request, await exportTextFile(request.payload))', + ], ['importMobileHostBridgeTextFile', 'ok(request, await importTextFile())'], - ['importMobileHostBridgeDocumentFile', 'ok(request, await importDocumentFile())'], - ['exportMobileHostBridgeImageFile', 'ok(request, await exportImageFile(request.payload))'], + [ + 'importMobileHostBridgeDocumentFile', + 'ok(request, await importDocumentFile())', + ], + [ + 'exportMobileHostBridgeImageFile', + 'ok(request, await exportImageFile(request.payload))', + ], ['importMobileHostBridgeImageFile', 'ok(request, await importImageFile())'], ['captureMobileHostBridgeImageFile', 'ok(request, await captureImageFile())'], - ['exportMobileHostBridgeAudioFile', 'ok(request, await exportAudioFile(request.payload))'], + [ + 'exportMobileHostBridgeAudioFile', + 'ok(request, await exportAudioFile(request.payload))', + ], ['importMobileHostBridgeAudioFile', 'ok(request, await importAudioFile())'], ]) { const wrapperBody = extractFunctionBody(filesSource, wrapperName); @@ -2521,7 +2794,7 @@ for (const [wrapperName, fileCall] of [ } for (const snippet of [ - 'describe(\'mobile HostBridge file actions\'', + "describe('mobile HostBridge file actions'", 'exportTextFile({', 'exportImageFile({', 'rejects every export before cache writes when system sharing is unavailable', @@ -2578,12 +2851,24 @@ for (const snippet of [ } } -if (filesSource.includes('console.warn(`mobile HostBridge file failed for ${label}`, error)')) { - throw new Error('mobile shell file diagnostics must not log native error objects'); +if ( + filesSource.includes( + 'console.warn(`mobile HostBridge file failed for ${label}`, error)', + ) +) { + throw new Error( + 'mobile shell file diagnostics must not log native error objects', + ); } -if (!filesSource.includes('console.warn(`mobile HostBridge file failed for ${label}`)')) { - throw new Error('mobile shell file diagnostics must use stable label-only logging'); +if ( + !filesSource.includes( + 'console.warn(`mobile HostBridge file failed for ${label}`)', + ) +) { + throw new Error( + 'mobile shell file diagnostics must use stable label-only logging', + ); } if ( @@ -2595,16 +2880,24 @@ if ( !dispatchSource.includes('captureMobileHostBridgeImageFile(request)') || !dispatchSource.includes('exportMobileHostBridgeAudioFile(request)') || !dispatchSource.includes('importMobileHostBridgeAudioFile(request)') || - dispatchSource.includes('ok(request, await exportTextFile(request.payload))') || + dispatchSource.includes( + 'ok(request, await exportTextFile(request.payload))', + ) || dispatchSource.includes('ok(request, await importTextFile())') || dispatchSource.includes('ok(request, await importDocumentFile())') || - dispatchSource.includes('ok(request, await exportImageFile(request.payload))') || + dispatchSource.includes( + 'ok(request, await exportImageFile(request.payload))', + ) || dispatchSource.includes('ok(request, await importImageFile())') || dispatchSource.includes('ok(request, await captureImageFile())') || - dispatchSource.includes('ok(request, await exportAudioFile(request.payload))') || + dispatchSource.includes( + 'ok(request, await exportAudioFile(request.payload))', + ) || dispatchSource.includes('ok(request, await importAudioFile())') ) { - throw new Error('mobile shell dispatch must delegate file requests to files.ts'); + throw new Error( + 'mobile shell dispatch must delegate file requests to files.ts', + ); } if ( @@ -2635,7 +2928,9 @@ for (const snippet of [ 'hostCapabilities', ]) { if (!hostBridgeNavigationTestSource.includes(snippet)) { - throw new Error(`mobile shell HostBridge navigation test missing ${snippet}`); + throw new Error( + `mobile shell HostBridge navigation test missing ${snippet}`, + ); } } if ( @@ -2657,7 +2952,9 @@ if ( dispatchSource.includes('ClipboardWriteTextPayload') || !dispatchSource.includes('writeMobileHostBridgeClipboardText(request)') || !dispatchSource.includes('readMobileHostBridgeClipboardText(request)') || - dispatchSource.includes('ok(request, await writeMobileHostBridgeClipboardText') || + dispatchSource.includes( + 'ok(request, await writeMobileHostBridgeClipboardText', + ) || dispatchSource.includes('ok(request, await readMobileHostBridgeClipboardText') ) { throw new Error( @@ -2683,11 +2980,23 @@ for (const snippet of [ throw new Error(`mobile shell clipboard module is missing ${snippet}`); } } -if (clipboardSource.includes('console.warn(`mobile clipboard failed for ${label}`, error)')) { - throw new Error('mobile shell clipboard diagnostics must not log native error objects'); +if ( + clipboardSource.includes( + 'console.warn(`mobile clipboard failed for ${label}`, error)', + ) +) { + throw new Error( + 'mobile shell clipboard diagnostics must not log native error objects', + ); } -if (!clipboardSource.includes('console.warn(`mobile clipboard failed for ${label}`)')) { - throw new Error('mobile shell clipboard diagnostics must use stable label-only logging'); +if ( + !clipboardSource.includes( + 'console.warn(`mobile clipboard failed for ${label}`)', + ) +) { + throw new Error( + 'mobile shell clipboard diagnostics must use stable label-only logging', + ); } if (!clipboardTestSource.includes("'猫'.repeat(100000)")) { throw new Error('mobile shell clipboard tests must cover Unicode truncation'); @@ -2705,7 +3014,9 @@ for (const snippet of [ "message: 'clipboard text unavailable'", ]) { if (!clipboardTestSource.includes(snippet)) { - throw new Error(`mobile shell clipboard tests must cover unavailable reads: ${snippet}`); + throw new Error( + `mobile shell clipboard tests must cover unavailable reads: ${snippet}`, + ); } } @@ -2720,7 +3031,9 @@ for (const snippet of [ "message: 'network status unavailable'", ]) { if (!hostBridgeNetworkSource.includes(snippet)) { - throw new Error(`mobile shell network HostBridge must stabilize native failures: ${snippet}`); + throw new Error( + `mobile shell network HostBridge must stabilize native failures: ${snippet}`, + ); } } for (const snippet of [ @@ -2730,7 +3043,9 @@ for (const snippet of [ "message: 'network status unavailable'", ]) { if (!hostBridgeNetworkTestSource.includes(snippet)) { - throw new Error(`mobile shell network tests must cover stable native failure responses: ${snippet}`); + throw new Error( + `mobile shell network tests must cover stable native failure responses: ${snippet}`, + ); } } @@ -2740,15 +3055,23 @@ if ( !hapticsSource.includes('type HostBridgeRequest') || !hapticsSource.includes('runMobileHostBridgeHapticsImpact(') || !hapticsSource.includes('request: HostBridgeRequest') || - !hapticsSource.includes('(request.payload as HapticsImpactPayload | undefined)?.style') || - !hapticsSource.includes("invalidRequest('haptics impact style must be light, medium, or heavy')") || + !hapticsSource.includes( + '(request.payload as HapticsImpactPayload | undefined)?.style', + ) || + !hapticsSource.includes( + "invalidRequest('haptics impact style must be light, medium, or heavy')", + ) || !hapticsSource.includes('Haptics.ImpactFeedbackStyle.Heavy') || !hapticsSource.includes('Haptics.ImpactFeedbackStyle.Medium') || !hapticsSource.includes('Haptics.ImpactFeedbackStyle.Light') || - !hapticsSource.includes('await Haptics.impactAsync(toExpoImpactStyle(style))') || + !hapticsSource.includes( + 'await Haptics.impactAsync(toExpoImpactStyle(style))', + ) || !hapticsSource.includes('logMobileHapticsFailure') || !hapticsSource.includes('mobile haptics failed for') || - !hapticsSource.includes("logMobileHapticsFailure('impact.dispatch', error)") || + !hapticsSource.includes( + "logMobileHapticsFailure('impact.dispatch', error)", + ) || !hapticsSource.includes("message: 'haptics impact unavailable'") || !hapticsSource.includes('ok(request, true)') ) { @@ -2772,8 +3095,12 @@ if ( dispatchSource.includes('Haptics.impactAsync') || dispatchSource.includes('Haptics.ImpactFeedbackStyle') || dispatchSource.includes('HapticsImpactPayload') || - dispatchSource.includes('haptics impact style must be light, medium, or heavy') || - dispatchSource.includes('ok(request, await runMobileHostBridgeHapticsImpact') || + dispatchSource.includes( + 'haptics impact style must be light, medium, or heavy', + ) || + dispatchSource.includes( + 'ok(request, await runMobileHostBridgeHapticsImpact', + ) || !dispatchSource.includes('runMobileHostBridgeHapticsImpact(request)') ) { throw new Error( @@ -2801,7 +3128,7 @@ for (const snippet of [ } for (const snippet of [ - 'describe(\'QrScannerOverlay\'', + "describe('QrScannerOverlay'", 'scanQrCode()', 'requestCameraPermissionsAsync', "message: 'qr scanner unavailable'", @@ -2854,7 +3181,7 @@ for (const snippet of [ "type: 'genarrative.mobile.historyState'", 'mobile host event failed for network.statusChanged', 'external WebView navigation native failures stay outside the WebView', - "expect(Linking.openURL).toHaveBeenCalledWith(", + 'expect(Linking.openURL).toHaveBeenCalledWith(', "'mobile shell navigation failed for external_navigation.open'", 'initial deep link read failures are logged without replacing the current WebView URL', "'mobile shell deep link failed for initial_url.read'", @@ -2867,7 +3194,9 @@ for (const snippet of [ 'mobile WebView process failed for render_process_gone', ]) { if (!shellAppTestSource.includes(snippet)) { - throw new Error(`mobile shell ShellApp HostBridge event test missing ${snippet}`); + throw new Error( + `mobile shell ShellApp HostBridge event test missing ${snippet}`, + ); } } @@ -2875,7 +3204,9 @@ if ( !dispatchSource.includes('scanMobileHostBridgeQrCode(request)') || dispatchSource.includes('ok(request, await scanQrCode())') ) { - throw new Error('mobile shell QR scanner HostBridge method must delegate to scanner module'); + throw new Error( + 'mobile shell QR scanner HostBridge method must delegate to scanner module', + ); } for (const scannerSnippet of [ @@ -2914,7 +3245,7 @@ for (const scannerTestSnippet of [ } } -const capabilityQuerySnippet = "capabilities: MOBILE_HOST_CAPABILITIES"; +const capabilityQuerySnippet = 'capabilities: MOBILE_HOST_CAPABILITIES'; if (shellAppSource.includes(capabilityQuerySnippet)) { throw new Error('mobile shell URL must resolve platform-aware capabilities'); } @@ -2924,11 +3255,17 @@ if (!shellAppSource.includes('capabilities: resolveMobileHostCapabilities()')) { } if (!shellAppSource.includes('hostVersion: MOBILE_SHELL_HOST_VERSION')) { - throw new Error('mobile shell URL must use the shared mobile shell host version'); + throw new Error( + 'mobile shell URL must use the shared mobile shell host version', + ); } -if (!hostBridgeRuntimeSource.includes('hostVersion: MOBILE_SHELL_HOST_VERSION')) { - throw new Error('mobile shell runtime response must use the shared mobile shell host version'); +if ( + !hostBridgeRuntimeSource.includes('hostVersion: MOBILE_SHELL_HOST_VERSION') +) { + throw new Error( + 'mobile shell runtime response must use the shared mobile shell host version', + ); } for (const runtimeSnippet of [ @@ -2955,10 +3292,16 @@ if ( dispatchSource.includes('getMobileRuntimePlatform') || dispatchSource.includes("shell: 'expo_mobile'") ) { - throw new Error('mobile shell dispatch must delegate host.getRuntime to runtime.ts'); + throw new Error( + 'mobile shell dispatch must delegate host.getRuntime to runtime.ts', + ); } -if (hostBridgeRuntimeSource.includes('capabilities: resolveMobileHostCapabilities(),')) { +if ( + hostBridgeRuntimeSource.includes( + 'capabilities: resolveMobileHostCapabilities(),', + ) +) { throw new Error( 'mobile shell runtime capabilities must use the same normalized platform reported in host.getRuntime', ); @@ -2993,14 +3336,18 @@ for (const snippet of [ } if (/export const MOBILE_SHELL_HOST_VERSION\s*=\s*'/.test(runtimeSource)) { - throw new Error('mobile shell host version must be resolved from Expo runtime config'); + throw new Error( + 'mobile shell host version must be resolved from Expo runtime config', + ); } if ( shellAppSource.includes("hostVersion: '0.1.0'") || hostBridgeSource.includes("hostVersion: '0.1.0'") ) { - throw new Error('mobile shell HostBridge version must not be duplicated in app or bridge source'); + throw new Error( + 'mobile shell HostBridge version must not be duplicated in app or bridge source', + ); } if (shellAppSource.includes(`bridge: '${sharedHostBridgeProtocol}'`)) { @@ -3062,7 +3409,9 @@ if (!iosMobileCapabilitySet.has('app.setBadgeCount')) { } if (mobileCapabilitySet.has('app.setBadgeCount')) { - throw new Error('Android mobile shell base capabilities must not include app.setBadgeCount'); + throw new Error( + 'Android mobile shell base capabilities must not include app.setBadgeCount', + ); } for (const snippet of [ 'MOBILE_HOST_CAPABILITIES', @@ -3087,7 +3436,9 @@ if ( dispatchSource.includes('normalizeHostBridgeBadgeCount') || dispatchSource.includes('HOST_BRIDGE_BADGE_COUNT_MAX') ) { - throw new Error('mobile shell badge HostBridge method must delegate to badge module'); + throw new Error( + 'mobile shell badge HostBridge method must delegate to badge module', + ); } for (const snippet of [ 'HOST_BRIDGE_BADGE_COUNT_MAX', @@ -3105,7 +3456,7 @@ for (const snippet of [ "logMobileBadgeFailure('permission.current', error)", "logMobileBadgeFailure('permission.request', error)", "logMobileBadgeFailure('update.set_count', error)", - "logMobileBadgeFailure(", + 'logMobileBadgeFailure(', "'update.rejected'", ]) { if (!badgeSource.includes(snippet)) { @@ -3153,12 +3504,16 @@ for (const snippet of [ } if ( - !dispatchSource.includes('getMobileHostBridgeAppearanceColorScheme(request)') || + !dispatchSource.includes( + 'getMobileHostBridgeAppearanceColorScheme(request)', + ) || dispatchSource.includes('ok(request, getMobileAppearanceColorScheme())') || dispatchSource.includes('Appearance.getColorScheme()') || dispatchSource.includes('normalizeHostBridgeColorScheme') ) { - throw new Error('mobile shell appearance HostBridge method must delegate to appearance module'); + throw new Error( + 'mobile shell appearance HostBridge method must delegate to appearance module', + ); } for (const snippet of [ 'Appearance.getColorScheme()', @@ -3178,7 +3533,7 @@ for (const snippet of [ 'wraps the system color scheme in the HostBridge response shape', `${'mo'}${'ck'}ReturnValue('light')`, `${'mo'}${'ck'}ReturnValue('dark')`, - 'colorScheme: \'unknown\'', + "colorScheme: 'unknown'", ]) { if (!appearanceTestSource.includes(snippet)) { throw new Error(`mobile shell appearance tests missing ${snippet}`); @@ -3187,11 +3542,19 @@ for (const snippet of [ if ( !dispatchSource.includes('openMobileHostBridgeExternalUrl(request)') || - !dispatchSource.includes('openMobileHostBridgeNativePage(request, navigation)') || - !dispatchSource.includes('reloadMobileHostBridgeWebView(request, navigation)') || - dispatchSource.includes('ok(request, await openMobileHostBridgeExternalUrl') || + !dispatchSource.includes( + 'openMobileHostBridgeNativePage(request, navigation)', + ) || + !dispatchSource.includes( + 'reloadMobileHostBridgeWebView(request, navigation)', + ) || + dispatchSource.includes( + 'ok(request, await openMobileHostBridgeExternalUrl', + ) || dispatchSource.includes('ok(request, reloadMobileHostBridgeWebView') || - dispatchSource.includes('ok(\\n request,\\n openMobileHostBridgeNativePage') || + dispatchSource.includes( + 'ok(\\n request,\\n openMobileHostBridgeNativePage', + ) || dispatchSource.includes("from 'expo-linking'") || dispatchSource.includes('Linking.') || dispatchSource.includes('openMobileShellExternalNavigation') || @@ -3199,7 +3562,9 @@ if ( dispatchSource.includes('normalizeHostBridgeExternalUrlPayload') || dispatchSource.includes('buildMobileShellUrl') ) { - throw new Error('mobile shell navigation HostBridge methods must delegate to navigation module'); + throw new Error( + 'mobile shell navigation HostBridge methods must delegate to navigation module', + ); } for (const snippet of [ 'openMobileHostBridgeExternalUrl', @@ -3207,7 +3572,7 @@ for (const snippet of [ "import * as Linking from 'expo-linking'", 'openMobileShellExternalNavigation(', 'externalUrlPayload.url', - "} catch (error) {", + '} catch (error) {', "logMobileHostBridgeNavigationFailure('external.open', error)", 'opened = false;', '(request.payload as OpenExternalUrlPayload | undefined)?.url', @@ -3238,9 +3603,13 @@ if ( !dispatchSource.includes('getMobileHostBridgeNetworkStatus(request)') || dispatchSource.includes('../shell/network') || dispatchSource.includes('getMobileNetworkStatus()') || - dispatchSource.includes('ok(request, await getMobileHostBridgeNetworkStatus())') + dispatchSource.includes( + 'ok(request, await getMobileHostBridgeNetworkStatus())', + ) ) { - throw new Error('mobile shell network HostBridge method must delegate to network module'); + throw new Error( + 'mobile shell network HostBridge method must delegate to network module', + ); } for (const snippet of [ 'getMobileHostBridgeNetworkStatus', @@ -3257,8 +3626,8 @@ for (const snippet of [ 'wraps Expo Network status in the HostBridge response shape', 'normalizes disconnected network state before wrapping response', 'converts native network failures to a stable host_error response', - 'connectionType: \'cellular\'', - 'connectionType: \'none\'', + "connectionType: 'cellular'", + "connectionType: 'none'", 'network query failed', 'network status unavailable', ]) { @@ -3267,7 +3636,11 @@ for (const snippet of [ } } -if (!shellAppSource.includes('openMobileShellExternalNavigation(Linking, request.url)')) { +if ( + !shellAppSource.includes( + 'openMobileShellExternalNavigation(Linking, request.url)', + ) +) { throw new Error( 'mobile shell WebView external navigation must use the tested external navigation helper', ); diff --git a/apps/mobile-shell/scripts/check-expo-config.mjs b/apps/mobile-shell/scripts/check-expo-config.mjs index ec5514918..e3ee16064 100644 --- a/apps/mobile-shell/scripts/check-expo-config.mjs +++ b/apps/mobile-shell/scripts/check-expo-config.mjs @@ -1,15 +1,17 @@ -import {spawnSync} from 'node:child_process'; +import { spawnSync } from 'node:child_process'; import fs from 'node:fs'; +import { createRequire } from 'node:module'; +import { dirname, resolve } from 'node:path'; const appConfigPath = new URL('../app.json', import.meta.url); const packagePath = new URL('../package.json', import.meta.url); -const configPluginsPackagePath = new URL( - '../../../node_modules/@expo/config-plugins/package.json', - import.meta.url, +const shellRequire = createRequire(packagePath); +const configPluginsPackagePath = shellRequire.resolve( + '@expo/config-plugins/package.json', ); -const expoPrivacyInfoPluginPath = new URL( - '../../../node_modules/@expo/config-plugins/build/ios/PrivacyInfo.js', - import.meta.url, +const expoPrivacyInfoPluginPath = resolve( + dirname(configPluginsPackagePath), + 'build/ios/PrivacyInfo.js', ); const sharedContractPath = new URL( '../../../packages/shared/src/contracts/hostBridge.ts', @@ -43,7 +45,9 @@ if (result.error) { } if (result.signal) { - throw new Error(`Expo config smoke was terminated by signal ${result.signal}`); + throw new Error( + `Expo config smoke was terminated by signal ${result.signal}`, + ); } if ((result.status ?? 0) !== 0) { @@ -194,7 +198,9 @@ const sharedPublicWebOrigin = extractStringConstExport( ); const sharedPublicWebOriginUrl = new URL(sharedPublicWebOrigin); if (sharedPublicWebOriginUrl.protocol !== 'https:') { - throw new Error('shared HostBridge public web origin must use https for mobile app links'); + throw new Error( + 'shared HostBridge public web origin must use https for mobile app links', + ); } const sharedPublicWebHost = sharedPublicWebOriginUrl.hostname; const sharedPublicWebAssociatedDomain = `applinks:${sharedPublicWebHost}`; @@ -206,7 +212,11 @@ assertEqual(expoConfig.version, packageConfig.version, 'version'); assertEqual(expoConfig.version, appConfig.version, 'app version'); assertEqual(expoConfig.orientation, 'default', 'orientation'); assertEqual(expoConfig.userInterfaceStyle, 'automatic', 'userInterfaceStyle'); -assertSameList(expoConfig.assetBundlePatterns, ['**/*'], 'asset bundle patterns'); +assertSameList( + expoConfig.assetBundlePatterns, + ['**/*'], + 'asset bundle patterns', +); assertEqual(expoConfig.icon, './assets/icon.png', 'icon'); assertEqual(expoConfig.splash?.image, './assets/icon.png', 'splash image'); assertEqual(expoConfig.splash?.resizeMode, 'contain', 'splash resize mode'); @@ -216,10 +226,14 @@ if (Object.keys(expoConfig.updates ?? {}).some((key) => key !== 'enabled')) { throw new Error('Expo config OTA update metadata must stay empty'); } if ('runtimeVersion' in expoConfig) { - throw new Error('Expo config runtimeVersion must not be set without a real OTA release channel'); + throw new Error( + 'Expo config runtimeVersion must not be set without a real OTA release channel', + ); } if ('releaseChannel' in expoConfig || 'channel' in expoConfig) { - throw new Error('Expo config release channel must not be set without a real release process'); + throw new Error( + 'Expo config release channel must not be set without a real release process', + ); } assertEqual( expoConfig.extra?.genarrativeHostBridgeVersion, @@ -259,7 +273,9 @@ const configPluginsMajor = Number( String(configPluginsPackageConfig.version).split('.')[0], ); if (!Number.isFinite(configPluginsMajor) || configPluginsMajor < 50) { - throw new Error('Expo config plugins must support built-in iOS privacy manifests'); + throw new Error( + 'Expo config plugins must support built-in iOS privacy manifests', + ); } for (const snippet of [ 'function withPrivacyInfo(config)', @@ -269,7 +285,9 @@ for (const snippet of [ 'mergePrivacyInfo(existing, privacyManifests)', ]) { if (!expoPrivacyInfoPluginSource.includes(snippet)) { - throw new Error(`Expo config plugins PrivacyInfo support missing ${snippet}`); + throw new Error( + `Expo config plugins PrivacyInfo support missing ${snippet}`, + ); } } @@ -300,7 +318,9 @@ assertSameSet( 'Android explicit permissions', ); if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) { - throw new Error('Expo source config must not hard-code Android CAMERA permission'); + throw new Error( + 'Expo source config must not hard-code Android CAMERA permission', + ); } assertIncludes( expoConfig.android?.blockedPermissions, @@ -312,11 +332,23 @@ assertIncludes( 'android.permission.READ_EXTERNAL_STORAGE', 'Android external download blocked permissions', ); -if (expoConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) { - throw new Error('Expo config Android permissions must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay'); +if ( + expoConfig.android?.blockedPermissions?.includes( + 'android.permission.RECORD_AUDIO', + ) +) { + throw new Error( + 'Expo config Android permissions must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay', + ); } -if (expoConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) { - throw new Error('Expo config Android permissions must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery'); +if ( + expoConfig.android?.blockedPermissions?.includes( + 'android.permission.POST_NOTIFICATIONS', + ) +) { + throw new Error( + 'Expo config Android permissions must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery', + ); } assertIncludes( expoConfig.android?.blockedPermissions, @@ -352,7 +384,9 @@ assertEqual( const appLinkFilters = expoConfig.android?.intentFilters ?? []; if (appLinkFilters.length !== 1) { - throw new Error('Expo config Android app link filter must be the only intent filter'); + throw new Error( + 'Expo config Android app link filter must be the only intent filter', + ); } const [appLinkFilter] = appLinkFilters; @@ -428,7 +462,9 @@ assertEqual( ); if (findPlugin('expo-updates')) { - throw new Error('Expo config must not include expo-updates without a real release channel'); + throw new Error( + 'Expo config must not include expo-updates without a real release channel', + ); } console.log('[mobile-shell:expo-config] OK'); diff --git a/apps/preview-deployer-web/package.json b/apps/preview-deployer-web/package.json index d535b82fd..18438be15 100644 --- a/apps/preview-deployer-web/package.json +++ b/apps/preview-deployer-web/package.json @@ -18,6 +18,8 @@ "vite": "^6.2.0" }, "devDependencies": { + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "typescript": "~5.8.2", diff --git a/deploy/container/README.md b/deploy/container/README.md index 523f3d4f4..1de8a3489 100644 --- a/deploy/container/README.md +++ b/deploy/container/README.md @@ -13,7 +13,7 @@ Docker Compose └─ otelcol :4317/4318,debug exporter,接收 traces / metrics / logs ``` -当前容器模拟参数按 `genarrative-release` 服务器采样值收口为 2 vCPU / 2 GiB RAM / 4096 soft nofile / 768 worker_connections,并已在 compose 里落实到 `spacetimedb cpus=1.0 mem_limit=896m`、`api-server cpus=2.0 mem_limit=1g`、`external-generation-worker cpus=2.0 mem_limit=1g`、`nginx cpus=0.5 mem_limit=128m`、`otelcol cpus=0.25 mem_limit=128m`。SpacetimeDB 同时设置 `--page_pool_max_size=402653184`,给 reducer、订阅与运行时保留更多非 page pool 内存。 +当前容器模拟参数按 `genarrative-release` 服务器采样值保留 CPU、`nofile` 与 Nginx 连接口径,并已在 compose 里落实到 `spacetimedb cpus=1.0 mem_limit=2g`、`api-server cpus=2.0 mem_limit=1g`、`external-generation-worker cpus=2.0 mem_limit=1g`、`nginx cpus=0.5 mem_limit=128m`、`otelcol cpus=0.25 mem_limit=128m`。SpacetimeDB 同时设置 `--page_pool_max_size=402653184`,给 reducer、订阅与运行时保留更多非 page pool 内存;当前完整模块首次实例化的 cgroup 峰值会超过旧 `896m` 上限,因此完整容器和分支预览统一使用 `2g`,避免 publish 期间被 OOM 杀死。 容器 `api-server` 默认 `GENARRATIVE_API_WORKER_THREADS=4`,用于让 Tokio 在 2 vCPU 配额内有更多 I/O 调度 worker;该值不会突破 compose 里的 `cpus=2.0` CPU 上限。 容器默认 `GENARRATIVE_EXTERNAL_GENERATION_MODE=queue`,用于验证不经过 BgFilter 的 `api-server -> external_generation_job -> external-generation-worker` 链路;会触发 BgFilter 的任务不属于当前 compose 验收范围。如只想本地同步排查非 BgFilter provider / OSS / SpacetimeDB 写回,可在本机 env 临时改为 `inline`,但该模式不会覆盖 worker 动态扩缩容验证。 Collector 镜像使用 `otel/opentelemetry-collector-contrib:0.151.0`。 @@ -56,7 +56,15 @@ Linux Docker Engine 若要从宿主机 CLI 连到容器内服务,直接用 `ht ## 构建工具链 -`api-server` 容器镜像只构建 Linux release API 二进制,不构建 `spacetime-module`。当前 `api-server -> spacetime-client -> spacetimedb-sdk 2.7.0` 依赖链继续兼容 Rust 1.93,因此 `deploy/container/api-server.Dockerfile` 的 Rust builder 固定为 `rust:1.93-bookworm`。镜像构建阶段会同时复制 `public/`,用于满足 API 二进制里 `include_bytes!` 引用的内置素材;不要把 `public/generated-*` 放入镜像上下文。如果本机 Docker Hub 拉取失败,可以先在本机准备同名本地 builder 镜像,但不要把临时 bootstrap 容器或私有 registry 凭据写入仓库。 +`api-server` 容器镜像只构建 Linux release API 二进制,不构建 `spacetime-module`。当前 `api-server -> spacetime-client -> spacetimedb-sdk 2.7.0` 依赖链继续兼容 Rust 1.93,因此 `deploy/container/api-server.Dockerfile` 的 Rust builder 固定为 `rust:1.93-bookworm`。Web builder 显式安装并校验 npm `10.9.7`,再按唯一根 workspace lock 执行一次 `npm ci`,不依赖 Node 基础镜像隐含的 npm 版本。镜像构建阶段会同时复制 `public/`,用于满足 API 二进制里 `include_bytes!` 引用的内置素材;不要把 `public/generated-*` 放入镜像上下文。如果本机 Docker Hub 拉取失败,可以先在本机准备同名本地 builder 镜像,但不要把临时 bootstrap 容器或私有 registry 凭据写入仓库。 + +### Jenkins 预览 secrets 镜像边界 + +Jenkins 分支预览构建固定从宿主 `/data/jenkins/preview-secrets/.env.secrets.local` 读取 secrets。目录由 Jenkins 运行账号所有且权限为 `0700`,文件由同一账号所有且权限为 `0600`;构建入口对缺失、链接、非普通文件、owner 不匹配和过宽权限均失败关闭。不要把真实值写入本 README、仓库示例或 Jenkins 参数。 + +该文件不复制到源码 checkout 和 Docker build context,而是以 BuildKit `secret` mount 只提供给 `api-runtime` stage。构建会把它安装到 API 运行镜像的 `/srv/genarrative/.env.secrets.local`,owner 为 `genarrative`、权限为 `0400`。Web builder、`nginx-runtime`、SpacetimeDB 和其它运行镜像不得获得该 mount 或目标文件;构建日志和 artifact 也不得回显或保存文件内容。容器的显式运行环境变量优先于该内置文件,可按预览实例覆盖其中的值。 + +修改宿主固定文件后必须重新构建并替换 API 镜像;重启旧容器不会读取宿主新内容。这个镜像不是可公开分发的无密钥产物:镜像持有者可以提取 `/srv/genarrative/.env.secrets.local`。只允许在当前受信任内网 Docker 主机使用,禁止 push 或 `docker save`、artifact 导出到跨信任边界的 registry、主机或存储。 ### Gitea CI 预构建 Job 镜像 @@ -71,9 +79,9 @@ bash scripts/gitea-ci-job-image.sh export /仓库外受控路径/genarrative-git bash scripts/gitea-ci-job-image.sh load-runner ``` -默认构建 tag 为 `genarrative/gitea-project-ci:20260807.1`。脚本通过 NUL 分隔白名单 tar 流只发送 Dockerfile、checkout 脚本、根与 AI 游戏创作壳的 npm manifests/lock,以及 server-rs、桌面壳和 AI 游戏创作壳的 Cargo manifests/lock;当前构建 context 约 `2.13 MB`,不会把业务源码、素材或本地私密文件发送给 Docker daemon。镜像除固定工具链外,还按上述五份 lock 预热 npm / Cargo 下载缓存;三个 `cargo fetch --locked` 最多执行 5 次整命令级有界重试,再分别以断网 `cargo fetch --locked` 验证缓存闭合,不包含 `node_modules` 或 Cargo `target`。`build` 完成后会自动运行环境校验,`load-runner` 还会比对宿主和 runner 内层的完整 Image ID,并在内层执行 bwrap 与 Chrome headless canary。当前验证镜像约 `1.85 GB`,完整 Image ID 为 `sha256:8b4b30f5a096522942947927b06cf47bdb1a1016dde9a3573f9780e79d8e40cf`。执行这些命令不要求必须使用 root,但执行账号必须有权访问宿主 Docker API 并管理 runner 容器;没有该权限时交给 runner 运维人员执行。 +默认构建 tag 为 `genarrative/gitea-project-ci:20260807.1`。脚本通过 NUL 分隔白名单 tar 流只发送 Dockerfile、checkout 脚本、根 workspace 的唯一 npm lock 与全部 workspace manifest,以及 server-rs、桌面壳和 AI 游戏创作壳的 Cargo manifests/lock;不会把业务源码、素材或本地私密文件发送给 Docker daemon。新镜像显式安装并精确校验 `npm 10.9.7`,不依赖 Node 发行包隐含的 npm 版本;除固定工具链外,还按一份 npm workspace lock 与三份 Cargo lock 预热下载缓存。npm 只执行一次忽略 lifecycle scripts 的 workspace `npm ci`,三个 `cargo fetch --locked` 最多执行 5 次整命令级有界重试,再分别以断网 `cargo fetch --locked` 验证缓存闭合,镜像不包含 `node_modules` 或 Cargo `target`。`build` 完成后会自动运行环境校验,`load-runner` 还会比对宿主和 runner 内层的完整 Image ID,并在内层执行 bwrap 与 Chrome headless canary。workspace lock 或 manifest 变化落地后必须按下述顺序重建并装载镜像;过渡期旧固定镜像缺少 `GENARRATIVE_GITEA_CI_NPM_VERSION` 时,校验只输出 `npm_version=partial` 和 Actions warning,继续由当前 job 的根 `npm ci` 验证唯一 lock,不能据此宣称 npm 版本或新依赖缓存已经闭合。执行这些命令不要求必须使用 root,但执行账号必须有权访问宿主 Docker API 并管理 runner 容器;没有该权限时交给 runner 运维人员执行。 -runner 配置保留原 `ubuntu-latest` 映射,另外增加 `genarrative-ci:docker://sha256:8b4b30f5a096522942947927b06cf47bdb1a1016dde9a3573f9780e79d8e40cf`。内层 Docker 数据必须持久化,`force_pull` 保持 `false`;该精确 Image ID 在内层不存在时 job 应直接失败,不回退到浮动 tag 或现场拉取。四个 job 使用镜像内 `genarrative-gitea-checkout` 直接从当前 Gitea 拉取事件 commit,带 5 次有界重试,不再运行时下载 GitHub checkout action;随后以 `GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1` 执行 `scripts/check-gitea-ci-job-image.sh`,同时校验工具链、五份缓存锁命中状态、bwrap 和 Chrome headless。锁不匹配时校验会输出 `partial` 和醒目的 Actions warning,提示在可信分支落地后刷新镜像。各 job 仍运行干净的 `npm ci` 以校验当前 lockfile 并隔离 PR 依赖,但统一通过 `scripts/ci-npm-ci-with-retry.sh` 最多执行 3 次整命令级有界重试,并使用镜像内 npm cache 和 `prefer-offline`;锁文件新增依赖时允许经受控网络补齐,本阶段不启用共享 Actions cache。 +runner 配置保留原 `ubuntu-latest` 映射,`genarrative-ci` 继续映射到经 `build / verify / load-runner` 验证并写入配置的完整 Image ID。内层 Docker 数据必须持久化,`force_pull` 保持 `false`;该精确 Image ID 在内层不存在时 job 应直接失败,不回退到浮动 tag 或现场拉取。四个 job 使用镜像内 `genarrative-gitea-checkout` 直接从当前 Gitea 拉取事件 commit,带 5 次有界重试,不再运行时下载 GitHub checkout action;随后以 `GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1` 执行 `scripts/check-gitea-ci-job-image.sh`,同时校验工具链、一份 npm workspace 缓存锁、三份 Cargo 缓存锁、bwrap 和 Chrome headless。锁不匹配时校验会输出 `partial` 和醒目的 Actions warning,提示在可信分支落地后刷新镜像。各 job 仍各自运行一次干净的根 `npm ci`,以唯一 workspace lock 校验全部 App/package/tool 依赖并隔离 PR 依赖;统一通过 `scripts/ci-npm-ci-with-retry.sh` 最多执行 3 次整命令级有界重试,并使用镜像内 npm cache 和 `prefer-offline`。锁文件新增依赖时允许经受控网络补齐,本阶段不启用共享 Actions cache。 更新顺序固定为: @@ -245,5 +253,5 @@ npm run container:logs -- otelcol - 不改生产 systemd 单元。 - 不改 Jenkins 发布主流程。 - 不要求真实 HTTPS 证书。 -- 不把真实 `.env`、`.env.local`、`.env.secrets.local` 或 `deploy/container/api-server.env` 放入 Docker build context。 +- 不把真实 `.env`、`.env.local`、`.env.secrets.local` 或 `deploy/container/api-server.env` 放入 Docker build context;Jenkins 预览只使用上述 BuildKit secret 边界将固定文件内置到 `api-runtime`。 - 不在容器镜像里内置 SpacetimeDB 数据或 token。 diff --git a/deploy/container/api-server.Dockerfile b/deploy/container/api-server.Dockerfile index 37ed259e6..d97732bce 100644 --- a/deploy/container/api-server.Dockerfile +++ b/deploy/container/api-server.Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1.7 + FROM rust:1.93-bookworm AS rust-builder WORKDIR /workspace @@ -21,6 +23,15 @@ COPY --from=rust-builder /tmp/api-server /usr/local/bin/api-server RUN mkdir -p /var/lib/genarrative/auth /var/lib/genarrative/tracking-outbox /var/lib/genarrative/wallet-refund-outbox && \ chown -R genarrative:genarrative /srv/genarrative /var/lib/genarrative +ARG GENARRATIVE_PREVIEW_SECRETS_SHA256= +RUN --mount=type=secret,id=genarrative_preview_secrets,required=false \ + if [ -n "${GENARRATIVE_PREVIEW_SECRETS_SHA256}" ]; then \ + test -f /run/secrets/genarrative_preview_secrets; \ + test "$(sha256sum /run/secrets/genarrative_preview_secrets | cut -d ' ' -f 1)" = "${GENARRATIVE_PREVIEW_SECRETS_SHA256}"; \ + install -o genarrative -g genarrative -m 0400 \ + /run/secrets/genarrative_preview_secrets /srv/genarrative/.env.secrets.local; \ + fi + USER genarrative EXPOSE 8082 @@ -34,8 +45,20 @@ CMD ["api-server"] FROM node:22-bookworm-slim AS web-builder WORKDIR /workspace +ARG NPM_VERSION=10.9.7 +RUN npm install --global "npm@${NPM_VERSION}" --no-audit --no-fund && \ + test "$(npm --version)" = "${NPM_VERSION}" + COPY package.json package-lock.json ./ COPY apps/admin-web/package.json ./apps/admin-web/package.json +COPY apps/ai-game-creator-shell/package.json ./apps/ai-game-creator-shell/package.json +COPY apps/desktop-shell/package.json ./apps/desktop-shell/package.json +COPY apps/mobile-shell/package.json ./apps/mobile-shell/package.json +COPY apps/preview-deployer-web/package.json ./apps/preview-deployer-web/package.json +COPY packages/image-canvas-core/package.json ./packages/image-canvas-core/package.json +COPY packages/image-canvas-react/package.json ./packages/image-canvas-react/package.json +COPY packages/shared/package.json ./packages/shared/package.json +COPY tools/spine-json-export-validator/package.json ./tools/spine-json-export-validator/package.json RUN npm ci COPY index.html metadata.json tsconfig.json vite.config.ts ./ diff --git a/deploy/container/docker-compose.loadtest.yml b/deploy/container/docker-compose.loadtest.yml index 93475d8d9..a924e4f9d 100644 --- a/deploy/container/docker-compose.loadtest.yml +++ b/deploy/container/docker-compose.loadtest.yml @@ -16,7 +16,7 @@ services: "--non-interactive", ] cpus: "1.0" - mem_limit: 896m + mem_limit: 2g ports: - "${GENARRATIVE_CONTAINER_SPACETIME_PORT:-13101}:3101" volumes: diff --git a/deploy/container/gitea-ci-job.Dockerfile b/deploy/container/gitea-ci-job.Dockerfile index 0f7e3fdc5..56e196772 100644 --- a/deploy/container/gitea-ci-job.Dockerfile +++ b/deploy/container/gitea-ci-job.Dockerfile @@ -56,6 +56,7 @@ FROM ${RUNNER_IMAGE} ARG NODE_VERSION=22.23.1 ARG NODE_LINUX_X64_SHA256=9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578 +ARG NPM_VERSION=10.9.7 ARG GOOGLE_CHROME_VERSION=150.0.7871.181-1 ARG GOOGLE_LINUX_SIGNING_KEY_FINGERPRINT=EB4C1BFD4F042F6DDDCCEC917721F63BD38B4796 @@ -122,26 +123,34 @@ RUN node_archive="node-v${NODE_VERSION}-linux-x64.tar.xz" \ && ln -sfn /usr/local/lib/genarrative-node/bin/node /usr/local/bin/node \ && ln -sfn /usr/local/lib/genarrative-node/bin/npm /usr/local/bin/npm \ && ln -sfn /usr/local/lib/genarrative-node/bin/npx /usr/local/bin/npx \ - && ln -sfn /usr/local/lib/genarrative-node/bin/corepack /usr/local/bin/corepack + && ln -sfn /usr/local/lib/genarrative-node/bin/corepack /usr/local/bin/corepack \ + && npm install --global "npm@${NPM_VERSION}" --no-audit --no-fund \ + && test "$(npm --version)" = "${NPM_VERSION}" COPY --from=rust-dependency-cache /usr/local/cargo /usr/local/cargo COPY --from=rust-dependency-cache /usr/local/rustup /usr/local/rustup ARG NPM_LOCK_SHA256 -ARG AGC_NPM_LOCK_SHA256 ARG SERVER_RUST_LOCK_SHA256 ARG DESKTOP_RUST_LOCK_SHA256 ARG AGC_RUST_LOCK_SHA256 COPY package.json package-lock.json /usr/local/share/genarrative-ci/npm/ -COPY apps/ai-game-creator-shell/package.json apps/ai-game-creator-shell/package-lock.json /usr/local/share/genarrative-ci/agc-npm/ +COPY apps/admin-web/package.json /usr/local/share/genarrative-ci/npm/apps/admin-web/package.json +COPY apps/ai-game-creator-shell/package.json /usr/local/share/genarrative-ci/npm/apps/ai-game-creator-shell/package.json +COPY apps/desktop-shell/package.json /usr/local/share/genarrative-ci/npm/apps/desktop-shell/package.json +COPY apps/mobile-shell/package.json /usr/local/share/genarrative-ci/npm/apps/mobile-shell/package.json +COPY apps/preview-deployer-web/package.json /usr/local/share/genarrative-ci/npm/apps/preview-deployer-web/package.json +COPY packages/image-canvas-core/package.json /usr/local/share/genarrative-ci/npm/packages/image-canvas-core/package.json +COPY packages/image-canvas-react/package.json /usr/local/share/genarrative-ci/npm/packages/image-canvas-react/package.json +COPY packages/shared/package.json /usr/local/share/genarrative-ci/npm/packages/shared/package.json +COPY tools/spine-json-export-validator/package.json /usr/local/share/genarrative-ci/npm/tools/spine-json-export-validator/package.json COPY server-rs/Cargo.lock /usr/local/share/genarrative-ci/locks/server-rs.Cargo.lock COPY apps/desktop-shell/src-tauri/Cargo.lock /usr/local/share/genarrative-ci/locks/desktop-shell.Cargo.lock COPY apps/ai-game-creator-shell/src-tauri/Cargo.lock /usr/local/share/genarrative-ci/locks/ai-game-creator-shell.Cargo.lock COPY deploy/container/gitea-ci-checkout.sh /usr/local/bin/genarrative-gitea-checkout RUN test -n "${NPM_LOCK_SHA256}" \ - && test -n "${AGC_NPM_LOCK_SHA256}" \ && test -n "${SERVER_RUST_LOCK_SHA256}" \ && test -n "${DESKTOP_RUST_LOCK_SHA256}" \ && test -n "${AGC_RUST_LOCK_SHA256}" \ @@ -149,10 +158,6 @@ RUN test -n "${NPM_LOCK_SHA256}" \ "${NPM_LOCK_SHA256}" \ /usr/local/share/genarrative-ci/npm/package-lock.json \ | sha256sum --check --strict \ - && printf '%s %s\n' \ - "${AGC_NPM_LOCK_SHA256}" \ - /usr/local/share/genarrative-ci/agc-npm/package-lock.json \ - | sha256sum --check --strict \ && printf '%s %s\n' \ "${SERVER_RUST_LOCK_SHA256}" \ /usr/local/share/genarrative-ci/locks/server-rs.Cargo.lock \ @@ -172,14 +177,11 @@ RUN test -n "${NPM_LOCK_SHA256}" \ --no-fund \ --prefer-offline \ --prefix /usr/local/share/genarrative-ci/npm \ - && rm -rf /usr/local/share/genarrative-ci/npm/node_modules \ - && npm ci \ - --ignore-scripts \ - --no-audit \ - --no-fund \ - --prefer-offline \ - --prefix /usr/local/share/genarrative-ci/agc-npm \ - && rm -rf /usr/local/share/genarrative-ci/agc-npm/node_modules \ + && rm -rf \ + /usr/local/share/genarrative-ci/npm/node_modules \ + /usr/local/share/genarrative-ci/npm/apps/*/node_modules \ + /usr/local/share/genarrative-ci/npm/packages/*/node_modules \ + /usr/local/share/genarrative-ci/npm/tools/*/node_modules \ && npm cache verify RUN install -m 0755 /usr/local/cargo/bin/rustup /usr/local/bin/rustup \ @@ -188,10 +190,10 @@ RUN install -m 0755 /usr/local/cargo/bin/rustup /usr/local/bin/rustup \ done ENV CARGO_HOME=/usr/local/cargo \ - GENARRATIVE_GITEA_CI_AGC_NPM_LOCK_SHA256=${AGC_NPM_LOCK_SHA256} \ GENARRATIVE_GITEA_CI_AGC_RUST_LOCK_SHA256=${AGC_RUST_LOCK_SHA256} \ GENARRATIVE_GITEA_CI_DESKTOP_RUST_LOCK_SHA256=${DESKTOP_RUST_LOCK_SHA256} \ GENARRATIVE_GITEA_CI_NPM_LOCK_SHA256=${NPM_LOCK_SHA256} \ + GENARRATIVE_GITEA_CI_NPM_VERSION=${NPM_VERSION} \ GENARRATIVE_GITEA_CI_SERVER_RUST_LOCK_SHA256=${SERVER_RUST_LOCK_SHA256} \ NPM_CONFIG_AUDIT=false \ NPM_CONFIG_FETCH_RETRIES=10 \ @@ -214,7 +216,7 @@ LABEL org.opencontainers.image.vendor="GenarrativeAI" \ com.genarrative.ci.definition-sha256="${IMAGE_REVISION}" RUN node --version \ - && npm --version \ + && test "$(npm --version)" = "${NPM_VERSION}" \ && rustup show active-toolchain \ && rustc --version \ && cargo --version \ diff --git a/deploy/container/gitea-ci-job.Dockerfile.dockerignore b/deploy/container/gitea-ci-job.Dockerfile.dockerignore index cb299b2bf..8b373d682 100644 --- a/deploy/container/gitea-ci-job.Dockerfile.dockerignore +++ b/deploy/container/gitea-ci-job.Dockerfile.dockerignore @@ -12,13 +12,29 @@ !server-rs/crates/*/ !server-rs/crates/*/Cargo.toml !apps/ +!apps/admin-web/ +!apps/admin-web/package.json !apps/ai-game-creator-shell/ !apps/ai-game-creator-shell/package.json -!apps/ai-game-creator-shell/package-lock.json !apps/ai-game-creator-shell/src-tauri/ !apps/ai-game-creator-shell/src-tauri/Cargo.toml !apps/ai-game-creator-shell/src-tauri/Cargo.lock !apps/desktop-shell/ +!apps/desktop-shell/package.json !apps/desktop-shell/src-tauri/ !apps/desktop-shell/src-tauri/Cargo.toml !apps/desktop-shell/src-tauri/Cargo.lock +!apps/mobile-shell/ +!apps/mobile-shell/package.json +!apps/preview-deployer-web/ +!apps/preview-deployer-web/package.json +!packages/ +!packages/image-canvas-core/ +!packages/image-canvas-core/package.json +!packages/image-canvas-react/ +!packages/image-canvas-react/package.json +!packages/shared/ +!packages/shared/package.json +!tools/ +!tools/spine-json-export-validator/ +!tools/spine-json-export-validator/package.json diff --git a/docs/README.md b/docs/README.md index 471c17e62..69a2a576f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -54,6 +54,7 @@ ### 测试与协作 +- [npm workspaces 统一依赖边界](./technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md) - [React 组件测试准则](./technical/【前端测试】React组件测试准则-2026-06-26.md) - [AI Web 工程静态预览验收清单](./technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md) - [当前阶段规划](./planning/README.md) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index a91c89223..838b2af4e 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -14317,3 +14317,23 @@ - 缺失当前服务器对应的本机开发者凭据时,客户端仍必须在请求远端创建 Key 前完成私有目录准备。既有 `~/.config/genarrative` 若 owner 已严格匹配当前进程 `TokenUser`,客户端自动把 DACL 收紧为禁止继承且仅当前用户 Full Control,用户不再需要手工执行 PowerShell ACL 修复。 - 自动收紧不等于接管:owner 不匹配、链接、reparse point、非目录或无法安全写入 DACL 时继续在远端请求前失败关闭;客户端不得删除、移动、覆盖或读取旧凭据内容,也不得因收紧失败自动创建远端 Key。 - Windows 回归测试必须构造“owner 为当前用户但仍继承 ACL”的既有目录,先证明严格校验失败,再通过正式目录准备入口收紧并复核私有 DACL。 + +## 2026-08-21 JavaScript 工程统一为 npm workspaces + +- 决策:根、Admin、AGC、Desktop、Mobile、Preview Deployer、三个 `packages/*` 和 Spine validator 统一进入显式 npm workspaces;固定 `packageManager=npm@10.9.7`,CI 镜像显式安装并校验同版 npm。仓库只提交根 `package-lock.json`,安装、CI、Jenkins 和容器缓存都只从根执行一次 `npm ci`。 +- 依赖边界:每个 workspace manifest 拥有自身直接依赖,根不再为子 App 重复声明。内部私有包使用匹配版本的普通 semver `0.1.0`,由 npm 自动链接;当前 npm 不接受 `workspace:*`。npm 默认 hoist,因此依赖所有权按 manifest 和 lock 的 workspace entry 检查,不能按统一 `node_modules` 或 lock 全局包条目判断。 +- 原生边界:根 H5 与 Desktop manifest 继续禁止 Tauri JS guest,AGC workspace 可以声明;统一 lock 出现 AGC guest 是合法聚合结果。Expo 沿用默认 npm monorepo 支持。AGC Cubone bundle、TypeScript、Tauri CLI 与 Windows Codex sidecar 都必须兼容根提升位置,不得依赖子 App 固定 `node_modules` 层级。 +- 锁与平台:删除 AGC 和 Spine 子 lock;统一根 lock 必须保留 optional、bundled 和跨平台二进制节点。Linux 干净安装不能替代 Windows AGC sidecar、Android Expo/EAS 或可用 macOS/iOS runner 的平台构建证据。 +- 权威方案:`docs/technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md`。 + +## 2026-08-22 完整容器 SpacetimeDB 内存上限统一为 2 GiB + +- 决策:`deploy/container/docker-compose.loadtest.yml` 的 SpacetimeDB `mem_limit` 从旧压测采样值 `896m` 调整为 `2g`,与分支预览 override 一致;CPU、page pool、API、worker、Nginx 与 Collector 配额保持不变。 +- 依据:当前完整模块首次 publish / init 的进程 RSS 会超过 `896m`,cgroup 会直接 OOM kill `spacetimedb-standalone`,客户端表现为上传连接提前关闭,后续重试连接拒绝。提高 ping 或 publish 重试次数不能修复内存上限。 +- 边界:这是本地/预发完整容器的模块实例化门槛,不修改生产服务资源合同;门禁同时锁定基础 Compose 与预览 override 均为 `2g`。 + +## 2026-08-22 Jenkins 预览只向 API 运行镜像内置固定 secrets + +- 决策:预览 secrets 权威源固定为 Jenkins 宿主 `/data/jenkins/preview-secrets/.env.secrets.local`;目录 / 文件由 Jenkins 运行账号所有且权限分别为 `0700` / `0600`,缺失、链接、非普通文件、owner 异常或权限过宽时构建失败关闭。 +- 构建边界:只通过 BuildKit secret mount 把文件提供给 `api-runtime` stage,并安装为 `/srv/genarrative/.env.secrets.local` (`genarrative:genarrative`, `0400`)。文件不进 Git、build context、日志或 artifact,不进入 Web / Nginx、SpacetimeDB 或其它镜像。容器显式运行 env 优先覆盖内置值。 +- 更新与分发:固定源文件更新后必须重建并替换镜像,只重启容器无效。镜像可读者必然可提取内置 secrets,因此只允许留在当前受信任内网 Docker 主机,禁止 push、`docker save` 或作为 artifact 导出到跨信任边界的 registry、主机或存储。 diff --git a/docs/project-memory/shared-memory/development-workflow.md b/docs/project-memory/shared-memory/development-workflow.md index c0d0b5c46..efc90924c 100644 --- a/docs/project-memory/shared-memory/development-workflow.md +++ b/docs/project-memory/shared-memory/development-workflow.md @@ -45,7 +45,7 @@ hermes npm install ``` -仓库当前不使用 npm workspaces,根目录 `npm install` 是统一安装入口。子包新增运行时依赖时,必须同步写入根 `package.json` 和根 `package-lock.json`;不能只修改子包 `package.json`。 +仓库使用 npm workspaces,根目录 `npm install` / `npm ci` 是全部 App、内部包和工具的唯一安装入口,唯一 npm lockfile 为根 `package-lock.json`。子包新增直接依赖时只修改所属 workspace 的 `package.json`,再从根更新 lock;禁止提交 workspace 内嵌套 `package-lock.json` 或继续执行子目录 `npm ci --prefix`。完整边界见 [`npm workspaces 统一依赖边界`](../../technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md)。 完整联调开发环境: @@ -525,7 +525,7 @@ npm run check:native-shells ``` 该命令会覆盖 H5 HostBridge 关键测试、微信 / Expo / Tauri 三端桥接层文件结构门禁、完整相对路径文档反查、微信 capability 到真实 WebView / 支付 / 分享页面流程和测试清单的映射门禁、H5 HostBridge 事件订阅双能力门控反查、H5 `navigation.canGoBack` 消费 hook 与直达二级页返回锚点测试、移动端和桌面端单端源码清单门禁、Expo 壳 typecheck / test / EAS build config smoke / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面壳 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描,确认 Expo managed config、移动端 EAS 原生包构建 profile、移动端 iOS / Android production bundle、打包 H5 资产、Tauri release 入口、H5 页面内导航保留完整原生宿主上下文和 H5 HostBridge 真实调用链没有漂移;扫描范围包含微信小程序壳生产 `.js`、Tauri `Info.plist`、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件,但不扫描 Expo export、Tauri `target/`、Cargo / Metro 缓存或 release 构建产物。移动壳配置检查必须反查 EAS 生产 profile、文本 / 文档 / 图片 / 音频导入边界都来自共享 HostBridge 契约。登录与支付外链跳转必须保持在该调用链扫描内,`src/services/authService.ts` 和 `src/services/payment/paymentRedirect.ts` 是必扫文件;`AuthGate` 的登录成功、退出登录、身份边界刷新和登录状态异常重试都必须通过 `app.reloadWebView` 优先路径,并由 `src/components/auth/AuthGate.test.tsx` 进入该门禁。壳源码和配置继续严格禁止 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时;H5 业务调用链允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但仍禁止 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹。 -根仓 Vitest 加载独立 AI 游戏客户端源码时,不得为了模块解析把 `@tauri-apps/api` 或 `@tauri-apps/plugin-*` 加入根 H5 依赖;根测试只通过 `vitest.config.ts` 的精确别名使用无副作用测试替身,独立客户端的正式 Tauri guest 依赖继续只由 `apps/ai-game-creator-shell/package.json` 与其 lock 管理。隔离 worktree 验收前需分别执行根 `npm ci` 和 `npm ci --prefix apps/ai-game-creator-shell`。 +根仓 Vitest 加载独立 AI 游戏客户端源码时,不得为了模块解析把 `@tauri-apps/api` 或 `@tauri-apps/plugin-*` 加入根 H5 或 Desktop workspace 的 manifest;根测试只通过 `vitest.config.ts` 的精确别名使用无副作用测试替身,正式 Tauri guest 只由 `apps/ai-game-creator-shell/package.json` 声明。统一根 lock 出现 AGC 的 Tauri guest 解析是正常聚合结果,依赖归属按 workspace manifest 检查。隔离 worktree 验收前只从仓库根执行一次 `npm ci`。 反馈页上传凭证在原生壳声明 `file.importImage` 时必须优先走宿主图片导入;移动壳声明 `file.captureImage` 时才显示拍摄凭证入口,并把拍摄图片同样转为 `File` 后复用反馈页原有数量、大小、MIME、data URL 预览和提交 payload 校验。 Expo / Tauri 声明 `navigation.openNativePage` 时,只用于现役同源 H5 路由的受控导航和宿主上下文续接;微信小程序不再声明该能力。旧儿童动作 Demo、模板工作台、生成页、结果页和运行态不得作为 HostBridge 导航验收入口。 H5 支付链接跳转在原生壳声明 `app.openExternalUrl` 时必须优先走宿主系统浏览器;原生壳未接真实支付 SDK 前不得声明 `payment.request`,也不得把外部 H5 支付跳转伪装成原生支付成功。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 4723327c5..113a0281c 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -4203,11 +4203,11 @@ - 现象:`Repository checks`、`Frontend tests`、`Backend tests` 和 `Native shell tests` 都从全新 job 容器开始,apt、setup-node、rustup 和原生系统库在不同 job 里重复安装;后端与原生壳的安装时间可达数分钟,并把软件源和代理瞬时失败放大为四份。 - 原因:Gitea Actions job 彼此隔离,上一个 job 在容器内安装的包不会自动进入下一个 job;把同一套不随 PR 变化的工具链写在 workflow step 中,必然每次重做。 -- 处理:用 `deploy/container/gitea-ci-job.Dockerfile` 预装 Node 22、Rust 1.96、`rustfmt`、Chrome、`bwrap`、`rg`、`ffmpeg`、`clang/lld` 和 Tauri / 后端系统依赖,并按锁预热根与 AI 游戏创作壳 npm、server-rs、桌面壳与 AI 游戏创作壳 Cargo 下载缓存。四个 job 统一 `runs-on: genarrative-ci`,先用镜像内脚本直接从 Gitea checkout,再以 runtime 模式运行 `scripts/check-gitea-ci-job-image.sh`,同时检查五份缓存锁、工具链、完整 bwrap 与 Chrome headless。`RUSTUP_AUTO_INSTALL=0`;`rust-toolchain.toml` 变更时先重建镜像,不把下载 fallback 放回 job。 +- 处理:用 `deploy/container/gitea-ci-job.Dockerfile` 预装 Node 22、固定 npm、Rust 1.96、`rustfmt`、Chrome、`bwrap`、`rg`、`ffmpeg`、`clang/lld` 和 Tauri / 后端系统依赖,并按锁预热唯一根 npm workspace、server-rs、桌面壳与 AI 游戏创作壳 Cargo 四份下载缓存。四个 job 统一 `runs-on: genarrative-ci`,先用镜像内脚本直接从 Gitea checkout,再以 runtime 模式运行 `scripts/check-gitea-ci-job-image.sh`,同时检查四份缓存锁、工具链、完整 bwrap 与 Chrome headless。`RUSTUP_AUTO_INSTALL=0`;`rust-toolchain.toml` 变更时先重建镜像,不把下载 fallback 放回 job。 - 依赖边界:每个 job 仍必须各自执行 `npm ci`,让当前 lockfile 和 PR 依赖在干净环境中验证;区别是命中镜像 cache 时只做本地解包,锁新增依赖时才走受控网络。不要把 `node_modules` 或 Cargo `target` 烘进镜像,也不要向不受信任 PR 挂载跨 job 可写 cache。 - 锁漂移边界:runtime 校验输出任一 `*_cache_lock=partial` 说明镜像内 lock 与当前 checkout 不同,不代表新增依赖已经缓存;必须同时输出 Actions warning,提示可信分支落地后刷新镜像。必须在新镜像中对 server-rs、桌面壳和 AI 游戏创作壳当前 lock 执行真实 `cargo fetch --locked --offline`;`cargo metadata --no-deps` 不会证明依赖 archive 可用,不能作为替代。 - 构建网络边界:`CARGO_NET_RETRY` 只覆盖部分 crate 下载,registry `config.json` / index TLS 握手仍可能直接终止整次 fetch。Dockerfile 对每个 `cargo fetch --locked` 再做最多 5 次整命令级有界重试,最终仍执行断网 fetch,不能降低为无锁重试或省略离线闭合验证。 -- 验证:workflow 不再出现 GitHub checkout action、apt、setup-node 或 rustup 安装 step;镜像能按五份当前 lock 完成缓存闭合,四个 job 的环境校验、经 3 次整命令级有界重试保护的干净 `npm ci` 和原有测试门禁仍全部执行。 +- 验证:workflow 不再出现 GitHub checkout action、apt、setup-node 或 rustup 安装 step;镜像能按四份当前 lock 完成缓存闭合,四个 job 的环境校验、经 3 次整命令级有界重试保护的单次根 `npm ci` 和原有测试门禁仍全部执行。 ## Gitea Actions HTTPS CONNECT 隧道必须双向收束 socket(2026-08-07) @@ -4216,16 +4216,16 @@ - 处理:CONNECT 一开始就为 client socket 注册 `error / close`,解析完成后为 upstream socket注册同样的双向销毁处理;DNS 返回、写 200 和开始 pipe 前都检查 client 是否已销毁。任一端 error、close 或 timeout 都幂等 destroy 两端,不把普通客户端 reset 写成错误日志。不要用进程级 `uncaughtException` 吞掉问题,也不要只增加 npm/Cargo 重试掩盖 gateway 崩溃。 - 验证:在独立 canary 和正式 gateway 上分别并发制造至少 500 次“CONNECT 后立即断开”,随后确认容器仍运行、restart count 不增加、日志无 EPIPE;再通过同一 proxy 对 npm registry 与 crates index 建立完整 TLS 隧道。切换前仍须确认 Gitea 无活跃 run 且 Runner 内层无 job 容器。 -## 独立 AGC lockfile 不能丢失可选 WASM 包的 bundled 依赖节点(2026-08-21) +## 统一 npm workspace lock 不能丢失可选 WASM 包的 bundled 依赖节点(2026-08-21) -- 现象:`npm ci --prefix apps/ai-game-creator-shell` 在安装前失败,报告 lockfile 缺少 `@emnapi/core` / `@emnapi/runtime`;错误版本可能是 registry 当前满足 `^1.11.1` 的最新版,而不是原 lock 中曾记录的版本。 -- 原因:重写或解决 `apps/ai-game-creator-shell/package-lock.json` 冲突时,保留了 `@tailwindcss/oxide-wasm32-wasi` 对 bundled `@emnapi` 包的声明,却删掉了对应嵌套 package 节点。npm 会重新解析当前 registry 版本并判定 manifest 与 lock 不同步;这不是单一 npm 版本问题,也不表示应用应直接依赖两个 `@emnapi` 包。 -- 处理:只在最新目标分支执行 `npm install --package-lock-only --ignore-scripts --prefix apps/ai-game-creator-shell`,保留 npm 对 bundled 节点及 `peer` / `optional` 标记的完整规范化结果;确认子包 `package.json` 没有变化,不要手工只补报错中的两个版本。 -- 验证:至少用 Jenkins 对应 npm major 和当前开发 npm 分别执行干净的 `npm ci --prefix apps/ai-game-creator-shell`,再运行 AGC typecheck、编码检查和 `git diff --check`;根目录 `npm ci` 不能替代独立子包 lock 验证。 +- 现象:根 `npm ci` 在安装前失败,报告统一 lock 缺少 `@emnapi/core` / `@emnapi/runtime`;错误版本可能是 registry 当前满足 `^1.11.1` 的最新版,而不是原 lock 中曾记录的版本。 +- 原因:重写或解决根 workspace lock 冲突时,保留了 AGC 使用的 `@tailwindcss/oxide-wasm32-wasi` 对 bundled `@emnapi` 包的声明,却删掉了对应嵌套 package 节点。npm 会重新解析当前 registry 版本并判定 manifest 与 lock 不同步;这不是单一 npm 版本问题,也不表示应用应直接依赖两个 `@emnapi` 包。 +- 处理:只在最新目标分支的仓库根执行固定 npm 的 `npm install --package-lock-only --ignore-scripts`,保留 npm 对全部 workspaces、bundled 节点及 `peer` / `optional` 标记的完整规范化结果;确认各 workspace manifest 没有意外变化,不要手工只补报错中的两个版本。 +- 验证:至少用 Jenkins 对应固定 npm 和当前开发环境分别执行干净的根 `npm ci`,核对 bundled 节点后再运行 `npm run check:npm-workspaces`、AGC typecheck、编码检查和 `git diff --check`;禁止恢复独立 AGC lock 或子目录 `npm ci`。 ## Windows 专属 Tauri resource 不能写进通用配置(2026-08-21) -- 现象:Linux CI 已完成 AGC `npm ci`,却在 Tauri custom build command 中报 `resources/codex/win-x64/...exe doesn't exist`;Windows 侧车的 Rust staging 受 `cfg(windows)` 保护,因此非 Windows 构建不会生成这些文件。 +- 现象:Linux CI 已完成根 workspace `npm ci`,却在 Tauri custom build command 中报 `resources/codex/win-x64/...exe doesn't exist`;Windows 侧车的 Rust staging 受 `cfg(windows)` 保护,因此非 Windows 构建不会生成这些文件。 - 原因:Tauri 会在所有平台校验通用 `tauri.conf.json` 的 bundle resource 源路径;把 Windows x64 资源映射写进通用配置,等于要求 Linux / macOS 也预先拥有不属于其安装闭包的 Windows 可执行文件。 - 处理:通用配置只保留跨平台 bundle 项;Windows 原生侧车的完整白名单放入 Tauri 自动合并的 `tauri.windows.conf.json`。不要提交二进制占位文件,也不要让非 Windows build script 下载或伪造 Windows 资源。 - 验证:配置门禁断言通用配置没有 Windows resource、Windows 平台配置保留完整固定白名单;Linux 运行原生壳门禁必须越过 Tauri resource 校验,Windows release 仍由 build script 对 npm 原生包、SHA-256 清单和目标布局失败关闭。 @@ -4825,3 +4825,17 @@ - 原因:两个 Cargo workspace 拥有独立 target 和锁文件,AGC 又以 path dependency 复用若干 server-rs crate;更主要的是全量 / 分组 `cargo test` 继承增量编译,每组 crate / feature / profile hash 都可以留下新会话,Cargo 不会按仓库期望自动收缩这些历史目录。 - 处理:保留两个 workspace 的产品 / 发布边界;两边 `[profile.test]` 关闭 incremental 并固定 `debug=1`,AGC dev profile 与 server-rs 对齐调试信息级别。日常用 `npm run audit:rust-build-cache` 只读核对;需要回收时先停止 Cargo / rustc,再显式运行 `npm run clean:rust-incremental -- --apply`,只删两个固定增量目录。 - 验证:清理前后各跑一次只读审计并核对磁盘可用空间;分别运行 server-rs 与 AGC 定向 `cargo test`,确认 test profile 不再生成持久 `debug/incremental` 堆积。共享 `CARGO_TARGET_DIR` 必须另做并发启动基准,不得为节省磁盘直接改变生产产物路径。 + +## BuildKit secret 不等于镜像内 secrets 不可提取(2026-08-22) + +- 现象:构建时使用 BuildKit secret mount,日志和普通 build context 都没有出现明文,于是误以为最终镜像也能不可提取地保存 secrets,随后将镜像 push 或导出给不同信任域。 +- 原因:BuildKit secret mount 只避免秘密作为 `ARG` / `COPY` 进入构建上下文和中间指令;一旦 Dockerfile 把 mount 的内容安装到最终 rootfs,任何能读取、保存或运行该镜像的主体都可以提取它。 +- 处理:预览固定 secrets 只从 Jenkins 宿主受控路径读取,严格校验目录 `0700`、文件 `0600`、owner、普通文件与非链接边界;只将其安装到 `api-runtime:/srv/genarrative/.env.secrets.local` 并设为 `0400`,明确排除 Nginx、Web、artifact 和其它镜像。镜像禁止推送或导出到跨信任边界。 +- 更新与验证:源文件变更不会改动已存镜像,必须重建并替换容器;不能用重启代替。验收同时扫描 transcript/context/artifact 零泄漏,检查只有 API 最终 rootfs 存在目标文件,并验证容器显式运行 env 优先覆盖内置值。 + +## SpacetimeDB ping 健康不代表完整模块能在内存上限内实例化(2026-08-22) + +- 现象:空库 `/v1/ping` 已成功且容器显示 healthy,但 `spacetime publish` 在 `Publishing module...` 后连接提前关闭,紧接着端口拒绝连接。 +- 原因:当前完整模块 init 的 RSS 会超过基础 Compose 旧 `896m` cgroup 上限;内核 OOM kill SpacetimeDB,客户端只看到传输错误,容易被误判为网络竞态。 +- 处理:先查 kernel journal 的 `Memory cgroup out of memory` 和目标容器 ID,再把本地/预发完整容器 SpacetimeDB 上限统一为 `2g`;保留 page pool 限制。不要只增加 publish 重试,也不要把 `/healthz` 或首页改成数据库就绪探针。 +- 验证:用新空卷完成模块 publish、五服务启动和 Web/API smoke,并确认容器未 OOM、SpacetimeDB 与 API/Nginx 最终 healthy。 diff --git a/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md b/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md index 878172c07..c75854292 100644 --- a/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md +++ b/docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md @@ -45,9 +45,17 @@ SpacetimeDB 与 OTLP 不映射宿主端口;Jenkins 通过受控 Compose 网络 SpacetimeDB 2.7 CLI 发布到受控 Compose 网络地址时固定使用 `--yes=remote,migrate,break-clients`,避免 Jenkins 等待非本地目标交互确认;该预览路径不传 `--delete-data`。Jenkins 同时固定 `GENARRATIVE_PREVIEW_WEB_HOST=192.168.35.82`,不得用默认路由自动探测结果生成页面链接,以免 VPN 或容器网卡地址泄漏到同事可见 URL。 -预览 Compose override 将 SpacetimeDB 内存上限设为 `2g`。基础 loadtest Compose 的 `896m` 是压测采样口径,当前完整模块首次发布和实例化会超过该上限;预览环境若沿用该值,容器会被 cgroup OOM 杀死并使模块上传中断。该覆盖只作用于分支预览实例,不修改生产或压测基线。 +基础 loadtest Compose 与预览 Compose override 都将 SpacetimeDB 内存上限设为 `2g`。当前完整模块首次发布和实例化的 cgroup 峰值会超过旧 `896m` 上限;完整容器或预览环境若沿用旧值,容器会被 OOM 杀死并使模块上传中断。预览 override 继续显式锁定该值并取消宿主端口映射;这不修改生产服务资源合同。 -每个预览实例使用独立 SpacetimeDB 空库,不继承生产账号和短信凭据。Jenkins 在实例私有 `api-server.env` 中开启预览专用认证:未注册的中国大陆手机号首次使用 6 到 128 位密码时自动创建预览账号;短信入口使用 `mock` provider 与固定预览验证码 `123456`,不向真实手机发送短信。该设置不写入公共 env 示例、生产配置或镜像层;实例重建会重建独立数据库,原预览账号不保留。 +每个预览实例使用独立 SpacetimeDB 空库,不继承生产账号数据。Jenkins 在实例私有 `api-server.env` 中开启预览专用认证:未注册的中国大陆手机号首次使用 6 到 128 位密码时自动创建预览账号;短信入口强制使用 `mock` provider 与固定预览验证码 `123456`,不调用镜像内置的真实短信凭据。该预览认证覆盖不写入公共 env 示例或生产配置;实例重建会重建独立数据库,原预览账号不保留。 + +## 预览 secrets 内置 + +Jenkins 节点上的预览 secrets 权威来源固定为 `/data/jenkins/preview-secrets/.env.secrets.local`。该文件不进 Git、Docker build context、构建日志或 artifact;构建时只通过 BuildKit `secret` mount 临时提供给 `api-runtime` stage,并在该运行镜像中安装为 `/srv/genarrative/.env.secrets.local`,权限固定为 `0400`。`nginx-runtime`、Web 静态产物、SpacetimeDB 镜像及其它镜像不得包含该文件。 + +宿主固定目录应由 Jenkins 运行账号所有且权限为 `0700`,源文件权限为 `0600`;缺失、不是普通文件、owner 不匹配或权限过宽时,预览构建必须失败关闭。源文件变更后必须重新构建并替换预览镜像,只重启容器不会刷新已内置的内容。容器启动时显式注入的运行环境变量优先级高于镜像内的 `.env.secrets.local`,用于按实例覆盖非通用值。 + +这种方案只隐藏构建传输过程,不能让内置后的 secrets 对镜像持有者保密:能读取、保存或运行 `api-runtime` 镜像的人可以提取该文件。因此该镜像只能留在当前受信任内网 Docker 主机,禁止 push 到公共或跨信任边界的 registry,也禁止通过 `docker save`/构建 artifact 导出传播。需要跨边界分发时必须改用不含 secrets 的镜像与运行时密钥注入。 ## Jenkins 参数与产物 @@ -110,6 +118,7 @@ Jenkins 在构建完成、归档 artifact 和更新 REST 状态之间可能短 - Jenkins service account 只授予 `shared/Genarrative-Preview-Deployer` 的 `Job/Read`、`Job/Build` 和读取构建产物所需权限,不授 `Overall/Administer`、`Job/Configure` 或 `Job/Delete`。 - 后端固定 Jenkins origin、Job 路径和参数白名单;客户端不能传 URL、Job 名、Compose project、容器名、宿主端口或 Jenkins 凭据。 - Git 查询固定使用本机 Gitea SSH 地址和服务端只读凭据;客户端不能传 remote、SSH 参数或凭据。Git 缓存只写入预览控制服务的受控状态目录,搜索接口需要控制台会话且结果有数量上限。 +- 预览 secrets 只从固定宿主路径读取,构建前校验 owner、类型和权限;不允许分支、Jenkins 参数或控制面请求改写 secrets 路径、BuildKit secret ID 或镜像内目标路径。 - Jenkins POST 支持动态 Crumb;API Token 即使免 Crumb,也不能把 Token 放进 URL 或日志。 - API 默认只接受同源请求,写请求校验 Origin;内网本身不作为认证。 - 同一 deployment 的发布和卸载串行执行;重复请求必须幂等或明确返回冲突。 @@ -120,5 +129,6 @@ Jenkins 在构建完成、归档 artifact 和更新 REST 状态之间可能短 - 后端:输入校验、登录会话、Origin、Crumb、Jenkins `401/403/404/5xx`、queue 到 build 状态机、artifact schema、卸载所有权和幂等测试。 - 前端:登录、分支与可选 commit、自动刷新、排队/构建/成功/失败状态、内网链接、卸载确认和刷新恢复测试。 - Jenkins:两个分支依次发布后在不同端口并存;同一分支换 commit 优先复用端口;非分支 commit 被拒绝;卸载只删除目标实例并释放端口。 -- 预览认证:新手机号可以首次密码登录并重复使用同一密码;错误密码被拒绝;获取验证码后使用 `123456` 可完成登录;容器不包含生产短信凭据。 +- 预览认证:新手机号可以首次密码登录并重复使用同一密码;错误密码被拒绝;获取验证码后使用 `123456` 可完成登录;短信入口保持 `mock` 且不调用内置的真实短信凭据。 +- secrets:构建 transcript、context 和 artifact 零出现原文;只有 `api-runtime` 包含 `0400` 的目标文件,`nginx-runtime` 与其它镜像均不包含;修改固定源文件后旧镜像不变、重建新镜像后摘要更新;显式运行 env 可覆盖内置值。 - 通用:`npm run check:encoding`、相关 typecheck/build/test、Rust 定向测试和 `git diff --check`。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index c52708d32..1f0912b42 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1206,3 +1206,10 @@ game-project/ - **真实 Provider 开发验收入口**:`--game-chat-smoke` 是受限 CLI 标记,只允许与默认 `project-supervisor` 的 `--swarm-chat --autonomous-game-build` 组合,将新根 Run 绑定为 `project-supervisor-game-chat`,不作为产品 UI、公开 API 或通用 source 覆盖能力。现有 playable harness 通过 `npm run ai-game-creator-shell:agent-runtime:supervisor-game-chat-single-main-playable-real-e2e` 显式启动该模式,启动前同时校验 `project-supervisor` 与 `code-prototype` 的 Provider 配置,并在隔离 AppData 中验收唯一单主 child、当前 revision 的 static smoke 及 desktop/mobile 试玩回执。只有显式执行这条真实 E2E 命令才会发起 Provider 请求;普通 self-test 不读取凭据、不调用 Provider。现场 smoke 若在配置门因缺少 API Key 阻断,必须报告 `providerUsed=false`,只能证明入口、验收逻辑与无 Provider 自测已落地,不能宣称真实现场验收完成。 - **fresh-init 取证边界**:`supervisor-game-chat-single-main-playable` 不再预写 `package.json`、`verify-e2e.mjs` 或 `game/index.html`,由正式 `--init` 生成生产 `DEFAULT_GAME_INDEX_HTML`。self-test 必须逐字节核对 harness 中的 canonical 默认入口与 Rust 生产常量,并证明空项目仍保留 Git、`AGENTS.md`、隔离 evidence 和敏感诱饵基线;真实报告必须同时证明初始 SHA-256 命中生产默认入口、根下唯一固定 child 为 `code-prototype`、最终入口已变化、static-smoke SHA-256 绑定最终入口且 desktop/mobile 各自通过。 - **不外推范围**:上述真实 Provider 命令只验收普通 Web 工作台所采用的 `project-supervisor-game-chat + autonomous-game-build` 单 Supervisor 链。显式 `professional-dag` 与固定 16 节点 CLI/GUI 链的 owner-artifact verify、产物所有权和 path-scope 仍是独立未解决项;seeded deterministic E2E、普通 self-test 或本 game-chat 报告都不得冒充该链已修复或已完成真实验收。 + +## 2026-08-21 npm workspace 安装与打包边界 + +- AGC 进入仓库根 npm workspaces,`apps/ai-game-creator-shell/package.json` 继续独占其 Tauri guest、Codex CLI 和 App 直接依赖,但不再维护独立 `package-lock.json`。开发、CI、Jenkins 与打包前只从仓库根执行一次 `npm ci`;禁止恢复子目录独立安装。 +- 统一根 lock 出现 `@tauri-apps/api`、`@tauri-apps/plugin-*` 或 `@openai/codex` 是 AGC workspace 的合法聚合结果,不代表根 H5 或 Desktop 获得这些能力。配置门禁必须按 workspace manifest/lock entry 判断归属。 +- npm 默认 hoist。Vite/Vitest 对 `@cubone/react-file-manager` 的已发布 ESM bundle、AGC TypeScript/Tauri CLI 和 Windows `@openai/codex-win32-x64` sidecar 解析必须兼容 workspace 本地与根提升位置,不得硬编码依赖只存在于 `apps/ai-game-creator-shell/node_modules`。 +- Windows game-chat release 仍必须核对固定 Codex 文件和 SHA-256 manifest;Linux 根 lock 与 typecheck 通过不能替代 Windows sidecar 打包 smoke。完整安装、锁与 CI 口径见 [`npm workspaces 统一依赖边界`](./【技术方案】npm-workspaces统一依赖边界-2026-08-21.md)。 diff --git a/docs/technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md b/docs/technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md new file mode 100644 index 000000000..f6d3ecb0d --- /dev/null +++ b/docs/technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md @@ -0,0 +1,126 @@ +# npm workspaces 统一依赖边界 + +更新时间:`2026-08-21` + +## 目标 + +Genarrative 的 JavaScript 工程统一使用 npm workspaces。仓库只提交根 `package-lock.json`,开发、CI、Jenkins 和容器构建都从仓库根执行一次干净安装;各 App、内部包和工具仍由自己的 `package.json` 声明直接依赖和脚本。 + +本方案只迁移包管理与依赖边界,不切换 pnpm,不改变 Cargo workspace、SpacetimeDB schema、前后端 DTO 或业务运行时。 + +## Workspace 范围 + +根 `package.json` 固定声明: + +```json +{ + "packageManager": "npm@10.9.7", + "workspaces": [ + "apps/admin-web", + "apps/ai-game-creator-shell", + "apps/desktop-shell", + "apps/mobile-shell", + "apps/preview-deployer-web", + "packages/image-canvas-core", + "packages/image-canvas-react", + "packages/shared", + "tools/spine-json-export-validator" + ] +} +``` + +当前纳入: + +- `apps/admin-web` +- `apps/ai-game-creator-shell` +- `apps/desktop-shell` +- `apps/mobile-shell` +- `apps/preview-deployer-web` +- `packages/image-canvas-core` +- `packages/image-canvas-react` +- `packages/shared` +- `tools/spine-json-export-validator` + +`.rag/runtime`、`.worktrees/`、`tmp/`、构建目录和各级 `node_modules` 不属于 workspace。RAG 继续保持独立、gitignored 的本地运行时,不进入根依赖。 + +## Manifest 与依赖所有权 + +- 根 manifest 只声明根 H5、仓库级脚本和统一测试/格式化工具的直接依赖,不再为 Mobile、AGC 或工具重复声明其专属依赖。 +- 每个 App 在自己的 manifest 声明运行时直接依赖;测试只由根统一 runner 承担的工具可以留在根,App 自己提供测试脚本时必须声明其直接测试依赖。 +- `@genarrative/image-canvas-react` 必须显式依赖 `@genarrative/image-canvas-core`;主站和 AGC 必须显式声明它们直接消费的内部画布包。 +- npm `10.9.7` 不支持依赖值 `workspace:*`。内部 workspace 依赖使用匹配本地包版本的普通 semver,例如 `"@genarrative/image-canvas-core": "0.1.0"`;npm 在根安装时自动生成本地 `link`。 +- npm 默认提升依赖,统一根 `node_modules` 中存在某包不代表根 H5 拥有该依赖。归属门禁必须检查对应 workspace manifest 和根 lock 中的 workspace package entry,不能按统一 lock 的全局 `node_modules/*` 条目判断归属。 + +`packages/shared` 本次纳入统一安装和 lock,但不顺手重写现有源码 import;后续若要把所有相对源码引用改为 `@genarrative/shared`,需先补完整 exports/build 合同并单独实施。 + +## 唯一 Lockfile + +- 唯一权威 npm lockfile 为根 `package-lock.json`。 +- 删除 `apps/ai-game-creator-shell/package-lock.json` 与 `tools/spine-json-export-validator/package-lock.json`。 +- 新增或修改任一 workspace 依赖后,只能从仓库根使用固定 npm 版本更新根 lock。 +- 仓库门禁必须校验 workspace 清单、固定 `packageManager`、根 lock 的 workspace package/link 条目,并拒绝受管 workspace 再提交嵌套 `package-lock.json` 或 `npm-shrinkwrap.json`。 +- optional dependency、平台二进制和 bundled dependency 仍由根 lock 完整记录;Linux 生成 lock 后仍需 Windows/macOS 对应构建门禁,不能把单平台安装等同于跨平台通过。 + +## 安装与脚本 + +标准安装入口: + +```bash +npm ci +``` + +开发者首次拉取或主动更新依赖时可使用根 `npm install`。Husky 的 `prepare` 只在根执行一次;各 workspace 不重复安装 hook。 + +现有 `npm run agc`、`npm run mobile-shell:*`、`npm run desktop-shell:*`、`npm run preview-deployer:web:*` 和 `npm run spine-export-validator:*` 等对外入口保持名称稳定。内部可继续使用 `npm --prefix`,或使用 `npm run