From cb27603da095ebcf60a4f2dae66eaad19c174d4b Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Mon, 21 Apr 2025 11:11:45 -0600 Subject: [PATCH 1/2] feat: browser support --- .github/actions/setup-playwright/action.yml | 32 + .github/workflows/tests.yml | 6 + .npmrc | 1 + package-lock.json | 667 ++++++++++++++++-- packages/mcp-server-supabase/package.json | 10 +- .../{eszip.test.ts => eszip/index.test.ts} | 54 +- .../src/{eszip.ts => eszip/index.ts} | 7 +- .../mcp-server-supabase/src/regions.test.ts | 16 +- .../mcp-server-supabase/src/server.test.ts | 21 +- packages/mcp-server-supabase/src/server.ts | 10 +- packages/mcp-server-supabase/test/llm.e2e.ts | 4 +- packages/mcp-server-supabase/test/mocks.ts | 107 ++- .../test/plugins/text-loader.ts | 15 + .../mcp-server-supabase/test/setup/browser.ts | 25 + .../{vitest.setup.ts => test/setup/env.ts} | 1 - .../test/{ => setup}/extensions.d.ts | 0 .../test/{ => setup}/extensions.ts | 0 .../mcp-server-supabase/test/setup/node.ts | 24 + .../test/setup/vitest.d.ts | 7 + .../mcp-server-supabase/vitest.workspace.ts | 69 +- 20 files changed, 854 insertions(+), 222 deletions(-) create mode 100644 .github/actions/setup-playwright/action.yml create mode 100644 .npmrc rename packages/mcp-server-supabase/src/{eszip.test.ts => eszip/index.test.ts} (51%) rename packages/mcp-server-supabase/src/{eszip.ts => eszip/index.ts} (94%) create mode 100644 packages/mcp-server-supabase/test/plugins/text-loader.ts create mode 100644 packages/mcp-server-supabase/test/setup/browser.ts rename packages/mcp-server-supabase/{vitest.setup.ts => test/setup/env.ts} (84%) rename packages/mcp-server-supabase/test/{ => setup}/extensions.d.ts (100%) rename packages/mcp-server-supabase/test/{ => setup}/extensions.ts (100%) create mode 100644 packages/mcp-server-supabase/test/setup/node.ts create mode 100644 packages/mcp-server-supabase/test/setup/vitest.d.ts diff --git a/.github/actions/setup-playwright/action.yml b/.github/actions/setup-playwright/action.yml new file mode 100644 index 0000000..9616178 --- /dev/null +++ b/.github/actions/setup-playwright/action.yml @@ -0,0 +1,32 @@ +name: 'Setup and Cache Playwright' +description: 'Installs playwright browsers and caches them.' + +inputs: + version: + description: 'The version of playwright' + required: true + +outputs: + cache-hit: + description: 'Whether the cache was hit or not' + value: ${{ steps.playwright-cache.outputs.cache-hit }} + +runs: + using: 'composite' + steps: + - name: Cache playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + key: playwright-browsers-${{ runner.os }}-${{ inputs.version }} + path: ~/.cache/ms-playwright + + - name: Install playwright browsers if they don't exist + shell: bash + run: npx playwright install --with-deps + if: steps.playwright-cache.outputs.cache-hit != 'true' + + - name: Install playwright dependencies if binaries exist + shell: bash + run: npx playwright install-deps + if: steps.playwright-cache.outputs.cache-hit == 'true' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 54b3041..da09c5f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,5 +20,11 @@ jobs: run: npm ci --ignore-scripts - name: Build libs run: npm run build + - name: Get playwright version + run: echo "PLAYWRIGHT_VERSION=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//')" >> $GITHUB_ENV + - name: Setup playwright + uses: ./.github/actions/setup-playwright + with: + version: ${{ env.PLAYWRIGHT_VERSION }} - name: Tests run: npm run test diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..41583e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@jsr:registry=https://npm.jsr.io diff --git a/package-lock.json b/package-lock.json index 603154a..7a31743 100644 --- a/package-lock.json +++ b/package-lock.json @@ -150,6 +150,19 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", @@ -195,55 +208,12 @@ } }, "node_modules/@deno/eszip": { - "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@deno/eszip/-/eszip-0.84.0.tgz", - "integrity": "sha512-kfTiJ3jYWy57gV/jjd2McRZdfn2dXHxR3UKL6HQksLAMEmRILHo+pZmN1PAjj8UxQiTBQbybsNHGLaqgHeVntQ==", - "license": "MIT", - "dependencies": { - "@deno/shim-deno": "~0.18.0", - "undici": "^6.0.0" - } - }, - "node_modules/@deno/shim-deno": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@deno/shim-deno/-/shim-deno-0.18.2.tgz", - "integrity": "sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA==", - "license": "MIT", - "dependencies": { - "@deno/shim-deno-test": "^0.5.0", - "which": "^4.0.0" - } - }, - "node_modules/@deno/shim-deno-test": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", - "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", + "name": "@gregnr/eszip", + "version": "0.84.0-web.2", + "resolved": "https://registry.npmjs.org/@gregnr/eszip/-/eszip-0.84.0-web.2.tgz", + "integrity": "sha512-RB0/+4Z6NiLvRT+k/iHlLbreN0tTOELqxTgeGigSKE8zqCh0z4jtYiWE5mnsRx+WYMAcvGKRVEcbHHzfRv6IvQ==", "license": "MIT" }, - "node_modules/@deno/shim-deno/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "license": "ISC", - "engines": { - "node": ">=16" - } - }, - "node_modules/@deno/shim-deno/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, "node_modules/@electric-sql/pglite": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.17.tgz", @@ -1251,6 +1221,29 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@redocly/ajv": { "version": "8.11.2", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", @@ -1623,6 +1616,13 @@ "win32" ] }, + "node_modules/@std/path": { + "name": "@jsr/std__path", + "version": "1.0.8", + "resolved": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.8.tgz", + "integrity": "sha512-eNBGlh/8ZVkMxtFH4bwIzlAeKoHYk5in4wrBZhi20zMdOiuX4QozP4+19mIXBT2lzHDjhuVLyECbhFeR304iDg==", + "dev": true + }, "node_modules/@supabase/auth-js": { "version": "2.67.3", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.67.3.tgz", @@ -2014,6 +2014,86 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@total-typescript/tsconfig": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@total-typescript/tsconfig/-/tsconfig-1.0.4.tgz", @@ -2021,6 +2101,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/common-tags": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.4.tgz", @@ -2338,6 +2425,16 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -2808,6 +2905,13 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", @@ -2947,9 +3051,9 @@ } }, "node_modules/expect-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", - "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3215,6 +3319,16 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -3507,6 +3621,16 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -3685,6 +3809,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4051,6 +4185,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -4148,6 +4329,44 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/proc-log": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", @@ -4227,6 +4446,13 @@ "node": ">=0.10.0" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/read-cmd-shim": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz", @@ -4265,6 +4491,13 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4473,6 +4706,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sirv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", + "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -4543,9 +4791,9 @@ } }, "node_modules/std-env": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", - "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", "dev": true, "license": "MIT" }, @@ -4793,9 +5041,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, "license": "MIT" }, @@ -4852,6 +5100,16 @@ "node": ">=0.6" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -5472,15 +5730,6 @@ "node": ">=14.17" } }, - "node_modules/undici": { - "version": "6.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", - "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -6342,6 +6591,28 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6519,7 +6790,7 @@ "version": "0.3.5", "license": "Apache-2.0", "dependencies": { - "@deno/eszip": "^0.84.0", + "@deno/eszip": "npm:@gregnr/eszip@^0.84.0-web.2", "@modelcontextprotocol/sdk": "^1.4.1", "@supabase/mcp-utils": "0.1.3", "common-tags": "^1.8.2", @@ -6532,9 +6803,12 @@ "devDependencies": { "@ai-sdk/anthropic": "^1.2.9", "@electric-sql/pglite": "^0.2.17", + "@playwright/test": "^1.52.0", + "@std/path": "npm:@jsr/std__path@^1.0.8", "@total-typescript/tsconfig": "^1.0.4", "@types/common-tags": "^1.8.4", "@types/node": "^22.8.6", + "@vitest/browser": "^3.1.1", "ai": "^4.3.4", "date-fns": "^4.1.0", "dotenv": "^16.5.0", @@ -6546,7 +6820,156 @@ "tsup": "^8.3.5", "tsx": "^4.19.2", "typescript": "^5.6.3", - "vitest": "^2.1.9" + "vitest": "^3.1.1" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/browser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-3.1.1.tgz", + "integrity": "sha512-A+A69mMtrj1RPh96LfXGc309KSXhy2MslvyL+cp9+Y5EVdoJD4KfXDx/3SSlRGN70+hIoJ3RRbTidTvj18PZ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/user-event": "^14.6.1", + "@vitest/mocker": "3.1.1", + "@vitest/utils": "3.1.1", + "magic-string": "^0.30.17", + "sirv": "^3.0.1", + "tinyrainbow": "^2.0.0", + "ws": "^8.18.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "3.1.1", + "webdriverio": "^7.0.0 || ^8.0.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/expect": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", + "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/mocker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", + "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/pretty-format": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", + "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/runner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", + "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.1", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/snapshot": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", + "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/spy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", + "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/@vitest/utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.1", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "packages/mcp-server-supabase/node_modules/nanoid": { @@ -6568,6 +6991,116 @@ "node": "^18 || >=20" } }, + "packages/mcp-server-supabase/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "packages/mcp-server-supabase/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "packages/mcp-server-supabase/node_modules/vite-node": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", + "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/mcp-server-supabase/node_modules/vitest": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", + "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.1", + "@vitest/mocker": "3.1.1", + "@vitest/pretty-format": "^3.1.1", + "@vitest/runner": "3.1.1", + "@vitest/snapshot": "3.1.1", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.0", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.8.1", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.1", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.1", + "@vitest/ui": "3.1.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "packages/mcp-utils": { "name": "@supabase/mcp-utils", "version": "0.1.3", diff --git a/packages/mcp-server-supabase/package.json b/packages/mcp-server-supabase/package.json index 13bd493..a19d247 100644 --- a/packages/mcp-server-supabase/package.json +++ b/packages/mcp-server-supabase/package.json @@ -11,8 +11,9 @@ "build": "tsup --clean", "prepublishOnly": "npm run build", "test": "vitest", + "test:unit:node": "vitest --project unit:node", + "test:unit:browser": "vitest --project unit:browser", "test:e2e": "vitest --project e2e", - "test:unit": "vitest --project unit", "generate:management-api-types": "openapi-typescript https://api.supabase.com/api/v1-json -o ./src/management-api/types.ts" }, "files": [ @@ -29,7 +30,7 @@ } }, "dependencies": { - "@deno/eszip": "^0.84.0", + "@deno/eszip": "npm:@gregnr/eszip@^0.84.0-web.2", "@modelcontextprotocol/sdk": "^1.4.1", "@supabase/mcp-utils": "0.1.3", "common-tags": "^1.8.2", @@ -39,9 +40,12 @@ "devDependencies": { "@ai-sdk/anthropic": "^1.2.9", "@electric-sql/pglite": "^0.2.17", + "@playwright/test": "^1.52.0", + "@std/path": "npm:@jsr/std__path@^1.0.8", "@total-typescript/tsconfig": "^1.0.4", "@types/common-tags": "^1.8.4", "@types/node": "^22.8.6", + "@vitest/browser": "^3.1.1", "ai": "^4.3.4", "date-fns": "^4.1.0", "dotenv": "^16.5.0", @@ -53,6 +57,6 @@ "tsup": "^8.3.5", "tsx": "^4.19.2", "typescript": "^5.6.3", - "vitest": "^2.1.9" + "vitest": "^3.1.1" } } diff --git a/packages/mcp-server-supabase/src/eszip.test.ts b/packages/mcp-server-supabase/src/eszip/index.test.ts similarity index 51% rename from packages/mcp-server-supabase/src/eszip.test.ts rename to packages/mcp-server-supabase/src/eszip/index.test.ts index 3059704..f081142 100644 --- a/packages/mcp-server-supabase/src/eszip.test.ts +++ b/packages/mcp-server-supabase/src/eszip/index.test.ts @@ -1,7 +1,6 @@ import { codeBlock } from 'common-tags'; -import { open, type FileHandle } from 'node:fs/promises'; import { describe, expect, test } from 'vitest'; -import { bundleFiles, extractFiles } from './eszip.js'; +import { bundleFiles, extractFiles } from './index.js'; describe('eszip', () => { test('extract files', async () => { @@ -46,54 +45,3 @@ describe('eszip', () => { await expect(extractedHelloFile!.text()).resolves.toBe(helloContent); }); }); - -export class Source implements UnderlyingSource { - type = 'bytes' as const; - autoAllocateChunkSize = 1024; - - path: string | URL; - controller?: ReadableByteStreamController; - file?: FileHandle; - - constructor(path: string | URL) { - this.path = path; - } - - async start(controller: ReadableStreamController) { - if (!('byobRequest' in controller)) { - throw new Error('ReadableStreamController does not support byobRequest'); - } - - this.file = await open(this.path); - this.controller = controller; - } - - async pull() { - if (!this.controller || !this.file) { - throw new Error('ReadableStream has not been started'); - } - - if (!this.controller.byobRequest) { - throw new Error('ReadableStreamController does not support byobRequest'); - } - - const view = this.controller.byobRequest.view as NodeJS.ArrayBufferView; - - if (!view) { - throw new Error('ReadableStreamController does not have a view'); - } - - const { bytesRead } = await this.file.read({ - buffer: view, - offset: view.byteOffset, - length: view.byteLength, - }); - - if (bytesRead === 0) { - await this.file.close(); - this.controller.close(); - } - - this.controller.byobRequest.respond(view.byteLength); - } -} diff --git a/packages/mcp-server-supabase/src/eszip.ts b/packages/mcp-server-supabase/src/eszip/index.ts similarity index 94% rename from packages/mcp-server-supabase/src/eszip.ts rename to packages/mcp-server-supabase/src/eszip/index.ts index 29eae7b..fc248c0 100644 --- a/packages/mcp-server-supabase/src/eszip.ts +++ b/packages/mcp-server-supabase/src/eszip/index.ts @@ -1,6 +1,5 @@ import { build, Parser } from '@deno/eszip'; -import { join, relative } from 'node:path'; -import { fileURLToPath } from 'node:url'; +import { fromFileUrl, join, relative } from '@std/path/posix'; import { z } from 'zod'; const parser = await Parser.createInstance(); @@ -45,7 +44,7 @@ export async function extractFiles( const sourceMapString: string = await parser.getModuleSourceMap(specifier); - const filePath = relative(pathPrefix, fileURLToPath(specifier)); + const filePath = relative(pathPrefix, fromFileUrl(specifier)); const file = new File([source], filePath, { type: 'text/plain', @@ -75,7 +74,7 @@ export async function extractFiles( } /** - * Bundles files into an eszip archive. + * Bundles files into an eszip archive. Currently only used for testing. * * Optionally prefixes the file names with a given path. */ diff --git a/packages/mcp-server-supabase/src/regions.test.ts b/packages/mcp-server-supabase/src/regions.test.ts index 89bcbb3..86d0bc4 100644 --- a/packages/mcp-server-supabase/src/regions.test.ts +++ b/packages/mcp-server-supabase/src/regions.test.ts @@ -5,10 +5,7 @@ import { getCountryCode, getCountryCoordinates, getDistance, - TRACE_URL, } from './regions.js'; -import { http, HttpResponse } from 'msw'; -import { setupServer } from 'msw/node'; const COUNTRY_CODE = 'US'; @@ -98,18 +95,7 @@ describe('getClosestRegion', () => { }); }); -describe('getCountryCode', () => { - const handlers = [ - http.get(TRACE_URL, () => { - return HttpResponse.text( - `fl=123abc\nvisit_scheme=https\nloc=${COUNTRY_CODE}\ntls=TLSv1.3\nhttp=http/2` - ); - }), - ]; - - const server = setupServer(...handlers); - server.listen({ onUnhandledRequest: 'error' }); - +describe('getCountryCode', async () => { it('should return the correct country code for a given location', async () => { const code = await getCountryCode(); expect(code).toEqual(COUNTRY_CODE); diff --git a/packages/mcp-server-supabase/src/server.test.ts b/packages/mcp-server-supabase/src/server.test.ts index a0bf458..609413d 100644 --- a/packages/mcp-server-supabase/src/server.test.ts +++ b/packages/mcp-server-supabase/src/server.test.ts @@ -5,8 +5,7 @@ import { } from '@modelcontextprotocol/sdk/types.js'; import { StreamTransport } from '@supabase/mcp-utils'; import { codeBlock } from 'common-tags'; -import { setupServer } from 'msw/node'; -import { beforeEach, describe, expect, test } from 'vitest'; +import { describe, expect, test } from 'vitest'; import { ACCESS_TOKEN, API_URL, @@ -15,23 +14,10 @@ import { createProject, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, - mockBranches, - mockManagementApi, - mockOrgs, - mockProjects, } from '../test/mocks.js'; import { BRANCH_COST_HOURLY, PROJECT_COST_MONTHLY } from './pricing.js'; import { createSupabaseMcpServer } from './server.js'; -beforeEach(async () => { - mockOrgs.clear(); - mockProjects.clear(); - mockBranches.clear(); - - const server = setupServer(...mockManagementApi); - server.listen({ onUnhandledRequest: 'error' }); -}); - type SetupOptions = { accessToken?: string; readOnly?: boolean; @@ -235,7 +221,10 @@ describe('tools', () => { region: 'us-east-1', organization_id: paidOrg.id, }); - priorProject.status = 'INACTIVE'; + + await priorProject.ready.then(() => { + priorProject.status = 'INACTIVE'; + }); const result = await callTool({ name: 'get_cost', diff --git a/packages/mcp-server-supabase/src/server.ts b/packages/mcp-server-supabase/src/server.ts index db6c025..2482e79 100644 --- a/packages/mcp-server-supabase/src/server.ts +++ b/packages/mcp-server-supabase/src/server.ts @@ -1,5 +1,5 @@ import { createMcpServer, tool } from '@supabase/mcp-utils'; -import { fileURLToPath } from 'node:url'; +import { fromFileUrl } from '@std/path/posix'; import { z } from 'zod'; import packageJson from '../package.json' with { type: 'json' }; import { @@ -7,7 +7,7 @@ import { getDeploymentId, getPathPrefix, } from './edge-function.js'; -import { extractFiles } from './eszip.js'; +import { extractFiles } from './eszip/index.js'; import { getLogQuery } from './logs.js'; import { assertSuccess, @@ -91,11 +91,13 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) { name: 'supabase', version, onInitialize(clientInfo) { + const userAgent = `supabase-mcp/${version} (${clientInfo.name}/${clientInfo.version})`; managementApiClient = createManagementApiClient( managementApiUrl, options.platform.accessToken, { - 'User-Agent': `supabase-mcp/${version} (${clientInfo.name}/${clientInfo.version})`, + 'User-Agent': userAgent, + 'X-User-Agent': userAgent, } ); }, @@ -446,7 +448,7 @@ export function createSupabaseMcpServer(options: SupabaseMcpServerOptions) { // Strip away path prefix so that we don't confuse the LLM // TODO: do we need to do this for import_map_path too? const entrypoint_path = edgeFunction.entrypoint_path - ? fileURLToPath(edgeFunction.entrypoint_path).replace( + ? fromFileUrl(edgeFunction.entrypoint_path).replace( pathPrefix, '' ) diff --git a/packages/mcp-server-supabase/test/llm.e2e.ts b/packages/mcp-server-supabase/test/llm.e2e.ts index aeb5da5..ea91a4b 100644 --- a/packages/mcp-server-supabase/test/llm.e2e.ts +++ b/packages/mcp-server-supabase/test/llm.e2e.ts @@ -1,4 +1,4 @@ -/// +/// import { anthropic } from '@ai-sdk/anthropic'; import { StreamTransport } from '@supabase/mcp-utils'; @@ -11,7 +11,7 @@ import { import { codeBlock } from 'common-tags'; import { setupServer } from 'msw/node'; import { beforeEach, describe, expect, test } from 'vitest'; -import { extractFiles } from '../src/eszip.js'; +import { extractFiles } from '../src/eszip/index.js'; import { createSupabaseMcpServer } from '../src/index.js'; import { ACCESS_TOKEN, diff --git a/packages/mcp-server-supabase/test/mocks.ts b/packages/mcp-server-supabase/test/mocks.ts index 35056fa..7476fee 100644 --- a/packages/mcp-server-supabase/test/mocks.ts +++ b/packages/mcp-server-supabase/test/mocks.ts @@ -2,12 +2,12 @@ import { PGlite, type PGliteInterface } from '@electric-sql/pglite'; import { format } from 'date-fns'; import { http, HttpResponse } from 'msw'; import { customAlphabet } from 'nanoid'; -import { join } from 'node:path'; +import { join } from '@std/path/posix'; import { expect } from 'vitest'; import { z } from 'zod'; import packageJson from '../package.json' with { type: 'json' }; import { getDeploymentId, getPathPrefix } from '../src/edge-function.js'; -import { bundleFiles } from '../src/eszip.js'; +import { bundleFiles } from '../src/eszip/index.js'; import type { components } from '../src/management-api/types.js'; import { TRACE_URL } from '../src/regions.js'; @@ -61,7 +61,8 @@ export const mockManagementApi = [ * Check user agent */ http.all(`${API_URL}/*`, ({ request }) => { - const userAgent = request.headers.get('user-agent'); + const userAgent = + request.headers.get('user-agent') ?? request.headers.get('x-user-agent'); expect(userAgent).toBe( `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION} (${MCP_CLIENT_NAME}/${MCP_CLIENT_VERSION})` ); @@ -207,21 +208,28 @@ export const mockManagementApi = [ RESET ROLE; `; - const statementResults = await db.exec(wrappedQuery); + try { + const statementResults = await db.exec(wrappedQuery); - // Remove last result, which is for the "RESET ROLE" statement - statementResults.pop(); + // Remove last result, which is for the "RESET ROLE" statement + statementResults.pop(); - const lastStatementResults = statementResults.at(-1); + const lastStatementResults = statementResults.at(-1); - if (!lastStatementResults) { - return HttpResponse.json( - { message: 'Failed to execute query' }, - { status: 500 } - ); - } + if (!lastStatementResults) { + return HttpResponse.json( + { message: 'Failed to execute query' }, + { status: 500 } + ); + } - return HttpResponse.json(lastStatementResults.rows); + return HttpResponse.json(lastStatementResults.rows); + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + return HttpResponse.json({ message: error.message }, { status: 400 }); + } } ), @@ -365,22 +373,31 @@ export const mockManagementApi = [ } const { db, migrations } = project; const { name, query } = await request.json(); - const [results] = await db.exec(query); - if (!results) { - return HttpResponse.json( - { message: 'Failed to execute query' }, - { status: 500 } - ); - } + try { + const [results] = await db.exec(query); - migrations.push({ - version: format(new Date(), 'yyyyMMddHHmmss'), - name, - query, - }); + if (!results) { + return HttpResponse.json( + { message: 'Failed to execute query' }, + { status: 500 } + ); + } + + migrations.push({ + version: format(new Date(), 'yyyyMMddHHmmss'), + name, + query, + }); + + return HttpResponse.json(results.rows); + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } - return HttpResponse.json(results.rows); + return HttpResponse.json({ message: error.message }, { status: 400 }); + } } ), @@ -660,14 +677,7 @@ export async function createOrganization(options: MockOrganizationOptions) { export async function createProject(options: MockProjectOptions) { const project = new MockProject(options); - mockProjects.set(project.id, project); - - // Change the project status to ACTIVE_HEALTHY after a delay - setTimeout(async () => { - project.status = 'ACTIVE_HEALTHY'; - }, 0); - return project; } @@ -686,6 +696,8 @@ export async function createBranch(options: { organization_id: parentProject.organization_id, }); + mockProjects.set(project.id, project); + const branch = new MockBranch({ name: options.name, project_ref: project.id, @@ -693,20 +705,31 @@ export async function createBranch(options: { is_default: false, }); - mockProjects.set(project.id, project); mockBranches.set(branch.id, branch); - project.migrations = [...parentProject.migrations]; - // Run migrations on the new branch in the background setTimeout(async () => { try { + branch.status = 'RUNNING_MIGRATIONS'; await project.applyMigrations(); branch.status = 'MIGRATIONS_PASSED'; } catch (error) { branch.status = 'MIGRATIONS_FAILED'; console.error('Migration error:', error); } + + try { + for (const edgeFunction of project.edge_functions.values()) { + await project.deployEdgeFunction({ + name: edgeFunction.name, + entrypoint_path: edgeFunction.entrypoint_path, + }); + } + branch.status = 'FUNCTIONS_DEPLOYED'; + } catch (error) { + branch.status = 'FUNCTIONS_FAILED'; + console.error('Edge function deployment error:', error); + } }, 0); return branch; @@ -843,6 +866,8 @@ export class MockProject { migrations: Migration[] = []; edge_functions = new Map(); + ready: Promise; + #db?: PGliteInterface; // Lazy load the database connection @@ -886,6 +911,14 @@ export class MockProject { postgres_engine: '15', release_channel: 'ga', }; + + this.ready = new Promise((resolve) => { + // Change the project status to ACTIVE_HEALTHY after a delay + setTimeout(async () => { + this.status = 'ACTIVE_HEALTHY'; + resolve(); + }, 0); + }); } async applyMigrations() { diff --git a/packages/mcp-server-supabase/test/plugins/text-loader.ts b/packages/mcp-server-supabase/test/plugins/text-loader.ts new file mode 100644 index 0000000..d3b7b5b --- /dev/null +++ b/packages/mcp-server-supabase/test/plugins/text-loader.ts @@ -0,0 +1,15 @@ +import { readFile } from 'fs/promises'; +import { Plugin } from 'vite'; + +export function textLoaderPlugin(extension: string): Plugin { + return { + name: 'text-loader', + async transform(code, id) { + if (id.endsWith(extension)) { + const textContent = await readFile(id, 'utf8'); + return `export default ${JSON.stringify(textContent)};`; + } + return code; + }, + }; +} diff --git a/packages/mcp-server-supabase/test/setup/browser.ts b/packages/mcp-server-supabase/test/setup/browser.ts new file mode 100644 index 0000000..5c3d6c5 --- /dev/null +++ b/packages/mcp-server-supabase/test/setup/browser.ts @@ -0,0 +1,25 @@ +import { setupWorker } from 'msw/browser'; +import { afterEach, beforeEach, inject } from 'vitest'; +import { + mockBranches, + mockManagementApi, + mockOrgs, + mockProjects, +} from '../mocks.js'; + +const worker = setupWorker(...mockManagementApi); + +beforeEach(async () => { + mockOrgs.clear(); + mockProjects.clear(); + mockBranches.clear(); + + await worker.start({ + onUnhandledRequest: inject('msw-on-unhandled-request'), + quiet: true, + }); +}); + +afterEach(async () => { + worker.stop(); +}); diff --git a/packages/mcp-server-supabase/vitest.setup.ts b/packages/mcp-server-supabase/test/setup/env.ts similarity index 84% rename from packages/mcp-server-supabase/vitest.setup.ts rename to packages/mcp-server-supabase/test/setup/env.ts index d4919ad..a51190c 100644 --- a/packages/mcp-server-supabase/vitest.setup.ts +++ b/packages/mcp-server-supabase/test/setup/env.ts @@ -1,6 +1,5 @@ import { config } from 'dotenv'; import { statSync } from 'fs'; -import './test/extensions.js'; if (!process.env.CI) { const envPath = '.env.local'; diff --git a/packages/mcp-server-supabase/test/extensions.d.ts b/packages/mcp-server-supabase/test/setup/extensions.d.ts similarity index 100% rename from packages/mcp-server-supabase/test/extensions.d.ts rename to packages/mcp-server-supabase/test/setup/extensions.d.ts diff --git a/packages/mcp-server-supabase/test/extensions.ts b/packages/mcp-server-supabase/test/setup/extensions.ts similarity index 100% rename from packages/mcp-server-supabase/test/extensions.ts rename to packages/mcp-server-supabase/test/setup/extensions.ts diff --git a/packages/mcp-server-supabase/test/setup/node.ts b/packages/mcp-server-supabase/test/setup/node.ts new file mode 100644 index 0000000..45bafde --- /dev/null +++ b/packages/mcp-server-supabase/test/setup/node.ts @@ -0,0 +1,24 @@ +import { setupServer } from 'msw/node'; +import { afterEach, beforeEach, inject } from 'vitest'; +import { + mockBranches, + mockManagementApi, + mockOrgs, + mockProjects, +} from '../mocks.js'; + +const server = setupServer(...mockManagementApi); + +beforeEach(async () => { + mockOrgs.clear(); + mockProjects.clear(); + mockBranches.clear(); + + server.listen({ + onUnhandledRequest: inject('msw-on-unhandled-request'), + }); +}); + +afterEach(async () => { + server.close(); +}); diff --git a/packages/mcp-server-supabase/test/setup/vitest.d.ts b/packages/mcp-server-supabase/test/setup/vitest.d.ts new file mode 100644 index 0000000..6b64aaa --- /dev/null +++ b/packages/mcp-server-supabase/test/setup/vitest.d.ts @@ -0,0 +1,7 @@ +import 'vitest'; + +declare module 'vitest' { + interface ProvidedContext { + 'msw-on-unhandled-request'?: 'error' | 'bypass' | 'warn'; + } +} diff --git a/packages/mcp-server-supabase/vitest.workspace.ts b/packages/mcp-server-supabase/vitest.workspace.ts index 7981c75..efae6ec 100644 --- a/packages/mcp-server-supabase/vitest.workspace.ts +++ b/packages/mcp-server-supabase/vitest.workspace.ts @@ -1,37 +1,66 @@ -import { readFile } from 'fs/promises'; -import { Plugin } from 'vite'; import { defineWorkspace } from 'vitest/config'; - -function sqlLoaderPlugin(): Plugin { - return { - name: 'sql-loader', - async transform(code, id) { - if (id.endsWith('.sql')) { - const textContent = await readFile(id, 'utf8'); - return `export default ${JSON.stringify(textContent)};`; - } - return code; - }, - }; -} +import { textLoaderPlugin } from './test/plugins/text-loader.ts'; export default defineWorkspace([ { - plugins: [sqlLoaderPlugin()], + plugins: [textLoaderPlugin('.sql')], test: { - name: 'unit', + name: 'unit:node', include: ['src/**/*.{test,spec}.ts'], - setupFiles: ['./vitest.setup.ts'], + setupFiles: ['./test/setup/node.ts'], testTimeout: 30_000, // PGlite can take a while to initialize + provide: { + 'msw-on-unhandled-request': 'error', + }, + }, + optimizeDeps: { + exclude: ['@deno/eszip', '@electric-sql/pglite'], }, }, { - plugins: [sqlLoaderPlugin()], + plugins: [textLoaderPlugin('.sql')], + test: { + name: 'unit:browser', + include: ['src/**/*.{test,spec}.ts'], + setupFiles: ['./test/setup/browser.ts'], + testTimeout: 30_000, // PGlite can take a while to initialize + provide: { + 'msw-on-unhandled-request': 'error', + }, + browser: { + enabled: true, + provider: 'playwright', + headless: true, + screenshotFailures: false, + instances: [ + { browser: 'chromium' }, + { browser: 'firefox' }, + { browser: 'webkit' }, + ], + }, + }, + optimizeDeps: { + exclude: ['@deno/eszip', '@electric-sql/pglite'], + }, + }, + { + plugins: [textLoaderPlugin('.sql')], test: { name: 'e2e', include: ['test/**/*.e2e.ts'], - setupFiles: ['./vitest.setup.ts'], + setupFiles: [ + './test/setup/node.ts', + './test/setup/env.ts', + './test/setup/extensions.ts', + ], testTimeout: 60_000, + provide: { + // e2e tests need to make real API requests to an LLM, so bypass unhandled requests + 'msw-on-unhandled-request': 'bypass', + }, + }, + optimizeDeps: { + exclude: ['@deno/eszip', '@electric-sql/pglite'], }, }, ]); From 0e17e7ec4aa864b0bd7e4aa5f616d862ea297c71 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Mon, 21 Apr 2025 11:48:35 -0600 Subject: [PATCH 2/2] feat: vercel edge runtime support --- package-lock.json | 24 +++++++++++++++++++ packages/mcp-server-supabase/package.json | 2 ++ .../test/setup/vercel-edge.ts | 4 ++++ .../mcp-server-supabase/vitest.workspace.ts | 18 ++++++++++++++ 4 files changed, 48 insertions(+) create mode 100644 packages/mcp-server-supabase/test/setup/vercel-edge.ts diff --git a/package-lock.json b/package-lock.json index 7a31743..c6623e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -214,6 +214,29 @@ "integrity": "sha512-RB0/+4Z6NiLvRT+k/iHlLbreN0tTOELqxTgeGigSKE8zqCh0z4jtYiWE5mnsRx+WYMAcvGKRVEcbHHzfRv6IvQ==", "license": "MIT" }, + "node_modules/@edge-runtime/primitives": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-6.0.0.tgz", + "integrity": "sha512-FqoxaBT+prPBHBwE1WXS1ocnu/VLTQyZ6NMUBAdbP7N2hsFTTxMC/jMu2D/8GAlMQfxeuppcPuCUk/HO3fpIvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@edge-runtime/vm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-5.0.0.tgz", + "integrity": "sha512-NKBGBSIKUG584qrS1tyxVpX/AKJKQw5HgjYEnPLC0QsTw79JrGn+qUr8CXFb955Iy7GUdiiUv1rJ6JBGvaKb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@edge-runtime/primitives": "6.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@electric-sql/pglite": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.17.tgz", @@ -6802,6 +6825,7 @@ }, "devDependencies": { "@ai-sdk/anthropic": "^1.2.9", + "@edge-runtime/vm": "^5.0.0", "@electric-sql/pglite": "^0.2.17", "@playwright/test": "^1.52.0", "@std/path": "npm:@jsr/std__path@^1.0.8", diff --git a/packages/mcp-server-supabase/package.json b/packages/mcp-server-supabase/package.json index a19d247..72b6975 100644 --- a/packages/mcp-server-supabase/package.json +++ b/packages/mcp-server-supabase/package.json @@ -12,6 +12,7 @@ "prepublishOnly": "npm run build", "test": "vitest", "test:unit:node": "vitest --project unit:node", + "test:unit:vercel-edge": "vitest --project unit:vercel-edge", "test:unit:browser": "vitest --project unit:browser", "test:e2e": "vitest --project e2e", "generate:management-api-types": "openapi-typescript https://api.supabase.com/api/v1-json -o ./src/management-api/types.ts" @@ -39,6 +40,7 @@ }, "devDependencies": { "@ai-sdk/anthropic": "^1.2.9", + "@edge-runtime/vm": "^5.0.0", "@electric-sql/pglite": "^0.2.17", "@playwright/test": "^1.52.0", "@std/path": "npm:@jsr/std__path@^1.0.8", diff --git a/packages/mcp-server-supabase/test/setup/vercel-edge.ts b/packages/mcp-server-supabase/test/setup/vercel-edge.ts new file mode 100644 index 0000000..3b7d019 --- /dev/null +++ b/packages/mcp-server-supabase/test/setup/vercel-edge.ts @@ -0,0 +1,4 @@ +// Shim to get PGlite to work in Vercel Edge Runtime +window.location = { + pathname: '/', +} as string & Location; diff --git a/packages/mcp-server-supabase/vitest.workspace.ts b/packages/mcp-server-supabase/vitest.workspace.ts index efae6ec..86c7588 100644 --- a/packages/mcp-server-supabase/vitest.workspace.ts +++ b/packages/mcp-server-supabase/vitest.workspace.ts @@ -6,6 +6,7 @@ export default defineWorkspace([ plugins: [textLoaderPlugin('.sql')], test: { name: 'unit:node', + environment: 'node', include: ['src/**/*.{test,spec}.ts'], setupFiles: ['./test/setup/node.ts'], testTimeout: 30_000, // PGlite can take a while to initialize @@ -17,6 +18,22 @@ export default defineWorkspace([ exclude: ['@deno/eszip', '@electric-sql/pglite'], }, }, + { + plugins: [textLoaderPlugin('.sql')], + test: { + name: 'unit:vercel-edge', + environment: 'edge-runtime', + include: ['src/**/*.{test,spec}.ts'], + setupFiles: ['./test/setup/node.ts', './test/setup/vercel-edge.ts'], + testTimeout: 30_000, // PGlite can take a while to initialize + provide: { + 'msw-on-unhandled-request': 'error', + }, + }, + optimizeDeps: { + exclude: ['@deno/eszip', '@electric-sql/pglite'], + }, + }, { plugins: [textLoaderPlugin('.sql')], test: { @@ -47,6 +64,7 @@ export default defineWorkspace([ plugins: [textLoaderPlugin('.sql')], test: { name: 'e2e', + environment: 'node', include: ['test/**/*.e2e.ts'], setupFiles: [ './test/setup/node.ts',