diff --git a/packages/cli/src/commands/export.ts b/packages/cli/src/commands/export.ts index 1bb50d5b..34942ba0 100644 --- a/packages/cli/src/commands/export.ts +++ b/packages/cli/src/commands/export.ts @@ -62,7 +62,8 @@ export default defineCommand({ return; } - console.log(serializeTailwindV4(result.data.theme)); + // serializeTailwindV4 already ends with a single trailing newline; avoid console.log's extra newline. + process.stdout.write(serializeTailwindV4(result.data.theme)); } else if (format === 'json-tailwind' || format === 'tailwind') { const handler = new TailwindEmitterHandler(); const result = handler.execute(report.designSystem); diff --git a/packages/cli/src/linter/tailwind/v4/serialize.test.ts b/packages/cli/src/linter/tailwind/v4/serialize.test.ts index 17efff9b..e1b89f43 100644 --- a/packages/cli/src/linter/tailwind/v4/serialize.test.ts +++ b/packages/cli/src/linter/tailwind/v4/serialize.test.ts @@ -93,4 +93,10 @@ describe('serializeToCss', () => { const out = serializeToCss(data); expect(out).toContain('--font-body: "Google Sans Display";'); }); + + it('ends with exactly one trailing newline (for stdout.write export path)', () => { + const out = serializeToCss({ colors: { primary: '#000000', 'on-primary': '#ffffff' } }); + expect(out.endsWith('\n')).toBe(true); + expect(out.endsWith('\n\n')).toBe(false); + }); }); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index 22756e4d..18aeee05 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -31,13 +31,16 @@ export async function readInput(filePath: string): Promise { try { return readFileSync(filePath, 'utf-8'); } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const hint = message.includes('ENOENT') + ? `Design file not found: ${filePath}. Create a DESIGN.md or pass an existing path.` + : message; console.error(JSON.stringify({ error: 'FILE_READ_ERROR', - message: error instanceof Error ? error.message : String(error), + message: hint, path: filePath, })); - process.exitCode = 2; - throw error; // bubbles up, but process will exit with code 2 if uncaught + process.exit(2); } }