diff --git a/.github/workflows/runtime-tests.yml b/.github/workflows/runtime-tests.yml index fd68c45b..7f6cf099 100644 --- a/.github/workflows/runtime-tests.yml +++ b/.github/workflows/runtime-tests.yml @@ -18,4 +18,20 @@ jobs: node-version: ${{ matrix.node-version }} cache: "pnpm" - run: pnpm install --frozen-lockfile - - run: pnpm run --filter @my-code/runtime test + - name: Install Boost + run: sudo apt-get update && sudo apt-get install -y libboost-dev + - name: Start unsandbox + run: | + pnpm exec unsandbox --port 3055 & + for i in {1..30}; do + if curl -s http://localhost:3055/api/list.json > /dev/null; then + echo "unsandbox is ready" + break + fi + echo "Waiting for unsandbox..." + sleep 1 + done + - name: Run runtime tests + env: + WANDBOX_URL: http://localhost:3055 + run: pnpm run --filter @my-code/runtime test diff --git a/README.md b/README.md index c350f300..f26a2b41 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ SENTRY_PROJECT= SENTRY_URL= SENTRY_AUTH_TOKEN= SENTRY_DSN= +# WANDBOX_URL=http://localhost:3055 # C++/Rustのローカル実行用(任意) ``` - チャット用にGeminiのAPIキーまたはOpenRouterのAPIキーのいずれかが必要です。未設定の場合チャットが使えません @@ -78,6 +79,27 @@ pnpm run lint でコードをチェックします。出てくるwarningやerrorはできるだけ直しましょう。 +### C++ / Rust のコード実行環境 (unsandbox) + +C++ および Rust のコード実行は、デフォルトでは [Wandbox](https://wandbox.org/) の公開 API を使用しますが、ローカルに [unsandbox](https://github.com/ut-code/unsandbox) を立ち上げて実行することも可能です(ローカル実行のほうが高速です)。 + +```bash +# unsandbox を起動(例: ポート 3055) +pnpm exec unsandbox --port 3055 +``` + +`.env` または `.env.local` に以下を設定すると、Next.js 開発サーバーやテスト実行時にローカルの unsandbox が自動で利用されます(dotenvによりテスト実行時も読み込まれます)。 + +```dotenv +WANDBOX_URL=http://localhost:3055 +``` + +※ コマンドラインから直接指定して一時的に切り替えることも可能です。 + +```bash +WANDBOX_URL=http://localhost:3055 pnpm --filter @my-code/runtime test +``` + ### データベースのスキーマ - データベースのスキーマ(./app/schema/hoge.ts)を編集した場合、 `pnpm run db-generate` (`pnpm exec drizzle-kit generate`) でmigrationファイルを作成し、 `pnpm run db-migrate` (`pnpm exec drizzle-kit migrate`) でデータベースに反映します。 diff --git a/next.config.ts b/next.config.ts index d6da978c..46b7e849 100644 --- a/next.config.ts +++ b/next.config.ts @@ -19,6 +19,7 @@ let nextConfig: NextConfig = { }, env: { SENTRY_DSN: process.env.SENTRY_DSN, + WANDBOX_URL: process.env.WANDBOX_URL, }, serverExternalPackages: [ "@prisma/client", diff --git a/package.json b/package.json index 79473915..2217607b 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "tsx": "^4.20.6", "typescript": "5.9.3", "unified": "^11.0.5", + "unsandbox": "^1.0.0", "webpack-license-plugin": "^4.5.1", "wrangler": "^4.27.0" } diff --git a/packages/runtime/README.md b/packages/runtime/README.md index e0581ce0..4a74f001 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -20,6 +20,11 @@ ## tests - `pnpm run --filter @my-code/runtime test` でvitestを使ってテストを実行できます +- ルートまたはパッケージ内の `.env` / `.env.local` に `WANDBOX_URL` を記述しておくことで、ローカルで立ち上げた [unsandbox](https://github.com/ut-code/unsandbox) サーバーに自動で接続して高速にテストを実行できます(コマンドラインでの `WANDBOX_URL=...` 指定も可能です)。 + ```bash + pnpm exec unsandbox --port 3055 & + pnpm run --filter @my-code/runtime test + ``` - my.code(); の /about/runtime ページでMochaを使ってテストを実行できます - どちらからでも実行できるようテスト本体は ./tests/repl.ts, ./tests/fileExecution.ts に記述しています。 新しい言語の実行環境を追加した場合、ここにテストケースを追加してください。 @@ -41,7 +46,7 @@ workerとの通信部分は言語によらず共通なので、それをworker/r ### Wandbox -wandbox.org のAPIを利用してコードを実行します。 +wandbox.org のAPIを利用してコードを実行します(環境変数 `WANDBOX_URL` でローカルの unsandbox 等のエンドポイントに切り替え可能です)。 APIから利用可能なコンパイラとオプションのリストが得られるので、言語ごとにそこからオプションを選択するロジックを実装しています。 diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 5f946aa1..d3467750 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -10,6 +10,7 @@ "test": "vitest run" }, "dependencies": { + "@my-code/js-eval": "workspace:*", "@ruby/wasm-wasi": "^2.7.2", "@typescript/vfs": "^1.6.2", "async-mutex": "^0.5.0", @@ -21,8 +22,7 @@ "react-dom": "^19", "swr": "^2", "typescript": "^5", - "zod": "^4.0.17", - "@my-code/js-eval": "workspace:*" + "zod": "^4.0.17" }, "devDependencies": { "@testing-library/dom": "^10.0.0", @@ -33,6 +33,7 @@ "@types/react-dom": "^19", "@vitejs/plugin-react": "^5.1.4", "@vitest/browser-webdriverio": "^4.1.10", + "dotenv": "^17.4.2", "vitest": "^4.1.10" } } diff --git a/packages/runtime/src/wandbox/api.ts b/packages/runtime/src/wandbox/api.ts index c4a297eb..aba94461 100644 --- a/packages/runtime/src/wandbox/api.ts +++ b/packages/runtime/src/wandbox/api.ts @@ -1,7 +1,8 @@ import { type Fetcher } from "swr"; import { type ReplOutput, RuntimeInfo } from "../interface"; -const WANDBOX = "https://wandbox.org"; +const WANDBOX = process.env.WANDBOX_URL || "https://wandbox.org"; + // https://github.com/melpon/wandbox/blob/ajax/kennel2/API.rst <- 古いけど、説明と例がある // https://github.com/melpon/wandbox/blob/master/feline/src/types.rs /* RustのVecはバイト配列ですが、serialize_with = "serialize_utf8"という指定があるため、 diff --git a/packages/runtime/vitest.config.ts b/packages/runtime/vitest.config.ts index 8e0ac6c2..53da8906 100644 --- a/packages/runtime/vitest.config.ts +++ b/packages/runtime/vitest.config.ts @@ -2,12 +2,23 @@ import { defineConfig } from "vitest/config"; import path from "path"; import react from "@vitejs/plugin-react"; import { webdriverio } from "@vitest/browser-webdriverio"; +import dotenv from "dotenv"; + +// Load .env and .env.local from project root and package directory +const rootDir = path.resolve(__dirname, "../.."); +dotenv.config({ path: path.resolve(rootDir, ".env") }); +dotenv.config({ path: path.resolve(rootDir, ".env.local"), override: true }); +dotenv.config({ path: path.resolve(__dirname, ".env"), override: true }); +dotenv.config({ path: path.resolve(__dirname, ".env.local"), override: true }); // webdriverioは自動でchromeをダウンロードして起動するはずだが、もし何らかの理由でchromeが動作しない場合は、 @puppeteer/browsers を使用してchromeをダウンロードし、環境変数でバイナリのパスを指定してみる const localChrome = process.env.CHROME_BIN; const localChromeDriver = process.env.CHROMEDRIVER_PATH; export default defineConfig({ + define: { + "process.env.WANDBOX_URL": JSON.stringify(process.env.WANDBOX_URL || ""), + }, resolve: { alias: { "@my-code/js-eval": path.resolve(__dirname, "../jsEval/src"), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a2a9283..fca691c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -407,6 +407,9 @@ importers: unified: specifier: ^11.0.5 version: 11.0.5 + unsandbox: + specifier: ^1.0.0 + version: 1.0.0 webpack-license-plugin: specifier: ^4.5.1 version: 4.5.1(webpack@5.108.4) @@ -496,6 +499,9 @@ importers: '@vitest/browser-webdriverio': specifier: ^4.1.10 version: 4.1.10(vite@7.3.2)(vitest@4.1.10)(webdriverio@9.29.1) + dotenv: + specifier: ^17.4.2 + version: 17.4.2 vitest: specifier: ^4.1.10 version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-webdriverio@4.1.10)(vite@7.3.2) @@ -1845,6 +1851,12 @@ packages: '@modelcontextprotocol/sdk': optional: true + '@hono/node-server@2.1.1': + resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} + engines: {node: '>=20'} + peerDependencies: + hono: ^4 + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -3448,6 +3460,7 @@ packages: '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xterm/addon-fit@0.11.0': resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} @@ -4550,6 +4563,7 @@ packages: eslint@9.39.3: resolution: {integrity: sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -4954,6 +4968,10 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hono@4.13.5: + resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} + engines: {node: '>=16.9.0'} + html-comment-regex@1.1.2: resolution: {integrity: sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==} @@ -6837,6 +6855,10 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + unsandbox@1.0.0: + resolution: {integrity: sha512-jwd5J/DnXPCVNEBCp7kKgTaowffUn+v740Ul0wkBUkfQnQqKNPvqu+jMp3MgBe2lTjtEw6pIo4uSd0dwmYB8hg==} + hasBin: true + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -8674,6 +8696,10 @@ snapshots: - supports-color - utf-8-validate + '@hono/node-server@2.1.1(hono@4.13.5)': + dependencies: + hono: 4.13.5 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -12149,6 +12175,8 @@ snapshots: highlightjs-vue@1.0.0: {} + hono@4.13.5: {} + html-comment-regex@1.1.2: {} html-url-attributes@3.0.1: {} @@ -14431,6 +14459,11 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + unsandbox@1.0.0: + dependencies: + '@hono/node-server': 2.1.1(hono@4.13.5) + hono: 4.13.5 + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: browserslist: 4.28.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7bf08cb7..9722faf7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,7 @@ minimumReleaseAge: 1440 minimumReleaseAgeStrict: true minimumReleaseAgeExclude: - daisyui@5.7.13 + - unsandbox@1.0.0 strictDepBuilds: true strictPeerDependencies: true