From f8183479e7cbce9faa2ea7bff69551952bcdcc0a Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 14 Apr 2026 18:44:28 +0900 Subject: [PATCH] chore(studio): switch to type: module and convert configs to ESM/TS (#44839) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch studio's package.json to `"type": "module"` so the package runs as native ESM. This aligns the runtime module system with what we actually write (`import`/`export`), improves tree-shaking, and reduces friction with ESM-only dependencies. **Changed:** - `next.config.js` → `next.config.ts` – ESM imports/exports, proper TS types, fixed type narrowing on redirect `has` and `basePath` fields - `csp.js` → `csp.ts` – `module.exports.getCSP` → named `export function` - `tailwind.config.js` → `tailwind.config.ts` – ESM imports - `postcss.config.js` – `module.exports` → `export default` (stays `.js` since PostCSS doesn't support TS configs) **Removed:** - Unused `path` import in next config - Deprecated Sentry `hideSourceMaps` option (default behavior in Sentry v10) **Added:** - Type declaration for `config/tailwind.config` CJS package ## To test - A general smoke test of studio should suffice ## Summary by CodeRabbit * **Refactor** * Modernized the Studio package to ES module style and improved TypeScript typings and config declarations to reduce build/runtime issues. * Updated styling and post-processing configuration format for more consistent tooling behavior. * **Chores** * Updated code ownership entries to reflect migrated/renamed configuration files. --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- .../ui/AIAssistantPanel/MessageMarkdown.tsx | 2 +- apps/studio/components/ui/AIEditor/index.tsx | 2 +- apps/studio/{csp.js => csp.ts} | 2 +- ...gment.js => require-safe-sql-fragment.cjs} | 0 apps/studio/eslint.type-checks.config.cjs | 2 +- apps/studio/instrumentation-client.ts | 2 +- .../studio/{next.config.js => next.config.ts} | 80 +++++++++---------- apps/studio/package.json | 1 + apps/studio/postcss.config.js | 5 +- ...{tailwind.config.js => tailwind.config.ts} | 9 ++- apps/studio/types/config.d.ts | 6 ++ 12 files changed, 61 insertions(+), 52 deletions(-) rename apps/studio/{csp.js => csp.ts} (99%) rename apps/studio/eslint-rules/{require-safe-sql-fragment.js => require-safe-sql-fragment.cjs} (100%) rename apps/studio/{next.config.js => next.config.ts} (90%) rename apps/studio/{tailwind.config.js => tailwind.config.ts} (94%) create mode 100644 apps/studio/types/config.d.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 32b4390a497..201d1b6dc87 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -14,7 +14,7 @@ /docker/ @supabase/cli @supabase/self-hosted -/apps/studio/csp.js @supabase/security +/apps/studio/csp.ts @supabase/security /apps/studio/components/interfaces/Billing/Payment @supabase/security /apps/studio/components/interfaces/Organization/Documents/ @supabase/security /apps/studio/pages/new/index.tsx @supabase/security diff --git a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx index b8370650bde..8376cc22684 100644 --- a/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/MessageMarkdown.tsx @@ -40,7 +40,7 @@ const Streamdown = dynamic( // Streamdown splits ordered lists with complex content (e.g. code blocks) into // separate
    elements. The `start` attribute preserves semantics for screen // readers, while `counterReset` is what actually fixes the visible numbering — -// the prose config (tailwind.config.js) uses a custom CSS counter named "item" +// the prose config (tailwind.config.ts) uses a custom CSS counter named "item" // with `listStyleType: 'none'`, so the `start` attribute alone has no visual effect. export const OrderedList = memo(({ children, start }: { children?: ReactNode; start?: number }) => (
      { diff --git a/apps/studio/csp.js b/apps/studio/csp.ts similarity index 99% rename from apps/studio/csp.js rename to apps/studio/csp.ts index 610cc22d161..c3dd416efc7 100644 --- a/apps/studio/csp.js +++ b/apps/studio/csp.ts @@ -80,7 +80,7 @@ const PUSHER_URL_WS = 'wss://*.pusher.com' const GOOGLE_MAPS_API_URL = 'https://maps.googleapis.com' -module.exports.getCSP = function getCSP() { +export function getCSP() { const DEFAULT_SRC_URLS = [ API_URL, SUPABASE_URL, diff --git a/apps/studio/eslint-rules/require-safe-sql-fragment.js b/apps/studio/eslint-rules/require-safe-sql-fragment.cjs similarity index 100% rename from apps/studio/eslint-rules/require-safe-sql-fragment.js rename to apps/studio/eslint-rules/require-safe-sql-fragment.cjs diff --git a/apps/studio/eslint.type-checks.config.cjs b/apps/studio/eslint.type-checks.config.cjs index e5bb7209a47..fe7a4570e2a 100644 --- a/apps/studio/eslint.type-checks.config.cjs +++ b/apps/studio/eslint.type-checks.config.cjs @@ -9,7 +9,7 @@ const { defineConfig } = require('eslint/config') const tsparser = require('@typescript-eslint/parser') -const requireSafeSqlFragment = require('./eslint-rules/require-safe-sql-fragment') +const requireSafeSqlFragment = require('./eslint-rules/require-safe-sql-fragment.cjs') const studioPlugin = { rules: { diff --git a/apps/studio/instrumentation-client.ts b/apps/studio/instrumentation-client.ts index 300a361c859..b9b26d37d26 100644 --- a/apps/studio/instrumentation-client.ts +++ b/apps/studio/instrumentation-client.ts @@ -105,7 +105,7 @@ Sentry.init({ // Tag errors whose stack trace only contains third-party frames (browser extensions, // injected scripts, etc.). This uses build-time code annotation via the applicationKey - // in next.config.js to reliably distinguish our code from third-party code. + // in next.config.ts to reliably distinguish our code from third-party code. // We use 'apply-tag' instead of 'drop' so that beforeSend can exempt error boundary // crashes — these may originate in third-party code but are caused by first-party bugs. return [ diff --git a/apps/studio/next.config.js b/apps/studio/next.config.ts similarity index 90% rename from apps/studio/next.config.js rename to apps/studio/next.config.ts index 7b865278390..513ca4c3128 100644 --- a/apps/studio/next.config.js +++ b/apps/studio/next.config.ts @@ -1,11 +1,14 @@ -const { withSentryConfig } = require('@sentry/nextjs') -const withBundleAnalyzer = require('@next/bundle-analyzer')({ +/* eslint-disable no-restricted-exports */ + +import bundleAnalyzer from '@next/bundle-analyzer' +import { withSentryConfig } from '@sentry/nextjs' +import type { NextConfig } from 'next' + +import { getCSP } from './csp' + +const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'true', }) -const { getCSP } = require('./csp') - -// Required for nextjs standalone build -const path = require('path') function getAssetPrefix() { // If not force enabled, but not production env, disable CDN @@ -23,13 +26,10 @@ function getAssetPrefix() { ? 'https://frontend-assets.supabase.green' : 'https://frontend-assets.supabase.com' - return `${SUPABASE_ASSETS_URL}/${process.env.SITE_NAME}/${process.env.VERCEL_GIT_COMMIT_SHA.substring(0, 12)}` + return `${SUPABASE_ASSETS_URL}/${process.env.SITE_NAME}/${process.env.VERCEL_GIT_COMMIT_SHA?.substring(0, 12) ?? 'unknown'}` } -/** - * @type {import('next').NextConfig} - */ -const nextConfig = { +const nextConfig: NextConfig = { basePath: process.env.NEXT_PUBLIC_BASE_PATH, assetPrefix: getAssetPrefix(), output: 'standalone', @@ -53,7 +53,7 @@ const nextConfig = { source: '/', has: [ { - type: 'query', + type: 'query' as const, key: 'next', value: 'new-project', }, @@ -468,7 +468,7 @@ const nextConfig = { { source: '/', destination: process.env.NEXT_PUBLIC_BASE_PATH, - basePath: false, + basePath: false as const, permanent: false, }, ] @@ -616,38 +616,34 @@ const nextConfig = { const platformConfig = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' ? withBundleAnalyzer(nextConfig) : nextConfig -module.exports = - process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && process.env.VERCEL === '1' - ? withSentryConfig(platformConfig, { - silent: true, +export default process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && process.env.VERCEL === '1' + ? withSentryConfig(platformConfig, { + silent: true, - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, - // Automatically annotate React components to show their full name in breadcrumbs and session replay - reactComponentAnnotation: { - enabled: true, - }, + // Automatically annotate React components to show their full name in breadcrumbs and session replay + reactComponentAnnotation: { + enabled: true, + }, - // Hides source maps from generated client bundles - hideSourceMaps: true, + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, + // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) + // See the following for more information: + // https://docs.sentry.io/product/crons/ + // https://vercel.com/docs/cron-jobs + automaticVercelMonitors: true, - // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.) - // See the following for more information: - // https://docs.sentry.io/product/crons/ - // https://vercel.com/docs/cron-jobs - automaticVercelMonitors: true, - - // Annotate bundles at build time so thirdPartyErrorFilterIntegration can - // distinguish our code from browser extensions / injected scripts at runtime. - unstable_sentryWebpackPluginOptions: { - applicationKey: 'supabase-studio', - }, - }) - : platformConfig + // Annotate bundles at build time so thirdPartyErrorFilterIntegration can + // distinguish our code from browser extensions / injected scripts at runtime. + unstable_sentryWebpackPluginOptions: { + applicationKey: 'supabase-studio', + }, + }) + : platformConfig diff --git a/apps/studio/package.json b/apps/studio/package.json index f2fadd709f4..d734050b20d 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -2,6 +2,7 @@ "name": "studio", "version": "0.0.9", "private": true, + "type": "module", "scripts": { "preinstall": "npx only-allow pnpm", "dev": "next dev -p ${STUDIO_PORT:-8082}", diff --git a/apps/studio/postcss.config.js b/apps/studio/postcss.config.js index 08a01d4d167..2259daa8a9d 100644 --- a/apps/studio/postcss.config.js +++ b/apps/studio/postcss.config.js @@ -1,4 +1,7 @@ -module.exports = { +/* eslint-disable no-restricted-exports */ +/* eslint-disable import/no-anonymous-default-export */ + +export default { plugins: { tailwindcss: {}, }, diff --git a/apps/studio/tailwind.config.js b/apps/studio/tailwind.config.ts similarity index 94% rename from apps/studio/tailwind.config.js rename to apps/studio/tailwind.config.ts index 8d57ee1f28b..b0f9e40fc1d 100644 --- a/apps/studio/tailwind.config.js +++ b/apps/studio/tailwind.config.ts @@ -1,6 +1,9 @@ -const config = require('config/tailwind.config') +/* eslint-disable no-restricted-exports */ -module.exports = config({ +import containerQueries from '@tailwindcss/container-queries' +import tailwindConfig from 'config/tailwind.config' + +export default tailwindConfig({ content: [ './pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', @@ -8,7 +11,7 @@ module.exports = config({ './../../packages/ui/src/**/*.{tsx,ts,js}', './../../packages/ui-patterns/src/**/*.{tsx,ts,js}', ], - plugins: [require('@tailwindcss/container-queries')], + plugins: [containerQueries], theme: { extend: { fontSize: { diff --git a/apps/studio/types/config.d.ts b/apps/studio/types/config.d.ts new file mode 100644 index 00000000000..c8ce1d21d56 --- /dev/null +++ b/apps/studio/types/config.d.ts @@ -0,0 +1,6 @@ +declare module 'config/tailwind.config' { + import type { Config } from 'tailwindcss' + const wrapper: (tailwindConfig: Partial) => Config + // eslint-disable-next-line no-restricted-exports + export default wrapper +}