From 24acce217b1b0e13bb326621cff54d2666c8b4cd Mon Sep 17 00:00:00 2001 From: Marla Hoggard Date: Mon, 23 Mar 2026 14:05:35 -0400 Subject: [PATCH] Support filtering by isDevIntegrated --- lib/src/commands/pull.test.ts | 2 + lib/src/formatters/json.test.ts | 2 + lib/src/formatters/shared/base.test.ts | 236 +++++++++++++++++++++++- lib/src/formatters/shared/base.ts | 10 + lib/src/formatters/shared/baseExport.ts | 1 + lib/src/http/components.test.ts | 2 + lib/src/http/textItems.test.ts | 4 +- lib/src/http/types.ts | 3 + lib/src/outputs/shared.ts | 1 + package.json | 2 +- 10 files changed, 260 insertions(+), 3 deletions(-) diff --git a/lib/src/commands/pull.test.ts b/lib/src/commands/pull.test.ts index f16b354..003799a 100644 --- a/lib/src/commands/pull.test.ts +++ b/lib/src/commands/pull.test.ts @@ -27,6 +27,7 @@ const createMockTextItem = (overrides: Partial = {}) => ({ status: "FINAL", notes: "", tags: [], + integrated: true, variableIds: [], pluralForm: null, projectId: "project-1", @@ -41,6 +42,7 @@ const createMockComponent = (overrides: Partial = {}) => ({ status: "FINAL", notes: "", tags: [], + integrated: true, variableIds: [], pluralForm: null, folderId: null, diff --git a/lib/src/formatters/json.test.ts b/lib/src/formatters/json.test.ts index 4e0cf30..0d45514 100644 --- a/lib/src/formatters/json.test.ts +++ b/lib/src/formatters/json.test.ts @@ -111,6 +111,7 @@ describe("JSONFormatter", () => { status: "FINAL", notes: "", tags: [], + integrated: true, variableIds: [], projectId: "project-1", variantId: null, @@ -127,6 +128,7 @@ describe("JSONFormatter", () => { status: "FINAL", notes: "", tags: [], + integrated: true, variableIds: [], folderId: null, variantId: null, diff --git a/lib/src/formatters/shared/base.test.ts b/lib/src/formatters/shared/base.test.ts index 4338928..7765f06 100644 --- a/lib/src/formatters/shared/base.test.ts +++ b/lib/src/formatters/shared/base.test.ts @@ -155,6 +155,101 @@ describe("BaseFormatter", () => { variants: undefined, }); }); + + it("should use projectConfig statuses and integrated when output does not override", () => { + const projectConfig = createMockProjectConfig({ + statuses: ["FINAL"], + integrated: true, + }); + const output = createMockOutput(); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const filters = formatter.generateTextItemPullFilter(); + + expect(filters).toEqual({ + projects: [], + variants: [], + statuses: ["FINAL"], + integrated: true, + }); + }); + + it("should override statuses with output.statuses when provided", () => { + const projectConfig = createMockProjectConfig({ + statuses: ["FINAL"], + integrated: true, + }); + const output = createMockOutput({ + statuses: ["WIP"], + }); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const filters = formatter.generateTextItemPullFilter(); + + expect(filters).toEqual({ + projects: [], + variants: [], + statuses: ["WIP"], + integrated: true, + }); + }); + + it("should override integrated with output.integrated when provided", () => { + const projectConfig = createMockProjectConfig({ + statuses: ["FINAL"], + integrated: true, + }); + const output = createMockOutput({ + integrated: false, + }); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const filters = formatter.generateTextItemPullFilter(); + + expect(filters).toEqual({ + projects: [], + variants: [], + statuses: ["FINAL"], + integrated: false, + }); + }); + + it("should override statuses and integrated when both are provided in output", () => { + const projectConfig = createMockProjectConfig({ + statuses: ["FINAL"], + integrated: true, + }); + const output = createMockOutput({ + statuses: ["WIP"], + integrated: false, + }); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const filters = formatter.generateTextItemPullFilter(); + + expect(filters).toEqual({ + projects: [], + variants: [], + statuses: ["WIP"], + integrated: false, + }); + }); }); /*********************************************************** @@ -282,6 +377,90 @@ describe("BaseFormatter", () => { }); expect(filters.folders).toBeUndefined(); }); + + it("should use statuses and integrated from projectConfig when output does not override", () => { + const filters = getComponentPullFilters({ + components: { + folders: [{ id: "folder1" }], + }, + statuses: ["WIP"], + integrated: true, + }); + + expect(filters).toEqual({ + folders: [{ id: "folder1" }], + variants: [], + statuses: ["WIP"], + integrated: true, + }); + }); + + it("should override statuses with output.statuses when provided", () => { + const filters = getComponentPullFilters( + { + components: { + folders: [{ id: "folder1" }], + }, + statuses: ["FINAL"], + integrated: true, + }, + { + statuses: ["WIP"], + } + ); + + expect(filters).toEqual({ + folders: [{ id: "folder1" }], + variants: [], + statuses: ["WIP"], + integrated: true, + }); + }); + + it("should override integrated with output.integrated when provided", () => { + const filters = getComponentPullFilters( + { + components: { + folders: [{ id: "folder1" }], + }, + statuses: ["FINAL"], + integrated: true, + }, + { + integrated: false, + } + ); + + expect(filters).toEqual({ + folders: [{ id: "folder1" }], + variants: [], + statuses: ["FINAL"], + integrated: false, + }); + }); + + it("should override statuses and integrated when both are provided in output", () => { + const filters = getComponentPullFilters( + { + components: { + folders: [{ id: "folder1" }], + }, + statuses: ["FINAL"], + integrated: true, + }, + { + statuses: ["WIP"], + integrated: false, + } + ); + + expect(filters).toEqual({ + folders: [{ id: "folder1" }], + variants: [], + statuses: ["WIP"], + integrated: false, + }); + }); }); /*********************************************************** @@ -315,6 +494,35 @@ describe("BaseFormatter", () => { expect(params.richText).toBeUndefined(); }); + it("should generate query params for provided optional text item filters", () => { + const projectConfig = createMockProjectConfig({ + projects: [{ id: "project1" }], + statuses: ["FINAL"], + integrated: true, + }); + const output = createMockOutput(); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const params = formatter.generateQueryParams( + formatter.generateTextItemPullFilter() + ); + + expect(params.filter).toBeDefined(); + expect(params.filter).toEqual(expect.any(String)); + const parsedFilter = JSON.parse(params.filter); + expect(parsedFilter).toEqual({ + projects: [{ id: "project1" }], + variants: [], + statuses: ["FINAL"], + integrated: true, + }); + expect(params.richText).toBeUndefined(); + }); + it("should generate query params with provided component filters", () => { const projectConfig = createMockProjectConfig({ components: { @@ -342,6 +550,33 @@ describe("BaseFormatter", () => { expect(params.richText).toBeUndefined(); }); + it("should generate query params for optional component filters", () => { + const projectConfig = createMockProjectConfig({ + statuses: ["WIP"], + integrated: false, + }); + const output = createMockOutput(); + const formatter = new TestBaseFormatter( + output, + projectConfig, + createMockMeta() + ); + + const params = formatter.generateQueryParams( + formatter.generateComponentPullFilter() + ); + + expect(params.filter).toBeDefined(); + const parsedFilter = JSON.parse(params.filter); + expect(parsedFilter).toEqual({ + folders: [], + variants: [], + statuses: ["WIP"], + integrated: false, + }); + expect(params.richText).toBeUndefined(); + }); + it("should include richText from projectConfig when set", () => { const projectConfig = createMockProjectConfig({ projects: [{ id: "project1" }], @@ -399,4 +634,3 @@ describe("BaseFormatter", () => { }); }); }); - diff --git a/lib/src/formatters/shared/base.ts b/lib/src/formatters/shared/base.ts index 63a6846..2340c70 100644 --- a/lib/src/formatters/shared/base.ts +++ b/lib/src/formatters/shared/base.ts @@ -40,6 +40,7 @@ export default class BaseFormatter { projects: this.projectConfig.projects, variants: this.projectConfig.variants, statuses: this.projectConfig.statuses, + integrated: this.projectConfig.integrated, }; if (this.output.projects) { @@ -54,6 +55,10 @@ export default class BaseFormatter { filters.statuses = this.output.statuses; } + if (this.output.integrated !== undefined) { + filters.integrated = this.output.integrated; + } + return filters; } @@ -64,6 +69,7 @@ export default class BaseFormatter { }), variants: this.projectConfig.variants, statuses: this.projectConfig.statuses, + integrated: this.projectConfig.integrated, }; if (this.output.components) { @@ -78,6 +84,10 @@ export default class BaseFormatter { filters.statuses = this.output.statuses; } + if (this.output.integrated !== undefined) { + filters.integrated = this.output.integrated; + } + return filters; } diff --git a/lib/src/formatters/shared/baseExport.ts b/lib/src/formatters/shared/baseExport.ts index 600782c..075c2a5 100644 --- a/lib/src/formatters/shared/baseExport.ts +++ b/lib/src/formatters/shared/baseExport.ts @@ -145,6 +145,7 @@ export default abstract class BaseExportFormatter< ...super.generateQueryParams({ projects: [{ id: project.id }], statuses: super.generateTextItemPullFilter().statuses, + integrated: super.generateTextItemPullFilter().integrated, }), variantId, format: this.exportFormat, diff --git a/lib/src/http/components.test.ts b/lib/src/http/components.test.ts index d7fe390..9f87f28 100644 --- a/lib/src/http/components.test.ts +++ b/lib/src/http/components.test.ts @@ -45,6 +45,7 @@ describe("fetchComponents", () => { richText: "

Rich HTML text

", status: "FINAL", notes: "Test note", + integrated: true, pluralForm: null, tags: ["tag1"], variableIds: ["var1"], @@ -76,6 +77,7 @@ describe("fetchComponents", () => { status: "FINAL", pluralForm: "one", notes: "", + integrated: true, tags: [], variableIds: [], folderId: null, diff --git a/lib/src/http/textItems.test.ts b/lib/src/http/textItems.test.ts index 9d113cd..c2ab57d 100644 --- a/lib/src/http/textItems.test.ts +++ b/lib/src/http/textItems.test.ts @@ -44,9 +44,10 @@ describe("fetchTextItems", () => { text: "Plain text", richText: "

Rich HTML text

", status: "FINAL", + tags: ["tag1"], notes: "Test note", + integrated: true, pluralForm: null, - tags: ["tag1"], variableIds: ["var1"], projectId: "project1", variantId: "variant1", @@ -77,6 +78,7 @@ describe("fetchTextItems", () => { status: "FINAL", notes: "", tags: [], + integrated: true, pluralForm: null, variableIds: [], projectId: "project1", diff --git a/lib/src/http/types.ts b/lib/src/http/types.ts index 89d0457..263619c 100644 --- a/lib/src/http/types.ts +++ b/lib/src/http/types.ts @@ -8,6 +8,7 @@ export interface PullFilters { }[]; variants?: { id: string }[]; statuses?: ITextStatus[]; + integrated?: boolean; } export interface PullQueryParams { filter: string; // Stringified PullFilters @@ -40,6 +41,7 @@ const ZBaseTextEntity = z.object({ status: z.string(), notes: z.string(), tags: z.array(z.string()), + integrated: z.boolean(), pluralForm: ZTextPluralType.nullable(), variableIds: z.array(z.string()), variantId: z.string().nullable(), @@ -163,6 +165,7 @@ export const ZExportSwiftFileRequest = z.object({ }) .optional(), statuses: z.array(ZTextStatus).optional(), + integrated: z.boolean().optional(), }); export type IExportSwiftFileRequest = z.infer; diff --git a/lib/src/outputs/shared.ts b/lib/src/outputs/shared.ts index ef57c0d..b498d7f 100644 --- a/lib/src/outputs/shared.ts +++ b/lib/src/outputs/shared.ts @@ -20,6 +20,7 @@ export const ZBaseOutputFilters = z.object({ }) .optional(), statuses: z.array(ZTextStatus).optional(), + integrated: z.boolean().optional(), variants: z.array(z.object({ id: z.string() })).optional(), outDir: z.string().optional(), richText: z.union([z.literal("html"), z.literal(false)]).optional(), diff --git a/package.json b/package.json index 530c165..0b5a46c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dittowords/cli", - "version": "5.3.1", + "version": "5.4.0", "description": "Command Line Interface for Ditto (dittowords.com).", "license": "MIT", "main": "bin/ditto.js",