orca/src/shared/setup-script-import-codex-e...

154 lines
4.2 KiB
TypeScript

import type { SetupScriptImportCandidate, SetupScriptImportFileRead } from './setup-script-imports'
const CODEX_ENVIRONMENT_PATH = '.codex/environments/environment.toml'
type CodexEnvironmentToml = {
setupScript?: string
cleanupScript?: string
unsupportedFields: string[]
}
export async function inspectCodexEnvironmentConfig(
readFile: SetupScriptImportFileRead
): Promise<SetupScriptImportCandidate | null> {
const content = await readFile(CODEX_ENVIRONMENT_PATH)
if (!content) {
return null
}
const parsed = parseCodexEnvironmentToml(content)
const setup = parsed.setupScript?.trim()
if (!setup) {
return null
}
return {
provider: 'codex',
label: 'Codex environment',
files: [CODEX_ENVIRONMENT_PATH],
setup,
archive: parsed.cleanupScript?.trim() || undefined,
unsupportedFields: parsed.unsupportedFields
}
}
function parseCodexEnvironmentToml(content: string): CodexEnvironmentToml {
const lines = content.split(/\r?\n/)
const unsupportedFields: string[] = []
let section = ''
let setupScript: string | undefined
let cleanupScript: string | undefined
for (let index = 0; index < lines.length; index++) {
const line = lines[index]
const trimmed = line.trim()
if (/^actions\s*=/.test(trimmed)) {
unsupportedFields.push('actions')
}
const sectionMatch = trimmed.match(/^\[([A-Za-z0-9_.-]+)\]\s*(?:#.*)?$/)
if (sectionMatch) {
section = sectionMatch[1]
if (section === 'actions' || section.startsWith('actions.')) {
unsupportedFields.push(`[${section}]`)
}
continue
}
if (section !== 'setup' && section !== 'cleanup') {
continue
}
const assignment = line.match(/^\s*script\s*=\s*(.*)$/)
if (!assignment) {
continue
}
const parsed = parseTomlStringValue(lines, index, assignment[1])
index = parsed.endLineIndex
if (section === 'setup') {
setupScript = parsed.value
} else {
cleanupScript = parsed.value
}
}
return { setupScript, cleanupScript, unsupportedFields }
}
function parseTomlStringValue(
lines: string[],
startLineIndex: number,
rawValue: string
): { value: string; endLineIndex: number } {
const value = rawValue.trimStart()
if (value.startsWith('"""') || value.startsWith("'''")) {
const delimiter = value.startsWith('"""') ? '"""' : "'''"
return parseTomlMultilineString(lines, startLineIndex, value.slice(3), delimiter)
}
if (value.startsWith('"')) {
return { value: parseTomlBasicString(value), endLineIndex: startLineIndex }
}
if (value.startsWith("'")) {
return { value: parseTomlLiteralString(value), endLineIndex: startLineIndex }
}
return { value: value.replace(/\s+#.*$/, '').trim(), endLineIndex: startLineIndex }
}
function parseTomlMultilineString(
lines: string[],
startLineIndex: number,
firstLineRemainder: string,
delimiter: '"""' | "'''"
): { value: string; endLineIndex: number } {
let content = ''
let remainder = firstLineRemainder
for (let index = startLineIndex; index < lines.length; index++) {
if (index > startLineIndex) {
remainder = lines[index]
}
const closeIndex = remainder.indexOf(delimiter)
if (closeIndex >= 0) {
return {
value: content + remainder.slice(0, closeIndex),
endLineIndex: index
}
}
content += `${remainder}\n`
}
return { value: content.trimEnd(), endLineIndex: lines.length - 1 }
}
function parseTomlBasicString(value: string): string {
const raw = value.slice(0, findTomlStringEnd(value, '"') + 1)
try {
return JSON.parse(raw) as string
} catch {
return raw.slice(1, -1)
}
}
function parseTomlLiteralString(value: string): string {
const end = findTomlStringEnd(value, "'")
return value.slice(1, end)
}
function findTomlStringEnd(value: string, quote: '"' | "'"): number {
for (let index = 1; index < value.length; index++) {
if (value[index] !== quote) {
continue
}
if (quote === "'" || !isEscaped(value, index)) {
return index
}
}
return value.length - 1
}
function isEscaped(value: string, index: number): boolean {
let slashCount = 0
for (let cursor = index - 1; cursor >= 0 && value[cursor] === '\\'; cursor--) {
slashCount++
}
return slashCount % 2 === 1
}