openpencil/packages/cli/src/commands/eval.ts
Danila Poyarkov 51eae14e4b chore(tests): remove targeted non-null assertions
- Add shared test helpers for required nodes and child ids
- Clean non-null assertions from noisy render, nudge, text undo, and fig roundtrip tests
- Enforce the non-null assertion ban for the cleaned test files
- Remove stale eslint disables around empty functions and broad mock transport typing
2026-05-06 01:14:50 +03:00

105 lines
3.3 KiB
TypeScript

import { isAppMode, requireFile, rpc } from '#cli/app-client'
import { printError } from '#cli/format'
import { loadDocument } from '#cli/headless'
import { defineCommand } from 'citty'
import { FigmaAPI } from '@open-pencil/core/figma-api'
function printResult(value: unknown, json: boolean) {
if (json || !process.stdout.isTTY) {
console.log(JSON.stringify(value, null, 2))
} else {
console.log(value)
}
}
function serializeResult(value: unknown): unknown {
if (value === undefined || value === null) return value
if (typeof value === 'object' && 'toJSON' in value && typeof value.toJSON === 'function') {
return value.toJSON()
}
if (Array.isArray(value)) return value.map(serializeResult)
return value
}
export default defineCommand({
meta: { description: 'Execute JavaScript with Figma plugin API' },
args: {
file: {
type: 'positional',
description: 'Document file path (omit to connect to running app)',
required: false
},
code: { type: 'string', alias: 'c', description: 'JavaScript code to execute' },
stdin: { type: 'boolean', description: 'Read code from stdin' },
write: { type: 'boolean', alias: 'w', description: 'Write changes back to the input file' },
output: {
type: 'string',
alias: 'o',
description: 'Write to a different file',
required: false
},
json: { type: 'boolean', description: 'Output as JSON' },
quiet: { type: 'boolean', alias: 'q', description: 'Suppress output' }
},
async run({ args }) {
let code = args.code
if (args.stdin) {
const chunks: Buffer[] = []
for await (const chunk of process.stdin) chunks.push(chunk as Buffer)
code = Buffer.concat(chunks).toString('utf-8')
}
if (!code) {
printError('Provide code via --code or --stdin')
process.exit(1)
}
if (isAppMode(args.file)) {
const result = await rpc('eval', { code })
if (!args.quiet && result !== undefined && result !== null) {
printResult(result, !!args.json)
}
return
}
const file = requireFile(args.file)
const graph = await loadDocument(file)
const figma = new FigmaAPI(graph)
type AsyncFunctionConstructor = new (
...args: string[]
) => (...args: unknown[]) => Promise<unknown>
const AsyncFunction = Object.getPrototypeOf(async () => undefined)
.constructor as AsyncFunctionConstructor
const wrappedCode = code.trim().startsWith('return')
? code
: `return (async () => { ${code} })()`
let result: unknown
try {
const fn = new AsyncFunction('figma', wrappedCode)
result = await fn(figma)
} catch (err) {
printError(err instanceof Error ? err.message : String(err))
process.exit(1)
}
if (!args.quiet && result !== undefined) {
printResult(serializeResult(result), !!args.json)
}
if (args.write || args.output) {
const { BUILTIN_IO_FORMATS, IORegistry } = await import('@open-pencil/core/io')
const io = new IORegistry(BUILTIN_IO_FORMATS)
const outPath = args.output ? args.output : file
const result = await io.writeDocument('fig', graph)
await Bun.write(outPath, result.data as Uint8Array)
if (!args.quiet) {
console.error(`Written to ${outPath}`)
}
}
}
})