Merge staging into lakees/db

- Resolve merge conflicts in input-format.tsx, workflow-block.tsx, providers/utils.ts
- Fix tests to use blockData/blockNameMapping for tag variable resolution
- Add getBlockOutputs mock to block.test.ts for schema validation tests
- Fix normalizeName import path in utils.test.ts
- Add sql.raw and sql.join to drizzle-orm mock for sql.test.ts
- Add new subBlock types (table-selector, filter-builder, sort-builder) to blocks.test.ts
This commit is contained in:
Vikhyath Mondreti
2026-01-21 16:33:31 -08:00
708 changed files with 51112 additions and 12566 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE "user_stats" ADD COLUMN "total_mcp_executions" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE "user_stats" ADD COLUMN "total_a2a_executions" integer DEFAULT 0 NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -1016,6 +1016,13 @@
"when": 1768602646955,
"tag": "0145_messy_archangel",
"breakpoints": true
},
{
"idx": 146,
"version": "7",
"when": 1768867605608,
"tag": "0146_cultured_ikaris",
"breakpoints": true
}
]
}

View File

@@ -698,6 +698,8 @@ export const userStats = pgTable('user_stats', {
totalWebhookTriggers: integer('total_webhook_triggers').notNull().default(0),
totalScheduledExecutions: integer('total_scheduled_executions').notNull().default(0),
totalChatExecutions: integer('total_chat_executions').notNull().default(0),
totalMcpExecutions: integer('total_mcp_executions').notNull().default(0),
totalA2aExecutions: integer('total_a2a_executions').notNull().default(0),
totalTokensUsed: integer('total_tokens_used').notNull().default(0),
totalCost: decimal('total_cost').notNull().default('0'),
currentUsageLimit: decimal('current_usage_limit').default(DEFAULT_FREE_CREDITS.toString()), // Default $20 for free plan, null for team/enterprise

View File

@@ -18,7 +18,6 @@ import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { ssoProvider, user } from '../schema'
// Simple console logger
const logger = {
info: (message: string, meta?: any) => {
const timestamp = new Date().toISOString()
@@ -43,7 +42,6 @@ const logger = {
},
}
// Get database URL from environment
const CONNECTION_STRING = process.env.POSTGRES_URL ?? process.env.DATABASE_URL
if (!CONNECTION_STRING) {
console.error('❌ POSTGRES_URL or DATABASE_URL environment variable is required')
@@ -88,7 +86,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
return false
}
// Get user
const targetUser = await getUser(userEmail)
if (!targetUser) {
return false
@@ -96,7 +93,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
logger.info(`Found user: ${targetUser.email} (ID: ${targetUser.id})`)
// Get SSO providers for this user
const providers = await db
.select()
.from(ssoProvider)
@@ -112,11 +108,9 @@ async function deregisterSSOProvider(): Promise<boolean> {
logger.info(` - Provider ID: ${provider.providerId}, Domain: ${provider.domain}`)
}
// Check if specific provider ID was requested
const specificProviderId = process.env.SSO_PROVIDER_ID
if (specificProviderId) {
// Delete specific provider
const providerToDelete = providers.find((p) => p.providerId === specificProviderId)
if (!providerToDelete) {
logger.error(`Provider '${specificProviderId}' not found for user ${targetUser.email}`)
@@ -133,7 +127,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
`✅ Successfully deleted SSO provider '${specificProviderId}' for user ${targetUser.email}`
)
} else {
// Delete all providers for this user
await db.delete(ssoProvider).where(eq(ssoProvider.userId, targetUser.id))
logger.info(
@@ -171,7 +164,6 @@ async function main() {
}
}
// Handle script execution
main().catch((error) => {
logger.error('Script execution failed:', { error })
process.exit(1)

View File

@@ -38,7 +38,6 @@ import postgres from 'postgres'
import { v4 as uuidv4 } from 'uuid'
import { ssoProvider, user } from '../schema'
// Self-contained SSO types (matching Better Auth's structure)
interface SSOMapping {
id: string
email: string
@@ -108,7 +107,6 @@ interface SSOProviderConfig {
samlConfig?: SAMLConfig
}
// Simple console logger (no dependencies)
const logger = {
info: (message: string, meta?: any) => {
const timestamp = new Date().toISOString()
@@ -133,14 +131,12 @@ const logger = {
},
}
// Get database URL from environment
const CONNECTION_STRING = process.env.POSTGRES_URL ?? process.env.DATABASE_URL
if (!CONNECTION_STRING) {
console.error('❌ POSTGRES_URL or DATABASE_URL environment variable is required')
process.exit(1)
}
// Initialize database connection (following migration script pattern)
const postgresClient = postgres(CONNECTION_STRING, {
prepare: false,
idle_timeout: 20,
@@ -161,7 +157,6 @@ interface SSOProviderData {
organizationId?: string
}
// Self-contained configuration builder (no external dependencies)
function buildSSOConfigFromEnv(): SSOProviderConfig | null {
const enabled = process.env.SSO_ENABLED === 'true'
if (!enabled) return null
@@ -182,7 +177,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
providerType,
}
// Build field mapping
config.mapping = {
id:
process.env.SSO_MAPPING_ID ||
@@ -202,7 +196,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
image: process.env.SSO_MAPPING_IMAGE || (providerType === 'oidc' ? 'picture' : undefined),
}
// Build provider-specific configuration
if (providerType === 'oidc') {
const clientId = process.env.SSO_OIDC_CLIENT_ID
const clientSecret = process.env.SSO_OIDC_CLIENT_SECRET
@@ -225,7 +218,8 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
userInfoEndpoint: process.env.SSO_OIDC_USERINFO_ENDPOINT,
jwksEndpoint: process.env.SSO_OIDC_JWKS_ENDPOINT,
discoveryEndpoint:
process.env.SSO_OIDC_DISCOVERY_ENDPOINT || `${issuer}/.well-known/openid-configuration`,
process.env.SSO_OIDC_DISCOVERY_ENDPOINT ||
`${issuer.replace(/\/$/, '')}/.well-known/openid-configuration`,
}
} else if (providerType === 'saml') {
const entryPoint = process.env.SSO_SAML_ENTRY_POINT
@@ -237,7 +231,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
const callbackUrl = process.env.SSO_SAML_CALLBACK_URL || `${issuer}/callback`
// Use custom metadata if provided, otherwise generate default
let spMetadata = process.env.SSO_SAML_SP_METADATA
if (!spMetadata) {
spMetadata = `<?xml version="1.0" encoding="UTF-8"?>
@@ -263,7 +256,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
entityID: issuer,
},
}
// Optionally include IDP metadata if provided
const idpMetadata = process.env.SSO_SAML_IDP_METADATA
if (idpMetadata) {
config.samlConfig.idpMetadata = {
@@ -275,7 +267,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
return config
}
// Self-contained example environment variables function
function getExampleEnvVars(
providerType: 'oidc' | 'saml',
provider?: string
@@ -358,7 +349,6 @@ async function getAdminUser(): Promise<{ id: string; email: string } | null> {
async function registerSSOProvider(): Promise<boolean> {
try {
// Build configuration from environment variables
const ssoConfig = buildSSOConfigFromEnv()
if (!ssoConfig) {
@@ -381,7 +371,6 @@ async function registerSSOProvider(): Promise<boolean> {
return false
}
// Get admin user
const adminUser = await getAdminUser()
if (!adminUser) {
return false
@@ -394,7 +383,6 @@ async function registerSSOProvider(): Promise<boolean> {
adminUser: adminUser.email,
})
// Validate issuer URL (same as Better Auth does)
try {
new URL(ssoConfig.issuer)
} catch {
@@ -402,7 +390,97 @@ async function registerSSOProvider(): Promise<boolean> {
return false
}
// Check if provider already exists
if (ssoConfig.providerType === 'oidc' && ssoConfig.oidcConfig) {
const needsDiscovery =
!ssoConfig.oidcConfig.authorizationEndpoint ||
!ssoConfig.oidcConfig.tokenEndpoint ||
!ssoConfig.oidcConfig.jwksEndpoint
if (needsDiscovery) {
const discoveryUrl =
ssoConfig.oidcConfig.discoveryEndpoint ||
`${ssoConfig.issuer.replace(/\/$/, '')}/.well-known/openid-configuration`
logger.info('Fetching OIDC discovery document for missing endpoints...', {
discoveryUrl,
hasAuthEndpoint: !!ssoConfig.oidcConfig.authorizationEndpoint,
hasTokenEndpoint: !!ssoConfig.oidcConfig.tokenEndpoint,
hasJwksEndpoint: !!ssoConfig.oidcConfig.jwksEndpoint,
})
try {
const response = await fetch(discoveryUrl, {
headers: { Accept: 'application/json' },
})
if (!response.ok) {
logger.error('Failed to fetch OIDC discovery document', {
status: response.status,
statusText: response.statusText,
})
logger.error(
'Provide all endpoints explicitly via SSO_OIDC_AUTHORIZATION_ENDPOINT, SSO_OIDC_TOKEN_ENDPOINT, SSO_OIDC_JWKS_ENDPOINT'
)
return false
}
const discovery = await response.json()
ssoConfig.oidcConfig.authorizationEndpoint =
ssoConfig.oidcConfig.authorizationEndpoint || discovery.authorization_endpoint
ssoConfig.oidcConfig.tokenEndpoint =
ssoConfig.oidcConfig.tokenEndpoint || discovery.token_endpoint
ssoConfig.oidcConfig.userInfoEndpoint =
ssoConfig.oidcConfig.userInfoEndpoint || discovery.userinfo_endpoint
ssoConfig.oidcConfig.jwksEndpoint =
ssoConfig.oidcConfig.jwksEndpoint || discovery.jwks_uri
logger.info('Merged OIDC endpoints (user-provided + discovery)', {
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
userInfoEndpoint: ssoConfig.oidcConfig.userInfoEndpoint,
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
})
} catch (error) {
logger.error('Error fetching OIDC discovery document', {
error: error instanceof Error ? error.message : 'Unknown error',
discoveryUrl,
})
logger.error(
'Please provide explicit endpoints via SSO_OIDC_AUTHORIZATION_ENDPOINT, SSO_OIDC_TOKEN_ENDPOINT, SSO_OIDC_JWKS_ENDPOINT'
)
return false
}
} else {
logger.info('Using explicitly provided OIDC endpoints (all present)', {
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
userInfoEndpoint: ssoConfig.oidcConfig.userInfoEndpoint,
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
})
}
if (
!ssoConfig.oidcConfig.authorizationEndpoint ||
!ssoConfig.oidcConfig.tokenEndpoint ||
!ssoConfig.oidcConfig.jwksEndpoint
) {
const missing: string[] = []
if (!ssoConfig.oidcConfig.authorizationEndpoint)
missing.push('SSO_OIDC_AUTHORIZATION_ENDPOINT')
if (!ssoConfig.oidcConfig.tokenEndpoint) missing.push('SSO_OIDC_TOKEN_ENDPOINT')
if (!ssoConfig.oidcConfig.jwksEndpoint) missing.push('SSO_OIDC_JWKS_ENDPOINT')
logger.error('Missing required OIDC endpoints after discovery merge', {
missing,
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
})
logger.error(`Please provide: ${missing.join(', ')}`)
return false
}
}
const existingProviders = await db
.select()
.from(ssoProvider)
@@ -413,9 +491,8 @@ async function registerSSOProvider(): Promise<boolean> {
logger.info('Updating existing provider...')
}
// Build provider data (following Better Auth's exact structure)
const providerData: SSOProviderData = {
id: uuidv4(), // Generate unique ID
id: uuidv4(),
issuer: ssoConfig.issuer,
domain: ssoConfig.domain,
userId: adminUser.id,
@@ -423,7 +500,6 @@ async function registerSSOProvider(): Promise<boolean> {
organizationId: process.env.SSO_ORGANIZATION_ID || undefined,
}
// Build OIDC config (same as Better Auth endpoint)
if (ssoConfig.providerType === 'oidc' && ssoConfig.oidcConfig) {
const oidcConfig = {
issuer: ssoConfig.issuer,
@@ -445,7 +521,6 @@ async function registerSSOProvider(): Promise<boolean> {
providerData.oidcConfig = JSON.stringify(oidcConfig)
}
// Build SAML config (same as Better Auth endpoint)
if (ssoConfig.providerType === 'saml' && ssoConfig.samlConfig) {
const samlConfig = {
issuer: ssoConfig.issuer,
@@ -467,7 +542,6 @@ async function registerSSOProvider(): Promise<boolean> {
providerData.samlConfig = JSON.stringify(samlConfig)
}
// Insert or update the SSO provider record
if (existingProviders.length > 0) {
await db
.update(ssoProvider)
@@ -521,7 +595,6 @@ async function main() {
console.log('This script directly inserts SSO provider records into the database.')
console.log("It follows Better Auth's exact registerSSOProvider logic.\n")
// Register the SSO provider using direct database access
const success = await registerSSOProvider()
if (success) {
@@ -538,7 +611,6 @@ async function main() {
}
}
// Handle script execution
main().catch((error) => {
logger.error('Script execution failed:', { error })
process.exit(1)

View File

@@ -33,22 +33,20 @@ export interface LoggerConfig {
enabled?: boolean
}
/**
* Get environment variable value
* Works in any JavaScript runtime (Node.js, Bun, etc.)
*/
const getEnvVar = (key: string): string | undefined => {
const getNodeEnv = (): string => {
if (typeof process !== 'undefined' && process.env) {
return process.env[key]
return process.env.NODE_ENV || 'development'
}
return 'development'
}
const getLogLevel = (): string | undefined => {
if (typeof process !== 'undefined' && process.env) {
return process.env.LOG_LEVEL
}
return undefined
}
/**
* Get the current environment (development, production, test)
*/
const getNodeEnv = (): string => getEnvVar('NODE_ENV') || 'development'
/**
* Get the minimum log level from environment variable or use defaults
* - Development: DEBUG (show all logs)
@@ -56,7 +54,7 @@ const getNodeEnv = (): string => getEnvVar('NODE_ENV') || 'development'
* - Test: ERROR (only show errors in tests)
*/
const getMinLogLevel = (): LogLevel => {
const logLevelEnv = getEnvVar('LOG_LEVEL')
const logLevelEnv = getLogLevel()
if (logLevelEnv && Object.values(LogLevel).includes(logLevelEnv as LogLevel)) {
return logLevelEnv as LogLevel
}
@@ -120,7 +118,6 @@ const formatObject = (obj: unknown, isDev: boolean): string => {
stack: isDev ? obj.stack : undefined,
name: obj.name,
}
// Copy any additional enumerable properties from the error
for (const key of Object.keys(obj)) {
if (!(key in errorObj)) {
errorObj[key] = (obj as unknown as Record<string, unknown>)[key]
@@ -181,7 +178,6 @@ export class Logger {
private shouldLog(level: LogLevel): boolean {
if (!this.config.enabled) return false
// In production, only log on server-side (where window is undefined)
if (getNodeEnv() === 'production' && typeof window !== 'undefined') {
return false
}

View File

@@ -5,11 +5,29 @@ import { vi } from 'vitest'
* Mimics drizzle-orm's sql tagged template.
*/
export function createMockSql() {
return (strings: TemplateStringsArray, ...values: any[]) => ({
const sqlFn = (strings: TemplateStringsArray, ...values: any[]) => ({
strings,
values,
toSQL: () => ({ sql: strings.join('?'), params: values }),
})
// Add sql.raw method used by some queries
sqlFn.raw = (rawSql: string) => ({
rawSql,
toSQL: () => ({ sql: rawSql, params: [] }),
})
// Add sql.join method used to combine multiple SQL fragments
sqlFn.join = (fragments: any[], separator: any) => ({
fragments,
separator,
toSQL: () => ({
sql: fragments.map((f) => f?.toSQL?.()?.sql || String(f)).join(separator?.rawSql || ', '),
params: fragments.flatMap((f) => f?.toSQL?.()?.params || []),
}),
})
return sqlFn
}
/**