Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/sim/app/api/workflows/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { verifyInternalToken } from '@/lib/auth/internal'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import { getUserEntityPermissions, hasAdminPermission } from '@/lib/permissions/utils'
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
Expand Down Expand Up @@ -215,7 +216,7 @@ export async function DELETE(
// This prevents "Block not found" errors when collaborative updates try to process
// after the workflow has been deleted
try {
const socketUrl = process.env.SOCKET_SERVER_URL || 'http://localhost:3002'
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
const socketResponse = await fetch(`${socketUrl}/api/workflow-deleted`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Skeleton } from '@/components/ui/skeleton'
import { Textarea } from '@/components/ui/textarea'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import { getBaseDomain } from '@/lib/urls/utils'
import { cn } from '@/lib/utils'
Expand All @@ -54,7 +55,7 @@ interface ChatDeployProps {
type AuthType = 'public' | 'password' | 'email'

const getDomainSuffix = (() => {
const suffix = process.env.NODE_ENV === 'development' ? `.${getBaseDomain()}` : '.simstudio.ai'
const suffix = env.NODE_ENV === 'development' ? `.${getBaseDomain()}` : '.simstudio.ai'
return () => suffix
})()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useParams, usePathname, useRouter } from 'next/navigation'
import { Skeleton } from '@/components/ui/skeleton'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useSession } from '@/lib/auth-client'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import {
getKeyboardShortcutText,
Expand All @@ -27,7 +28,7 @@ import { WorkspaceHeader } from './components/workspace-header/workspace-header'

const logger = createLogger('Sidebar')

const IS_DEV = process.env.NODE_ENV === 'development'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this still need to be here or can we just import isDev from environment.ts?

const IS_DEV = env.NODE_ENV === 'development'

export function Sidebar() {
useGlobalShortcuts()
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/blocks/blocks/file.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DocumentIcon } from '@/components/icons'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'
import type { FileParserOutput } from '@/tools/file/types'
import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types'

const logger = createLogger('FileBlock')

const shouldEnableURLInput = process.env.NODE_ENV === 'production'
const shouldEnableURLInput = env.NODE_ENV === 'production'

const inputMethodBlock: SubBlockConfig = {
id: 'inputMethod',
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/blocks/blocks/mistral_parse.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { MistralIcon } from '@/components/icons'
import { env } from '@/lib/env'
import type { MistralParserOutput } from '@/tools/mistral/types'
import type { BlockConfig, SubBlockConfig, SubBlockLayout, SubBlockType } from '../types'

const shouldEnableFileUpload = process.env.NODE_ENV === 'production'
const shouldEnableFileUpload = env.NODE_ENV === 'production'

const inputMethodBlock: SubBlockConfig = {
id: 'inputMethod',
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/components/emails/workspace-invitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Section,
Text,
} from '@react-email/components'
import { env } from '@/lib/env'
import { baseStyles } from './base-styles'
import EmailFooter from './footer'

Expand All @@ -20,7 +21,7 @@ interface WorkspaceInvitationEmailProps {
invitationLink?: string
}

const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai'
const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://simstudio.ai'

export const WorkspaceInvitationEmail = ({
workspaceName = 'Workspace',
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/contexts/socket-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'react'
import { useParams } from 'next/navigation'
import { io, type Socket } from 'socket.io-client'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console-logger'

const logger = createLogger('SocketContext')
Expand Down Expand Up @@ -134,7 +135,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
// Generate initial token for socket authentication
const token = await generateSocketToken()

const socketUrl = process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'
const socketUrl = env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'

logger.info('Attempting to connect to Socket.IO server', {
url: socketUrl,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ declare global {
}

export const db = global.database || drizzleClient
if (process.env.NODE_ENV !== 'production') global.database = db
if (env.NODE_ENV !== 'production') global.database = db
11 changes: 6 additions & 5 deletions apps/sim/lib/auth-client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { stripeClient } from '@better-auth/stripe/client'
import { emailOTPClient, genericOAuthClient, organizationClient } from 'better-auth/client/plugins'
import { createAuthClient } from 'better-auth/react'
import { env } from './env'

const clientEnv = {
NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
NODE_ENV: process.env.NODE_ENV,
VERCEL_ENV: process.env.VERCEL_ENV || '',
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
NEXT_PUBLIC_VERCEL_URL: env.NEXT_PUBLIC_VERCEL_URL,
NEXT_PUBLIC_APP_URL: env.NEXT_PUBLIC_APP_URL,
NODE_ENV: env.NODE_ENV,
VERCEL_ENV: env.VERCEL_ENV || '',
BETTER_AUTH_URL: env.BETTER_AUTH_URL,
}

export function getBaseURL() {
Expand Down
11 changes: 7 additions & 4 deletions apps/sim/lib/email/unsubscribe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
verifyUnsubscribeToken,
} from './unsubscribe'

vi.stubEnv('BETTER_AUTH_SECRET', 'test-secret-key')
vi.mock('../env', () => ({
env: {
BETTER_AUTH_SECRET: 'test-secret-key',
},
}))

describe('unsubscribe utilities', () => {
const testEmail = 'test@example.com'
Expand Down Expand Up @@ -75,10 +79,9 @@ describe('unsubscribe utilities', () => {
it.concurrent('should handle legacy tokens (2 parts) and default to marketing', () => {
// Generate a real legacy token using the actual hashing logic to ensure backward compatibility
const salt = 'abc123'
const secret = 'test-secret-key'
const { createHash } = require('crypto')
const hash = createHash('sha256')
.update(`${testEmail}:${salt}:${process.env.BETTER_AUTH_SECRET}`)
.digest('hex')
const hash = createHash('sha256').update(`${testEmail}:${salt}:${secret}`).digest('hex')
const legacyToken = `${salt}:${hash}`

// This should return valid since we're using the actual legacy format properly
Expand Down
7 changes: 4 additions & 3 deletions apps/sim/lib/email/unsubscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { eq } from 'drizzle-orm'
import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { settings, user } from '@/db/schema'
import { env } from '../env'
import type { EmailType } from './mailer'

const logger = createLogger('Unsubscribe')
Expand All @@ -20,7 +21,7 @@ export interface EmailPreferences {
export function generateUnsubscribeToken(email: string, emailType = 'marketing'): string {
const salt = randomBytes(16).toString('hex')
const hash = createHash('sha256')
.update(`${email}:${salt}:${emailType}:${process.env.BETTER_AUTH_SECRET}`)
.update(`${email}:${salt}:${emailType}:${env.BETTER_AUTH_SECRET}`)
.digest('hex')

return `${salt}:${hash}:${emailType}`
Expand All @@ -41,7 +42,7 @@ export function verifyUnsubscribeToken(
if (parts.length === 2) {
const [salt, expectedHash] = parts
const hash = createHash('sha256')
.update(`${email}:${salt}:${process.env.BETTER_AUTH_SECRET}`)
.update(`${email}:${salt}:${env.BETTER_AUTH_SECRET}`)
.digest('hex')

return { valid: hash === expectedHash, emailType: 'marketing' }
Expand All @@ -52,7 +53,7 @@ export function verifyUnsubscribeToken(
if (!salt || !expectedHash || !emailType) return { valid: false }

const hash = createHash('sha256')
.update(`${email}:${salt}:${emailType}:${process.env.BETTER_AUTH_SECRET}`)
.update(`${email}:${salt}:${emailType}:${env.BETTER_AUTH_SECRET}`)
.digest('hex')

return { valid: hash === expectedHash, emailType }
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export const env = createEnv({
SLACK_CLIENT_ID: z.string().optional(),
SLACK_CLIENT_SECRET: z.string().optional(),
SOCKET_SERVER_URL: z.string().url().optional(),
SOCKET_PORT: z.number().optional(),
PORT: z.number().optional(),
},

client: {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/freestyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ export async function executeCode(
nodeModules: packages,
timeout: null,
// Add environment variables if needed
envVars: Object.entries(process.env).reduce(
envVars: Object.entries(env).reduce(
(acc, [key, value]) => {
if (value !== undefined) {
acc[key] = value
acc[key] = value as string
}
return acc
},
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/lib/logs/console-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* It is separate from the user-facing logging system in logging.ts.
*/
import chalk from 'chalk'
import { env } from '../env'

/**
* LogLevel enum defines the severity levels for logging
Expand Down Expand Up @@ -55,7 +56,7 @@ const LOG_CONFIG = {
}

// Get current environment
const ENV = (process.env.NODE_ENV || 'development') as keyof typeof LOG_CONFIG
const ENV = (env.NODE_ENV || 'development') as keyof typeof LOG_CONFIG
const config = LOG_CONFIG[ENV] || LOG_CONFIG.development

// Format objects for logging
Expand Down
5 changes: 2 additions & 3 deletions apps/sim/lib/subscription/subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createLogger } from '@/lib/logs/console-logger'
import { db } from '@/db'
import { member, subscription, userStats } from '@/db/schema'
import { client } from '../auth-client'
import { env } from '../env'
import { calculateUsageLimit, checkEnterprisePlan, checkProPlan, checkTeamPlan } from './utils'

const logger = createLogger('Subscription')
Expand Down Expand Up @@ -172,9 +173,7 @@ export async function hasExceededCostLimit(userId: string): Promise<boolean> {
limit,
})
} else {
limit = process.env.FREE_TIER_COST_LIMIT
? Number.parseFloat(process.env.FREE_TIER_COST_LIMIT)
: 5
limit = env.FREE_TIER_COST_LIMIT || 5
logger.info('Using free tier limit', { userId, limit })
}

Expand Down
22 changes: 9 additions & 13 deletions apps/sim/lib/subscription/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { calculateUsageLimit, checkEnterprisePlan } from './utils'

const ORIGINAL_ENV = { ...process.env }

beforeAll(() => {
process.env.FREE_TIER_COST_LIMIT = '5'
process.env.PRO_TIER_COST_LIMIT = '20'
process.env.TEAM_TIER_COST_LIMIT = '40'
process.env.ENTERPRISE_TIER_COST_LIMIT = '200'
})

afterAll(() => {
process.env = ORIGINAL_ENV
})
vi.mock('../env', () => ({
env: {
FREE_TIER_COST_LIMIT: 5,
PRO_TIER_COST_LIMIT: 20,
TEAM_TIER_COST_LIMIT: 40,
ENTERPRISE_TIER_COST_LIMIT: 200,
},
}))

describe('Subscription Utilities', () => {
describe('checkEnterprisePlan', () => {
Expand Down
12 changes: 7 additions & 5 deletions apps/sim/lib/subscription/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { env } from '../env'

export function checkEnterprisePlan(subscription: any): boolean {
return subscription?.plan === 'enterprise' && subscription?.status === 'active'
}
Expand All @@ -17,16 +19,16 @@ export function checkTeamPlan(subscription: any): boolean {
*/
export function calculateUsageLimit(subscription: any): number {
if (!subscription || subscription.status !== 'active') {
return Number.parseFloat(process.env.FREE_TIER_COST_LIMIT!)
return env.FREE_TIER_COST_LIMIT || 0
}

const seats = subscription.seats || 1

if (subscription.plan === 'pro') {
return Number.parseFloat(process.env.PRO_TIER_COST_LIMIT!)
return env.PRO_TIER_COST_LIMIT || 0
}
if (subscription.plan === 'team') {
return seats * Number.parseFloat(process.env.TEAM_TIER_COST_LIMIT!)
return seats * (env.TEAM_TIER_COST_LIMIT || 0)
}
if (subscription.plan === 'enterprise') {
const metadata = subscription.metadata || {}
Expand All @@ -39,8 +41,8 @@ export function calculateUsageLimit(subscription: any): number {
return Number.parseFloat(metadata.totalAllowance)
}

return seats * Number.parseFloat(process.env.ENTERPRISE_TIER_COST_LIMIT!)
return seats * (env.ENTERPRISE_TIER_COST_LIMIT || 0)
}

return Number.parseFloat(process.env.FREE_TIER_COST_LIMIT!)
return env.FREE_TIER_COST_LIMIT || 0
}
12 changes: 7 additions & 5 deletions apps/sim/lib/urls/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { env } from '../env'

/**
* Returns the base URL of the application, respecting environment variables for deployment environments
* @returns The base URL string (e.g., 'http://localhost:3000' or 'https://example.com')
Expand All @@ -7,13 +9,13 @@ export function getBaseUrl(): string {
return window.location.origin
}

const baseUrl = process.env.NEXT_PUBLIC_APP_URL
const baseUrl = env.NEXT_PUBLIC_APP_URL
if (baseUrl) {
if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) {
return baseUrl
}

const isProd = process.env.NODE_ENV === 'production'
const isProd = env.NODE_ENV === 'production'
const protocol = isProd ? 'https://' : 'http://'
return `${protocol}${baseUrl}`
}
Expand All @@ -30,11 +32,11 @@ export function getBaseDomain(): string {
const url = new URL(getBaseUrl())
return url.host // host includes port if specified
} catch (_e) {
const fallbackUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
const fallbackUrl = env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
try {
return new URL(fallbackUrl).host
} catch {
const isProd = process.env.NODE_ENV === 'production'
const isProd = env.NODE_ENV === 'production'
return isProd ? 'simstudio.ai' : 'localhost:3000'
}
}
Expand All @@ -49,7 +51,7 @@ export function getEmailDomain(): string {
const baseDomain = getBaseDomain()
return baseDomain.startsWith('www.') ? baseDomain.substring(4) : baseDomain
} catch (_e) {
const isProd = process.env.NODE_ENV === 'production'
const isProd = env.NODE_ENV === 'production'
return isProd ? 'simstudio.ai' : 'localhost:3000'
}
}
8 changes: 4 additions & 4 deletions apps/sim/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ const nextConfig: NextConfig = {
},
...(env.NODE_ENV === 'development' && {
allowedDevOrigins: [
...(process.env.NEXT_PUBLIC_APP_URL
...(env.NEXT_PUBLIC_APP_URL
? (() => {
try {
return [new URL(process.env.NEXT_PUBLIC_APP_URL).host]
return [new URL(env.NEXT_PUBLIC_APP_URL).host]
} catch {
return []
}
Expand Down Expand Up @@ -81,7 +81,7 @@ const nextConfig: NextConfig = {
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
{
key: 'Access-Control-Allow-Origin',
value: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001',
value: env.NEXT_PUBLIC_APP_URL || 'http://localhost:3001',
},
{
key: 'Access-Control-Allow-Methods',
Expand Down Expand Up @@ -158,7 +158,7 @@ const nextConfig: NextConfig = {
},
{
key: 'Content-Security-Policy',
value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${process.env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${process.env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
},
],
},
Expand Down
Loading