diff --git a/.talismanrc b/.talismanrc index 8b5afad01..fcfa7c760 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,10 +1,150 @@ fileignoreconfig: -- filename: packages/contentstack-export/test/unit/utils/export-config-handler.test.ts - checksum: f612661a8b6784b20ea62794b35192b340a34d8424a54a1a2cffec1878ab7528 -- filename: packages/contentstack-import/test/unit/utils/marketplace-app-helper.test.ts - checksum: de694e861560c4c242100eaf17a6d8e230247b99f94e4b428e55ef065084c10c +- filename: pnpm-lock.yaml + checksum: 2e1116588e19b058f0d6a5efa28f54c6a3af11b0f0d8c442ed9866a90b77b713 +- filename: packages/contentstack-export-to-csv/test/unit/commands/export-to-csv.test.ts + checksum: cc82f4b0c50783dd42d8d57ba2af1e5c9aa3857399c5abf39bc5dc3fac105658 +- filename: packages/contentstack-export-to-csv/src/messages/index.ts + checksum: 5711d4fd1139547a025c826d7dfae4d213ff3f31044c3dc8a9f855f69f7e439c +- filename: packages/contentstack-apps-cli/src/messages/index.ts + checksum: 5d7991adb5056a14359c5a43db995460a72234916138dbc5ec14f726faf0054b +- filename: packages/contentstack-export-to-csv/test/unit/utils/error-handler.test.ts + checksum: cef8f515ab6c0441f67748693473bf1499c2d54945aab1b89baaad37be8af2d9 +- filename: packages/contentstack-export-to-csv/test/unit/utils/teams-export.functional.test.ts + checksum: bcf70dbb201a0a1755f0b8f59550aae95a3e1d03700959eee7e8d415f9647746 +- filename: packages/contentstack-import/test/unit/commands/cm/stacks/import.test.ts + checksum: d241111877a997f00702eaf171c1fde53a075a9c8f7971a8e6b1577c1c83279d +- filename: packages/contentstack-import/test/unit/import/modules/assets.test.ts + checksum: 259d5c325ec7de08d0f45099df9d3eb7c97374691c1c4d281221e112b9097191 +- filename: packages/contentstack-import/test/unit/import/modules/base-class.test.ts + checksum: 40c504d508d40b6416ccedb5a323365dd97f313b1dcc885e4fec3639e807b383 +- filename: packages/contentstack-apps-cli/eslint.config.js + checksum: 22e7f364a33612f0100f7b5f6e2f1d28491298cbaca1debf806cd0a988c8bb2c +- filename: packages/contentstack-import/test/unit/import/modules/content-types.test.ts + checksum: 662cb42217c12af2b4dad5d1355858095c5df062e42c89e09b2680fdbee80699 +- filename: packages/contentstack-import/test/unit/import/modules/entries.test.ts + checksum: a7311e3077fee52bfd23c46d51ebe9ce58d202dd735514a44ec230615f4cd917 +- filename: packages/contentstack-import/test/unit/import/modules/global-fields.test.ts + checksum: 2b4e0d5d11f825d21d45569f681acd886c5f84717873b26998b4dab2e91bb439 +- filename: packages/contentstack-import/test/unit/import/modules/index.test.ts + checksum: 27dd2c5f60f9f4c70f2ebf4c08cf8bcbe2d5e4dc7fe52a53972af3fdb57e0e63 +- filename: packages/contentstack-bulk-operations/test/unit/utils/config-builder.test.ts + checksum: e5891257999e9e940956ca77f5a7834be35d259edccd41c06f77736194ad7454 +- filename: packages/contentstack-asset-management/eslint.config.js + checksum: b951a153138f42ee34ab7c0e17827637e25e2d7cb89e150ba378ba533f6d23a7 +- filename: packages/contentstack-bootstrap/test/bootstrap-integration.test.js + checksum: 67579b79fc5c1b4feead8f537e7bdfd14fac58aa93c127f590505e47d26e0eb0 +- filename: packages/contentstack-asset-management/test/unit/import/workspaces.test.ts + checksum: 67c11d312ba6838659c0b2cefd59d706d4b3c1a9807ed551dcb6d94f4d1d60a3 +- filename: packages/contentstack-import/test/unit/import/modules/marketplace-apps.test.ts + checksum: 17b3e6e0f7ce12ddabd688996c72d04dba5245fd300a23b541cf7bdfbbf7ef1a +- filename: packages/contentstack-import/test/unit/utils/common-helper.test.ts + checksum: 7a10c4625d666cf705cd9869b74b3bc2dcec94926cbb5f719e4f8dc4f053b2c7 +- filename: packages/contentstack-import/test/unit/utils/interactive.test.ts + checksum: dad20e229e02e847b5e939fdaf987b1a6c9ff4e1529b6d96a51130f7293285a9 +- filename: packages/contentstack-import-setup/src/commands/cm/stacks/import-setup.ts + checksum: 5b00b1df4ef25313dc1451c19af32748d9e54d72c39752870086f225007ee044 +- filename: packages/contentstack-import/test/unit/utils/login-handler.test.ts + checksum: a60f46b4d79ccdbeecfed27ffffbc97a44d6753f36ee74b68b725c8b20bbb2eb +- filename: packages/contentstack-export/eslint.config.js + checksum: bb451c301e84929aca8c284cb74636de5fd851b18654e07d0eae5e88b0a729ac +- filename: packages/contentstack-export/test/unit/export/module-exporter.test.ts + checksum: 485bbd175cc95aff6c1c3c6b2904f92c3d4ea1272a056158a901da3eff9692d6 +- filename: packages/contentstack-import-setup/src/utils/import-config-handler.ts + checksum: c13ca4996b3767fae9d2716fe9ae7d90d98b26012e064f42ea9fdd054f684761 +- filename: packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts + checksum: 07eef22a3c471e8da12b1e7e51d45b76410725c4c160a0f35432d9aac5916974 +- filename: packages/contentstack-export/test/unit/utils/common-helper.test.ts + checksum: a4bfba19846e1075821521a202d856203f02a6bc0e5ec4f51b5265801cb4ddd9 +- filename: packages/contentstack-import-setup/test/unit/common-helper.test.ts + checksum: ccbd8209d342bf651c2c38326cba13bc3e5c2af4200d7110fdd7c3de4824d21d +- filename: packages/contentstack-asset-management/test/unit/import/fields.test.ts + checksum: 9e97001a6c92d213a9f55a1458bf39c0996952467e0b4aabd954ddd8ad1805b9 +- filename: packages/contentstack-branches/eslint.config.js + checksum: 7b043a59fc9c523d5f772c1b81d6d4b6c65fb7f8edb8df73e48ba821e7298f0b +- filename: packages/contentstack-asset-management/src/export/assets.ts + checksum: 0a4e04bc91f65cb695a4ca0415dc042b64e6563f0b4a3b718cd9a0ac0d1d7fab +- filename: packages/contentstack-migrate-rte/test/commands/json-migration.test.js + checksum: 0d18641a32f7cf33d013a5a7c447c1067ba77022513b42304a727f873c741e99 +- filename: packages/contentstack-asset-management/src/export/base.ts + checksum: a74fe9f5b20f7c0636d571062bce1f12d4078a0354d23fb6879b13c316a2fa06 +- filename: packages/contentstack-migrate-rte/test/utils/index.js + checksum: f67235e607a446e5b2dd624046966a1ac5af7bce128aa080ec3c420c48b8bddc +- filename: packages/contentstack-export/src/utils/export-config-handler.ts + checksum: 68351337b78f0962475498946fe34245a681182db756528c9ff6fab7153392e3 +- filename: packages/contentstack-import-setup/README.md + checksum: 192a98001d5587baffeae64fded2542e57031dc83c375c04b79fa92c882602ea +- filename: packages/contentstack-bulk-operations/test/unit/utils/revert-retry-handler.test.ts + checksum: c34a4bdea186f4ce48171d90b8ab0f1c94162e441acb23e3a0e00cb9f9f9255f +- filename: packages/contentstack-asset-management/test/unit/utils/detect-asset-management-export.test.ts + checksum: 1917c51ebf4ba7f94cc440c27341d840911b5c315e250a451732c2d37518db64 +- filename: packages/contentstack-bulk-operations/test/unit/base-bulk-command.test.ts + checksum: 522b91a2d4bb6a73ebe5ab92c0111e74d567c4360470beefd6a7ca8d30a6e311 +- filename: packages/contentstack-asset-management/test/unit/export/assets.test.ts + checksum: bafd24cb0e809fd9510a876a8e9fe53ff616f1dbc9df49bb797f6bcff433ccb7 +- filename: packages/contentstack-clone/test/lib/helpers/command-helpers.test.ts + checksum: 32d385a677a76faa54cb3ddfe92fb1f1f53cfab878a78e2792c823a363956bd2 +- filename: packages/contentstack-content-type/tests/core/contentstack/client.test.ts + checksum: da15b04aa0a7ab0e48f70b847e68c05372943279e51c22f8539b01bb48033183 +- filename: packages/contentstack-variants/test/unit/export/variant-entries.test.ts + checksum: dd7018cd5cc8b5fab73004b2968afed7ce33abec0b20e1b17f15f2be23b117db +- filename: packages/contentstack-import/eslint.config.js + checksum: 2aeebb2c8d4836490b8aacdda15a9951df41a405c72d5758ef656fe8a31314cc +- filename: packages/contentstack-clone/test/commands/stack-clone.test.ts + checksum: 4547ba010cc3c9954de49e2b2bef45288dd070c4ad000b6ae3e9a8c9bb1ad296 +- filename: packages/contentstack-import-setup/eslint.config.js + checksum: d487ec978f0dc2471c68c618bf77f9cc7feb1d745d4bb1d84c28f218f132a2b3 +- filename: packages/contentstack-bulk-operations/test/unit/utils/bulk-operation-log-handler.test.ts + checksum: 8698f77bbb828df10ddc3a94726076a262fbf47d2468233c7e10e66cac42f97d +- filename: packages/contentstack-query-export/README.md + checksum: 2fc7639a5d2f25c0020b270fa88252583c110e81947cfbadd37342c7aabf0520 +- filename: packages/contentstack-import/src/utils/import-config-handler.ts + checksum: 999092528b445b6e9de6883150f02b56bf5050dc64129335d84246c157bfe303 +- filename: packages/contentstack-query-export/test/unit/common-helper.test.ts + checksum: a0aa399a2ec15760fe25f7e952d64792b55d3c38d2281858a48e64f0f9081344 +- filename: packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts + checksum: e469a60f09a9a5fd3e2ce80dd6df8bf4f2c82acad1afebde00f458afa26d36c9 +- filename: packages/contentstack-seed/test/seed/interactive.test.ts + checksum: 8364557eb69b9a297281f868420c13c5b61b26b79bc2ceb0a9b548cfb0365d6d +- filename: packages/contentstack-bulk-operations/src/messages/index.ts + checksum: 3573b80a461f84f28b6eda568bc011d904bcd26908bb7ce013da02e2c5ad71ef +- filename: packages/contentstack-query-export/eslint.config.js + checksum: b87cecdd9c351066fbda88fc5984a48cade05b227a5ce5fbb84aed36805bf9ed +- filename: packages/contentstack-seed/test/seed/contentstack/client.test.ts + checksum: 63bd3325e2717e5653f12e4afa362938e58eeaad5fbe07c1d41e2bd15e3451bd +- filename: packages/contentstack-export/test/unit/utils/interactive.test.ts + checksum: 0ea47db305f576c8c9da8cb34ae915f6b01992208ff1c4380cb7ffd93f538e5d +- filename: packages/contentstack-export/test/unit/utils/marketplace-app-helper.test.ts + checksum: 5648d90b0f7dc33a5b020324db5218707861ebd7201802f7fef00f1c03c5ce82 +- filename: packages/contentstack-asset-management/test/unit/import/spaces.test.ts + checksum: d2363470664a6cdeaf59c305fa6307704e48b73f92b7d471433c17528daa5450 +- filename: packages/contentstack-content-type/tests/core/command.test.ts + checksum: 81da32fc47083a3421f5f1d9c04149ab313cb238d6416d3182f8b6d64dc8827d +- filename: packages/contentstack-clone/eslint.config.js + checksum: 148993140399d12ae033602585f84c06c6a04b0a96b8d7811303211543962ba7 +- filename: packages/contentstack-asset-management/test/unit/import/asset-types.test.ts + checksum: 76aad963f5977059f7632d69a6fb36004229f3455b1618c372579dfa3cbc2d62 +- filename: packages/contentstack-content-type/tests/utils/index.test.ts + checksum: 8510fedf5eb6078c27d6a0d65d8bff4aa2d41b545246aed220d1a6a643188ad8 +- filename: packages/contentstack-clone/test/lib/util/clone-handler.branch.test.ts + checksum: db8c090aaada21d3952c5bc382228a0a11cebb63926512a5e2581bdaf5f4c2f6 +- filename: packages/contentstack-external-migrate/README.md + checksum: 220ed03e300d38036b27ca99f02b15c573e96731d33460910ec43385b2e377bc +- filename: packages/contentstack-asset-management/test/unit/import/assets.test.ts + checksum: f5f0494c9263ebcf7eabff806ff41bd7beed0cb6a0f9a631dd70ffa01524a304 +- filename: packages/contentstack-asset-management/test/unit/import/base.test.ts + checksum: 53168bda5251300d8dfd0effd470070a5733c37b77abacc846d91e7086c8c022 +- filename: packages/contentstack-content-type/eslint.config.js + checksum: 26da78717a38d8e7464a069626213dd3010efa6e50f91efbc996f26b18346948 +- filename: packages/contentstack-export-to-csv/README.md + checksum: 54f70d5191a4e1941855f94c78db9b8d2a9bcd88a2cc9c9170b4c5584b509780 +- filename: packages/contentstack-asset-management/src/utils/cs-assets-api-adapter.ts + checksum: 80fb2531b43eefe9af0cebddbd43413bbc7adb7cf5c25ddcd6372675cefa0de9 +- filename: packages/contentstack-asset-management/test/unit/utils/cs-assets-api-adapter.test.ts + checksum: b4a678fde7c3acce5518cb0303d604b195ddff2165a1269690d60c740c713fe3 - filename: packages/contentstack-import-setup/test/unit/login-handler.test.ts checksum: 2df43be75a96e1a3a00dd256628e191909aa9d7f192b672b56cb3772e074958c -- filename: pnpm-lock.yaml - checksum: eae269c31146f807a80978acb68b83b761742b6d8e82116b6af48dcc0156f085 +- filename: packages/contentstack-import/test/unit/utils/marketplace-app-helper.test.ts + checksum: de694e861560c4c242100eaf17a6d8e230247b99f94e4b428e55ef065084c10c +- filename: packages/contentstack-export/test/unit/utils/export-config-handler.test.ts + checksum: f612661a8b6784b20ea62794b35192b340a34d8424a54a1a2cffec1878ab7528 version: "" diff --git a/packages/contentstack-bulk-operations/src/base-bulk-command.ts b/packages/contentstack-bulk-operations/src/base-bulk-command.ts index 55436fc33..0bddca9d9 100644 --- a/packages/contentstack-bulk-operations/src/base-bulk-command.ts +++ b/packages/contentstack-bulk-operations/src/base-bulk-command.ts @@ -132,7 +132,7 @@ export abstract class BaseBulkCommand extends Command { protected rateLimiter!: AdaptiveRateLimiter; protected retryStrategy!: RetryStrategy; protected operationExecutor!: OperationExecutor; - private batchResults: Map = new Map(); + protected batchResults: Map = new Map(); protected parsedFlags: any; /** @@ -164,7 +164,7 @@ export abstract class BaseBulkCommand extends Command { } // Fill missing required flags via interactive prompts - flags = await fillMissingFlags(flags); + flags = await this.resolveFlagsInteractively(flags); this.parsedFlags = flags; await this.buildConfiguration(flags); @@ -264,6 +264,13 @@ export abstract class BaseBulkCommand extends Command { } } + /** + * Resolve flags interactively — subclasses can override to skip prompts for specific modes. + */ + protected async resolveFlagsInteractively(flags: any): Promise { + return await fillMissingFlags(flags); + } + /** * Build operation configuration */ diff --git a/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts b/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts index 71122a91d..c50b12766 100644 --- a/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts +++ b/packages/contentstack-bulk-operations/src/commands/cm/stacks/bulk-assets.ts @@ -1,12 +1,17 @@ -import { flags, handleAndLogError, FlagInput } from '@contentstack/cli-utilities'; +import * as fs from 'fs'; +import * as path from 'path'; -import { ResourceType } from '../../../interfaces'; +import { flags, handleAndLogError, log } from '@contentstack/cli-utilities'; + +import { AssetPublishData, BulkOperationResult, OperationType, ResourceType } from '../../../interfaces'; import { BaseBulkCommand } from '../../../base-bulk-command'; -import { $t, messages, fetchAssets } from '../../../utils'; +import { $t, messages, fetchAssets, scanDataDirStats, BATCH_CONSTANTS, categorizeByScanStatus } from '../../../utils'; +import type { DataDirScanStats } from '../../../utils'; +import { AssetService } from '../../../services'; /** * Bulk operations command for assets - * Supports publish, unpublish, and cross publish operations + * Supports publish, unpublish, cross-publish, and data-dir publish operations */ export default class BulkAssets extends BaseBulkCommand { static description = messages.BULK_ASSETS_DESCRIPTION; @@ -32,25 +37,40 @@ export default class BulkAssets extends BaseBulkCommand { // Revert (unpublish) previously published assets using success log '<%= config.bin %> <%= command.id %> --revert ./bulk-operation -a myAlias', + + // Publish assets from exported content folder (e.g. after asset scanning clears) + '<%= config.bin %> <%= command.id %> --data-dir ./content --operation publish -k blt123', ]; - static flags: FlagInput = { + static flags = { ...BaseBulkCommand.baseFlags, 'folder-uid': flags.string({ description: messages.FOLDER_UID, }), + 'data-dir': flags.string({ + char: 'd', + description: messages.DATA_DIR_FLAG_DESC, + }), + 'dry-run': flags.boolean({ + description: messages.DRY_RUN_FLAG_DESC, + default: false, + }), }; protected resourceType: ResourceType = ResourceType.ASSET; async run(): Promise { try { - // Handle cross-publish separately if source-env is specified if (this.bulkOperationConfig.sourceEnv) { await this.handleCrossPublish(this.parsedFlags); return; } + if (this.bulkOperationConfig.dataDir) { + await this.runDataDirFlow(); + return; + } + const assets = await this.fetchItems(); if (assets.length === 0) { @@ -58,18 +78,39 @@ export default class BulkAssets extends BaseBulkCommand { return; } - this.logger.info( - $t(messages.FOUND_ASSETS_TO_OPERATE, { count: assets.length, operation: this.parsedFlags.operation || '' }) - ); + const { clean, pending, quarantined, noStatus } = categorizeByScanStatus(assets); + const scanningEnabled = clean.length + pending.length + quarantined.length > 0; + const publishable = scanningEnabled ? clean : [...clean, ...noStatus]; - // Confirm operation - const confirmed = await this.confirmOperation(assets); + if (scanningEnabled) { + // Log individual skipped assets + pending.forEach((a) => this.logger.warn($t(messages.SCAN_STATUS_SKIPPED_PENDING, { uid: a.uid }))); + quarantined.forEach((a) => this.logger.warn($t(messages.SCAN_STATUS_SKIPPED_QUARANTINED, { uid: a.uid }))); + + this.printScanningDashboard({ + total: assets.length, + clean: clean.length, + pending: pending.length, + quarantined: quarantined.length, + }); + + if (publishable.length === 0) { + this.logger.warn($t(messages.NO_PUBLISHABLE_ASSETS)); + return; + } + } else { + log.info( + $t(messages.FOUND_ASSETS_TO_OPERATE, { count: assets.length, operation: this.parsedFlags.operation || '' }) + ); + } + + const confirmed = await this.confirmOperation(publishable); if (!confirmed) { this.logger.warn($t(messages.OPERATION_CANCELLED)); return; } - const result = await this.executeBulkOperation(assets); + const result = await this.executeBulkOperation(publishable); this.printOperationSummary(result); } catch (error) { handleAndLogError(error); @@ -78,6 +119,218 @@ export default class BulkAssets extends BaseBulkCommand { } } + private async runDataDirFlow(): Promise { + const { dataDir, dryRun } = this.bulkOperationConfig; + + // Capture original CLI locales/envs before pass 1 overwrites them on the config. + const cliLocales = [...(this.bulkOperationConfig.locales || [])]; + const cliEnvs = [...(this.bulkOperationConfig.environments || [])]; + + // Pass 1 — count-only scan: no AssetPublishData objects built, one chunk in memory at a time. + let stats: DataDirScanStats; + try { + stats = await scanDataDirStats(dataDir!, cliEnvs, cliLocales, this.logger); + } catch (err: any) { + this.logger.error($t(messages.DATA_DIR_READ_ERROR, { path: dataDir!, error: err.message || String(err) })); + return; + } + + this.bulkOperationConfig.environments = stats.environments; + this.bulkOperationConfig.locales = stats.locales; + + // Pass 1.5 — fetch scan status for all target UIDs (post-import UIDs on the destination stack). + const targetUids = Object.values(stats.assetUidMapper); + const assetService = new AssetService(this.managementStack, this.deliveryStack, this.logger); + const scanStatusMap = await assetService.fetchScanStatusByUIDs(targetUids); + + let cleanCount = 0; + let pendingCount = 0; + let quarantinedCount = 0; + for (const uid of targetUids) { + const status = scanStatusMap.get(uid); + if (status === 'pending') pendingCount++; + else if (status === 'quarantined') quarantinedCount++; + else cleanCount++; // clean or undefined (scanning disabled) — both are publishable + } + + this.printScanningDashboard({ + total: stats.eligible + stats.skipped + stats.unmapped, + localSkipped: stats.skipped, + unmapped: stats.unmapped, + clean: cleanCount, + pending: pendingCount, + quarantined: quarantinedCount, + }); + + if (cleanCount === 0) { + this.logger.warn($t(messages.NO_PUBLISHABLE_ASSETS)); + return; + } + + // new Array(n) has .length === n but allocates no elements — just for the count. + const confirmed = await this.confirmOperation(new Array(cleanCount)); + if (!confirmed) { + this.logger.warn($t(messages.OPERATION_CANCELLED)); + return; + } + + if (dryRun) { + log.info($t(messages.DATA_DIR_DRY_RUN)); + return; + } + + // Pass 2 — stream and publish: one chunk at a time, batches of ≤50 items enqueued directly. + // stats.assetUidMapper and stats.assetsIndex are reused from pass 1 — no second disk read. + const result = await this.streamAndPublish( + dataDir!, + cliLocales, + stats.totalItems, + stats.assetUidMapper, + stats.assetsIndex, + scanStatusMap + ); + this.printOperationSummary(result); + } + + /** + * Pass 2 of the data-dir flow. + * Reads chunk files one at a time, fills a working batch of ≤50 AssetPublishData items, + * and enqueues each batch directly into the queue manager without ever holding the full + * asset list in memory. Peak memory: one chunk file + one batch of ≤50 items. + * + * assetUidMapper and assetsIndex are passed in from pass 1 to avoid re-reading those files. + * scanStatusMap filters out non-clean assets before enqueueing. + */ + private async streamAndPublish( + dataDir: string, + cliLocales: string[], + totalItemCount: number, + assetUidMapper: Record, + assetsIndex: Record, + scanStatusMap: Map + ): Promise { + // Snapshot both arrays so in-flight mutations to bulkOperationConfig can't corrupt payloads. + const environments = [...this.bulkOperationConfig.environments!]; + const locales = [...this.bulkOperationConfig.locales!]; + const operation = this.bulkOperationConfig.operation as OperationType; + const startTime = Date.now(); + + // Warn early if the mapper is empty — all assets will be skipped and the user needs to know why. + if (Object.keys(assetUidMapper).length === 0) { + this.logger.warn( + 'Asset UID mapper is empty — all assets will be skipped. Ensure the import completed successfully.' + ); + } + + const useOverrideLocales = cliLocales.length > 0; + const BATCH_SIZE = BATCH_CONSTANTS.maxItems; + // totalItemCount comes from pass 1 using identical counting logic — used as upper bound for totalBatches. + // Scan status filtering may reduce the actual count; the invariant check below will log any mismatch. + const totalBatches = Math.ceil(totalItemCount / BATCH_SIZE); + + let workingBatch: AssetPublishData[] = []; + let batchNumber = 0; + let totalSubmitted = 0; + + this.batchResults.clear(); + + const flushBatch = (): void => { + if (workingBatch.length === 0) return; + batchNumber++; + this.queueManager.enqueue(ResourceType.ASSET, operation, { + items: [...workingBatch], + environments, + locales, + batchNumber, + totalBatches, + operation, + }); + totalSubmitted += workingBatch.length; + workingBatch = []; + }; + + for (const chunkFilename of Object.values(assetsIndex)) { + const chunkPath = path.join(dataDir, 'assets', chunkFilename); + const chunkData: Record = JSON.parse(fs.readFileSync(chunkPath, 'utf-8')); + + for (const asset of Object.values(chunkData)) { + if (!asset.publish_details || asset.publish_details.length === 0) continue; + const targetUid = assetUidMapper[asset.uid as string]; + if (!targetUid) continue; + + // Skip assets that did not pass scanning. + const scanStatus = scanStatusMap.get(targetUid); + if (scanStatus === 'quarantined') { + this.logger.warn($t(messages.SCAN_STATUS_SKIPPED_QUARANTINED, { uid: targetUid })); + continue; + } + if (scanStatus === 'pending') { + this.logger.warn($t(messages.SCAN_STATUS_SKIPPED_PENDING, { uid: targetUid })); + continue; + } + + const assetLocales: string[] = useOverrideLocales + ? cliLocales + : [...new Set(asset.publish_details.map((pd: any) => pd.locale as string))]; + + for (const locale of assetLocales) { + workingBatch.push({ type: 'asset', uid: targetUid, locale, version: asset._version }); + if (workingBatch.length >= BATCH_SIZE) { + flushBatch(); + } + } + } + // chunkData falls out of scope here — GC can reclaim it before the next chunk is read. + } + + flushBatch(); + + // Invariant: pass 1 and pass 2 use identical counting logic (excluding scan status filtering). + // If batchNumber < totalBatches, scan status filtering reduced the published count — expected. + if (batchNumber !== totalBatches) { + this.logger.debug( + `Batch count: predicted ${totalBatches}, actual ${batchNumber}. Difference is expected when assets are skipped due to scan status.` + ); + } + + await this.queueManager.waitForCompletion(); + + const duration = Date.now() - startTime; + const jobIds = [...this.batchResults.values()].map((r) => r.jobId).filter((id): id is string => !!id); + + return { success: 0, failed: 0, total: totalSubmitted, duration, jobIds }; + } + + private printScanningDashboard(opts: { + total: number; + clean: number; + pending: number; + quarantined: number; + localSkipped?: number; + unmapped?: number; + }): void { + const { total, clean, pending, quarantined, localSkipped, unmapped } = opts; + const SEP = '─'.repeat(42); + + log.info(''); + log.info(` ${messages.DATA_DIR_ASSET_SCANNING_HEADER}`); + log.info(' ' + SEP); + log.info(` ${messages.DATA_DIR_TOTAL.padEnd(38)} ${total}`); + if (localSkipped !== undefined) { + log.warn(` ${messages.DATA_DIR_NO_PUBLISH_DETAILS.padEnd(38)} ${localSkipped}`); + } + if (unmapped !== undefined) { + log.warn(` ${messages.DATA_DIR_UNMAPPED.padEnd(38)} ${unmapped}`); + } + log.info(' ' + SEP); + log.info(` ${messages.SCAN_STATUS_CLEAN.padEnd(38)} ${clean}`); + if (pending > 0) log.warn(` ${messages.SCAN_STATUS_PENDING.padEnd(38)} ${pending}`); + if (quarantined > 0) log.warn(` ${messages.SCAN_STATUS_QUARANTINED.padEnd(38)} ${quarantined}`); + log.info(' ' + SEP); + log.info(` ${messages.DATA_DIR_WILL_PUBLISH.padEnd(38)} ${clean}`); + log.info(''); + } + protected async fetchItems(): Promise { return await fetchAssets(this.bulkOperationConfig, this.managementStack, this.deliveryStack, this.logger); } diff --git a/packages/contentstack-bulk-operations/src/interfaces/index.ts b/packages/contentstack-bulk-operations/src/interfaces/index.ts index 04b3ed901..876de7f6d 100644 --- a/packages/contentstack-bulk-operations/src/interfaces/index.ts +++ b/packages/contentstack-bulk-operations/src/interfaces/index.ts @@ -59,6 +59,8 @@ export interface BulkOperationConfig { // Asset-specific options folderUid?: string; + dataDir?: string; + dryRun?: boolean; // Cross-publish sourceEnv?: string; @@ -136,6 +138,7 @@ export interface Asset { title?: string; _version?: number; publish_details?: PublishDetails[]; + _asset_scan_status?: 'pending' | 'clean' | 'quarantined'; [key: string]: any; } @@ -197,6 +200,8 @@ export interface CommandFlags { // Asset-specific flags 'folder-uid'?: string; + 'data-dir'?: string; + 'dry-run'?: boolean; /** CS Assets bulk delete/move */ 'space-uid'?: string; @@ -255,6 +260,7 @@ export interface AssetPublishData { locale: string; version?: number; publish_details?: PublishDetails[]; + _asset_scan_status?: 'pending' | 'clean' | 'quarantined'; } /** One row for CS Assets bulk-delete payload `{ uid, locale }[]`. */ diff --git a/packages/contentstack-bulk-operations/src/messages/index.ts b/packages/contentstack-bulk-operations/src/messages/index.ts index ccc90ebb5..94fe95899 100644 --- a/packages/contentstack-bulk-operations/src/messages/index.ts +++ b/packages/contentstack-bulk-operations/src/messages/index.ts @@ -210,6 +210,25 @@ const bulkAssetsMsg = { CROSS_PUBLISHING: 'Cross-publishing from {sourceEnv} to {targetEnvs}', SYNCED_ASSETS: 'Synced {count} assets from {sourceEnv}', ASSETS_READY_FOR_CROSS_PUBLISH: '{count} assets ready for cross-publish', + + // Data-dir / scanning dashboard + DATA_DIR_ASSET_SCANNING_HEADER: 'Asset Scan Status', + DATA_DIR_TOTAL: 'Total assets found', + DATA_DIR_VALID: 'Clean (will publish)', + DATA_DIR_NO_PUBLISH_DETAILS: 'No publish details (skipped)', + DATA_DIR_UNMAPPED: 'Not imported / UID unmapped (skipped)', + DATA_DIR_WILL_PUBLISH: 'Will publish', + DATA_DIR_DRY_RUN: 'Dry run — no publish API calls will be made.', + DATA_DIR_FLAG_DESC: 'Path to exported content folder containing asset publish details.', + DRY_RUN_FLAG_DESC: 'Preview the publish plan without making any API calls.', + DATA_DIR_READ_ERROR: 'Failed to read data directory at {path}: {error}', + SCAN_STATUS_CLEAN: 'Clean (will publish)', + SCAN_STATUS_PENDING: 'Still scanning (skipped)', + SCAN_STATUS_QUARANTINED: 'Quarantined (skipped)', + SCAN_STATUS_SKIPPED_PENDING: 'Skipped (still scanning): {uid}', + SCAN_STATUS_SKIPPED_QUARANTINED: 'Skipped (quarantined): {uid}', + SCAN_STATUS_FETCHING: 'Checking asset scan status for {count} assets...', + NO_PUBLISHABLE_ASSETS: 'No publishable assets — all assets are either still scanning or quarantined.', }; /** diff --git a/packages/contentstack-bulk-operations/src/services/asset-service.ts b/packages/contentstack-bulk-operations/src/services/asset-service.ts index aba844fb1..562e316af 100644 --- a/packages/contentstack-bulk-operations/src/services/asset-service.ts +++ b/packages/contentstack-bulk-operations/src/services/asset-service.ts @@ -37,7 +37,7 @@ export class AssetService { const batchUids = uids.slice(i, i + BATCH_CONSTANTS.assetFetchBatchSize); const batchPromises = batchUids.map(async (uid) => { try { - const asset = this.deliveryStack ? await this.deliveryStack.asset(uid).fetch() : undefined; + const asset = await this.deliveryStack?.asset(uid).fetch(); return asset; } catch (error: any) { // Asset might not exist or not be published to this environment @@ -125,7 +125,13 @@ export class AssetService { try { while (hasMore) { - const queryOptions: any = { skip, limit, include_count: true, include_publish_details: true }; + const queryOptions: any = { + skip, + limit, + include_count: true, + include_publish_details: true, + include_asset_scan_status: true, + }; // Add any filters from options if (options.query) { @@ -205,9 +211,14 @@ export class AssetService { try { while (hasMore) { - const query = this.stack - .asset() - .query({ skip, limit, include_count: true, include_publish_details: true, folder: folderUid }); + const query = this.stack.asset().query({ + skip, + limit, + include_count: true, + include_publish_details: true, + include_asset_scan_status: true, + folder: folderUid, + }); const response = await query.find(); const assets = response.items || []; @@ -273,4 +284,34 @@ export class AssetService { throw error; } } + + /** + * Fetch scan status for a specific list of asset UIDs. + * Batches requests at 100 UIDs per call to stay within API limits. + * Returns a Map — undefined means scanning is not enabled on the stack. + */ + async fetchScanStatusByUIDs(uids: string[]): Promise> { + const statusMap = new Map(); + if (uids.length === 0) return statusMap; + + this.logger.info($t(messages.SCAN_STATUS_FETCHING, { count: uids.length })); + + const BATCH = 100; + for (let i = 0; i < uids.length; i += BATCH) { + const batch = uids.slice(i, i + BATCH); + try { + const response = await this.stack + .asset() + .query({ uid: { $in: batch }, include_asset_scan_status: true, limit: BATCH }) + .find(); + for (const asset of response.items || []) { + statusMap.set(asset.uid, asset._asset_scan_status); + } + } catch (error: any) { + this.logger.warn(`Failed to fetch scan status for batch starting at index ${i}: ${error?.message}`); + } + } + + return statusMap; + } } diff --git a/packages/contentstack-bulk-operations/src/services/bulk-operation-service.ts b/packages/contentstack-bulk-operations/src/services/bulk-operation-service.ts index 705d1a90c..18c9618f5 100644 --- a/packages/contentstack-bulk-operations/src/services/bulk-operation-service.ts +++ b/packages/contentstack-bulk-operations/src/services/bulk-operation-service.ts @@ -47,13 +47,15 @@ export class BulkOperationService { async executeBulkPublish( items: Array, operation: OperationType, - resourceType: ResourceType + resourceType: ResourceType, + environments?: string[], + locales?: string[] ): Promise { this.logger.info($t(messages.SUBMITTING_BULK_JOB, { operation, count: items.length })); try { // Step 1: Submit bulk job - const jobId = await this.submitBulkJob(items, operation, resourceType); + const jobId = await this.submitBulkJob(items, operation, resourceType, environments, locales); this.logger.debug($t(messages.BULK_JOB_CREATED, { jobId })); // Return immediate result after job submission @@ -78,10 +80,12 @@ export class BulkOperationService { private async submitBulkJob( items: Array, operation: OperationType, - resourceType: ResourceType + resourceType: ResourceType, + environments?: string[], + locales?: string[] ): Promise { try { - const payload = this.prepareBulkPayload(items, operation, resourceType); + const payload = this.prepareBulkPayload(items, operation, resourceType, environments, locales); let response: any; switch (operation) { case OperationType.PUBLISH: @@ -203,16 +207,23 @@ export class BulkOperationService { private prepareBulkPayload( items: Array, operation: OperationType, - resourceType: ResourceType + resourceType: ResourceType, + environments?: string[], + locales?: string[] ): any { if (resourceType === ResourceType.ENTRY) { - return this.prepareEntryBulkPayload(items as EntryPublishData[], operation); + return this.prepareEntryBulkPayload(items as EntryPublishData[], operation, environments, locales); } else { - return this.prepareAssetBulkPayload(items as AssetPublishData[], operation); + return this.prepareAssetBulkPayload(items as AssetPublishData[], operation, environments, locales); } } - private prepareEntryBulkPayload(items: EntryPublishData[], operation: OperationType): any { + private prepareEntryBulkPayload( + items: EntryPublishData[], + operation: OperationType, + batchEnvironments?: string[], + batchLocales?: string[] + ): any { const entries = items.map((item) => { const entry: any = { uid: item.uid, @@ -233,8 +244,17 @@ export class BulkOperationService { return entry; }); - const environments = items[0]?.publish_details?.map((pd) => pd.environment) || []; - const locales = Array.from(new Set(items.map((item) => item.locale))); + const environments = batchEnvironments?.length + ? batchEnvironments + : items[0]?.publish_details?.map((pd) => pd.environment) || []; + const locales = batchLocales?.length ? batchLocales : Array.from(new Set(items.map((item) => item.locale))); + + if (!environments.length) { + throw new Error('No environments for bulk publish. Ensure entries have publish_details with environment data.'); + } + if (!locales.length) { + throw new Error('No locales for bulk publish. Ensure entries have a locale field.'); + } return { entries, @@ -244,14 +264,28 @@ export class BulkOperationService { }; } - private prepareAssetBulkPayload(items: AssetPublishData[], operation: OperationType): any { + private prepareAssetBulkPayload( + items: AssetPublishData[], + operation: OperationType, + batchEnvironments?: string[], + batchLocales?: string[] + ): any { const assets = items.map((item) => ({ uid: item.uid, version: item.version, })); - const environments = items[0]?.publish_details?.map((pd) => pd.environment) || []; - const locales = items[0]?.publish_details?.map((pd) => pd.locale) || []; + const environments = batchEnvironments?.length + ? batchEnvironments + : items[0]?.publish_details?.map((pd) => pd.environment) || []; + const locales = batchLocales?.length ? batchLocales : items[0]?.publish_details?.map((pd) => pd.locale) || []; + + if (!environments.length) { + throw new Error('No environments for bulk publish. Ensure assets have publish_details with environment data.'); + } + if (!locales.length) { + throw new Error('No locales for bulk publish. Ensure assets have publish_details with locale data.'); + } return { assets, diff --git a/packages/contentstack-bulk-operations/src/utils/batch-queue-handler.ts b/packages/contentstack-bulk-operations/src/utils/batch-queue-handler.ts index e32eadcd8..4e624e5da 100644 --- a/packages/contentstack-bulk-operations/src/utils/batch-queue-handler.ts +++ b/packages/contentstack-bulk-operations/src/utils/batch-queue-handler.ts @@ -21,7 +21,7 @@ export function setupBatchQueueListeners(config: BatchQueueConfig) { } logger.info( - `Processing batch ${batch.batchNumber ?? 0}/${batch.totalBatches ?? 0}: ` + + `Processing batch ${batch.batchNumber}/${batch.totalBatches}: ` + `${batch.items.length} items, ` + `${batch.locales.length} locales, ` + `${batch.environments.length} environments` @@ -29,7 +29,13 @@ export function setupBatchQueueListeners(config: BatchQueueConfig) { (async () => { try { - const result = await bulkService.executeBulkPublish(batch.items, batch.operation, resourceType); + const result = await bulkService.executeBulkPublish( + batch.items, + batch.operation, + resourceType, + batch.environments, + batch.locales + ); batchResults.set(item.id, result); queueManager.updateItemStatus(item.id, OperationStatus.SUCCESS); @@ -76,7 +82,7 @@ export function setupBatchQueueListeners(config: BatchQueueConfig) { if (!batch) return; handleAndLogError(error, { - batchNumber: `${batch.batchNumber ?? 0}/${batch.totalBatches ?? 0}`, + batchNumber: `${batch.batchNumber}/${batch.totalBatches}`, itemCount: batch.items.length, }); @@ -109,7 +115,7 @@ async function handleRetryOrFailure({ : retryStrategy.getDelay(item.retryCount); logger.warn( - `Batch ${batch.batchNumber ?? 0}/${batch.totalBatches ?? 0} failed with ${ + `Batch ${batch.batchNumber}/${batch.totalBatches} failed with ${ isRateLimit ? '429 Rate Limit' : getErrorCode(error) }, retrying in ${Math.ceil(delay / 1000)}s` ); diff --git a/packages/contentstack-bulk-operations/src/utils/config-builder.ts b/packages/contentstack-bulk-operations/src/utils/config-builder.ts index c53deb98c..e92e9fb7c 100644 --- a/packages/contentstack-bulk-operations/src/utils/config-builder.ts +++ b/packages/contentstack-bulk-operations/src/utils/config-builder.ts @@ -69,24 +69,28 @@ function validateConfig(config: BulkOperationConfig): string[] { errors.push(`Invalid operation type: ${config.operation}. Must be 'publish' or 'unpublish'`); } - // Environments validation - if ( - (operation === OperationType.PUBLISH || operation === OperationType.UNPUBLISH) && - (!config.environments || config.environments.length === 0) - ) { - errors.push('Environments are required for publish/unpublish operations'); - } - if (config.environments?.some((env) => !env || env.trim() === '')) { - errors.push('Environment list cannot contain empty values'); + // Environments validation — skipped when assets are read from a data directory + if (!config.dataDir) { + if ( + (operation === OperationType.PUBLISH || operation === OperationType.UNPUBLISH) && + (!config.environments || config.environments.length === 0) + ) { + errors.push('Environments are required for publish/unpublish operations'); + } + if (config.environments?.some((env) => !env || env.trim() === '')) { + errors.push('Environment list cannot contain empty values'); + } } - // Locales validation + // Locales validation — skipped when assets are read from a data directory const isNonLocalized = config.filter === FilterType.NON_LOCALIZED; - if (!isNonLocalized && (!config.locales || config.locales.length === 0)) { - errors.push('Locales are required'); - } - if (config.locales?.some((locale) => !locale || locale.trim() === '')) { - errors.push('Locale list cannot contain empty values'); + if (!config.dataDir) { + if (!isNonLocalized && (!config.locales || config.locales.length === 0)) { + errors.push('Locales are required'); + } + if (config.locales?.some((locale) => !locale || locale.trim() === '')) { + errors.push('Locale list cannot contain empty values'); + } } // Filter validation @@ -144,24 +148,28 @@ function validateCommandFlags(flags: CommandFlags): string[] { const operation = flags.operation as OperationType; - // Environment validation - if ( - (operation === OperationType.PUBLISH || operation === OperationType.UNPUBLISH) && - (!flags.environments || flags.environments.length === 0) - ) { - errors.push('Environments are required for publish/unpublish operations'); - } - if (flags.environments?.some((env) => !env || env.trim() === '')) { - errors.push('Environment list cannot contain empty values'); + // Environment validation — skipped when assets are read from a data directory + if (!flags['data-dir']) { + if ( + (operation === OperationType.PUBLISH || operation === OperationType.UNPUBLISH) && + (!flags.environments || flags.environments.length === 0) + ) { + errors.push('Environments are required for publish/unpublish operations'); + } + if (flags.environments?.some((env) => !env || env.trim() === '')) { + errors.push('Environment list cannot contain empty values'); + } } - // Locale validation + // Locale validation — skipped when assets are read from a data directory const isNonLocalized = flags.filter === FilterType.NON_LOCALIZED; - if (!isNonLocalized && (!flags.locales || flags.locales.length === 0)) { - errors.push('Locales are required'); - } - if (flags.locales?.some((locale) => !locale || locale.trim() === '')) { - errors.push('Locale list cannot contain empty values'); + if (!flags['data-dir']) { + if (!isNonLocalized && (!flags.locales || flags.locales.length === 0)) { + errors.push('Locales are required'); + } + if (flags.locales?.some((locale) => !locale || locale.trim() === '')) { + errors.push('Locale list cannot contain empty values'); + } } // Content types validation @@ -243,6 +251,8 @@ export function buildConfig(flags: CommandFlags): BulkOperationConfig { contentTypes: flags['content-types'] !== undefined ? expandFlagStringList(flags['content-types']) : undefined, includeVariants: flags['include-variants'], folderUid: flags['folder-uid'], + dataDir: flags['data-dir'], + dryRun: flags['dry-run'], sourceEnv: flags['source-env'], publishMode: (flags['publish-mode'] as PublishMode) || PublishMode.BULK, apiVersion: flags['api-version'] || '3', diff --git a/packages/contentstack-bulk-operations/src/utils/data-dir-asset-fetcher.ts b/packages/contentstack-bulk-operations/src/utils/data-dir-asset-fetcher.ts new file mode 100644 index 000000000..71ea47bd3 --- /dev/null +++ b/packages/contentstack-bulk-operations/src/utils/data-dir-asset-fetcher.ts @@ -0,0 +1,114 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +export interface DataDirScanStats { + /** Number of assets eligible for publish (have publish_details + mapped UID). */ + eligible: number; + /** Total AssetPublishData items that will be created (eligible × locale expansions). */ + totalItems: number; + skipped: number; + unmapped: number; + environments: string[]; + locales: string[]; + /** Reusable in pass 2 — already loaded during pass 1, avoids a second disk read. */ + assetUidMapper: Record; + /** Reusable in pass 2 — already loaded during pass 1, avoids a second disk read. */ + assetsIndex: Record; +} + +/** + * Pass 1: count-only scan of the data directory. + * Reads chunk files one at a time, counts eligible/skipped/unmapped, and + * discovers environments and locales — without building any AssetPublishData objects. + * Memory footprint: uid mapper + env map + one chunk at a time. + * + * Returns assetUidMapper and assetsIndex so pass 2 (streamAndPublish) can reuse them + * without re-reading the same files from disk. + */ +export async function scanDataDirStats( + dataDir: string, + overrideEnvs?: string[], + overrideLocales?: string[], + logger?: any +): Promise { + const assetsIndexPath = path.join(dataDir, 'assets', 'assets.json'); + const environmentsPath = path.join(dataDir, 'environments', 'environments.json'); + const assetUidMapperPath = path.join(dataDir, 'mapper', 'assets', 'uid-mapping.json'); + + if (!fs.existsSync(assetsIndexPath)) { + throw new Error( + `Asset index not found: ${assetsIndexPath}. Ensure --data-dir points to the import backup directory.` + ); + } + + let assetUidMapper: Record = {}; + if (fs.existsSync(assetUidMapperPath)) { + assetUidMapper = JSON.parse(fs.readFileSync(assetUidMapperPath, 'utf-8')); + } else { + logger?.warn( + `Asset UID mapper not found: ${assetUidMapperPath}. Ensure --data-dir points to the import backup directory.` + ); + } + + const environmentsMap: Record = {}; + if (fs.existsSync(environmentsPath)) { + const envData: Record = JSON.parse(fs.readFileSync(environmentsPath, 'utf-8')); + for (const [uid, env] of Object.entries(envData)) { + environmentsMap[uid] = (env as any).name || uid; + } + } else { + logger?.warn(`Environments file not found: ${environmentsPath}`); + } + + const assetsIndex: Record = JSON.parse(fs.readFileSync(assetsIndexPath, 'utf-8')); + + let eligible = 0; + let totalItems = 0; + let skipped = 0; + let unmapped = 0; + const allEnvs = new Set(); + const allLocales = new Set(); + + for (const chunkFilename of Object.values(assetsIndex)) { + const chunkPath = path.join(dataDir, 'assets', chunkFilename); + const chunkData: Record = JSON.parse(fs.readFileSync(chunkPath, 'utf-8')); + + for (const asset of Object.values(chunkData)) { + if (!asset.publish_details || asset.publish_details.length === 0) { + skipped++; + continue; + } + + const targetUid = assetUidMapper[asset.uid as string]; + if (!targetUid) { + unmapped++; + continue; + } + + eligible++; + + if (!overrideLocales?.length) { + for (const pd of asset.publish_details) { + if (pd.locale) allLocales.add(pd.locale as string); + } + } + if (!overrideEnvs?.length) { + for (const pd of asset.publish_details) { + const envName = environmentsMap[pd.environment] || pd.environment; + if (envName) allEnvs.add(envName as string); + } + } + + const localeCount = overrideLocales?.length + ? overrideLocales.length + : new Set(asset.publish_details.map((pd: any) => pd.locale as string)).size; + totalItems += localeCount; + } + // chunkData falls out of scope here — GC reclaims it + } + + const environments = overrideEnvs?.length ? overrideEnvs : [...allEnvs]; + const locales = overrideLocales?.length ? overrideLocales : [...allLocales]; + + return { eligible, totalItems, skipped, unmapped, environments, locales, assetUidMapper, assetsIndex }; +} diff --git a/packages/contentstack-bulk-operations/src/utils/helpers.ts b/packages/contentstack-bulk-operations/src/utils/helpers.ts index 7e5beb260..8b7666048 100644 --- a/packages/contentstack-bulk-operations/src/utils/helpers.ts +++ b/packages/contentstack-bulk-operations/src/utils/helpers.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { getLogPath } from '@contentstack/cli-utilities'; import { $t, messages } from './index'; -import { AssetPublishData, EntryPublishData, BulkOperationResult, BulkJobResult } from '../interfaces'; +import { AssetPublishData, EntryPublishData, BulkOperationResult, BulkJobResult, Asset } from '../interfaces'; export function chunkArray(array: T[], chunkSize: number): T[][] { const chunks: T[][] = []; @@ -122,3 +122,37 @@ export function logSummary(result: any): void { console.log(''); } + +/** + * Categorize assets by their _asset_scan_status field. + * Assets with no status field belong to stacks where scanning is disabled — treat as publishable. + */ +export function categorizeByScanStatus(assets: Asset[]): { + clean: Asset[]; + pending: Asset[]; + quarantined: Asset[]; + noStatus: Asset[]; +} { + const clean: Asset[] = []; + const pending: Asset[] = []; + const quarantined: Asset[] = []; + const noStatus: Asset[] = []; + + for (const asset of assets) { + switch (asset._asset_scan_status) { + case 'clean': + clean.push(asset); + break; + case 'pending': + pending.push(asset); + break; + case 'quarantined': + quarantined.push(asset); + break; + default: + noStatus.push(asset); + } + } + + return { clean, pending, quarantined, noStatus }; +} diff --git a/packages/contentstack-bulk-operations/src/utils/index.ts b/packages/contentstack-bulk-operations/src/utils/index.ts index aa1d8801f..d9cb77bb5 100644 --- a/packages/contentstack-bulk-operations/src/utils/index.ts +++ b/packages/contentstack-bulk-operations/src/utils/index.ts @@ -21,6 +21,7 @@ import { aggregateBatchResults, createOperationResult, logSummary, + categorizeByScanStatus, } from './helpers'; import { setupBatchQueueListeners } from './batch-queue-handler'; import { confirmOperation } from './operation-confirmation'; @@ -51,6 +52,8 @@ import { validateAndBuildBulkDeleteItems, LoadAssetUidsError, } from './asset-uids-from-file'; +import { scanDataDirStats } from './data-dir-asset-fetcher'; +import type { DataDirScanStats } from './data-dir-asset-fetcher'; import { compareFieldValues, compareNonLocalizedFields, @@ -90,6 +93,7 @@ export { fetchAssets, fetchEntries, logSummary, + categorizeByScanStatus, logOperationInfo, validateBatch, enqueueIndividualItems, @@ -118,4 +122,6 @@ export { loadBulkDeleteItemsFromFile, validateAndBuildBulkDeleteItems, LoadAssetUidsError, + scanDataDirStats, }; +export type { DataDirScanStats }; diff --git a/packages/contentstack-bulk-operations/src/utils/interactive.ts b/packages/contentstack-bulk-operations/src/utils/interactive.ts index 448d1d5ca..31dba9260 100644 --- a/packages/contentstack-bulk-operations/src/utils/interactive.ts +++ b/packages/contentstack-bulk-operations/src/utils/interactive.ts @@ -162,13 +162,18 @@ export async function fillMissingFlags(flags: any): Promise { // Track if we prompted for anything let didPrompt = false; + // The presence of --data-dir is what selects the import-backup publish flow: + // environments and locales are then derived per-asset from the backup, so we + // neither prompt for the data-dir path nor for environments/locales here. + const hasDataDir = !!updatedFlags['data-dir']; + // Check if any required fields are missing const needsCredentials = !updatedFlags.alias && !updatedFlags['stack-api-key']; const needsOperation = !updatedFlags.operation; - const needsEnvironments = !updatedFlags.environments || updatedFlags.environments.length === 0; // Check if non-localized filter is used const isNonLocalized = updatedFlags.filter === FilterType.NON_LOCALIZED; - const needsLocales = !isNonLocalized && (!updatedFlags.locales || updatedFlags.locales.length === 0); + const needsEnvironments = !hasDataDir && (!updatedFlags.environments || updatedFlags.environments.length === 0); + const needsLocales = !hasDataDir && !isNonLocalized && (!updatedFlags.locales || updatedFlags.locales.length === 0); // Only show interactive mode header if we need to prompt if (needsCredentials || needsOperation || needsEnvironments || needsLocales) { diff --git a/packages/contentstack-bulk-operations/src/utils/item-fetcher.ts b/packages/contentstack-bulk-operations/src/utils/item-fetcher.ts index 35076ade2..c794520ce 100644 --- a/packages/contentstack-bulk-operations/src/utils/item-fetcher.ts +++ b/packages/contentstack-bulk-operations/src/utils/item-fetcher.ts @@ -221,6 +221,7 @@ export async function fetchAssets( environment: env, locale, })), + _asset_scan_status: asset._asset_scan_status, }); } } diff --git a/packages/contentstack-bulk-operations/test/unit/services/bulk-operation-service.test.ts b/packages/contentstack-bulk-operations/test/unit/services/bulk-operation-service.test.ts index 60e1d6881..b34a84e20 100644 --- a/packages/contentstack-bulk-operations/test/unit/services/bulk-operation-service.test.ts +++ b/packages/contentstack-bulk-operations/test/unit/services/bulk-operation-service.test.ts @@ -132,7 +132,7 @@ describe('BulkOperationService', () => { content_type: 'blog', locale: 'en-us', version: 1, - publish_details: [], + publish_details: [{ environment: 'production', locale: 'en-us', version: 1 }], }, ]; @@ -168,7 +168,7 @@ describe('BulkOperationService', () => { content_type: 'blog', locale: 'en-us', version: 1, - publish_details: [], + publish_details: [{ environment: 'production', locale: 'en-us', version: 1 }], }, ]; @@ -390,14 +390,9 @@ describe('BulkOperationService', () => { } as EntryPublishData, ]; - const payload = (bulkOperationService as any).prepareBulkPayload( - mockItems, - OperationType.PUBLISH, - ResourceType.ENTRY - ); - - expect(payload.entries).to.have.lengthOf(1); - expect(payload.environments).to.deep.equal([]); + expect(() => + (bulkOperationService as any).prepareBulkPayload(mockItems, OperationType.PUBLISH, ResourceType.ENTRY) + ).to.throw('No environments for bulk publish'); }); }); diff --git a/packages/contentstack-import/src/commands/cm/stacks/import.ts b/packages/contentstack-import/src/commands/cm/stacks/import.ts index 6a2448450..7dbbf9b05 100644 --- a/packages/contentstack-import/src/commands/cm/stacks/import.ts +++ b/packages/contentstack-import/src/commands/cm/stacks/import.ts @@ -179,6 +179,11 @@ export default class ImportCommand extends Command { backupDir = importConfig.backupDir; //Note: Final summary is now handled by summary manager CLIProgressManager.printGlobalSummary(); + if (importConfig.assetScanningEnabled) { + cliux.print('\nAsset Scanning is enabled — assets were not published.', { color: 'yellow' }); + cliux.print(' Once scanning completes, publish your assets using:', { color: 'yellow' }); + cliux.print(` csdx cm:stacks:bulk-assets --data-dir ${backupDir} --stack-api-key ${importConfig.apiKey} --operation publish`, { color: 'cyan' }); + } this.logSuccessAndBackupMessages(backupDir, importConfig); // Clear progress module setting now that import is complete clearProgressModuleSetting(); diff --git a/packages/contentstack-import/src/import/module-importer.ts b/packages/contentstack-import/src/import/module-importer.ts index 930350a18..109c54160 100755 --- a/packages/contentstack-import/src/import/module-importer.ts +++ b/packages/contentstack-import/src/import/module-importer.ts @@ -26,6 +26,12 @@ class ModuleImporter { const stackDetails: Record = await this.stackAPIClient.fetch(); this.importConfig.stackName = stackDetails.name as string; this.importConfig.org_uid = stackDetails.org_uid as string; + + const assetScanningEnabled = await this.detectAssetScanning(this.importConfig.org_uid); + if (assetScanningEnabled) { + this.importConfig.assetScanningEnabled = true; + this.importConfig.skipAssetsPublish = true; + } } await this.resolveImportPath(); @@ -194,6 +200,16 @@ class ModuleImporter { log.error(`Audit failed with following error. ${error}`, this.importConfig.context); } } + + private async detectAssetScanning(orgUid: string): Promise { + try { + const orgDetails = await this.managementAPIClient.organization(orgUid).fetch({ include_plan: true }); + const features: Array<{ uid: string; enabled?: boolean }> = orgDetails?.plan?.features || []; + return features.some((f) => (f.uid === 'assetsScan' || f.uid === 'amAssetsScan') && f.enabled === true); + } catch { + return false; + } + } } export default ModuleImporter; diff --git a/packages/contentstack-import/src/import/modules/assets.ts b/packages/contentstack-import/src/import/modules/assets.ts index c65345d91..f6158d5a8 100644 --- a/packages/contentstack-import/src/import/modules/assets.ts +++ b/packages/contentstack-import/src/import/modules/assets.ts @@ -197,6 +197,13 @@ export default class ImportAssets extends BaseClass { this.completeProgress(true); log.success('Assets imported successfully!', this.importConfig.context); + + if (this.importConfig.assetScanningEnabled) { + log.info(' Asset Scanning is enabled for this stack.', this.importConfig.context); + log.info(' Assets cannot be published immediately — scanning must complete first.', this.importConfig.context); + log.info(' Once scanning is done, publish your assets using:', this.importConfig.context); + log.info(' csdx cm:stacks:bulk-assets --data-dir ./content --stack-api-key --operation publish', this.importConfig.context); + } } catch (error) { this.completeProgress(false, error?.message || 'Asset import failed'); handleAndLogError(error, { ...this.importConfig.context }); diff --git a/packages/contentstack-import/src/types/import-config.ts b/packages/contentstack-import/src/types/import-config.ts index 847010f49..90db2ca08 100644 --- a/packages/contentstack-import/src/types/import-config.ts +++ b/packages/contentstack-import/src/types/import-config.ts @@ -14,6 +14,7 @@ export default interface ImportConfig extends DefaultConfig, ExternalConfig { authenticationMethod?: string; skipAssetsPublish?: boolean; skipEntriesPublish?: boolean; + assetScanningEnabled?: boolean; skipTaxonomyPublish?: boolean; cliLogsPath: string; canCreatePrivateApp: boolean; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8c990cac..b756676c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -393,7 +393,7 @@ importers: version: 2.0.0-beta.10(@types/node@20.19.43) '@contentstack/delivery-sdk': specifier: ^5.2.0 - version: 5.2.2 + version: 5.3.0 '@contentstack/management': specifier: ^1.30.2 version: 1.30.4(debug@4.4.3) @@ -1663,76 +1663,76 @@ packages: '@asamuzakjp/dom-selector@2.0.2': resolution: {integrity: sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==} - '@aws-sdk/checksums@3.1000.16': - resolution: {integrity: sha512-EKnvkXSmz3IpA99tCNuI+dLFXyZyClSm8zns9sB/elvkU+MTuomAs6toJMPMBf98/fICG/urXDkzGz0/c3yyAQ==} + '@aws-sdk/checksums@3.1000.18': + resolution: {integrity: sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-cloudfront@3.1086.0': - resolution: {integrity: sha512-MbqWAu9sNRgnCvuTvVSMgsUD9g4DCvhmiXc5G77/Lq3ty6joCsk/VdQ8naqw/IqMZc3HSVZMTqVugI8yHtq4Rw==} + '@aws-sdk/client-cloudfront@3.1088.0': + resolution: {integrity: sha512-ljqqpLZZrkeWR5U4A42YstyQyRhbcZHfGnh28ovDALlZ24rq+yP2o+nV84OlObjxRoRYYSZUxNga0Qi8aXNJtg==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-s3@3.1086.0': - resolution: {integrity: sha512-6+7mVMPKetZmmF2L1yRJ+rN9b1OwVgc5sju2mj8ixdxuGjtVZ0ekFlcWGBFMQT9gpFk55PPLBng/XRYO3qah5A==} + '@aws-sdk/client-s3@3.1088.0': + resolution: {integrity: sha512-9ryKKxnjtJRKLDI24P+2gQZki7k28BeV6gz1AySvFDWayhjAZuh4QZjCyx55tqR8Oz0IJxWutgJRO43dDOcXoQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.975.1': - resolution: {integrity: sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==} + '@aws-sdk/core@3.975.3': + resolution: {integrity: sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.57': - resolution: {integrity: sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==} + '@aws-sdk/credential-provider-env@3.972.59': + resolution: {integrity: sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.59': - resolution: {integrity: sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==} + '@aws-sdk/credential-provider-http@3.972.61': + resolution: {integrity: sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.973.1': - resolution: {integrity: sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==} + '@aws-sdk/credential-provider-ini@3.973.3': + resolution: {integrity: sha512-WpuqYX4gGkx++fCTSWE8+41JzkZVcrI50SH48Ml4CsG1pyuHKyMmpw/FixBHDrmjoQ553PmeCLa/fZIcst+WyA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.63': - resolution: {integrity: sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==} + '@aws-sdk/credential-provider-login@3.972.65': + resolution: {integrity: sha512-xr9rgjYEdmC2Tpg2lwt9o+nOEaK9Qpd+dBjzrVCuWWyQfvhO91Ezu0Hh9ts2VUxOZxmS/k5T9msa34e4R1bnrQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.67': - resolution: {integrity: sha512-oYlzWst56rlhhjbYnexwv5hVLYe1cW4liLObhDfxDLI4RAQzleMVHQgQgx7XsC4HKj4e3kjT8v9DId+Pi/dndw==} + '@aws-sdk/credential-provider-node@3.972.69': + resolution: {integrity: sha512-wbJGGesd0Tl18bmUcbj1xJ+e7CpuRJ6PIpMywLFuUttGy615lua87cJ0EA8pFpY/QgPuUXbnupWBtSPJ9tyZhg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.57': - resolution: {integrity: sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==} + '@aws-sdk/credential-provider-process@3.972.59': + resolution: {integrity: sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.973.1': - resolution: {integrity: sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==} + '@aws-sdk/credential-provider-sso@3.973.3': + resolution: {integrity: sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.63': - resolution: {integrity: sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==} + '@aws-sdk/credential-provider-web-identity@3.972.65': + resolution: {integrity: sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.62': - resolution: {integrity: sha512-k8JJwYXVYlOOjWnPZDThQS1xDFJgi5Dokt73qFlDtrZAbdcint5aIdjB9XgJAAQVP5OoqcefQmh1FYXiPpvsvw==} + '@aws-sdk/middleware-sdk-s3@3.972.64': + resolution: {integrity: sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.997.31': - resolution: {integrity: sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==} + '@aws-sdk/nested-clients@3.997.33': + resolution: {integrity: sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==} engines: {node: '>=20.0.0'} - '@aws-sdk/signature-v4-multi-region@3.996.39': - resolution: {integrity: sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==} + '@aws-sdk/signature-v4-multi-region@3.996.41': + resolution: {integrity: sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.1083.0': - resolution: {integrity: sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==} + '@aws-sdk/token-providers@3.1088.0': + resolution: {integrity: sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==} engines: {node: '>=20.0.0'} - '@aws-sdk/types@3.974.0': - resolution: {integrity: sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==} + '@aws-sdk/types@3.974.2': + resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} engines: {node: '>=20.0.0'} - '@aws-sdk/xml-builder@3.972.34': - resolution: {integrity: sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==} + '@aws-sdk/xml-builder@3.972.36': + resolution: {integrity: sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==} engines: {node: '>=20.0.0'} '@aws/lambda-invoke-store@0.3.0': @@ -2368,8 +2368,8 @@ packages: '@contentstack/core@1.4.1': resolution: {integrity: sha512-QfLa8WUwquWSwvF8EltLyzQTkeNE2I9b9PBkPe21w0d5PnHOagxFzDNCYN4VO/zuJ52sNtKLFIFUcvLsOPk9ww==} - '@contentstack/delivery-sdk@5.2.2': - resolution: {integrity: sha512-hSRYbeWAIq46nkg94LH10jFTmUUJOQF18fw6BTvhfFbK0q98m6QYTLY8BswpQYuhqcH6HNu3wGam+SKzGwSGkg==} + '@contentstack/delivery-sdk@5.3.0': + resolution: {integrity: sha512-jtEV/SOX4iqzQYM4zaWtGhttyl7sio09XMf28FiiJ/TkaneRQNBWK5u2B0CWKHxtxp/nEWYhH2sgUB/HdR56tw==} engines: {node: '>=18'} '@contentstack/json-rte-serializer@2.1.0': @@ -3373,24 +3373,24 @@ packages: Deprecated: no longer maintained and no longer used by Sinon packages. See https://github.com/sinonjs/nise/issues/243 for replacement details. - '@smithy/core@3.29.3': - resolution: {integrity: sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==} + '@smithy/core@3.29.4': + resolution: {integrity: sha512-G1GRglAabzEhqghJMBAd54FkRS7SAFGHEwbhcI9r+O+LIMuFsLyXkLZkCoFSgAglRu8s/URVXJB0hglq3ZipIg==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.4.8': - resolution: {integrity: sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==} + '@smithy/credential-provider-imds@4.4.9': + resolution: {integrity: sha512-2nfV4qRKiYeXU4zD2vvSCfg5dfp/BuhrM73vt7q9gzBhxs4rbPxXY21wo+kyI3bRmXcEGRnCLTaW8O437jzHIg==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.6.5': - resolution: {integrity: sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==} + '@smithy/fetch-http-handler@5.6.6': + resolution: {integrity: sha512-NHLgAlORUFZjn5ZfhYuyyKMlXA1WLYOdGxEhyNxrPpbJzoacGbl0chn1lN2KiZ8mpNVk0tV5607CSYlYs/OFgw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.9.5': - resolution: {integrity: sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==} + '@smithy/node-http-handler@4.9.6': + resolution: {integrity: sha512-odd+HYx3OLcXRSEz0ZeF3JQdSYdK8QnRgA2N87cPW7coWIbKfRk7a9VQjfeWQLqnzrDLk23KMEn46p8N7M/JFg==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.6.4': - resolution: {integrity: sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==} + '@smithy/signature-v4@5.6.5': + resolution: {integrity: sha512-MO5VEhwVl0BN7xVoVeNrZfiUFoQtqxUbgl6/RwOTlMMxCSjblG8twSrVTwz3J4w9WZxd2rBfBAUXjH77agspBg==} engines: {node: '>=18.0.0'} '@smithy/types@4.16.1': @@ -4483,8 +4483,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001805: - resolution: {integrity: sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==} + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -5117,8 +5117,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.389: - resolution: {integrity: sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==} + electron-to-chromium@1.5.392: + resolution: {integrity: sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==} elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} @@ -9152,8 +9152,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.21.0: - resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -9282,176 +9282,176 @@ snapshots: css-tree: 2.3.1 is-potential-custom-element-name: 1.0.1 - '@aws-sdk/checksums@3.1000.16': + '@aws-sdk/checksums@3.1000.18': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/client-cloudfront@3.1086.0': + '@aws-sdk/client-cloudfront@3.1088.0': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/credential-provider-node': 3.972.67 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/fetch-http-handler': 5.6.5 - '@smithy/node-http-handler': 4.9.5 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-node': 3.972.69 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/fetch-http-handler': 5.6.6 + '@smithy/node-http-handler': 4.9.6 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/client-s3@3.1086.0': - dependencies: - '@aws-sdk/checksums': 3.1000.16 - '@aws-sdk/core': 3.975.1 - '@aws-sdk/credential-provider-node': 3.972.67 - '@aws-sdk/middleware-sdk-s3': 3.972.62 - '@aws-sdk/signature-v4-multi-region': 3.996.39 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/fetch-http-handler': 5.6.5 - '@smithy/node-http-handler': 4.9.5 + '@aws-sdk/client-s3@3.1088.0': + dependencies: + '@aws-sdk/checksums': 3.1000.18 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-node': 3.972.69 + '@aws-sdk/middleware-sdk-s3': 3.972.64 + '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/fetch-http-handler': 5.6.6 + '@smithy/node-http-handler': 4.9.6 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/core@3.975.1': + '@aws-sdk/core@3.975.3': dependencies: - '@aws-sdk/types': 3.974.0 - '@aws-sdk/xml-builder': 3.972.34 + '@aws-sdk/types': 3.974.2 + '@aws-sdk/xml-builder': 3.972.36 '@aws/lambda-invoke-store': 0.3.0 - '@smithy/core': 3.29.3 - '@smithy/signature-v4': 5.6.4 + '@smithy/core': 3.29.4 + '@smithy/signature-v4': 5.6.5 '@smithy/types': 4.16.1 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.972.57': + '@aws-sdk/credential-provider-env@3.972.59': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.972.59': + '@aws-sdk/credential-provider-http@3.972.61': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/fetch-http-handler': 5.6.5 - '@smithy/node-http-handler': 4.9.5 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/fetch-http-handler': 5.6.6 + '@smithy/node-http-handler': 4.9.6 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.973.1': - dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/credential-provider-env': 3.972.57 - '@aws-sdk/credential-provider-http': 3.972.59 - '@aws-sdk/credential-provider-login': 3.972.63 - '@aws-sdk/credential-provider-process': 3.972.57 - '@aws-sdk/credential-provider-sso': 3.973.1 - '@aws-sdk/credential-provider-web-identity': 3.972.63 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/credential-provider-imds': 4.4.8 + '@aws-sdk/credential-provider-ini@3.973.3': + dependencies: + '@aws-sdk/core': 3.975.3 + '@aws-sdk/credential-provider-env': 3.972.59 + '@aws-sdk/credential-provider-http': 3.972.61 + '@aws-sdk/credential-provider-login': 3.972.65 + '@aws-sdk/credential-provider-process': 3.972.59 + '@aws-sdk/credential-provider-sso': 3.973.3 + '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/credential-provider-imds': 4.4.9 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-login@3.972.63': + '@aws-sdk/credential-provider-login@3.972.65': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-node@3.972.67': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.57 - '@aws-sdk/credential-provider-http': 3.972.59 - '@aws-sdk/credential-provider-ini': 3.973.1 - '@aws-sdk/credential-provider-process': 3.972.57 - '@aws-sdk/credential-provider-sso': 3.973.1 - '@aws-sdk/credential-provider-web-identity': 3.972.63 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/credential-provider-imds': 4.4.8 + '@aws-sdk/credential-provider-node@3.972.69': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.59 + '@aws-sdk/credential-provider-http': 3.972.61 + '@aws-sdk/credential-provider-ini': 3.973.3 + '@aws-sdk/credential-provider-process': 3.972.59 + '@aws-sdk/credential-provider-sso': 3.973.3 + '@aws-sdk/credential-provider-web-identity': 3.972.65 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/credential-provider-imds': 4.4.9 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-process@3.972.57': + '@aws-sdk/credential-provider-process@3.972.59': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.973.1': + '@aws-sdk/credential-provider-sso@3.973.3': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/token-providers': 3.1083.0 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/token-providers': 3.1088.0 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-web-identity@3.972.63': + '@aws-sdk/credential-provider-web-identity@3.972.65': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.972.62': + '@aws-sdk/middleware-sdk-s3@3.972.64': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/signature-v4-multi-region': 3.996.39 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.997.31': + '@aws-sdk/nested-clients@3.997.33': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/signature-v4-multi-region': 3.996.39 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 - '@smithy/fetch-http-handler': 5.6.5 - '@smithy/node-http-handler': 4.9.5 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/signature-v4-multi-region': 3.996.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 + '@smithy/fetch-http-handler': 5.6.6 + '@smithy/node-http-handler': 4.9.6 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.996.39': + '@aws-sdk/signature-v4-multi-region@3.996.41': dependencies: - '@aws-sdk/types': 3.974.0 - '@smithy/signature-v4': 5.6.4 + '@aws-sdk/types': 3.974.2 + '@smithy/signature-v4': 5.6.5 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.1083.0': + '@aws-sdk/token-providers@3.1088.0': dependencies: - '@aws-sdk/core': 3.975.1 - '@aws-sdk/nested-clients': 3.997.31 - '@aws-sdk/types': 3.974.0 - '@smithy/core': 3.29.3 + '@aws-sdk/core': 3.975.3 + '@aws-sdk/nested-clients': 3.997.33 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/types@3.974.0': + '@aws-sdk/types@3.974.2': dependencies: '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.34': + '@aws-sdk/xml-builder@3.972.36': dependencies: '@smithy/types': 4.16.1 tslib: 2.8.1 @@ -10587,7 +10587,7 @@ snapshots: - debug - supports-color - '@contentstack/delivery-sdk@5.2.2': + '@contentstack/delivery-sdk@5.3.0': dependencies: '@contentstack/core': 1.4.1 '@contentstack/utils': 1.9.1 @@ -10638,7 +10638,7 @@ snapshots: '@contentstack/types-generator@3.10.2(graphql@16.14.2)': dependencies: - '@contentstack/delivery-sdk': 5.2.2 + '@contentstack/delivery-sdk': 5.3.0 '@gql2ts/from-schema': 2.0.0-4(graphql@16.14.2) async: 3.2.6 axios: 1.18.0 @@ -12281,32 +12281,32 @@ snapshots: '@sinonjs/text-encoding@0.7.3': {} - '@smithy/core@3.29.3': + '@smithy/core@3.29.4': dependencies: '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.4.8': + '@smithy/credential-provider-imds@4.4.9': dependencies: - '@smithy/core': 3.29.3 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.6.5': + '@smithy/fetch-http-handler@5.6.6': dependencies: - '@smithy/core': 3.29.3 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.9.5': + '@smithy/node-http-handler@4.9.6': dependencies: - '@smithy/core': 3.29.3 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/signature-v4@5.6.4': + '@smithy/signature-v4@5.6.5': dependencies: - '@smithy/core': 3.29.3 + '@smithy/core': 3.29.4 '@smithy/types': 4.16.1 tslib: 2.8.1 @@ -13825,8 +13825,8 @@ snapshots: browserslist@4.28.6: dependencies: baseline-browser-mapping: 2.10.43 - caniuse-lite: 1.0.30001805 - electron-to-chromium: 1.5.389 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.392 node-releases: 2.0.51 update-browserslist-db: 1.2.3(browserslist@4.28.6) @@ -13911,7 +13911,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001805: {} + caniuse-lite@1.0.30001806: {} capital-case@1.0.4: dependencies: @@ -14584,7 +14584,7 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.389: {} + electron-to-chromium@1.5.392: {} elegant-spinner@1.0.1: {} @@ -17318,7 +17318,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.21.0 + ws: 8.21.1 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -18017,8 +18017,8 @@ snapshots: oclif@4.23.27(@types/node@14.18.63): dependencies: - '@aws-sdk/client-cloudfront': 3.1086.0 - '@aws-sdk/client-s3': 3.1086.0 + '@aws-sdk/client-cloudfront': 3.1088.0 + '@aws-sdk/client-s3': 3.1088.0 '@inquirer/confirm': 3.2.0 '@inquirer/input': 2.3.0 '@inquirer/select': 2.5.0 @@ -18046,8 +18046,8 @@ snapshots: oclif@4.23.27(@types/node@18.19.130): dependencies: - '@aws-sdk/client-cloudfront': 3.1086.0 - '@aws-sdk/client-s3': 3.1086.0 + '@aws-sdk/client-cloudfront': 3.1088.0 + '@aws-sdk/client-s3': 3.1088.0 '@inquirer/confirm': 3.2.0 '@inquirer/input': 2.3.0 '@inquirer/select': 2.5.0 @@ -18075,8 +18075,8 @@ snapshots: oclif@4.23.27(@types/node@20.19.43): dependencies: - '@aws-sdk/client-cloudfront': 3.1086.0 - '@aws-sdk/client-s3': 3.1086.0 + '@aws-sdk/client-cloudfront': 3.1088.0 + '@aws-sdk/client-s3': 3.1088.0 '@inquirer/confirm': 3.2.0 '@inquirer/input': 2.3.0 '@inquirer/select': 2.5.0 @@ -18104,8 +18104,8 @@ snapshots: oclif@4.23.27(@types/node@22.20.1): dependencies: - '@aws-sdk/client-cloudfront': 3.1086.0 - '@aws-sdk/client-s3': 3.1086.0 + '@aws-sdk/client-cloudfront': 3.1088.0 + '@aws-sdk/client-s3': 3.1088.0 '@inquirer/confirm': 3.2.0 '@inquirer/input': 2.3.0 '@inquirer/select': 2.5.0 @@ -20209,7 +20209,7 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.21.0: {} + ws@8.21.1: {} xdg-basedir@4.0.0: {}