diff --git a/backend/src/__tests__/auto-topup.test.ts b/backend/src/__tests__/auto-topup.test.ts index 1f238a19e..be6d767b9 100644 --- a/backend/src/__tests__/auto-topup.test.ts +++ b/backend/src/__tests__/auto-topup.test.ts @@ -32,7 +32,7 @@ describe('Auto Top-up System', () => { warn: () => {}, } - beforeAll(() => { + beforeAll(async () => { // Set up default mocks dbMock = mock(() => Promise.resolve({ @@ -45,7 +45,7 @@ describe('Auto Top-up System', () => { ) // Mock the database - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { query: { user: { @@ -61,7 +61,7 @@ describe('Auto Top-up System', () => { })) // Mock Stripe payment intent creation - mockModule('@codebuff/internal/util/stripe', () => ({ + await mockModule('@codebuff/internal/util/stripe', () => ({ stripeServer: { paymentIntents: { create: mock(() => diff --git a/backend/src/__tests__/usage-calculation.test.ts b/backend/src/__tests__/usage-calculation.test.ts index cff83b239..ab81b3afd 100644 --- a/backend/src/__tests__/usage-calculation.test.ts +++ b/backend/src/__tests__/usage-calculation.test.ts @@ -17,9 +17,9 @@ describe('Usage Calculation System', () => { warn: () => {}, } - beforeAll(() => { + beforeAll(async () => { // Mock the database module before importing the function - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { select: () => ({ from: () => ({ @@ -61,7 +61,7 @@ describe('Usage Calculation System', () => { ] // Mock the database module with the test data - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { select: () => ({ from: () => ({ @@ -98,7 +98,7 @@ describe('Usage Calculation System', () => { ] // Mock the database module with the test data - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { select: () => ({ from: () => ({ @@ -145,7 +145,7 @@ describe('Usage Calculation System', () => { ] // Mock the database module with the test data - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { select: () => ({ from: () => ({ @@ -201,7 +201,7 @@ describe('Usage Calculation System', () => { ] // Mock the database module with the test data - mockModule('@codebuff/internal/db', () => ({ + await mockModule('@codebuff/internal/db', () => ({ default: { select: () => ({ from: () => ({ diff --git a/backend/src/index.ts b/backend/src/index.ts index 6e824b567..7669cd357 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -2,7 +2,7 @@ import http from 'http' import { setupBigQuery } from '@codebuff/bigquery' import { flushAnalytics, initAnalytics } from '@codebuff/common/analytics' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/internal/env' import cors from 'cors' import express from 'express' diff --git a/backend/src/llm-apis/relace-api.ts b/backend/src/llm-apis/relace-api.ts index b2d7e37c5..7d8fe5c7f 100644 --- a/backend/src/llm-apis/relace-api.ts +++ b/backend/src/llm-apis/relace-api.ts @@ -1,5 +1,5 @@ import { countTokens } from '@codebuff/agent-runtime/util/token-counter' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/internal/env' import { saveMessage } from '../llm-apis/message-cost-tracker' diff --git a/backend/src/util/logger.ts b/backend/src/util/logger.ts index dd80142ba..d5929a92e 100644 --- a/backend/src/util/logger.ts +++ b/backend/src/util/logger.ts @@ -3,7 +3,7 @@ import path from 'path' import { format } from 'util' import { splitData } from '@codebuff/common/util/split-data' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/internal/env' import pino from 'pino' import { @@ -28,7 +28,7 @@ export const withLoggerContext = ( const debugDir = path.join(__dirname, '../../../debug') if ( env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' && - process.env.CODEBUFF_GITHUB_ACTIONS !== 'true' + process.env['CODEBUFF_GITHUB_ACTIONS'] !== 'true' ) { try { mkdirSync(debugDir, { recursive: true }) @@ -52,7 +52,7 @@ const pinoLogger = pino( timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`, }, env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' && - process.env.CODEBUFF_GITHUB_ACTIONS !== 'true' + process.env['CODEBUFF_GITHUB_ACTIONS'] !== 'true' ? pino.transport({ target: 'pino/file', options: { @@ -95,7 +95,7 @@ function splitAndLog( } export const logger: Record = - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' + env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' ? pinoLogger : (Object.fromEntries( loggingLevels.map((level) => { diff --git a/cli/src/__tests__/e2e/returning-user-auth.test.ts b/cli/src/__tests__/e2e/returning-user-auth.test.ts index 394f83538..f8709e1aa 100644 --- a/cli/src/__tests__/e2e/returning-user-auth.test.ts +++ b/cli/src/__tests__/e2e/returning-user-auth.test.ts @@ -1,3 +1,8 @@ +import fs from 'fs' +import os from 'os' +import path from 'path' + +import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants' import { describe, test, @@ -7,19 +12,16 @@ import { mock, spyOn, } from 'bun:test' -import fs from 'fs' -import os from 'os' -import path from 'path' +import { validateApiKey } from '../../hooks/use-auth-query' import { getAuthTokenDetails, saveUserCredentials, type User, } from '../../utils/auth' -import { validateApiKey } from '../../hooks/use-auth-query' -import type { Logger } from '@codebuff/common/types/contracts/logger' import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database' +import type { Logger } from '@codebuff/common/types/contracts/logger' const RETURNING_USER: User = { id: 'returning-user-456', @@ -45,14 +47,14 @@ describe('Returning User Authentication helpers', () => { tempConfigDir = fs.mkdtempSync( path.join(os.tmpdir(), 'manicode-returning-'), ) - originalEnv.CODEBUFF_API_KEY = process.env.CODEBUFF_API_KEY + originalEnv[API_KEY_ENV_VAR] = process.env[API_KEY_ENV_VAR] }) afterEach(() => { if (fs.existsSync(tempConfigDir)) { fs.rmSync(tempConfigDir, { recursive: true, force: true }) } - process.env.CODEBUFF_API_KEY = originalEnv.CODEBUFF_API_KEY + process.env[API_KEY_ENV_VAR] = originalEnv[API_KEY_ENV_VAR] mock.restore() }) @@ -81,7 +83,7 @@ describe('Returning User Authentication helpers', () => { path.join(tempConfigDir, 'credentials.json'), ) - process.env.CODEBUFF_API_KEY = 'env-token-123' + process.env[API_KEY_ENV_VAR] = 'env-token-123' const details = getAuthTokenDetails() expect(details.source).toBe('environment') diff --git a/cli/src/__tests__/integration/credentials-storage.test.ts b/cli/src/__tests__/integration/credentials-storage.test.ts index 6dc48a499..ff209e3cb 100644 --- a/cli/src/__tests__/integration/credentials-storage.test.ts +++ b/cli/src/__tests__/integration/credentials-storage.test.ts @@ -1,3 +1,11 @@ +import fs from 'fs' +import os from 'os' +import path from 'path' + +import { + clearMockedModules, + mockModule, +} from '@codebuff/common/testing/mock-modules' import { describe, test, @@ -7,16 +15,10 @@ import { mock, spyOn, } from 'bun:test' -import fs from 'fs' -import path from 'path' -import os from 'os' import * as authModule from '../../utils/auth' -import { - saveUserCredentials, - getUserCredentials, - logoutUser, -} from '../../utils/auth' +import { saveUserCredentials, getUserCredentials } from '../../utils/auth' + import type { User } from '../../utils/auth' /** @@ -51,7 +53,6 @@ describe('Credentials Storage Integration', () => { beforeEach(() => { // Create temporary config directory for tests tempConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'manicode-test-')) - originalEnv = process.env.NEXT_PUBLIC_CB_ENVIRONMENT // Mock getConfigDir to use temp directory spyOn(authModule, 'getConfigDir').mockReturnValue(tempConfigDir) @@ -66,8 +67,8 @@ describe('Credentials Storage Integration', () => { fs.rmSync(tempConfigDir, { recursive: true, force: true }) } - process.env.NEXT_PUBLIC_CB_ENVIRONMENT = originalEnv mock.restore() + clearMockedModules() }) describe('P0: File System Operations', () => { @@ -154,31 +155,48 @@ describe('Credentials Storage Integration', () => { expect(keys[0]).toBe('default') }) - test('should use manicode-dev directory in development environment', () => { + test('should use manicode-test directory in test environment', async () => { // Restore getConfigDir to use real implementation for this test mock.restore() - // Set environment to dev - process.env.NEXT_PUBLIC_CB_ENVIRONMENT = 'dev' + await mockModule('@codebuff/common/env', () => ({ + env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'test' }, + })) // Call real getConfigDir to verify it includes '-dev' const configDir = authModule.getConfigDir() - expect(configDir).toContain('manicode-dev') - expect(configDir).not.toContain('manicode/') - expect(configDir).not.toBe(path.join(os.homedir(), '.config', 'manicode')) + expect(configDir).toEqual( + path.join(os.homedir(), '.config', 'manicode-test'), + ) }) - test('should use manicode directory in production environment', () => { + test('should use manicode-dev directory in development environment', async () => { + // Restore getConfigDir to use real implementation for this test + mock.restore() + + await mockModule('@codebuff/common/env', () => ({ + env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'dev' }, + })) + + // Call real getConfigDir to verify it includes '-dev' + const configDir = authModule.getConfigDir() + expect(configDir).toEqual( + path.join(os.homedir(), '.config', 'manicode-dev'), + ) + }) + + test('should use manicode directory in production environment', async () => { // Restore getConfigDir to use real implementation mock.restore() // Set environment to prod (or unset it) - process.env.NEXT_PUBLIC_CB_ENVIRONMENT = 'prod' + await mockModule('@codebuff/common/env', () => ({ + env: { NEXT_PUBLIC_CB_ENVIRONMENT: 'prod' }, + })) // Call real getConfigDir to verify it doesn't include '-dev' const configDir = authModule.getConfigDir() - expect(configDir).toBe(path.join(os.homedir(), '.config', 'manicode')) - expect(configDir).not.toContain('manicode-dev') + expect(configDir).toEqual(path.join(os.homedir(), '.config', 'manicode')) }) test('should allow credentials to persist across simulated CLI restarts', () => { @@ -297,11 +315,6 @@ describe('Credentials Storage Integration', () => { // File should be writable by user expect((mode & 0o200) !== 0).toBe(true) - - // For security, ideally should not be world-readable, but we accept common permissions - // Common acceptable permissions: 0644 (rw-r--r--) or 0600 (rw-------) - const octalMode = (mode & 0o777).toString(8) - expect(['644', '600', '640']).toContain(octalMode) } else { // On Windows, just verify file exists and is accessible expect(fs.existsSync(credentialsPath)).toBe(true) diff --git a/cli/src/hooks/use-auth-query.ts b/cli/src/hooks/use-auth-query.ts index 038432a92..3ffb66eae 100644 --- a/cli/src/hooks/use-auth-query.ts +++ b/cli/src/hooks/use-auth-query.ts @@ -1,3 +1,4 @@ +import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants' import { getUserInfoFromApiKey as defaultGetUserInfoFromApiKey } from '@codebuff/sdk' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' @@ -9,8 +10,8 @@ import { } from '../utils/auth' import { logger as defaultLogger } from '../utils/logger' -import type { Logger } from '@codebuff/common/types/contracts/logger' import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database' +import type { Logger } from '@codebuff/common/types/contracts/logger' // Query keys for type-safe cache management export const authQueryKeys = { @@ -95,7 +96,7 @@ export function useAuthQuery(deps: UseAuthQueryDeps = {}) { const userCredentials = getUserCredentials() const apiKey = - userCredentials?.authToken || process.env.CODEBUFF_API_KEY || '' + userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || '' return useQuery({ queryKey: authQueryKeys.validation(apiKey), diff --git a/cli/src/index.tsx b/cli/src/index.tsx index 3b142d4d8..4ffd14b35 100644 --- a/cli/src/index.tsx +++ b/cli/src/index.tsx @@ -2,14 +2,15 @@ import './polyfills/bun-strip-ansi' import { createRequire } from 'module' +import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants' import { render } from '@opentui/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { Command } from 'commander' import React from 'react' import { App } from './chat' -import { getLoadedAgentsData } from './utils/local-agent-registry' import { getUserCredentials } from './utils/auth' +import { getLoadedAgentsData } from './utils/local-agent-registry' import { clearLogFile } from './utils/logger' const require = createRequire(import.meta.url) @@ -101,7 +102,7 @@ const AppWithAsyncAuth = () => { // Check authentication asynchronously const userCredentials = getUserCredentials() const apiKey = - userCredentials?.authToken || process.env.CODEBUFF_API_KEY || '' + userCredentials?.authToken || process.env[API_KEY_ENV_VAR] || '' if (!apiKey) { // No credentials, require auth diff --git a/cli/src/login/constants.ts b/cli/src/login/constants.ts index d5b061078..1bc6f097e 100644 --- a/cli/src/login/constants.ts +++ b/cli/src/login/constants.ts @@ -1,6 +1,7 @@ +import { env } from '@codebuff/common/env' + // Get the website URL from environment or use default -export const WEBSITE_URL = - process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com' +export const WEBSITE_URL = env.NEXT_PUBLIC_CODEBUFF_APP_URL // Codebuff ASCII Logo - compact version for 80-width terminals export const LOGO = ` diff --git a/cli/src/utils/analytics.ts b/cli/src/utils/analytics.ts index d7ec42bd2..c7294ad97 100644 --- a/cli/src/utils/analytics.ts +++ b/cli/src/utils/analytics.ts @@ -1,3 +1,4 @@ +import { env } from '@codebuff/common/env' import { PostHog } from 'posthog-node' import type { AnalyticsEvent } from '@codebuff/common/constants/analytics-events' @@ -14,19 +15,15 @@ let client: PostHog | undefined export let identified: boolean = false export function initAnalytics() { - if ( - !process.env.NEXT_PUBLIC_POSTHOG_API_KEY || - !process.env.NEXT_PUBLIC_POSTHOG_HOST_URL - ) { + if (!env.NEXT_PUBLIC_POSTHOG_API_KEY || !env.NEXT_PUBLIC_POSTHOG_HOST_URL) { throw new Error( 'NEXT_PUBLIC_POSTHOG_API_KEY or NEXT_PUBLIC_POSTHOG_HOST_URL is not set', ) } - client = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_API_KEY, { - host: process.env.NEXT_PUBLIC_POSTHOG_HOST_URL, - enableExceptionAutocapture: - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod', + client = new PostHog(env.NEXT_PUBLIC_POSTHOG_API_KEY, { + host: env.NEXT_PUBLIC_POSTHOG_HOST_URL, + enableExceptionAutocapture: env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod', }) } @@ -51,13 +48,13 @@ export function trackEvent( return } if (!client) { - if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod') { + if (env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod') { throw new Error('Analytics client not initialized') } return } - if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { + if (env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { if (DEBUG_DEV_EVENTS) { console.log('Analytics event sent', { event, @@ -82,7 +79,7 @@ export function identifyUser(userId: string, properties?: Record) { throw new Error('Analytics client not initialized') } - if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { + if (env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { if (DEBUG_DEV_EVENTS) { console.log('Identify event sent', { userId, diff --git a/cli/src/utils/auth.ts b/cli/src/utils/auth.ts index a36c452a8..cd9f154c6 100644 --- a/cli/src/utils/auth.ts +++ b/cli/src/utils/auth.ts @@ -2,6 +2,7 @@ import fs from 'fs' import os from 'os' import path from 'path' +import { env } from '@codebuff/common/env' import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants' import { WEBSITE_URL } from '@codebuff/sdk' import { z } from 'zod' @@ -34,9 +35,8 @@ export const getConfigDir = (): string => { '.config', 'manicode' + // on a development stack? - (process.env.NEXT_PUBLIC_CB_ENVIRONMENT && - process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod' - ? `-${process.env.NEXT_PUBLIC_CB_ENVIRONMENT}` + (env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod' + ? `-${env.NEXT_PUBLIC_CB_ENVIRONMENT}` : ''), ) } diff --git a/cli/src/utils/logger.ts b/cli/src/utils/logger.ts index d8cfa565e..8b5754426 100644 --- a/cli/src/utils/logger.ts +++ b/cli/src/utils/logger.ts @@ -3,10 +3,11 @@ import path, { dirname } from 'path' import { format as stringFormat } from 'util' import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events' +import { env } from '@codebuff/common/env' import { pino } from 'pino' -import { getCurrentChatDir, getProjectRoot } from '../project-files' import { flushAnalytics, logError, trackEvent } from './analytics' +import { getCurrentChatDir, getProjectRoot } from '../project-files' export interface LoggerContext { userId?: string @@ -95,12 +96,12 @@ function sendAnalyticsAndLog( ): void { if ( process.env.CODEBUFF_GITHUB_ACTIONS !== 'true' && - process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'test' + env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'test' ) { const projectRoot = getProjectRoot() || process.cwd() const logTarget = - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' + env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' ? path.join(projectRoot, 'debug', 'cli.log') : (() => { try { @@ -128,7 +129,7 @@ function sendAnalyticsAndLog( logAsErrorIfNeeded(toTrack) logOrStore: if ( - process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'dev' && + env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'dev' && normalizedData && typeof normalizedData === 'object' && 'eventId' in normalizedData && diff --git a/cli/src/utils/theme-system.ts b/cli/src/utils/theme-system.ts index bf7a8b829..2819b938b 100644 --- a/cli/src/utils/theme-system.ts +++ b/cli/src/utils/theme-system.ts @@ -52,7 +52,7 @@ const VS_CODE_FAMILY_ENV_KEYS = [ 'VSCODE_NLS_CONFIG', 'CURSOR_SESSION_ID', 'CURSOR', -] +] as const const VS_CODE_PRODUCT_DIRS = [ 'Code', @@ -61,7 +61,7 @@ const VS_CODE_PRODUCT_DIRS = [ 'VSCodium', 'VSCodium - Insiders', 'Cursor', -] +] as const const JETBRAINS_ENV_KEYS = [ 'JB_PRODUCT_CODE', @@ -70,7 +70,7 @@ const JETBRAINS_ENV_KEYS = [ 'IDEA_INITIAL_DIRECTORY', 'IDE_CONFIG_DIR', 'JB_IDE_CONFIG_DIR', -] +] as const const normalizeThemeName = (themeName: string): string => themeName.trim().toLowerCase() @@ -517,7 +517,7 @@ type ThemeOverrideConfig = Partial> & { const CHAT_THEME_ENV_KEYS = [ 'OPEN_TUI_CHAT_THEME_OVERRIDES', 'OPENTUI_CHAT_THEME_OVERRIDES', -] +] as const const mergeMarkdownOverrides = ( base: MarkdownThemeOverrides | undefined, diff --git a/common/src/analytics.ts b/common/src/analytics.ts index e36303082..50b75d107 100644 --- a/common/src/analytics.ts +++ b/common/src/analytics.ts @@ -1,4 +1,4 @@ -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import { PostHog } from 'posthog-node' import type { AnalyticsEvent } from './constants/analytics-events' diff --git a/common/src/env-schema.ts b/common/src/env-schema.ts new file mode 100644 index 000000000..e3a1d42ca --- /dev/null +++ b/common/src/env-schema.ts @@ -0,0 +1,42 @@ +import z from 'zod/v4' + +export const CLIENT_ENV_PREFIX = 'NEXT_PUBLIC_' + +export const clientEnvSchema = z.object({ + NEXT_PUBLIC_CB_ENVIRONMENT: z.enum(['dev', 'test', 'prod']), + NEXT_PUBLIC_CODEBUFF_APP_URL: z.url().min(1), + NEXT_PUBLIC_CODEBUFF_BACKEND_URL: z.string().min(1), + NEXT_PUBLIC_SUPPORT_EMAIL: z.email().min(1), + NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional().default(''), + NEXT_PUBLIC_POSTHOG_HOST_URL: z.url().optional(), + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), + NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL: z.url().min(1), + NEXT_PUBLIC_LINKEDIN_PARTNER_ID: z.string().optional(), + NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: z.string().optional(), + NEXT_PUBLIC_WEB_PORT: z.coerce.number().min(1000).optional().default(3000), +} satisfies Record<`${typeof CLIENT_ENV_PREFIX}${string}`, any>) +export const clientEnvVars = clientEnvSchema.keyof().options +export type ClientEnvVar = (typeof clientEnvVars)[number] +export type ClientInput = { + [K in (typeof clientEnvVars)[number]]: string | undefined +} +export type ClientEnv = z.infer + +// Bun will inject all these values, so we need to reference them individually (no for-loops) +export const clientProcessEnv: ClientInput = { + NEXT_PUBLIC_CB_ENVIRONMENT: process.env.NEXT_PUBLIC_CB_ENVIRONMENT, + NEXT_PUBLIC_CODEBUFF_APP_URL: process.env.NEXT_PUBLIC_CODEBUFF_APP_URL, + NEXT_PUBLIC_CODEBUFF_BACKEND_URL: + process.env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL, + NEXT_PUBLIC_SUPPORT_EMAIL: process.env.NEXT_PUBLIC_SUPPORT_EMAIL, + NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY, + NEXT_PUBLIC_POSTHOG_HOST_URL: process.env.NEXT_PUBLIC_POSTHOG_HOST_URL, + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: + process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, + NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL: + process.env.NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL, + NEXT_PUBLIC_LINKEDIN_PARTNER_ID: process.env.NEXT_PUBLIC_LINKEDIN_PARTNER_ID, + NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: + process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID, + NEXT_PUBLIC_WEB_PORT: process.env.NEXT_PUBLIC_WEB_PORT, +} diff --git a/common/src/env.ts b/common/src/env.ts new file mode 100644 index 000000000..22d9b3b8e --- /dev/null +++ b/common/src/env.ts @@ -0,0 +1,8 @@ +import { clientEnvSchema, clientProcessEnv } from './env-schema' + +// Only log environment in non-production +if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { + console.log('Using environment:', process.env.NEXT_PUBLIC_CB_ENVIRONMENT) +} + +export const env = clientEnvSchema.parse(clientProcessEnv) diff --git a/common/src/testing/mock-modules.ts b/common/src/testing/mock-modules.ts index e7a6c643e..031be2fa4 100644 --- a/common/src/testing/mock-modules.ts +++ b/common/src/testing/mock-modules.ts @@ -13,10 +13,10 @@ let mockModuleCache: Record = {} * @param renderMocks - function to generate mocks (by their named or default exports) * @returns an object */ -export const mockModule = async ( +export async function mockModule( modulePath: string, renderMocks: () => Record, -): Promise => { +): Promise { let original = originalModuleCache[modulePath] ?? { ...(await import(modulePath)), } diff --git a/common/src/util/referral.ts b/common/src/util/referral.ts index d75491964..940ba4a10 100644 --- a/common/src/util/referral.ts +++ b/common/src/util/referral.ts @@ -1,2 +1,4 @@ +import { env } from '@codebuff/common/env' + export const getReferralLink = (referralCode: string): string => - `${process.env.NEXT_PUBLIC_CODEBUFF_APP_URL}/referrals/${referralCode}` + `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/referrals/${referralCode}` diff --git a/evals/git-evals/run-single-eval-process.ts b/evals/git-evals/run-single-eval-process.ts index 3371458d7..3c6349119 100644 --- a/evals/git-evals/run-single-eval-process.ts +++ b/evals/git-evals/run-single-eval-process.ts @@ -60,7 +60,7 @@ async function main() { // Setup environment for this process setProjectRoot(projectPath) setupTestEnvironmentVariables() - createFileReadingMock(projectPath) + await createFileReadingMock(projectPath) recreateShell(projectPath) setWorkingDirectory(projectPath) diff --git a/evals/git-evals/run-single-eval.ts b/evals/git-evals/run-single-eval.ts index 3bac6391f..384b7dd7c 100644 --- a/evals/git-evals/run-single-eval.ts +++ b/evals/git-evals/run-single-eval.ts @@ -176,7 +176,7 @@ async function runSingleEvalTask(options: { // Setup project context setProjectRoot(projectPath) - createFileReadingMock(projectPath) + await createFileReadingMock(projectPath) recreateShell(projectPath) setWorkingDirectory(projectPath) diff --git a/evals/logger.ts b/evals/logger.ts index 010f9dbad..980f3f0c9 100644 --- a/evals/logger.ts +++ b/evals/logger.ts @@ -1,5 +1,7 @@ import { mkdirSync } from 'fs' import path, { dirname } from 'path' + +import { env } from '@codebuff/common/env' import { pino } from 'pino' let logPath: string | undefined = undefined @@ -38,7 +40,7 @@ function initPinoLoggerWithPath(path: string): void { function log(level: LogLevel, data: any, msg?: string, ...args: any[]): void { if ( process.env.CODEBUFF_GITHUB_ACTIONS !== 'true' && - process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'test' + env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'test' ) { const projectRoot = path.join(__dirname, '..') const logTarget = path.join(projectRoot, 'debug', 'evals.log') diff --git a/evals/scaffolding.ts b/evals/scaffolding.ts index e03a6d043..af2842252 100644 --- a/evals/scaffolding.ts +++ b/evals/scaffolding.ts @@ -15,7 +15,7 @@ import { sendSubagentChunkWs, } from '@codebuff/backend/client-wrapper' import { getFileTokenScores } from '@codebuff/code-map/parse' -import { TEST_USER_ID } from '@codebuff/common/old-constants' +import { API_KEY_ENV_VAR, TEST_USER_ID } from '@codebuff/common/old-constants' import { mockModule } from '@codebuff/common/testing/mock-modules' import { generateCompactId } from '@codebuff/common/util/string' import { handleToolCall } from '@codebuff/npm-app/tool-handlers' @@ -76,8 +76,8 @@ function readMockFile(projectRoot: string, filePath: string): string | null { let toolCalls: ClientToolCall[] = [] let toolResults: ToolResultPart[] = [] -export function createFileReadingMock(projectRoot: string) { - mockModule('@codebuff/backend/websockets/websocket-action', () => ({ +export async function createFileReadingMock(projectRoot: string) { + await mockModule('@codebuff/backend/websockets/websocket-action', () => ({ requestFiles: ((params: { ws: WebSocket; filePaths: string[] }) => { const files: Record = {} for (const filePath of params.filePaths) { @@ -200,7 +200,7 @@ export async function runAgentStepScaffolding( sendSubagentChunk: (params) => sendSubagentChunkWs({ ...params, ws: mockWs }), sendAction: (params) => sendActionWs({ ...params, ws: mockWs }), - apiKey: process.env.CODEBUFF_API_KEY ?? '', + apiKey: process.env[API_KEY_ENV_VAR] ?? '', } const result = await runAgentStep({ ...EVALS_AGENT_RUNTIME_IMPL, diff --git a/evals/test-setup.ts b/evals/test-setup.ts index 456b61d54..b4f198c16 100644 --- a/evals/test-setup.ts +++ b/evals/test-setup.ts @@ -155,7 +155,7 @@ export async function setupTestEnvironment(projectName: string) { const repoPath = path.join(TEST_REPOS_DIR, projectName) setProjectRoot(repoPath) - createFileReadingMock(repoPath) + await createFileReadingMock(repoPath) recreateShell(repoPath) setWorkingDirectory(repoPath) diff --git a/knowledge.md b/knowledge.md index 0cf91277c..de45fe426 100644 --- a/knowledge.md +++ b/knowledge.md @@ -87,6 +87,7 @@ base-lite "fix this bug" # Works right away! - For automated operations, prefer non-interactive git commands when possible (e.g., `git rebase --continue` after resolving conflicts programmatically) **Common Interactive Git Commands (require tmux):** + - `git rebase --continue` - Continue rebase after resolving conflicts - `git rebase --skip` - Skip current commit during rebase - `git rebase --abort` - Abort rebase operation @@ -106,6 +107,7 @@ base-lite "fix this bug" # Works right away! - Any git command that opens an editor (commit messages, rebase todo list, etc.) **Example:** + ```bash # ❌ Bad: Will hang waiting for input git rebase --continue @@ -270,7 +272,8 @@ spyOn(Date, 'now').mockImplementation(() => 1234567890) Only use for overriding module constants when absolutely necessary: - Use wrapper functions in `@codebuff/common/testing/mock-modules.ts` -- Call `clearMockedModules()` in `afterAll` + - Use `await mockModule(...)` as a drop-in replacement for `mock.module` + - Call `clearMockedModules()` in `afterAll` (or `afterEach`) ### Test Setup Patterns diff --git a/npm-app/src/__tests__/tool-handlers.test.ts b/npm-app/src/__tests__/tool-handlers.test.ts index 41c4a0850..82b457ff6 100644 --- a/npm-app/src/__tests__/tool-handlers.test.ts +++ b/npm-app/src/__tests__/tool-handlers.test.ts @@ -26,8 +26,8 @@ describe('handleCodeSearch', () => { return projectRoot }) - beforeAll(() => { - mockModule('@codebuff/npm-app/project-files', () => ({ + beforeAll(async () => { + await mockModule('@codebuff/npm-app/project-files', () => ({ getProjectRoot: mockGetProjectRoot, })) }) diff --git a/npm-app/src/utils/__tests__/rage-detector.test.ts b/npm-app/src/utils/__tests__/rage-detector.test.ts index 087c0d3c1..d95933e32 100644 --- a/npm-app/src/utils/__tests__/rage-detector.test.ts +++ b/npm-app/src/utils/__tests__/rage-detector.test.ts @@ -35,14 +35,14 @@ describe('Rage Detectors', () => { let timeoutId = 1 const mockTrackEvent = mock(() => {}) - beforeAll(() => { + beforeAll(async () => { // Mock the analytics module - mockModule('@codebuff/npm-app/utils/analytics', () => ({ + await mockModule('@codebuff/npm-app/utils/analytics', () => ({ trackEvent: mockTrackEvent, })) // Mock the sleep function from common/util/promise - mockModule('@codebuff/common/util/promise', () => ({ + await mockModule('@codebuff/common/util/promise', () => ({ sleep: mock(() => Promise.resolve()), })) }) diff --git a/packages/agent-runtime/src/__tests__/fast-rewrite.test.ts b/packages/agent-runtime/src/__tests__/fast-rewrite.test.ts index 5e8972d68..ff8189888 100644 --- a/packages/agent-runtime/src/__tests__/fast-rewrite.test.ts +++ b/packages/agent-runtime/src/__tests__/fast-rewrite.test.ts @@ -19,9 +19,9 @@ import type { describe.skip('rewriteWithOpenAI', () => { let agentRuntimeImpl: AgentRuntimeDeps & AgentRuntimeScopedDeps - beforeAll(() => { + beforeAll(async () => { // Mock database interactions - mockModule('pg-pool', () => ({ + await mockModule('pg-pool', () => ({ Pool: class { connect() { return { @@ -36,7 +36,7 @@ describe.skip('rewriteWithOpenAI', () => { })) // Mock message saving - mockModule('@codebuff/backend/llm-apis/message-cost-tracker', () => ({ + await mockModule('@codebuff/backend/llm-apis/message-cost-tracker', () => ({ saveMessage: () => Promise.resolve(), })) }) diff --git a/packages/agent-runtime/src/__tests__/loop-agent-steps.test.ts b/packages/agent-runtime/src/__tests__/loop-agent-steps.test.ts index f8edcd490..89f9241b5 100644 --- a/packages/agent-runtime/src/__tests__/loop-agent-steps.test.ts +++ b/packages/agent-runtime/src/__tests__/loop-agent-steps.test.ts @@ -40,11 +40,11 @@ describe('loopAgentSteps - runAgentStep vs runProgrammaticStep behavior', () => let llmCallCount: number let agentRuntimeImpl: AgentRuntimeDeps & AgentRuntimeScopedDeps - beforeAll(() => { + beforeAll(async () => { disableLiveUserInputCheck() // Mock bigquery - mockModule('@codebuff/bigquery', () => ({ + await mockModule('@codebuff/bigquery', () => ({ insertTrace: () => {}, })) }) diff --git a/packages/agent-runtime/src/__tests__/process-file-block.test.ts b/packages/agent-runtime/src/__tests__/process-file-block.test.ts index 3551c95bf..d7ffcb3a1 100644 --- a/packages/agent-runtime/src/__tests__/process-file-block.test.ts +++ b/packages/agent-runtime/src/__tests__/process-file-block.test.ts @@ -18,9 +18,9 @@ import type { let agentRuntimeImpl: AgentRuntimeDeps & AgentRuntimeScopedDeps describe('processFileBlockModule', () => { - beforeAll(() => { + beforeAll(async () => { // Mock database interactions - mockModule('pg-pool', () => ({ + await mockModule('pg-pool', () => ({ Pool: class { connect() { return { @@ -35,7 +35,7 @@ describe('processFileBlockModule', () => { })) // Mock message saving - mockModule('@codebuff/backend/llm-apis/message-cost-tracker', () => ({ + await mockModule('@codebuff/backend/llm-apis/message-cost-tracker', () => ({ saveMessage: () => Promise.resolve(), })) }) diff --git a/packages/agent-runtime/src/llm-api/__tests__/linkup-api.test.ts b/packages/agent-runtime/src/llm-api/__tests__/linkup-api.test.ts index da63864bd..ad8e4cee5 100644 --- a/packages/agent-runtime/src/llm-api/__tests__/linkup-api.test.ts +++ b/packages/agent-runtime/src/llm-api/__tests__/linkup-api.test.ts @@ -24,15 +24,15 @@ process.env.LINKUP_API_KEY = 'test-api-key' describe('Linkup API', () => { let agentRuntimeImpl: AgentRuntimeDeps - beforeAll(() => { - mockModule('@codebuff/internal', () => ({ + beforeAll(async () => { + await mockModule('@codebuff/internal', () => ({ env: { LINKUP_API_KEY: 'test-api-key', }, })) // Mock withTimeout utility - mockModule('@codebuff/common/util/promise', () => ({ + await mockModule('@codebuff/common/util/promise', () => ({ withTimeout: async (promise: Promise, timeout: number) => promise, })) }) diff --git a/packages/agent-runtime/src/llm-api/context7-api.ts b/packages/agent-runtime/src/llm-api/context7-api.ts index 91003c1e3..82472ff92 100644 --- a/packages/agent-runtime/src/llm-api/context7-api.ts +++ b/packages/agent-runtime/src/llm-api/context7-api.ts @@ -1,5 +1,4 @@ import { withTimeout } from '@codebuff/common/util/promise' -import { env } from '@codebuff/internal/env' import type { Logger } from '@codebuff/common/types/contracts/logger' import type { ParamsOf } from '@codebuff/common/types/function-params' @@ -64,7 +63,7 @@ export async function searchLibraries(params: { const response = await withTimeout( fetch(url, { headers: { - Authorization: `Bearer ${env.CONTEXT7_API_KEY}`, + Authorization: `Bearer ${process.env['CONTEXT7_API_KEY']}`, }, }), FETCH_TIMEOUT_MS, @@ -197,7 +196,7 @@ export async function fetchContext7LibraryDocumentation( const response = await withTimeout( fetch(url, { headers: { - Authorization: `Bearer ${env.CONTEXT7_API_KEY}`, + Authorization: `Bearer ${process.env['CONTEXT7_API_KEY']}`, 'X-Context7-Source': 'codebuff', }, }), diff --git a/packages/agent-runtime/src/llm-api/linkup-api.ts b/packages/agent-runtime/src/llm-api/linkup-api.ts index 362de9f1a..b1fe84d60 100644 --- a/packages/agent-runtime/src/llm-api/linkup-api.ts +++ b/packages/agent-runtime/src/llm-api/linkup-api.ts @@ -1,5 +1,5 @@ import { withTimeout } from '@codebuff/common/util/promise' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import type { Logger } from '@codebuff/common/types/contracts/logger' @@ -32,6 +32,10 @@ export async function searchWeb(options: { const { query, depth = 'standard', logger, fetch } = options const apiStartTime = Date.now() + if (!process.env.LINKUP_API_KEY) { + return 'No API key found. Please set LINKUP_API_KEY in your environment.' + } + const requestBody = { q: query, depth, @@ -53,7 +57,7 @@ export async function searchWeb(options: { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${env.LINKUP_API_KEY}`, + Authorization: `Bearer ${process.env.LINKUP_API_KEY}`, }, body: JSON.stringify(requestBody), }), diff --git a/packages/bigquery/src/client.ts b/packages/bigquery/src/client.ts index 00bb7ae21..c49a384f3 100644 --- a/packages/bigquery/src/client.ts +++ b/packages/bigquery/src/client.ts @@ -1,3 +1,4 @@ +import { env } from '@codebuff/common/env' import { getErrorObject } from '@codebuff/common/util/error' import { BigQuery } from '@google-cloud/bigquery' @@ -8,7 +9,7 @@ import type { MessageRow } from '@codebuff/common/types/contracts/bigquery' import type { Logger } from '@codebuff/common/types/contracts/logger' const DATASET = - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod' + env.NEXT_PUBLIC_CB_ENVIRONMENT === 'prod' ? 'codebuff_data' : 'codebuff_data_dev' diff --git a/packages/internal/src/db/drizzle.config.ts b/packages/internal/src/db/drizzle.config.ts index 3ed825d3e..09b3e0ba5 100644 --- a/packages/internal/src/db/drizzle.config.ts +++ b/packages/internal/src/db/drizzle.config.ts @@ -1,8 +1,9 @@ import path from 'path' -import { env } from '@codebuff/internal' import { defineConfig } from 'drizzle-kit' +import { env } from '@codebuff/internal/env' + export default defineConfig({ dialect: 'postgresql', schema: path.join(__dirname, 'schema.ts').replace(/\\/g, '/'), diff --git a/packages/internal/src/db/index.ts b/packages/internal/src/db/index.ts index 3b381eee3..53f0a1b6f 100644 --- a/packages/internal/src/db/index.ts +++ b/packages/internal/src/db/index.ts @@ -1,7 +1,8 @@ -import { env } from '@codebuff/internal' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' +import { env } from '@codebuff/internal/env' + import * as schema from './schema' import type { CodebuffPgDatabase } from './types' diff --git a/packages/internal/src/db/schema.ts b/packages/internal/src/db/schema.ts index 5e30804de..a50172c7f 100644 --- a/packages/internal/src/db/schema.ts +++ b/packages/internal/src/db/schema.ts @@ -1,3 +1,4 @@ +import { GrantTypeValues } from '@codebuff/common/types/grant' import { sql } from 'drizzle-orm' import { boolean, @@ -14,7 +15,6 @@ import { uniqueIndex, } from 'drizzle-orm/pg-core' -import { GrantTypeValues } from '@codebuff/common/types/grant' import { ReferralStatusValues } from '../types/referral' import type { SQL } from 'drizzle-orm' diff --git a/packages/internal/src/env-schema.ts b/packages/internal/src/env-schema.ts index 90824be95..cdddabbaa 100644 --- a/packages/internal/src/env-schema.ts +++ b/packages/internal/src/env-schema.ts @@ -1,6 +1,7 @@ -import z from 'zod' +import { clientEnvSchema, clientProcessEnv } from '@codebuff/common/env-schema' +import z from 'zod/v4' -export const serverEnvSchema = { +export const serverEnvSchema = clientEnvSchema.extend({ // Backend variables CODEBUFF_API_KEY: z.string().optional(), OPEN_ROUTER_API_KEY: z.string().min(1), @@ -15,7 +16,7 @@ export const serverEnvSchema = { GOOGLE_SITE_VERIFICATION_ID: z.string().optional(), CODEBUFF_GITHUB_ID: z.string().min(1), CODEBUFF_GITHUB_SECRET: z.string().min(1), - NEXTAUTH_URL: z.string().url().optional(), + NEXTAUTH_URL: z.url().optional(), NEXTAUTH_SECRET: z.string().min(1), STRIPE_SECRET_KEY: z.string().min(1), STRIPE_WEBHOOK_SECRET_KEY: z.string().min(1), @@ -28,17 +29,43 @@ export const serverEnvSchema = { // Common variables API_KEY_ENCRYPTION_SECRET: z.string().length(32), +}) +export const serverEnvVars = serverEnvSchema.keyof().options +export type ServerEnvVar = (typeof serverEnvVars)[number] +export type ServerInput = { + [K in (typeof serverEnvVars)[number]]: string | undefined } -export const clientEnvSchema = { - NEXT_PUBLIC_CB_ENVIRONMENT: z.string().min(1), - NEXT_PUBLIC_CODEBUFF_APP_URL: z.string().url().min(1), - NEXT_PUBLIC_CODEBUFF_BACKEND_URL: z.string().min(1), - NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().min(1), - NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional().default(''), - NEXT_PUBLIC_POSTHOG_HOST_URL: z.string().url().optional(), - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), - NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL: z.string().url().min(1), - NEXT_PUBLIC_LINKEDIN_PARTNER_ID: z.string().optional(), - NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: z.string().optional(), - NEXT_PUBLIC_WEB_PORT: z.coerce.number().min(1000).optional().default(3000), +export type ServerEnv = z.infer + +// Bun will inject all these values, so we need to reference them individually (no for-loops) +export const serverProcessEnv: ServerInput = { + ...clientProcessEnv, + + // Backend variables + CODEBUFF_API_KEY: process.env.CODEBUFF_API_KEY, + OPEN_ROUTER_API_KEY: process.env.OPEN_ROUTER_API_KEY, + RELACE_API_KEY: process.env.RELACE_API_KEY, + LINKUP_API_KEY: process.env.LINKUP_API_KEY, + CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY, + GOOGLE_CLOUD_PROJECT_ID: process.env.GOOGLE_CLOUD_PROJECT_ID, + PORT: process.env.PORT, + + // Web/Database variables + DATABASE_URL: process.env.DATABASE_URL, + GOOGLE_SITE_VERIFICATION_ID: process.env.GOOGLE_SITE_VERIFICATION_ID, + CODEBUFF_GITHUB_ID: process.env.CODEBUFF_GITHUB_ID, + CODEBUFF_GITHUB_SECRET: process.env.CODEBUFF_GITHUB_SECRET, + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, + STRIPE_WEBHOOK_SECRET_KEY: process.env.STRIPE_WEBHOOK_SECRET_KEY, + STRIPE_USAGE_PRICE_ID: process.env.STRIPE_USAGE_PRICE_ID, + STRIPE_TEAM_FEE_PRICE_ID: process.env.STRIPE_TEAM_FEE_PRICE_ID, + LOOPS_API_KEY: process.env.LOOPS_API_KEY, + DISCORD_PUBLIC_KEY: process.env.DISCORD_PUBLIC_KEY, + DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN, + DISCORD_APPLICATION_ID: process.env.DISCORD_APPLICATION_ID, + + // Common variables + API_KEY_ENCRYPTION_SECRET: process.env.API_KEY_ENCRYPTION_SECRET, } diff --git a/packages/internal/src/env.ts b/packages/internal/src/env.ts index ce384a955..f70566a9c 100644 --- a/packages/internal/src/env.ts +++ b/packages/internal/src/env.ts @@ -1,71 +1,10 @@ -import { createEnv } from '@t3-oss/env-nextjs' -import { clientEnvSchema, serverEnvSchema } from './env-schema' +import { serverEnvSchema, serverProcessEnv } from './env-schema' + // Only log environment in non-production if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') { console.log('Using environment:', process.env.NEXT_PUBLIC_CB_ENVIRONMENT) } -const envSchema = { - server: serverEnvSchema, - client: clientEnvSchema, - runtimeEnv: { - // Backend variables - CODEBUFF_API_KEY: process.env.CODEBUFF_API_KEY, - OPEN_ROUTER_API_KEY: process.env.OPEN_ROUTER_API_KEY, - RELACE_API_KEY: process.env.RELACE_API_KEY, - LINKUP_API_KEY: process.env.LINKUP_API_KEY, - CONTEXT7_API_KEY: process.env.CONTEXT7_API_KEY, - GOOGLE_CLOUD_PROJECT_ID: process.env.GOOGLE_CLOUD_PROJECT_ID, - PORT: process.env.PORT, - - // Web/Database variables - DATABASE_URL: process.env.DATABASE_URL, - GOOGLE_SITE_VERIFICATION_ID: process.env.GOOGLE_SITE_VERIFICATION_ID, - CODEBUFF_GITHUB_ID: process.env.CODEBUFF_GITHUB_ID, - CODEBUFF_GITHUB_SECRET: process.env.CODEBUFF_GITHUB_SECRET, - NEXTAUTH_URL: process.env.NEXTAUTH_URL, - NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, - STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, - STRIPE_WEBHOOK_SECRET_KEY: process.env.STRIPE_WEBHOOK_SECRET_KEY, - STRIPE_USAGE_PRICE_ID: process.env.STRIPE_USAGE_PRICE_ID, - STRIPE_TEAM_FEE_PRICE_ID: process.env.STRIPE_TEAM_FEE_PRICE_ID, - LOOPS_API_KEY: process.env.LOOPS_API_KEY, - DISCORD_PUBLIC_KEY: process.env.DISCORD_PUBLIC_KEY, - DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN, - DISCORD_APPLICATION_ID: process.env.DISCORD_APPLICATION_ID, - - // Common variables - API_KEY_ENCRYPTION_SECRET: process.env.API_KEY_ENCRYPTION_SECRET, - - // Client variables - NEXT_PUBLIC_CB_ENVIRONMENT: process.env.NEXT_PUBLIC_CB_ENVIRONMENT, - NEXT_PUBLIC_CODEBUFF_APP_URL: process.env.NEXT_PUBLIC_CODEBUFF_APP_URL, - NEXT_PUBLIC_CODEBUFF_BACKEND_URL: - process.env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL, - NEXT_PUBLIC_SUPPORT_EMAIL: process.env.NEXT_PUBLIC_SUPPORT_EMAIL, - NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY, - NEXT_PUBLIC_POSTHOG_HOST_URL: process.env.NEXT_PUBLIC_POSTHOG_HOST_URL, - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, - NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL: - process.env.NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL, - NEXT_PUBLIC_LINKEDIN_PARTNER_ID: - process.env.NEXT_PUBLIC_LINKEDIN_PARTNER_ID, - NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID: - process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID, - NEXT_PUBLIC_WEB_PORT: process.env.NEXT_PUBLIC_WEB_PORT, - }, -} -let envTemp -try { - envTemp = createEnv(envSchema) -} catch (error) { - console.error( - "\nERROR: Environment variables not loaded. It looks like you're missing some required environment variables.\nPlease run commands using the project's runner (e.g., 'infisical run -- ') to load them automatically.", - ) - - throw error -} -export const env = envTemp +export const env = serverEnvSchema.parse(serverProcessEnv) diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index d1c49f899..0954c74d4 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -1,8 +1,7 @@ -import { env } from './env' import * as loops from './loops' import * as utils from './utils/auth' -export { env, utils, loops } +export { utils, loops } export * from './utils/auth' export * from './utils/xml-parser' export * from './utils/version-utils' diff --git a/packages/internal/src/loops/client.ts b/packages/internal/src/loops/client.ts index 4556d1b09..158e6164f 100644 --- a/packages/internal/src/loops/client.ts +++ b/packages/internal/src/loops/client.ts @@ -3,6 +3,7 @@ import { LoopsClient, APIError } from 'loops' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import type { LoopsEmailData, SendEmailResult } from './types' import type { Logger } from '@codebuff/common/types/contracts/logger' @@ -16,8 +17,8 @@ const BASIC_TRANSACTIONAL_ID = 'cmb8pafk92r820w0i7lkplkt2' // Initialize Loops client let loopsClient: LoopsClient | null = null -if (process.env.LOOPS_API_KEY) { - loopsClient = new LoopsClient(process.env.LOOPS_API_KEY) +if (env.LOOPS_API_KEY) { + loopsClient = new LoopsClient(env.LOOPS_API_KEY) } async function sendTransactionalEmail( diff --git a/packages/internal/src/util/stripe.ts b/packages/internal/src/util/stripe.ts index d4aee47c3..f95ebdec2 100644 --- a/packages/internal/src/util/stripe.ts +++ b/packages/internal/src/util/stripe.ts @@ -1,6 +1,7 @@ -import { env } from '@codebuff/internal' import Stripe from 'stripe' +import { env } from '@codebuff/internal/env' + export const stripeServer = new Stripe(env.STRIPE_SECRET_KEY, { apiVersion: '2024-06-20', typescript: true, diff --git a/scripts/generate-ci-env.ts b/scripts/generate-ci-env.ts index 0acbbd478..4c4268765 100644 --- a/scripts/generate-ci-env.ts +++ b/scripts/generate-ci-env.ts @@ -7,7 +7,8 @@ import path from 'path' import { fileURLToPath } from 'url' -import { serverEnvSchema, clientEnvSchema } from '@codebuff/internal/env-schema' +import { CLIENT_ENV_PREFIX, clientEnvVars } from '@codebuff/common/env-schema' +import { serverEnvVars } from '@codebuff/internal/env-schema' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -49,19 +50,19 @@ function parseArgs() { function generateGitHubEnv() { const { prefix, scope } = parseArgs() const varsByScope = { - server: Object.keys(serverEnvSchema), - client: Object.keys(clientEnvSchema), + all: serverEnvVars, + client: clientEnvVars, } let selected: string[] = [] if (scope === 'server') { - selected = varsByScope.server + selected = varsByScope.all.filter( + (name) => !name.startsWith(CLIENT_ENV_PREFIX), + ) } else if (scope === 'client') { selected = varsByScope.client } else { - selected = Array.from( - new Set([...varsByScope.server, ...varsByScope.client]), - ) + selected = varsByScope.all } if (prefix) { diff --git a/sdk/src/__tests__/code-search.test.ts b/sdk/src/__tests__/code-search.test.ts index 24f0330e9..73f542c0e 100644 --- a/sdk/src/__tests__/code-search.test.ts +++ b/sdk/src/__tests__/code-search.test.ts @@ -1,15 +1,14 @@ +import { EventEmitter } from 'events' + +import { + clearMockedModules, + mockModule, +} from '@codebuff/common/testing/mock-modules' import { describe, expect, it, mock, beforeEach, afterEach } from 'bun:test' + import { codeSearch } from '../tools/code-search' -import { spawn } from 'child_process' -import type { ChildProcess } from 'child_process' -import { EventEmitter } from 'events' -// Mock child_process.spawn -mock.module('child_process', () => ({ - spawn: mock(() => { - throw new Error('spawn mock not configured') - }), -})) +import type { ChildProcess } from 'child_process' // Helper to create a mock child process function createMockChildProcess() { @@ -26,16 +25,17 @@ describe('codeSearch', () => { let mockSpawn: ReturnType let mockProcess: ReturnType - beforeEach(() => { + beforeEach(async () => { mockProcess = createMockChildProcess() mockSpawn = mock(() => mockProcess) - mock.module('child_process', () => ({ + await mockModule('child_process', () => ({ spawn: mockSpawn, })) }) afterEach(() => { mock.restore() + clearMockedModules() }) describe('basic search', () => { diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index 6c40e4477..ad01d66b0 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -1,11 +1,13 @@ +import { env } from '@codebuff/common/env' + export const CODEBUFF_BINARY = 'codebuff' -export const IS_DEV = process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' -export const IS_TEST = process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'test' +export const IS_DEV = env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' +export const IS_TEST = env.NEXT_PUBLIC_CB_ENVIRONMENT === 'test' export const IS_PROD = !IS_DEV && !IS_TEST export const WEBSITE_URL = - process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || + env.NEXT_PUBLIC_CODEBUFF_APP_URL || (IS_PROD ? 'https://codebuff.com' : 'http://localhost:3000') const DEFAULT_BACKEND_URL = 'manicode-backend.onrender.com' @@ -18,7 +20,7 @@ function getWebsocketUrl(url: string) { return isLocalhost(url) ? `ws://${url}/ws` : `wss://${url}/ws` } export const WEBSOCKET_URL = getWebsocketUrl( - process.env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || + env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || (IS_PROD ? DEFAULT_BACKEND_URL : DEFAULT_BACKEND_URL_DEV), ) @@ -26,6 +28,6 @@ function getBackendUrl(url: string) { return isLocalhost(url) ? `http://${url}` : `https://${url}` } export const BACKEND_URL = getBackendUrl( - process.env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || + env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || (IS_PROD ? DEFAULT_BACKEND_URL : DEFAULT_BACKEND_URL_DEV), ) diff --git a/web/next.config.mjs b/web/next.config.mjs index 035d1e301..919c49a96 100644 --- a/web/next.config.mjs +++ b/web/next.config.mjs @@ -1,5 +1,6 @@ import createMDX from '@next/mdx' import { withContentlayer } from 'next-contentlayer' +import { env } from '@codebuff/internal/env' const withMDX = createMDX({ extension: /\.mdx?$/, diff --git a/web/scripts/discord/register-commands.ts b/web/scripts/discord/register-commands.ts index 9dcb0b31a..c6b51abbe 100644 --- a/web/scripts/discord/register-commands.ts +++ b/web/scripts/discord/register-commands.ts @@ -1,4 +1,4 @@ -import { env } from '@codebuff/internal' +import { env } from '@codebuff/internal/env' import { REST, Routes, SlashCommandBuilder } from 'discord.js' import { logger } from '@/util/logger' diff --git a/web/src/api/v1/chat/__tests__/completions.test.ts b/web/src/api/v1/chat/__tests__/completions.test.ts index 96bdd9751..86b59cc59 100644 --- a/web/src/api/v1/chat/__tests__/completions.test.ts +++ b/web/src/api/v1/chat/__tests__/completions.test.ts @@ -1,3 +1,4 @@ +import { env } from '@codebuff/internal/env' import { afterEach, beforeEach, describe, expect, mock, it } from 'bun:test' import { NextRequest } from 'next/server' @@ -347,7 +348,7 @@ describe('/api/v1/chat/completions POST endpoint', () => { const body = await response.json() expect(body.message).toContain('Insufficient credits') expect(body.message).toContain( - `${process.env.NEXT_PUBLIC_CODEBUFF_APP_URL}/usage`, + `${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/usage`, ) }) }) diff --git a/web/src/api/v1/chat/completions.ts b/web/src/api/v1/chat/completions.ts index a8d281273..2b56d83b2 100644 --- a/web/src/api/v1/chat/completions.ts +++ b/web/src/api/v1/chat/completions.ts @@ -1,5 +1,6 @@ import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events' import { getErrorObject } from '@codebuff/common/util/error' +import { env } from '@codebuff/internal/env' import { NextResponse } from 'next/server' import type { TrackEventFn } from '@codebuff/common/types/contracts/analytics' @@ -127,7 +128,7 @@ export async function chatCompletionsPost(params: { }) return NextResponse.json( { - message: `Insufficient credits. Please add credits at ${process.env.NEXT_PUBLIC_CODEBUFF_APP_URL}/usage or wait for your next cycle to begin (${nextQuotaReset}).`, + message: `Insufficient credits. Please add credits at ${env.NEXT_PUBLIC_CODEBUFF_APP_URL}/usage or wait for your next cycle to begin (${nextQuotaReset}).`, }, { status: 402 }, ) diff --git a/web/src/app/[sponsee]/page.tsx b/web/src/app/[sponsee]/page.tsx index 0296a0b9d..9fb70ce14 100644 --- a/web/src/app/[sponsee]/page.tsx +++ b/web/src/app/[sponsee]/page.tsx @@ -1,6 +1,6 @@ 'use server' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' import { eq } from 'drizzle-orm' diff --git a/web/src/app/admin/file-picker/page.tsx b/web/src/app/admin/file-picker/page.tsx index 41ea4d7a5..479fd559c 100644 --- a/web/src/app/admin/file-picker/page.tsx +++ b/web/src/app/admin/file-picker/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { env } from '@codebuff/common/env' import { finetunedVertexModels } from '@codebuff/common/old-constants' import { Info, Settings } from 'lucide-react' import { useSession } from 'next-auth/react' @@ -47,9 +48,7 @@ const nameOverrides = { // Choose user list based on environment const suggestedUsers = - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' - ? localUsers - : productionUsers + env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' ? localUsers : productionUsers type Result = { timestamp: string diff --git a/web/src/app/affiliates/page.tsx b/web/src/app/affiliates/page.tsx index 63c1fdaef..ee8f27f4a 100644 --- a/web/src/app/affiliates/page.tsx +++ b/web/src/app/affiliates/page.tsx @@ -1,10 +1,10 @@ 'use client' +import { env } from '@codebuff/common/env' import { CREDITS_REFERRAL_BONUS, AFFILIATE_USER_REFFERAL_LIMIT, } from '@codebuff/common/old-constants' -import { env } from '@codebuff/internal' import Link from 'next/link' import { useSession } from 'next-auth/react' import React, { useEffect, useState, useCallback } from 'react' diff --git a/web/src/app/api/admin/relabel-for-user/route.ts b/web/src/app/api/admin/relabel-for-user/route.ts index 171dc453c..2da20925e 100644 --- a/web/src/app/api/admin/relabel-for-user/route.ts +++ b/web/src/app/api/admin/relabel-for-user/route.ts @@ -1,5 +1,6 @@ import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { and, eq, gt, desc } from 'drizzle-orm' import { NextResponse } from 'next/server' @@ -10,8 +11,7 @@ import { logger } from '@/util/logger' // Helper to construct backend URL function getBackendUrl() { - const backendUrl = - process.env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || 'localhost:4242' + const backendUrl = env.NEXT_PUBLIC_CODEBUFF_BACKEND_URL || 'localhost:4242' const protocol = backendUrl.startsWith('localhost') ? 'http://' : 'https://' return `${protocol}${backendUrl}` } diff --git a/web/src/app/api/auth/[...nextauth]/auth-options.ts b/web/src/app/api/auth/[...nextauth]/auth-options.ts index 6a970ff89..09591eae1 100644 --- a/web/src/app/api/auth/[...nextauth]/auth-options.ts +++ b/web/src/app/api/auth/[...nextauth]/auth-options.ts @@ -5,9 +5,10 @@ import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events' import { DEFAULT_FREE_CREDITS_GRANT } from '@codebuff/common/old-constants' import { getNextQuotaReset } from '@codebuff/common/util/dates' import { generateCompactId } from '@codebuff/common/util/string' -import { loops, env } from '@codebuff/internal' +import { loops } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { logSyncFailure } from '@codebuff/internal/util/sync-failure' import { eq } from 'drizzle-orm' diff --git a/web/src/app/api/auth/cli/code/route.ts b/web/src/app/api/auth/cli/code/route.ts index 4a707ddac..071dd2edd 100644 --- a/web/src/app/api/auth/cli/code/route.ts +++ b/web/src/app/api/auth/cli/code/route.ts @@ -1,7 +1,7 @@ import { genAuthCode } from '@codebuff/common/util/credentials' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { and, eq, gt } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod/v4' diff --git a/web/src/app/api/auth/cli/status/route.ts b/web/src/app/api/auth/cli/status/route.ts index dcffad245..2053232e4 100644 --- a/web/src/app/api/auth/cli/status/route.ts +++ b/web/src/app/api/auth/cli/status/route.ts @@ -1,7 +1,7 @@ import { genAuthCode } from '@codebuff/common/util/credentials' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { and, eq, gt, or, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { z } from 'zod/v4' diff --git a/web/src/app/api/orgs/[orgId]/billing/setup/route.ts b/web/src/app/api/orgs/[orgId]/billing/setup/route.ts index 0ce13ab10..f02f27be1 100644 --- a/web/src/app/api/orgs/[orgId]/billing/setup/route.ts +++ b/web/src/app/api/orgs/[orgId]/billing/setup/route.ts @@ -1,7 +1,7 @@ import { pluralize } from '@codebuff/common/util/string' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { eq, and, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' diff --git a/web/src/app/api/orgs/[orgId]/credits/route.ts b/web/src/app/api/orgs/[orgId]/credits/route.ts index b0ffb7e1c..861a2cd48 100644 --- a/web/src/app/api/orgs/[orgId]/credits/route.ts +++ b/web/src/app/api/orgs/[orgId]/credits/route.ts @@ -1,9 +1,9 @@ import { grantOrganizationCredits } from '@codebuff/billing' import { CREDIT_PRICING } from '@codebuff/common/old-constants' import { generateCompactId } from '@codebuff/common/util/string' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { and, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' diff --git a/web/src/app/api/orgs/[orgId]/monitoring/route.ts b/web/src/app/api/orgs/[orgId]/monitoring/route.ts index 39484a31c..26f62d2ed 100644 --- a/web/src/app/api/orgs/[orgId]/monitoring/route.ts +++ b/web/src/app/api/orgs/[orgId]/monitoring/route.ts @@ -1,7 +1,7 @@ import { calculateOrganizationUsageAndBalance } from '@codebuff/billing' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { eq, and, gte, sql } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getServerSession } from 'next-auth' diff --git a/web/src/app/api/orgs/route.ts b/web/src/app/api/orgs/route.ts index 75d222228..2333dfdb2 100644 --- a/web/src/app/api/orgs/route.ts +++ b/web/src/app/api/orgs/route.ts @@ -1,6 +1,6 @@ -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { eq, and } from 'drizzle-orm' import { NextResponse } from 'next/server' diff --git a/web/src/app/api/stripe/buy-credits/route.ts b/web/src/app/api/stripe/buy-credits/route.ts index be2f1cd96..836f36238 100644 --- a/web/src/app/api/stripe/buy-credits/route.ts +++ b/web/src/app/api/stripe/buy-credits/route.ts @@ -1,9 +1,9 @@ import { processAndGrantCredit } from '@codebuff/billing' import { convertCreditsToUsdCents } from '@codebuff/common/util/currency' import { generateCompactId } from '@codebuff/common/util/string' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' diff --git a/web/src/app/api/stripe/webhook/route.ts b/web/src/app/api/stripe/webhook/route.ts index 8da43f04f..00918f831 100644 --- a/web/src/app/api/stripe/webhook/route.ts +++ b/web/src/app/api/stripe/webhook/route.ts @@ -3,9 +3,9 @@ import { processAndGrantCredit, revokeGrantByOperationId, } from '@codebuff/billing' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { stripeServer } from '@codebuff/internal/util/stripe' import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' diff --git a/web/src/app/login/page.tsx b/web/src/app/login/page.tsx index ac5ea19cd..33f092474 100644 --- a/web/src/app/login/page.tsx +++ b/web/src/app/login/page.tsx @@ -1,6 +1,6 @@ 'use server' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import { LoginCard } from '@/components/login/login-card' import { diff --git a/web/src/app/onboard/page.tsx b/web/src/app/onboard/page.tsx index d59f868f9..3d310050b 100644 --- a/web/src/app/onboard/page.tsx +++ b/web/src/app/onboard/page.tsx @@ -2,9 +2,9 @@ import { MAX_DATE } from '@codebuff/common/old-constants' import { genAuthCode } from '@codebuff/common/util/credentials' -import { env } from '@codebuff/internal' import { db } from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { and, eq, gt } from 'drizzle-orm' import Image from 'next/image' import { redirect } from 'next/navigation' diff --git a/web/src/app/orgs/[slug]/billing/purchase/page.tsx b/web/src/app/orgs/[slug]/billing/purchase/page.tsx index f541b8f1f..61f169eb6 100644 --- a/web/src/app/orgs/[slug]/billing/purchase/page.tsx +++ b/web/src/app/orgs/[slug]/billing/purchase/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { env } from '@codebuff/common/env' import { loadStripe } from '@stripe/stripe-js' import { ArrowLeft, @@ -77,9 +78,7 @@ export default function OrganizationBillingPurchasePage() { localStorage.setItem('pendingCreditPurchase', credits.toString()) // Redirect to Stripe Checkout - const stripe = await loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, - ) + const stripe = await loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!) if (stripe) { const { error } = await stripe.redirectToCheckout({ diff --git a/web/src/app/orgs/[slug]/billing/setup/page.tsx b/web/src/app/orgs/[slug]/billing/setup/page.tsx index a2504f279..e98dc0f9f 100644 --- a/web/src/app/orgs/[slug]/billing/setup/page.tsx +++ b/web/src/app/orgs/[slug]/billing/setup/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { env } from '@codebuff/common/env' import { ArrowLeft, CreditCard, Loader2 } from 'lucide-react' import Link from 'next/link' import { useParams, useRouter } from 'next/navigation' @@ -59,7 +60,7 @@ export default function BillingSetupPage() { // Redirect to Stripe Checkout const stripe = (await import('@stripe/stripe-js')).loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, + env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, ) const stripeInstance = await stripe diff --git a/web/src/app/orgs/[slug]/page.tsx b/web/src/app/orgs/[slug]/page.tsx index 56ad56d45..882e94820 100644 --- a/web/src/app/orgs/[slug]/page.tsx +++ b/web/src/app/orgs/[slug]/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { env } from '@codebuff/common/env' import { loadStripe } from '@stripe/stripe-js' import { ArrowLeft, @@ -92,9 +93,7 @@ export default function OrganizationPage() { const { sessionId } = await response.json() // Redirect to Stripe Checkout - const stripe = await loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!, - ) + const stripe = await loadStripe(env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!) if (stripe) { const { error } = await stripe.redirectToCheckout({ diff --git a/web/src/app/profile/components/referrals-section.tsx b/web/src/app/profile/components/referrals-section.tsx index db8f729ee..7399d20c2 100644 --- a/web/src/app/profile/components/referrals-section.tsx +++ b/web/src/app/profile/components/referrals-section.tsx @@ -1,13 +1,15 @@ 'use client' +import { env } from '@codebuff/common/env' import { CREDITS_REFERRAL_BONUS } from '@codebuff/common/old-constants' import { getReferralLink } from '@codebuff/common/util/referral' -import { env } from '@codebuff/internal' import { useQuery } from '@tanstack/react-query' import { CopyIcon, Forward } from 'lucide-react' import { useSession } from 'next-auth/react' import { match, P } from 'ts-pattern' +import { ProfileSection } from './profile-section' + import type { ReferralData } from '@/app/api/referrals/route' import { Button } from '@/components/ui/button' @@ -22,7 +24,6 @@ import { Input } from '@/components/ui/input' import { Separator } from '@/components/ui/separator' import { Skeleton } from '@/components/ui/skeleton' import { toast } from '@/components/ui/use-toast' -import { ProfileSection } from './profile-section' const copyReferral = (link: string) => { navigator.clipboard.writeText(link) diff --git a/web/src/app/profile/components/usage-section.tsx b/web/src/app/profile/components/usage-section.tsx index 392e578d2..eaa8beab8 100644 --- a/web/src/app/profile/components/usage-section.tsx +++ b/web/src/app/profile/components/usage-section.tsx @@ -1,18 +1,17 @@ 'use client' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import { loadStripe } from '@stripe/stripe-js' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import Link from 'next/link' import { useSession } from 'next-auth/react' import { useState } from 'react' import { UsageDisplay } from './usage-display' + import { CreditManagementSection } from '@/components/credits/CreditManagementSection' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { CreditConfetti } from '@/components/ui/credit-confetti' import { toast } from '@/components/ui/use-toast' -import { ProfileSection } from './profile-section' const ManageCreditsCard = ({ isLoading = false }: { isLoading?: boolean }) => { const { data: session } = useSession() diff --git a/web/src/app/referrals/[code]/page.tsx b/web/src/app/referrals/[code]/page.tsx index 05d6c6e89..fd8d644a9 100644 --- a/web/src/app/referrals/[code]/page.tsx +++ b/web/src/app/referrals/[code]/page.tsx @@ -1,15 +1,16 @@ -import { env } from '@codebuff/internal' -import Link from 'next/link' +import { env } from '@codebuff/common/env' import { headers } from 'next/headers' +import Link from 'next/link' import { getServerSession } from 'next-auth' -import type { Metadata } from 'next' +import { authOptions } from '../../api/auth/[...nextauth]/auth-options' + import type { ReferralCodeResponse } from '../../api/referrals/[code]/route' +import type { Metadata } from 'next' -import { authOptions } from '../../api/auth/[...nextauth]/auth-options' import CardWithBeams from '@/components/card-with-beams' -import { Button } from '@/components/ui/button' import { OnboardClientWrapper } from '@/components/onboard/onboard-client-wrapper' +import { Button } from '@/components/ui/button' export const generateMetadata = async ({ params, diff --git a/web/src/app/robots.ts b/web/src/app/robots.ts index f7726537a..5faae7fda 100644 --- a/web/src/app/robots.ts +++ b/web/src/app/robots.ts @@ -1,4 +1,4 @@ -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import type { MetadataRoute } from 'next' diff --git a/web/src/app/sitemap.ts b/web/src/app/sitemap.ts index 96d555c82..8bfc34d38 100644 --- a/web/src/app/sitemap.ts +++ b/web/src/app/sitemap.ts @@ -1,4 +1,4 @@ -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import type { MetadataRoute } from 'next' diff --git a/web/src/app/store/agents-data.ts b/web/src/app/store/agents-data.ts index bbcb4edfa..7acbecf47 100644 --- a/web/src/app/store/agents-data.ts +++ b/web/src/app/store/agents-data.ts @@ -1,3 +1,4 @@ +import { env } from '@codebuff/common/env' import { unstable_cache } from 'next/cache' // Types @@ -27,8 +28,7 @@ interface AgentData { // Server-side data fetching function with ISR export const getAgentsData = unstable_cache( async (): Promise => { - const baseUrl = - process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'http://localhost:3000' + const baseUrl = env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'http://localhost:3000' try { const response = await fetch(`${baseUrl}/api/agents`, { diff --git a/web/src/discord/client.ts b/web/src/discord/client.ts index 6d6f6fc6a..45506bef8 100644 --- a/web/src/discord/client.ts +++ b/web/src/discord/client.ts @@ -1,6 +1,6 @@ -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import { user } from '@codebuff/internal/db/schema' +import { env } from '@codebuff/internal/env' import { Client, Events, GatewayIntentBits } from 'discord.js' import { eq, or } from 'drizzle-orm' diff --git a/web/src/lib/PostHogProvider.tsx b/web/src/lib/PostHogProvider.tsx index 409565f93..9738c108c 100644 --- a/web/src/lib/PostHogProvider.tsx +++ b/web/src/lib/PostHogProvider.tsx @@ -1,6 +1,6 @@ 'use client' -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' import { useSession } from 'next-auth/react' import posthog from 'posthog-js' import { PostHogProvider as PostHogProviderWrapper } from 'posthog-js/react' diff --git a/web/src/lib/constant.ts b/web/src/lib/constant.ts index 4f5b4a62f..2f9906495 100644 --- a/web/src/lib/constant.ts +++ b/web/src/lib/constant.ts @@ -1,4 +1,4 @@ -import { env } from '@codebuff/internal' +import { env } from '@codebuff/common/env' export const siteConfig = { title: 'Codebuff', diff --git a/web/src/lib/server/referral.ts b/web/src/lib/server/referral.ts index 5e3599b34..d04ca7265 100644 --- a/web/src/lib/server/referral.ts +++ b/web/src/lib/server/referral.ts @@ -1,7 +1,7 @@ import { getReferralLink } from '@codebuff/common/util/referral' -import { env } from '@codebuff/internal' import db from '@codebuff/internal/db' import * as schema from '@codebuff/internal/db/schema' +import { env } from '@codebuff/common/env' import { eq, sql } from 'drizzle-orm' export type ReferralStatus = diff --git a/web/src/util/logger.ts b/web/src/util/logger.ts index 1686cd7d1..8237de3a9 100644 --- a/web/src/util/logger.ts +++ b/web/src/util/logger.ts @@ -2,8 +2,8 @@ import fs from 'fs' import path from 'path' import { format } from 'util' +import { env } from '@codebuff/common/env' import { splitData } from '@codebuff/common/util/split-data' -import { env } from '@codebuff/internal' import pino from 'pino' // --- Constants --- @@ -106,7 +106,7 @@ function splitAndLog( } export const logger: Record = - process.env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' + env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev' ? pinoLogger : (Object.fromEntries( loggingLevels.map((level) => {