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

215 lines
6.0 KiB
TypeScript

import type { SetupScriptImportCandidate, SetupScriptImportFileRead } from './setup-script-imports'
import {
isSetupScriptImportFieldWithinLimit,
SETUP_SCRIPT_IMPORT_MAX_FIELD_BYTES,
SETUP_SCRIPT_IMPORT_MAX_FIELD_CODE_UNITS,
SETUP_SCRIPT_IMPORT_MAX_TOML_LINES,
SETUP_SCRIPT_IMPORT_MAX_UNSUPPORTED_FIELDS
} from './setup-script-import-limits'
import { measureUtf8ByteLength } from './utf8-byte-limits'
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 = normalizeCodexScript(parsed.setupScript)
if (!setup) {
return null
}
return {
provider: 'codex',
label: 'Codex environment',
files: [CODEX_ENVIRONMENT_PATH],
setup,
archive: normalizeCodexScript(parsed.cleanupScript) || undefined,
unsupportedFields: parsed.unsupportedFields
}
}
function parseCodexEnvironmentToml(content: string): CodexEnvironmentToml {
if (countTomlLines(content) > SETUP_SCRIPT_IMPORT_MAX_TOML_LINES) {
return { unsupportedFields: [] }
}
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)) {
pushUnsupportedField(unsupportedFields, 'actions')
}
const sectionMatch = trimmed.match(/^\[([A-Za-z0-9_.-]+)\]\s*(?:#.*)?$/)
if (sectionMatch) {
section = sectionMatch[1]
if (section === 'actions' || section.startsWith('actions.')) {
pushUnsupportedField(unsupportedFields, `[${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 } {
const chunks: string[] = []
let retainedBytes = 0
let retainedCodeUnits = 0
let remainder = firstLineRemainder
let oversized = false
const append = (value: string): boolean => {
if (retainedCodeUnits + value.length > SETUP_SCRIPT_IMPORT_MAX_FIELD_CODE_UNITS) {
return false
}
const measurement = measureUtf8ByteLength(value, {
stopAfterBytes: SETUP_SCRIPT_IMPORT_MAX_FIELD_BYTES - retainedBytes
})
if (measurement.exceededLimit) {
return false
}
chunks.push(value)
retainedBytes += measurement.byteLength
retainedCodeUnits += value.length
return true
}
for (let index = startLineIndex; index < lines.length; index++) {
if (index > startLineIndex) {
remainder = lines[index]
}
const closeIndex = remainder.indexOf(delimiter)
if (closeIndex >= 0) {
if (!oversized && !append(remainder.slice(0, closeIndex))) {
oversized = true
}
return {
value: oversized ? '' : chunks.join(''),
endLineIndex: index
}
}
if (!oversized && !append(`${remainder}\n`)) {
oversized = true
chunks.length = 0
}
}
return {
value: oversized ? '' : chunks.join('').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
}
function normalizeCodexScript(value: string | undefined): string {
if (!value || !isSetupScriptImportFieldWithinLimit(value)) {
return ''
}
return value.trim()
}
function countTomlLines(content: string): number {
let lines = 1
for (let index = 0; index < content.length; index++) {
if (content.charCodeAt(index) === 10 && ++lines > SETUP_SCRIPT_IMPORT_MAX_TOML_LINES) {
return lines
}
}
return lines
}
function pushUnsupportedField(fields: string[], value: string): void {
if (fields.length < SETUP_SCRIPT_IMPORT_MAX_UNSUPPORTED_FIELDS) {
fields.push(value)
}
}