6bf8bebf51
CI / Test and Build (push) Failing after 1s
CI / Migrate Dev DB (push) Has been skipped
CI / Migrate DB (push) Has been skipped
CodeQL / Analyze actions (push) Has been cancelled
CodeQL / Analyze javascript-typescript (push) Has been cancelled
CI / Detect Version (push) Has been cancelled
CI / Detect Desktop Changes (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/cron.Dockerfile, ubuntu-latest, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build AMD64 (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build AMD64 (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/cron.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/db.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/pii.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-4vcpu-ubuntu-2404-arm, ./docker/realtime.Dockerfile, ubuntu-24.04-arm, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (blacksmith-8vcpu-ubuntu-2404-arm, ./docker/app.Dockerfile, linux-arm64-8-core, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
Helm Chart / Lint, test, and validate chart (push) Has been cancelled
Helm Chart / Chart version bumped (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Build Dev ECR (blacksmith-8vcpu-ubuntu-2404, ./docker/app.Dockerfile, ECR_APP, linux-x64-8-core) (push) Has been cancelled
CI / Promote Images (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/cron) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-2vcpu-ubuntu-2404, ./docker/db.Dockerfile, ECR_MIGRATIONS, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/pii.Dockerfile, ECR_PII, ubuntu-latest) (push) Has been cancelled
CI / Build Dev ECR (blacksmith-4vcpu-ubuntu-2404, ./docker/realtime.Dockerfile, ECR_REALTIME, ubuntu-latest) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Check Desktop Signing Secrets (push) Has been cancelled
CI / Desktop Release (push) Has been cancelled
CI / Create Desktop Prerelease (push) Has been cancelled
CI / Desktop Prerelease Build (push) Has been cancelled
CI / Publish Desktop Prerelease (push) Has been cancelled
CI / Prune Desktop Prereleases (push) Has been cancelled
Helm Chart / Install on kind and run helm test (push) Has been cancelled
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
import { isLikelyReferenceSegment } from '@/lib/workflows/sanitization/references'
|
|
import { REFERENCE } from '@/executor/constants'
|
|
|
|
/**
|
|
* Creates a regex pattern for matching variable references.
|
|
* Uses [^<>]+ to prevent matching across nested brackets (e.g., "<3 <real.ref>" matches separately).
|
|
*/
|
|
export function createReferencePattern(): RegExp {
|
|
return new RegExp(
|
|
`${REFERENCE.START}([^${REFERENCE.START}${REFERENCE.END}]+)${REFERENCE.END}`,
|
|
'g'
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Creates a regex pattern for matching environment variables {{variable}}
|
|
*/
|
|
export function createEnvVarPattern(): RegExp {
|
|
return new RegExp(`\\${REFERENCE.ENV_VAR_START}([^}]+)\\${REFERENCE.ENV_VAR_END}`, 'g')
|
|
}
|
|
|
|
export interface EnvVarResolveOptions {
|
|
allowEmbedded?: boolean
|
|
resolveExactMatch?: boolean
|
|
trimKeys?: boolean
|
|
onMissing?: 'keep' | 'throw' | 'empty'
|
|
deep?: boolean
|
|
missingKeys?: string[]
|
|
onResolved?: (name: string, value: string) => void
|
|
}
|
|
|
|
/**
|
|
* Standard defaults for env var resolution across all contexts.
|
|
*
|
|
* - `resolveExactMatch: true` - Resolves `{{VAR}}` when it's the entire value
|
|
* - `allowEmbedded: true` - Resolves `{{VAR}}` embedded in strings like `https://{{HOST}}/api`
|
|
* - `trimKeys: true` - `{{ VAR }}` works the same as `{{VAR}}` (whitespace tolerant)
|
|
* - `onMissing: 'keep'` - Unknown patterns pass through (e.g., Grafana's `{{instance}}`)
|
|
* - `deep: false` - Only processes strings by default; set `true` for nested objects
|
|
*/
|
|
export const ENV_VAR_RESOLVE_DEFAULTS: Required<
|
|
Omit<EnvVarResolveOptions, 'missingKeys' | 'onResolved'>
|
|
> = {
|
|
resolveExactMatch: true,
|
|
allowEmbedded: true,
|
|
trimKeys: true,
|
|
onMissing: 'keep',
|
|
deep: false,
|
|
} as const
|
|
|
|
/**
|
|
* Resolve {{ENV_VAR}} references in values using provided env vars.
|
|
*/
|
|
export function resolveEnvVarReferences(
|
|
value: unknown,
|
|
envVars: Record<string, string>,
|
|
options: EnvVarResolveOptions = {}
|
|
): unknown {
|
|
const {
|
|
allowEmbedded = ENV_VAR_RESOLVE_DEFAULTS.allowEmbedded,
|
|
resolveExactMatch = ENV_VAR_RESOLVE_DEFAULTS.resolveExactMatch,
|
|
trimKeys = ENV_VAR_RESOLVE_DEFAULTS.trimKeys,
|
|
onMissing = ENV_VAR_RESOLVE_DEFAULTS.onMissing,
|
|
deep = ENV_VAR_RESOLVE_DEFAULTS.deep,
|
|
} = options
|
|
|
|
if (typeof value === 'string') {
|
|
if (resolveExactMatch) {
|
|
const exactMatchPattern = new RegExp(
|
|
`^\\${REFERENCE.ENV_VAR_START}([^}]+)\\${REFERENCE.ENV_VAR_END}$`
|
|
)
|
|
const exactMatch = exactMatchPattern.exec(value)
|
|
if (exactMatch) {
|
|
const envKey = trimKeys ? exactMatch[1].trim() : exactMatch[1]
|
|
const envValue = envVars[envKey]
|
|
if (envValue !== undefined) {
|
|
if (Object.hasOwn(envVars, envKey)) options.onResolved?.(envKey, envValue)
|
|
return envValue
|
|
}
|
|
if (options.missingKeys) options.missingKeys.push(envKey)
|
|
if (onMissing === 'throw') {
|
|
throw new Error(`Environment variable "${envKey}" was not found`)
|
|
}
|
|
if (onMissing === 'empty') {
|
|
return ''
|
|
}
|
|
return value
|
|
}
|
|
}
|
|
|
|
if (!allowEmbedded) return value
|
|
|
|
const envVarPattern = createEnvVarPattern()
|
|
return value.replace(envVarPattern, (match, varName) => {
|
|
const envKey = trimKeys ? String(varName).trim() : String(varName)
|
|
const envValue = envVars[envKey]
|
|
if (envValue !== undefined) {
|
|
if (Object.hasOwn(envVars, envKey)) options.onResolved?.(envKey, envValue)
|
|
return envValue
|
|
}
|
|
if (options.missingKeys) options.missingKeys.push(envKey)
|
|
if (onMissing === 'throw') {
|
|
throw new Error(`Environment variable "${envKey}" was not found`)
|
|
}
|
|
if (onMissing === 'empty') {
|
|
return ''
|
|
}
|
|
return match
|
|
})
|
|
}
|
|
|
|
if (deep && Array.isArray(value)) {
|
|
return value.map((item) => resolveEnvVarReferences(item, envVars, options))
|
|
}
|
|
|
|
if (deep && value !== null && typeof value === 'object') {
|
|
const resolved: Record<string, any> = {}
|
|
for (const [key, val] of Object.entries(value)) {
|
|
resolved[key] = resolveEnvVarReferences(val, envVars, options)
|
|
}
|
|
return resolved
|
|
}
|
|
|
|
return value
|
|
}
|
|
|
|
/**
|
|
* Creates a regex pattern for matching workflow variables <variable.name>
|
|
* Captures the variable name (after "variable.") in group 1
|
|
*/
|
|
export function createWorkflowVariablePattern(): RegExp {
|
|
return new RegExp(
|
|
`${REFERENCE.START}${REFERENCE.PREFIX.VARIABLE}\\${REFERENCE.PATH_DELIMITER}([^${REFERENCE.START}${REFERENCE.END}]+)${REFERENCE.END}`,
|
|
'g'
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Combined pattern matching both <reference> and {{env_var}}
|
|
*/
|
|
export function createCombinedPattern(): RegExp {
|
|
return new RegExp(
|
|
`${REFERENCE.START}[^${REFERENCE.START}${REFERENCE.END}]+${REFERENCE.END}|` +
|
|
`\\${REFERENCE.ENV_VAR_START}[^}]+\\${REFERENCE.ENV_VAR_END}`,
|
|
'g'
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Replaces variable references with smart validation.
|
|
* Distinguishes < operator from < bracket using isLikelyReferenceSegment.
|
|
*/
|
|
export function replaceValidReferences(
|
|
template: string,
|
|
replacer: (match: string, index: number, template: string) => string
|
|
): string {
|
|
const pattern = createReferencePattern()
|
|
|
|
return template.replace(pattern, (match, _content, index) => {
|
|
if (!isLikelyReferenceSegment(match)) {
|
|
return match
|
|
}
|
|
return replacer(match, index, template)
|
|
})
|
|
}
|