import { expect, setDefaultTimeout, test } from 'bun:test' import { randomUUID } from 'node:crypto' import { tmpdir } from 'node:os' import { join } from 'node:path' import { cliSourcePath, repoPath, requireBuiltWorkspacePackages } from '#tests/helpers/paths' import { heavy } from '#tests/helpers/test-utils' requireBuiltWorkspacePackages() setDefaultTimeout(30_000) const CLI = cliSourcePath('index.ts') const FIXTURE = repoPath('tests/fixtures/gold-preview.fig') async function run( args: string[], stdin?: string ): Promise<{ stdout: string; stderr: string; exitCode: number }> { const proc = Bun.spawn(['bun', CLI, ...args], { stdout: 'pipe', stderr: 'pipe', stdin: stdin ? Buffer.from(stdin) : undefined }) const [stdout, stderr] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text() ]) const exitCode = await proc.exited return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode } } heavy('eval CLI', () => { test('returns page name', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--code', 'return figma.currentPage.name' ]) expect(exitCode).toBe(0) expect(stdout.length).toBeGreaterThan(0) }) test('returns primitive number', async () => { const { stdout, exitCode } = await run(['eval', FIXTURE, '--code', 'return 42']) expect(exitCode).toBe(0) expect(stdout).toBe('42') }) test('returns page count', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--code', 'return figma.root.children.length' ]) expect(exitCode).toBe(0) const count = Number.parseInt(stdout, 10) expect(count).toBeGreaterThan(0) }) test('findAll queries nodes', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', 'return figma.currentPage.findAll(n => n.type === "TEXT").slice(0, 3).map(n => ({ name: n.name, type: n.type }))' ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(Array.isArray(data)).toBe(true) expect(data.length).toBeGreaterThan(0) expect(data[0].type).toBe('TEXT') }) test('create frame and return properties', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', `const f = figma.createFrame(); f.name = "Test"; f.resize(300, 200); return f` ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(data.name).toBe('Test') expect(data.width).toBe(300) expect(data.height).toBe(200) expect(data.type).toBe('FRAME') }) test('auto-layout script', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', `const f = figma.createFrame() f.layoutMode = "VERTICAL" f.itemSpacing = 8 f.paddingTop = f.paddingBottom = 16 return { layoutMode: f.layoutMode, itemSpacing: f.itemSpacing, paddingTop: f.paddingTop }` ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(data.layoutMode).toBe('VERTICAL') expect(data.itemSpacing).toBe(8) expect(data.paddingTop).toBe(16) }) test('text node with characters', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', `const t = figma.createText() t.characters = "Hello" t.fontSize = 24 return { characters: t.characters, fontSize: t.fontSize }` ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(data.characters).toBe('Hello') expect(data.fontSize).toBe(24) }) test('top-level await works', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--code', 'await figma.loadFontAsync({ family: "Inter", style: "Regular" }); return "ok"' ]) expect(exitCode).toBe(0) expect(stdout).toContain('ok') }) test('stdin reads code from pipe', async () => { const proc = Bun.spawn(['bun', CLI, 'eval', FIXTURE, '--stdin'], { stdout: 'pipe', stderr: 'pipe', stdin: 'pipe' }) proc.stdin.write('return figma.currentPage.name') proc.stdin.end() const [stdout, stderr] = await Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text() ]) const exitCode = await proc.exited if (exitCode !== 0) throw new Error(`exit ${exitCode}: ${stderr}`) expect(stdout.trim().length).toBeGreaterThan(0) }) test('--json outputs valid JSON', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', 'return { a: 1, b: "two" }' ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(data).toEqual({ a: 1, b: 'two' }) }) test('syntax error exits with error', async () => { const { exitCode, stderr } = await run(['eval', FIXTURE, '--code', 'return {{{']) expect(exitCode).not.toBe(0) expect(stderr.length).toBeGreaterThan(0) }) test('runtime error exits with error', async () => { const { exitCode, stderr } = await run(['eval', FIXTURE, '--code', 'throw new Error("boom")']) expect(exitCode).not.toBe(0) expect(stderr).toContain('boom') }) test('no code exits with error', async () => { const { exitCode, stderr } = await run(['eval', FIXTURE]) expect(exitCode).not.toBe(0) expect(stderr).toContain('code') }) test('undefined result produces no output', async () => { const { stdout, exitCode } = await run(['eval', FIXTURE, '--code', 'figma.createFrame()']) expect(exitCode).toBe(0) expect(stdout).toBe('') }) test('--output writes file', async () => { const outFile = join(tmpdir(), `eval-test-${randomUUID()}.fig`) const { exitCode, stderr } = await run([ 'eval', FIXTURE, '--quiet', '--code', 'const f = figma.createFrame(); f.name = "Written"', '-o', outFile ]) // exportFigFile may fail on files with COMPONENT nodes (Kiwi schema limitation) // but the eval itself should succeed — verify it at least attempted to write if (exitCode === 0) { const stat = Bun.file(outFile) expect(await stat.exists()).toBe(true) expect(stat.size).toBeGreaterThan(0) } else { expect(stderr).toContain('NodeType') } }) test('array of nodes serializes', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', `const a = figma.createFrame(); a.name = "A" const b = figma.createRectangle(); b.name = "B" return [a, b]` ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(Array.isArray(data)).toBe(true) expect(data[0].name).toBe('A') expect(data[1].name).toBe('B') }) test('getNodeById on real file', async () => { const { stdout, exitCode } = await run([ 'eval', FIXTURE, '--json', '--code', `const page = figma.currentPage const first = page.children[0] const found = figma.getNodeById(first.id) return { same: found.id === first.id, name: found.name }` ]) expect(exitCode).toBe(0) const data = JSON.parse(stdout) expect(data.same).toBe(true) }) })