diff --git a/packages/cli/src/commands/dom.ts b/packages/cli/src/commands/dom.ts index 114c57ead..bfce145f4 100644 --- a/packages/cli/src/commands/dom.ts +++ b/packages/cli/src/commands/dom.ts @@ -10,7 +10,8 @@ import { serializeHTML, tailwindHTMLToDesignDocument, tailwindHTMLToSceneGraph, - type DesignDocument + type DesignDocument, + type SerializeHTMLOptions } from '@open-pencil/dom-css' import { requireFile } from '#cli/app-client' @@ -18,6 +19,7 @@ import { fmtList, ok, printError } from '#cli/format' const io = new IORegistry(BUILTIN_IO_FORMATS) const OUTPUT_FORMATS = new Set(['fig', 'html', 'json']) +const HTML_STYLES = new Set(['inline', 'tailwind']) interface DomArgs { file?: string @@ -28,6 +30,7 @@ interface DomArgs { tailwind?: string tailwindFile?: string pageName: string + htmlStyle: SerializeHTMLOptions['style'] json?: boolean } @@ -97,7 +100,7 @@ async function writeOutput( } if (format === 'html') { - await Bun.write(output, serializeHTML(document)) + await Bun.write(output, serializeHTML(document, { style: args.htmlStyle })) return output } @@ -151,6 +154,11 @@ export default defineCommand({ description: 'Scene graph page name (default: DOM/CSS)', default: 'DOM/CSS' }, + htmlStyle: { + type: 'string', + description: 'HTML style output: inline or tailwind (default: inline)', + default: 'inline' + }, json: { type: 'boolean', description: 'Print a machine-readable summary to stdout' @@ -162,9 +170,16 @@ export default defineCommand({ printError(`Invalid format "${args.format}". Use fig, html, or json.`) process.exit(1) } + const htmlStyleArg = args.htmlStyle.toLowerCase() + if (!HTML_STYLES.has(htmlStyleArg)) { + printError(`Invalid htmlStyle "${args.htmlStyle}". Use inline or tailwind.`) + process.exit(1) + } + const htmlStyle = htmlStyleArg as SerializeHTMLOptions['style'] - const { document, graph } = await convertDom(args) - const output = await writeOutput(args, document, graph) + const normalizedArgs = { ...args, htmlStyle } + const { document, graph } = await convertDom(normalizedArgs) + const output = await writeOutput(normalizedArgs, document, graph) const pages = graph.getPages() const summary = { input: requireFile(args.file), diff --git a/packages/dom-css/src/serialize.ts b/packages/dom-css/src/serialize.ts index 23348a0db..b06ce7b8f 100644 --- a/packages/dom-css/src/serialize.ts +++ b/packages/dom-css/src/serialize.ts @@ -62,8 +62,11 @@ function mergeClassNames(...values: Array): string | undefin function serializeAttrs(node: DesignElement, options: SerializeHTMLOptions): string { const style = serializeStyle(node) const tailwindClass = options.style === 'tailwind' ? serializeTailwindClasses(node) : undefined + const attrsWithoutStyle = { ...node.attrs } + delete attrsWithoutStyle.style + const sourceAttrs = options.style === 'tailwind' && tailwindClass ? attrsWithoutStyle : node.attrs const attrs = { - ...node.attrs, + ...sourceAttrs, ...(tailwindClass ? { class: mergeClassNames(node.attrs.class, tailwindClass) } : {}), ...(style && options.style !== 'tailwind' ? { style } : {}) } diff --git a/tests/engine/cli/dom.test.ts b/tests/engine/cli/dom.test.ts index d004dde7c..12c777749 100644 --- a/tests/engine/cli/dom.test.ts +++ b/tests/engine/cli/dom.test.ts @@ -162,6 +162,36 @@ test('dom CLI writes serialized HTML output', async () => { expect(html).toContain('class="card"') }) +test('dom CLI can write HTML styles as Tailwind classes', async () => { + const dir = await mkdtemp(join(tmpdir(), 'open-pencil-dom-cli-html-tailwind-')) + const htmlPath = join(dir, 'card.html') + const output = join(dir, 'card.out.html') + + await Bun.write( + htmlPath, + '
Tailwind HTML
' + ) + + const { stderr, exitCode } = await run([ + 'dom', + htmlPath, + '--format', + 'html', + '--htmlStyle', + 'tailwind', + '--output', + output + ]) + + expect(stderr).toBe('') + expect(exitCode).toBe(0) + + const html = await Bun.file(output).text() + expect(html).toContain('Tailwind HTML') + expect(html).toContain('class="card flex p-4 gap-2 bg-white"') + expect(html).not.toContain('style=') +}) + test('dom CLI writes a .fig that core IO can import', async () => { const { htmlPath, cssPath, dir } = await createFixture() const output = join(dir, 'card.fig')