Files
WeHub Mirror 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
WeHub snapshot of cb28d14c6f2c081de7a0d8729a8c816c9adef67a
2026-08-10 11:17:50 +08:00

224 lines
8.1 KiB
TypeScript

import { setRecordValue } from '@/lib/core/utils/records'
import type {
CodePlaceholderBinding,
CodePlaceholderCompilationContext,
CodePlaceholderOccurrence,
CodePlaceholderRuntimeBinding,
CompiledCodePlaceholders,
InternalCompileCodePlaceholdersInput,
ResolvedCodePlaceholderOccurrence,
ResolvedCodePlaceholderValueOccurrence,
} from '@/lib/execution/code-placeholders/types'
const MAX_PLACEHOLDERS = 10_000
const PLACEHOLDER_PATTERN = /\{\{([^}]+)\}\}/g
export class CodePlaceholderCompileError extends Error {
readonly line?: number
readonly column?: number
constructor(message: string, code?: string, offset?: number) {
super(message)
this.name = 'CodePlaceholderCompileError'
if (code !== undefined && offset !== undefined) {
const prefix = code.slice(0, offset)
this.line = prefix.split('\n').length
this.column = offset - prefix.lastIndexOf('\n')
}
}
}
export function collectCodePlaceholderOccurrences(code: string): CodePlaceholderOccurrence[] {
const occurrences: CodePlaceholderOccurrence[] = []
let match: RegExpExecArray | null
PLACEHOLDER_PATTERN.lastIndex = 0
while ((match = PLACEHOLDER_PATTERN.exec(code)) !== null) {
const name = match[1].trim()
if (!name) continue
occurrences.push({
start: match.index,
end: match.index + match[0].length,
raw: match[0],
name,
})
if (occurrences.length > MAX_PLACEHOLDERS) {
throw new CodePlaceholderCompileError(
`Code contains more than ${MAX_PLACEHOLDERS} variable placeholders`
)
}
}
return occurrences
}
function chooseNamespace(code: string, reservedNames: ReadonlySet<string>): string {
const occupied = new Set<number>()
const collectOccupiedNamespaces = (value: string): void => {
for (const match of value.matchAll(/__sim_code_(\d+)/g)) occupied.add(Number(match[1]))
}
collectOccupiedNamespaces(code)
const normalizedCode = code.normalize('NFKC')
if (normalizedCode !== code) collectOccupiedNamespaces(normalizedCode)
for (const name of reservedNames) {
const match = /^__sim_code_(\d+)(?:_|$)/.exec(name)
if (match) occupied.add(Number(match[1]))
}
for (let index = 0; ; index += 1) {
if (!occupied.has(index)) return `__sim_code_${index}`
}
}
function stringifyResolverValues(
params: Record<string, unknown>,
environmentVariables: Record<string, string>,
referencedNames: ReadonlySet<string>
): Record<string, string> {
const values: Record<string, string> = Object.create(null)
for (const [name, value] of Object.entries(params)) {
if (!referencedNames.has(name)) continue
if (value !== undefined && value !== null) setRecordValue(values, name, String(value))
}
for (const [name, value] of Object.entries(environmentVariables)) {
if (!referencedNames.has(name)) continue
if (value !== undefined && value !== null) setRecordValue(values, name, value)
}
return values
}
export function createCodePlaceholderCompilationContext(
input: InternalCompileCodePlaceholdersInput,
options: { identifierSuffix?: string } = {}
): CodePlaceholderCompilationContext {
const params = input.params ?? {}
const environmentVariables = input.environmentVariables ?? {}
const occurrences = collectCodePlaceholderOccurrences(input.code)
const referencedNames = new Set(occurrences.map((occurrence) => occurrence.name))
const values = stringifyResolverValues(params, environmentVariables, referencedNames)
const reservedNames = new Set(input.reservedNames ?? [])
for (const name of Object.keys(environmentVariables)) reservedNames.add(name)
for (const name of Object.keys(params)) reservedNames.add(name)
const namespace =
occurrences.length > 0 ? chooseNamespace(input.code, reservedNames) : '__sim_code_0'
const bindingByName = new Map<string, CodePlaceholderBinding>()
const runtimeBindingByKind = new Map<
CodePlaceholderRuntimeBinding['kind'],
CodePlaceholderRuntimeBinding
>()
const resolvedSecretNameOffsets = new Map<string, number>()
const privateInputs: Array<{ environmentVariable: string; content: string }> = []
const privateInputByContent = new Map<string, { environmentVariable: string; content: string }>()
const internalIdentifiers = new Set<string>()
const hasValue = (name: string): boolean =>
input.analysisOnly === true || Object.hasOwn(values, name)
const recordResolution = (occurrence: CodePlaceholderOccurrence): void => {
if (!input.analysisOnly && !Object.hasOwn(environmentVariables, occurrence.name)) return
const currentOffset = resolvedSecretNameOffsets.get(occurrence.name)
if (currentOffset === undefined || occurrence.start < currentOffset) {
resolvedSecretNameOffsets.set(occurrence.name, occurrence.start)
}
}
const resolveValue = (
occurrence: CodePlaceholderOccurrence
): ResolvedCodePlaceholderValueOccurrence | undefined => {
if (!hasValue(occurrence.name)) return undefined
recordResolution(occurrence)
return {
...occurrence,
value: Object.hasOwn(values, occurrence.name) ? values[occurrence.name] : '',
}
}
const ensureBinding = (name: string, value: string): CodePlaceholderBinding => {
const existing = bindingByName.get(name)
if (existing) return existing
const bindingName = `${namespace}_binding_${bindingByName.size}${options.identifierSuffix ?? ''}`
reservedNames.add(bindingName)
internalIdentifiers.add(bindingName)
const binding = { name: bindingName, value }
bindingByName.set(name, binding)
return binding
}
return {
occurrences,
hasValue,
resolveValue,
runtimeBindingFor(kind) {
const existing = runtimeBindingByKind.get(kind)
if (existing) return existing
const name = `${namespace}_runtime_${runtimeBindingByKind.size}${options.identifierSuffix ?? ''}`
reservedNames.add(name)
internalIdentifiers.add(name)
const binding = { name, kind }
runtimeBindingByKind.set(kind, binding)
return binding
},
registerInternalIdentifier(identifier) {
internalIdentifiers.add(identifier)
},
resolve(occurrence): ResolvedCodePlaceholderOccurrence | undefined {
const resolved = resolveValue(occurrence)
if (!resolved) return undefined
const binding = ensureBinding(occurrence.name, resolved.value)
return {
...resolved,
bindingName: binding.name,
}
},
createPrivateInput(content) {
const existing = privateInputByContent.get(content)
if (existing) return existing
const environmentVariable = `${namespace}_input_${privateInputs.length}${options.identifierSuffix ?? ''}`
reservedNames.add(environmentVariable)
internalIdentifiers.add(environmentVariable)
const privateInput = { environmentVariable, content }
privateInputs.push(privateInput)
privateInputByContent.set(content, privateInput)
return privateInput
},
finish(code): CompiledCodePlaceholders {
return {
code,
bindings: [...bindingByName.values()],
privateInputs,
runtimeBindings: [...runtimeBindingByKind.values()],
resolvedSecretNames: [...resolvedSecretNameOffsets]
.sort((left, right) => left[1] - right[1])
.map(([name]) => name),
internalIdentifiers: [...internalIdentifiers],
}
},
}
}
export interface SourceEdit {
start: number
end: number
text: string
}
export function applySourceEdits(code: string, edits: SourceEdit[]): string {
if (edits.length === 0) return code
const sorted = [...edits].sort((left, right) => left.start - right.start || left.end - right.end)
let cursor = 0
let output = ''
for (const edit of sorted) {
if (edit.start < cursor || edit.end < edit.start || edit.end > code.length) {
throw new CodePlaceholderCompileError('Overlapping code placeholder transformations')
}
output += code.slice(cursor, edit.start)
output += edit.text
cursor = edit.end
}
return output + code.slice(cursor)
}
export function isOffsetInRanges(offset: number, ranges: ReadonlyArray<[number, number]>): boolean {
return ranges.some(([start, end]) => offset >= start && offset < end)
}