Files
orca/src/relay/remote-artifact-cli-input.ts

96 lines
3.2 KiB
TypeScript

import { basename, extname, resolve } from 'node:path'
import type { RemoteArtifactInput } from '../shared/artifact-cli-bridge'
import { ARTIFACT_CLI_MAX_RPC_BYTES } from '../shared/artifacts'
import { readArtifactFileWithinLimit } from '../shared/artifact-file-read'
import { MAX_MESSAGE_SIZE } from './protocol'
export type PreparedRemoteArtifactCliInput = {
stdin?: string
artifactInput?: RemoteArtifactInput
}
const BOOLEAN_FLAGS = new Set(['help', 'json'])
const VALUE_FLAGS = new Set(['api-url', 'environment', 'file', 'pairing-code'])
function parseArtifactInvocation(argv: string[]): { command: string; file?: string } | null {
const positionals: string[] = []
let file: string | undefined
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index]
if (!token.startsWith('--')) {
positionals.push(token)
continue
}
const assignment = token.slice(2)
const equalsIndex = assignment.indexOf('=')
const flag = equalsIndex === -1 ? assignment : assignment.slice(0, equalsIndex)
if (flag === 'file' && equalsIndex !== -1) {
file = assignment.slice(equalsIndex + 1)
continue
}
if (equalsIndex !== -1 || BOOLEAN_FLAGS.has(flag)) {
continue
}
if (VALUE_FLAGS.has(flag) && argv[index + 1] && !argv[index + 1].startsWith('--')) {
if (flag === 'file') {
file = argv[index + 1]
}
index += 1
}
}
if (positionals[0] !== 'artifacts' || !['share', 'update', 'unshare'].includes(positionals[1])) {
return null
}
return { command: positionals[1], file: file ?? positionals[2] }
}
function contentTypeForPath(path: string): NonNullable<RemoteArtifactInput['contentType']> | null {
const extension = extname(path).toLowerCase()
return ['.html', '.htm'].includes(extension)
? 'text/html'
: ['.md', '.markdown'].includes(extension)
? 'text/markdown'
: null
}
export async function prepareRemoteArtifactCliInput(
argv: string[],
cwd: string
): Promise<PreparedRemoteArtifactCliInput> {
const invocation = parseArtifactInvocation(argv)
if (!invocation || argv.includes('--help')) {
return {}
}
if (!invocation.file) {
return {}
}
const sourceKey = resolve(cwd, invocation.file)
if (invocation.command === 'unshare') {
return { artifactInput: { sourceKey, fileName: basename(sourceKey) } }
}
const contentType = contentTypeForPath(sourceKey)
if (!contentType) {
throw new Error('Artifacts must be HTML or Markdown files.')
}
const result = await readArtifactFileWithinLimit(sourceKey, ARTIFACT_CLI_MAX_RPC_BYTES)
if (result.status === 'not-file') {
throw new Error('Artifact file was not found or is not a file.')
}
if (result.status === 'too-large') {
throw new Error(
'Artifact is too large for the Orca CLI transport. Use the browser upload page instead.'
)
}
if (result.status === 'empty') {
throw new Error('Artifact file is empty.')
}
const prepared = {
stdin: result.content,
artifactInput: { sourceKey, fileName: basename(sourceKey), contentType }
}
if (Buffer.byteLength(JSON.stringify(prepared), 'utf8') > MAX_MESSAGE_SIZE - 64 * 1024) {
throw new Error('Artifact content exceeds the SSH relay message limit.')
}
return prepared
}