From c06fbe2e72b8ff34c577774ae95c4cc463114cd3 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Fri, 29 May 2026 22:34:19 +0100 Subject: [PATCH 1/2] feat(events): render json-layouts/json-values as a table by default (FT-1970) --- src/commands/events/events.test.ts | 80 ++++++++++++++++++++++++++---- src/commands/events/index.ts | 4 +- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/commands/events/events.test.ts b/src/commands/events/events.test.ts index 19597fd..b8e32d6 100644 --- a/src/commands/events/events.test.ts +++ b/src/commands/events/events.test.ts @@ -208,8 +208,8 @@ describe('events command', () => { '--match', 'la8186|la8153', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; - expect(printed.rows.map((r) => r[0])).toEqual(['LA8186']); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as Array<{ value: unknown }>; + expect(printed.map((r) => r.value)).toEqual(['LA8186']); }); it('passes json-values output through unchanged when no --match is set', async () => { @@ -223,8 +223,8 @@ describe('events command', () => { '--path', 'items/0/segment_flight_number', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; - expect(printed.rows.length).toBe(4); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as unknown[]; + expect(printed.length).toBe(4); }); it('should get event json layouts', async () => { @@ -270,8 +270,8 @@ describe('events command', () => { '--match', 'PAGE|segment_flight', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; - expect(printed.rows.map((r) => r[0])).toEqual(['items/0/segment_flight_number', 'pageName']); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as Array<{ key: unknown }>; + expect(printed.map((r) => r.key)).toEqual(['items/0/segment_flight_number', 'pageName']); }); it('filters json-layouts paths by --top-level', async () => { @@ -286,8 +286,8 @@ describe('events command', () => { 'after_enrichment', '--top-level', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; - expect(printed.rows.map((r) => r[0])).toEqual(['currency', 'items', 'pageName']); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as Array<{ key: unknown }>; + expect(printed.map((r) => r.key)).toEqual(['currency', 'items', 'pageName']); }); it('filters json-layouts paths by --max-depth', async () => { @@ -303,8 +303,8 @@ describe('events command', () => { '--max-depth', '2', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; - expect(printed.rows.map((r) => r[0])).toEqual(['currency', 'items', 'items/0', 'pageName']); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as Array<{ key: unknown }>; + expect(printed.map((r) => r.key)).toEqual(['currency', 'items', 'items/0', 'pageName']); }); it('passes json-layouts output through unchanged when no client filter is set', async () => { @@ -318,10 +318,68 @@ describe('events command', () => { '--phase', 'after_enrichment', ]); - const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { rows: unknown[][] }; + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as unknown[]; + expect(printed.length).toBe(5); + }); + + it('renders json-layouts as row objects (table) by default', async () => { + mockClient.getEventJsonLayouts.mockResolvedValueOnce(columnarLayouts); + await eventsCommand.parseAsync([ + 'node', + 'test', + 'json-layouts', + '--source', + 'unit_goal_property', + '--phase', + 'after_enrichment', + ]); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as Array< + Record + >; + expect(Array.isArray(printed)).toBe(true); + expect(printed[0]).toEqual({ key: 'currency', value_type: 'string', last_event_at: 1 }); + }); + + it('keeps the columnar shape for json-layouts with --raw', async () => { + vi.mocked(getGlobalOptions).mockReturnValueOnce({ raw: true, output: 'json' } as any); + mockClient.getEventJsonLayouts.mockResolvedValueOnce(columnarLayouts); + await eventsCommand.parseAsync([ + 'node', + 'test', + 'json-layouts', + '--source', + 'unit_goal_property', + '--phase', + 'after_enrichment', + ]); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { + columnNames: string[]; + rows: unknown[][]; + }; + expect(printed.columnNames).toEqual(['key', 'value_type', 'last_event_at']); expect(printed.rows.length).toBe(5); }); + it('keeps the columnar shape for json-values with --raw', async () => { + vi.mocked(getGlobalOptions).mockReturnValueOnce({ raw: true, output: 'json' } as any); + mockClient.getEventJsonValues.mockResolvedValueOnce(columnarValues); + await eventsCommand.parseAsync([ + 'node', + 'test', + 'json-values', + '--event-type', + 'goal', + '--path', + 'items/0/segment_flight_number', + ]); + const printed = vi.mocked(printFormatted).mock.calls.at(-1)?.[0] as { + columnNames: string[]; + rows: unknown[][]; + }; + expect(printed.columnNames).toEqual(['value', 'last_event_at']); + expect(printed.rows.length).toBe(4); + }); + it('rejects an invalid --match regex with a clear error', async () => { mockClient.getEventJsonLayouts.mockResolvedValueOnce(columnarLayouts); try { diff --git a/src/commands/events/index.ts b/src/commands/events/index.ts index f63f542..4e302c8 100644 --- a/src/commands/events/index.ts +++ b/src/commands/events/index.ts @@ -202,7 +202,7 @@ const jsonValuesCommand = new Command('json-values') const filtered = filterColumnarRows(result.data, 'value', { match: options.match as string | undefined, }); - printFormatted(filtered, globalOptions); + printFormatted(globalOptions.raw ? filtered : columnarToRows(filtered), globalOptions); }) ); @@ -239,7 +239,7 @@ const jsonLayoutsCommand = new Command('json-layouts') topLevel: options.topLevel as boolean | undefined, maxDepth: options.maxDepth as number | undefined, }); - printFormatted(filtered, globalOptions); + printFormatted(globalOptions.raw ? filtered : columnarToRows(filtered), globalOptions); }) ); From 6a2a16626d37ec4bfee5463ee4aa3915f8ea09a1 Mon Sep 17 00:00:00 2001 From: Jonas Alves Date: Fri, 29 May 2026 22:34:19 +0100 Subject: [PATCH 2/2] chore(release): bump cli to 1.10.0 (FT-1970) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2935475..f1ba37a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@absmartly/cli", - "version": "1.9.0", + "version": "1.10.0", "description": "ABSmartly CLI - A/B Testing and Feature Flags command-line tool for AI agents and humans", "type": "module", "main": "./dist/index.js",