Skip to content

Commit f3c7b14

Browse files
committed
fix(storage): address review feedback and fix env mock for CI
- Add envBoolean to the shared env test mock (createEnvMock) so config.ts's forcePathStyle coercion resolves — fixes failing knowledge/utils.test.ts - Declare S3_FORCE_PATH_STYLE as z.string() (every other env var's pattern); it's coerced via envBoolean at the consumption site, avoiding a boolean type that never matches the string process.env value - Log path-style from S3_CONFIG.forcePathStyle (envBoolean) instead of a separate isTruthy call, so the startup log can't disagree with the client - Make buildObjectFallbackUrl honor forcePathStyle: virtual-hosted-style URL (bucket as subdomain) for R2, path-style only when forcePathStyle is set
1 parent 74afb56 commit f3c7b14

4 files changed

Lines changed: 27 additions & 7 deletions

File tree

apps/sim/lib/core/config/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export const env = createEnv({
219219
S3_OG_IMAGES_BUCKET_NAME: z.string().optional(), // S3 bucket for OpenGraph images
220220
S3_WORKSPACE_LOGOS_BUCKET_NAME: z.string().optional(), // S3 bucket for workspace logos
221221
S3_ENDPOINT: z.string().optional(), // Custom endpoint for S3-compatible storage (Cloudflare R2, MinIO, Backblaze B2). Leave unset for AWS S3
222-
S3_FORCE_PATH_STYLE: z.boolean().optional(), // Force path-style addressing (MinIO/Ceph RGW). Defaults to false (AWS S3, R2)
222+
S3_FORCE_PATH_STYLE: z.string().optional(), // Force path-style addressing (MinIO/Ceph RGW). Defaults to false (AWS S3, R2). Coerced via envBoolean at the consumption site
223223

224224
// Cloud Storage - Azure Blob
225225
AZURE_ACCOUNT_NAME: z.string().optional(), // Azure storage account name

apps/sim/lib/uploads/core/setup.server.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { existsSync } from 'fs'
22
import { mkdir } from 'fs/promises'
33
import path, { join } from 'path'
44
import { createLogger } from '@sim/logger'
5-
import { env, isTruthy } from '@/lib/core/config/env'
6-
import { getStorageProvider, USE_BLOB_STORAGE, USE_S3_STORAGE } from '@/lib/uploads/config'
5+
import { env } from '@/lib/core/config/env'
6+
import {
7+
getStorageProvider,
8+
S3_CONFIG,
9+
USE_BLOB_STORAGE,
10+
USE_S3_STORAGE,
11+
} from '@/lib/uploads/config'
712

813
const logger = createLogger('UploadsSetup')
914

@@ -82,7 +87,7 @@ if (typeof process !== 'undefined') {
8287

8388
if (env.S3_ENDPOINT) {
8489
logger.info(
85-
`Using S3-compatible endpoint: ${env.S3_ENDPOINT} (path-style: ${isTruthy(env.S3_FORCE_PATH_STYLE)})`
90+
`Using S3-compatible endpoint: ${env.S3_ENDPOINT} (path-style: ${S3_CONFIG.forcePathStyle})`
8691
)
8792
}
8893
} else {

apps/sim/lib/uploads/providers/s3/client.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,20 @@ export async function getS3MultipartPartUrls(
390390

391391
/**
392392
* Build a fallback object URL for when the SDK omits `Location` on multipart
393-
* completion. Honors a custom `S3_CONFIG.endpoint` (R2/MinIO) with path-style
394-
* addressing; otherwise falls back to the AWS virtual-hosted-style host.
393+
* completion. For a custom `S3_CONFIG.endpoint` it matches the configured
394+
* addressing mode — path-style for MinIO/Ceph (`forcePathStyle`), virtual-hosted
395+
* (bucket as a subdomain) for R2 and friends. Falls back to the AWS
396+
* virtual-hosted host when no custom endpoint is set.
395397
*/
396398
function buildObjectFallbackUrl(bucket: string, region: string, key: string): string {
397399
if (S3_CONFIG.endpoint) {
398400
const base = S3_CONFIG.endpoint.replace(/\/+$/, '')
399-
return `${base}/${bucket}/${key}`
401+
if (S3_CONFIG.forcePathStyle) {
402+
return `${base}/${bucket}/${key}`
403+
}
404+
const url = new URL(base)
405+
url.hostname = `${bucket}.${url.hostname}`
406+
return `${url.origin}/${key}`
400407
}
401408
return `https://${bucket}.s3.${region}.amazonaws.com/${key}`
402409
}

packages/testing/src/mocks/env.mock.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ export function createEnvMock(overrides: Record<string, string | undefined> = {}
5353
typeof value === 'string'
5454
? value.toLowerCase() === 'false' || value === '0'
5555
: value === false,
56+
envBoolean: (value: boolean | string | undefined | null): boolean | undefined => {
57+
if (typeof value === 'boolean') return value
58+
if (value === undefined || value === null || value === '') return undefined
59+
const normalized = String(value).trim().toLowerCase()
60+
return (
61+
normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on'
62+
)
63+
},
5664
envNumber: (
5765
value: number | string | undefined | null,
5866
fallback: number,

0 commit comments

Comments
 (0)