From 805278a42c7a990f6294fb345a3acbec3b2e588f Mon Sep 17 00:00:00 2001 From: Ben Pearey Date: Mon, 23 Feb 2026 15:46:10 +0000 Subject: [PATCH 1/4] feat: passing sync options to support force sync --- docs/CONTENT-ITEM.md | 20 +++++----- .../content-item/sync.service.spec.ts | 18 ++++++--- src/commands/content-item/sync.service.ts | 11 ++++- src/commands/content-item/sync.spec.ts | 20 +++++++++- src/commands/content-item/sync.ts | 40 ++++++++++++++----- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/docs/CONTENT-ITEM.md b/docs/CONTENT-ITEM.md index e13ca27e..148267c3 100644 --- a/docs/CONTENT-ITEM.md +++ b/docs/CONTENT-ITEM.md @@ -514,15 +514,17 @@ If no `id` is provided, all content items in all content repositories in the spe #### Options -| Option | Alias | Description | -| -------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--destinationHubId` | | The destination hub ID to sync the content item with | -| `--repoId` | | The ID of a content repository to restrict sync scope. _(Optional)_ | -| `--folderId` | | The ID of a folder to restrict sync scope. _(Optional)_ | -| `--facet` | | Filter content using facets. Format:
`label:example name,locale:en-GB`
Regex supported with `/pattern/`.
See README for more examples. | -| `-f`, `--force` | | Skip confirmation prompts before sync. | -| `-s`, `--silent` | | Disable log file creation. | -| `--logFile` | | Path to write the log file.
Default: `(log_filename)` | +| Option | Alias | Description | +| -------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--destinationHubId` | | The destination hub ID to sync the content item with | +| `--repoId` | | The ID of a content repository to restrict sync scope. _(Optional)_ | +| `--folderId` | | The ID of a folder to restrict sync scope. _(Optional)_ | +| `--facet` | | Filter content using facets. Format:
`label:example name,locale:en-GB`
Regex supported with `/pattern/`.
See README for more examples. | +| `-f`, `--force` | | Skip confirmation prompts before sync. | +| `-s`, `--silent` | | Disable log file creation. | +| `--logFile` | | Path to write the log file.
Default: `(log_filename)` | +| `--ignoreSchemaValidation` | | Ignore schema validation when syncing content items. _(Optional)_ | +| `--forceSync` | | Sync destination content item when modified (overwrite destination modifications). _(Optional)_ | --- diff --git a/src/commands/content-item/sync.service.spec.ts b/src/commands/content-item/sync.service.spec.ts index 34b129a8..58375505 100644 --- a/src/commands/content-item/sync.service.spec.ts +++ b/src/commands/content-item/sync.service.spec.ts @@ -11,7 +11,7 @@ describe('sync.service', () => { }); describe('ContentItemSyncService', () => { describe('sync', () => { - it('should add a content item sync job to the queue and process the queue item', async () => { + it('should add a content item sync job to the queue and process the queue item (with sync options enabled)', async () => { const JOB_ID = '68e5289f0aba3024bde050f9'; const DEST_HUB_ID = '67d2a201642fa239dbe1523d'; const CONTENT_ITEM_ID = 'c5b659df-680e-4711-bfbe-84eaa10d76cc'; @@ -22,11 +22,15 @@ describe('sync.service', () => { hub.related.jobs.get.mockResolvedValue(new Job({ id: JOB_ID, status: 'COMPLETED' })); const syncService = new ContentItemSyncService(); - syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}); + syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}, { + ignoreSchemaValidation: true, + forceSync: true + }); await syncService.onIdle(); expect(hub.related.jobs.createDeepSyncJob).toHaveBeenCalledWith({ label: `dc-cli content item: sync service test`, + forceSync: true, ignoreSchemaValidation: true, destinationHubId: DEST_HUB_ID, input: { rootContentItemIds: [CONTENT_ITEM_ID] } @@ -47,12 +51,13 @@ describe('sync.service', () => { .mockResolvedValueOnce(new Job({ id: JOB_ID, status: 'COMPLETED' })); const syncService = new ContentItemSyncService(); - syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}); + syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}, {}); await syncService.onIdle(); expect(hub.related.jobs.createDeepSyncJob).toHaveBeenCalledWith({ label: `dc-cli content item: sync service test`, - ignoreSchemaValidation: true, + forceSync: false, + ignoreSchemaValidation: false, destinationHubId: DEST_HUB_ID, input: { rootContentItemIds: [CONTENT_ITEM_ID] } }); @@ -70,12 +75,13 @@ describe('sync.service', () => { hub.related.jobs.get.mockResolvedValueOnce(new Job({ id: JOB_ID, status: 'FAILED' })); const syncService = new ContentItemSyncService(); - syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}); + syncService.sync(DEST_HUB_ID, hub as unknown as Hub, contentItem, () => {}, {}); await syncService.onIdle(); expect(hub.related.jobs.createDeepSyncJob).toHaveBeenCalledWith({ label: `dc-cli content item: sync service test`, - ignoreSchemaValidation: true, + forceSync: false, + ignoreSchemaValidation: false, destinationHubId: DEST_HUB_ID, input: { rootContentItemIds: [CONTENT_ITEM_ID] } }); diff --git a/src/commands/content-item/sync.service.ts b/src/commands/content-item/sync.service.ts index 52fc7d4c..94399327 100644 --- a/src/commands/content-item/sync.service.ts +++ b/src/commands/content-item/sync.service.ts @@ -12,12 +12,19 @@ export class ContentItemSyncService { this.queue = new BurstableQueue({ concurrency: 1 }); } - sync(destinationHubId: string, hub: Hub, contentItem: ContentItem, action: (job: Job) => void): void { + sync( + destinationHubId: string, + hub: Hub, + contentItem: ContentItem, + action: (job: Job) => void, + options: { ignoreSchemaValidation?: boolean; forceSync?: boolean } + ): void { this.queue.add(async () => { const createSyncJob = await hub.related.jobs.createDeepSyncJob( new CreateDeepSyncJobRequest({ label: `dc-cli content item: ${contentItem.label}`, - ignoreSchemaValidation: true, + ignoreSchemaValidation: options.ignoreSchemaValidation || false, + forceSync: options.forceSync || false, destinationHubId, input: { rootContentItemIds: [contentItem.id] } }) diff --git a/src/commands/content-item/sync.spec.ts b/src/commands/content-item/sync.spec.ts index b0893c3d..77c57480 100644 --- a/src/commands/content-item/sync.spec.ts +++ b/src/commands/content-item/sync.spec.ts @@ -102,6 +102,18 @@ describe('content-item sync', () => { requiresArg: true, demandOption: true }); + + expect(spyOption).toHaveBeenCalledWith('ignoreSchemaValidation', { + type: 'boolean', + boolean: true, + describe: 'Ignore schema validation when syncing content items.' + }); + + expect(spyOption).toHaveBeenCalledWith('forceSync', { + type: 'boolean', + boolean: true, + describe: 'Sync destination content item when modified (overwrite destination modifications).' + }); }); }); @@ -149,6 +161,8 @@ describe('content-item sync', () => { ...globalArgs, id: CONTENT_ITEM_ID, destinationHubId: DEST_HUB_ID, + ignoreSchemaValidation: true, + forceSync: true, logFile: mockLog }); @@ -158,7 +172,8 @@ describe('content-item sync', () => { DEST_HUB_ID, expect.any(Hub), expect.any(ContentItem), - expect.any(Function) + expect.any(Function), + { forceSync: true, ignoreSchemaValidation: true } ); }); it('should sync content items by query', async () => { @@ -200,7 +215,8 @@ describe('content-item sync', () => { DEST_HUB_ID, expect.any(Hub), expect.any(ContentItem), - expect.any(Function) + expect.any(Function), + { forceSync: undefined, ignoreSchemaValidation: undefined } ); }); }); diff --git a/src/commands/content-item/sync.ts b/src/commands/content-item/sync.ts index 05827ae8..f3ba6ed3 100644 --- a/src/commands/content-item/sync.ts +++ b/src/commands/content-item/sync.ts @@ -65,6 +65,17 @@ export const builder = (yargs: Argv): void => { describe: 'The ID of a destination hub to sync with.', requiresArg: true, demandOption: true + }) + .option('ignoreSchemaValidation', { + type: 'boolean', + boolean: true, + describe: 'Ignore schema validation when syncing content items.' + }) + + .option('forceSync', { + type: 'boolean', + boolean: true, + describe: 'Sync destination content item when modified (overwrite destination modifications).' }); }; @@ -77,10 +88,13 @@ export default interface SyncOptions { force?: boolean; silent?: boolean; destinationHubId: string; + ignoreSchemaValidation?: boolean; + forceSync?: boolean; } export const handler = async (argv: Arguments): Promise => { - const { id, logFile, force, silent, hubId, repoId, folderId, destinationHubId } = argv; + const { id, logFile, force, silent, hubId, repoId, folderId, destinationHubId, ignoreSchemaValidation, forceSync } = + argv; const log = logFile.open(); const client = dynamicContentClientFactory(argv); const facet = withOldFilters(argv.facet, argv); @@ -143,15 +157,21 @@ export const handler = async (argv: Arguments { log.addComment(`Requesting content item sync: ${contentItem.label}`); - syncService.sync(destinationHubId, hub, contentItem, (syncJob: Job) => { - progress.increment(); - const logComment = - syncJob.status === 'FAILED' - ? `Failed content item sync job ${syncJob.id}: ${JSON.stringify(syncJob.errors)}` - : `Content item synced: ${contentItem.label} (jobId: ${syncJob.id})`; - - log.addComment(logComment); - }); + syncService.sync( + destinationHubId, + hub, + contentItem, + (syncJob: Job) => { + progress.increment(); + const logComment = + syncJob.status === 'FAILED' + ? `Failed content item sync job ${syncJob.id}: ${JSON.stringify(syncJob.errors)}` + : `Content item synced: ${contentItem.label} (jobId: ${syncJob.id})`; + + log.addComment(logComment); + }, + { ignoreSchemaValidation, forceSync } + ); }); await syncService.onIdle(); From 4214e787ad9ac9f9c8dc730350245bb4f3b68457 Mon Sep 17 00:00:00 2001 From: Ben Pearey Date: Wed, 25 Feb 2026 15:45:54 +0000 Subject: [PATCH 2/4] chore: bump to latest version of management sdk --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c9698776..ce9b35b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "bottleneck": "2.19.5", "chalk": "2.4.2", "cli-progress": "3.12.0", - "dc-management-sdk-js": "3.2.0", + "dc-management-sdk-js": "^3.3.0", "enquirer": "2.3.6", "fs-extra": "10.1.0", "graceful-fs": "4.2.11", @@ -5591,9 +5591,9 @@ } }, "node_modules/dc-management-sdk-js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dc-management-sdk-js/-/dc-management-sdk-js-3.2.0.tgz", - "integrity": "sha512-S8aoObfEYlTtOvoo1Gt7Jt3ezNc4EDoK1t7fMPpk38XGuM8w12hSuKo6QhUpJZrV2agzbsyli2G9a4NgIsuT8g==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dc-management-sdk-js/-/dc-management-sdk-js-3.3.0.tgz", + "integrity": "sha512-eKB5TBdFRUh4ZPbRhUDLYUPsxP5YboWBYYkom++pEnko4GSr3LFMpSw1j1tRicrWcQHntdQsOUPGrA+DKfKBBQ==", "license": "Apache-2.0", "dependencies": { "axios": "1.12.2", diff --git a/package.json b/package.json index 5aafdccb..951a2fc6 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "bottleneck": "2.19.5", "chalk": "2.4.2", "cli-progress": "3.12.0", - "dc-management-sdk-js": "3.2.0", + "dc-management-sdk-js": "3.3.0", "enquirer": "2.3.6", "fs-extra": "10.1.0", "graceful-fs": "4.2.11", From da482cd1c3d1640b6e1ba9e628abbaf713e13838 Mon Sep 17 00:00:00 2001 From: Ben Pearey Date: Wed, 25 Feb 2026 16:05:50 +0000 Subject: [PATCH 3/4] fix: default ignore schema validation to true --- src/commands/content-item/sync.service.spec.ts | 4 ++-- src/commands/content-item/sync.service.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/content-item/sync.service.spec.ts b/src/commands/content-item/sync.service.spec.ts index 58375505..0fa9a06d 100644 --- a/src/commands/content-item/sync.service.spec.ts +++ b/src/commands/content-item/sync.service.spec.ts @@ -57,7 +57,7 @@ describe('sync.service', () => { expect(hub.related.jobs.createDeepSyncJob).toHaveBeenCalledWith({ label: `dc-cli content item: sync service test`, forceSync: false, - ignoreSchemaValidation: false, + ignoreSchemaValidation: true, destinationHubId: DEST_HUB_ID, input: { rootContentItemIds: [CONTENT_ITEM_ID] } }); @@ -81,7 +81,7 @@ describe('sync.service', () => { expect(hub.related.jobs.createDeepSyncJob).toHaveBeenCalledWith({ label: `dc-cli content item: sync service test`, forceSync: false, - ignoreSchemaValidation: false, + ignoreSchemaValidation: true, destinationHubId: DEST_HUB_ID, input: { rootContentItemIds: [CONTENT_ITEM_ID] } }); diff --git a/src/commands/content-item/sync.service.ts b/src/commands/content-item/sync.service.ts index 94399327..4c8becb6 100644 --- a/src/commands/content-item/sync.service.ts +++ b/src/commands/content-item/sync.service.ts @@ -19,11 +19,12 @@ export class ContentItemSyncService { action: (job: Job) => void, options: { ignoreSchemaValidation?: boolean; forceSync?: boolean } ): void { + console.log(options.ignoreSchemaValidation, options.ignoreSchemaValidation ?? true); this.queue.add(async () => { const createSyncJob = await hub.related.jobs.createDeepSyncJob( new CreateDeepSyncJobRequest({ label: `dc-cli content item: ${contentItem.label}`, - ignoreSchemaValidation: options.ignoreSchemaValidation || false, + ignoreSchemaValidation: options.ignoreSchemaValidation ?? true, forceSync: options.forceSync || false, destinationHubId, input: { rootContentItemIds: [contentItem.id] } From 27e4f70942db0ab1620c918014fa1acf91008d22 Mon Sep 17 00:00:00 2001 From: Ben Pearey Date: Mon, 2 Mar 2026 10:34:43 +0000 Subject: [PATCH 4/4] fix: remove unwanted console.log --- src/commands/content-item/sync.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/commands/content-item/sync.service.ts b/src/commands/content-item/sync.service.ts index 4c8becb6..ddd68aa9 100644 --- a/src/commands/content-item/sync.service.ts +++ b/src/commands/content-item/sync.service.ts @@ -19,7 +19,6 @@ export class ContentItemSyncService { action: (job: Job) => void, options: { ignoreSchemaValidation?: boolean; forceSync?: boolean } ): void { - console.log(options.ignoreSchemaValidation, options.ignoreSchemaValidation ?? true); this.queue.add(async () => { const createSyncJob = await hub.related.jobs.createDeepSyncJob( new CreateDeepSyncJobRequest({