- 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
105 lines
3.3 KiB
TypeScript
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}`)
|
|
}
|
|
}
|
|
}
|
|
})
|