Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
de3413c
enhc: request publish details on taxonomy list export
cs-raj Apr 23, 2026
a4e185c
Merge pull request #106 from contentstack/enhc/DX-6002
cs-raj Apr 23, 2026
5925b94
enhc: Added taxonomy publish details for all taxonomy
cs-raj Apr 27, 2026
0f16186
Merge pull request #112 from contentstack/enhc/DX-6002
cs-raj Apr 27, 2026
68c32c7
feat(import): publish taxonomies after import
cs-raj May 2, 2026
20a3e9c
refactored taxonomy publish import
cs-raj May 11, 2026
324590e
Merge pull request #124 from contentstack/feat/DX-6003
cs-raj May 14, 2026
469bc03
Merge branch 'v2-dev' into feat/taxonomy-publishing
cs-raj May 15, 2026
4e39de1
added assets support in export-query
shafeeqd959 May 18, 2026
ab3991a
updated unit test pipeline
shafeeqd959 May 18, 2026
b007415
updated naming
shafeeqd959 May 18, 2026
59e2068
Merge branch 'v2-dev' into feat/taxonomy-publishing
cs-raj May 19, 2026
6333e8f
feat: migrate @contentstack/apps-cli to cli-plugins monorepo and add …
harshitha-cstk May 21, 2026
ee1001c
chore: update pnpm-lock.yaml with new dependencies for contentstack-a…
harshitha-cstk May 21, 2026
739b0b3
chore: add Snyk policy file to manage known vulnerabilities for conte…
harshitha-cstk May 22, 2026
137f9ae
Merge pull request #158 from contentstack/enh/dx-7524-migrate-apps-cl…
harshitha-cstk May 22, 2026
c261260
enh: migrate bulk-operations to cli-plugins
harshitha-cstk May 22, 2026
deb123e
feat: migrate @contentstack/cli-cm-migrate-rte to cli-plugins monorep…
harshitha-cstk May 22, 2026
deb79d1
feat(DX-7531): migrate contentstack-cli-tsgen v1 into cli-plugins mon…
cs-raj May 24, 2026
2535ee5
feat(DX-7525): migrate contentstack-cli-content-type v1 into cli-plug…
cs-raj May 24, 2026
dc71f79
fixed path issue
shafeeqd959 May 25, 2026
a69c382
feat: migrate @contentstack/cli-cm-regex-validate to cli-plugins mono…
cs-raj May 25, 2026
1c00c82
fix: correct esModuleInterop imports and tsconfig for regex-validate
cs-raj May 25, 2026
8f23871
Merge pull request #163 from contentstack/enh/dx-7527-migrate-bulk-op…
harshitha-cstk May 25, 2026
f5b9176
Merge branch 'feat/migrate-external-cli-plugins-v1' into enhc/DX-7531…
cs-raj May 25, 2026
ace877a
Merge pull request #167 from contentstack/enhc/DX-7531-migrate-tsgen-v1
cs-raj May 25, 2026
e69019b
Merge branch 'feat/migrate-external-cli-plugins-v1' into enhc/DX-7530…
cs-raj May 25, 2026
f3de42a
Merge pull request #173 from contentstack/enhc/DX-7530-migrate-cli-re…
cs-raj May 25, 2026
05794eb
Merge branch 'feat/migrate-external-cli-plugins-v1' into enhc/DX-7525…
cs-raj May 25, 2026
6744b75
Merge pull request #169 from contentstack/enhc/DX-7525-content-type-m…
cs-raj May 25, 2026
60d5412
Merge branch 'development' into feat/migrate-external-cli-plugins-v1
harshitha-cstk May 26, 2026
84c65f8
Merge branch 'v2-dev' of github.com:contentstack/cli-plugins into DX-…
shafeeqd959 May 26, 2026
515a889
fix: reference error in bulk publish
May 26, 2026
81347e5
chore: version bump
May 26, 2026
6b5ae76
remove unused fancy-test dependency
harshitha-cstk May 28, 2026
0bdbfc7
remove unused dependency
harshitha-cstk May 28, 2026
b4efa18
update package json
harshitha-cstk May 27, 2026
967ecc0
override minimatch
harshitha-cstk May 27, 2026
f868183
update build command
harshitha-cstk May 27, 2026
d76aab8
update unit test workflow
harshitha-cstk May 28, 2026
a40f85e
update unit test workflow
harshitha-cstk May 28, 2026
c84a1d4
update unit test: regex
harshitha-cstk May 28, 2026
a7acf37
Merge pull request #180 from contentstack/fix/DX-7894
netrajpatel May 28, 2026
1ef2739
update apps cli unit test
harshitha-cstk May 28, 2026
a2df45f
update unit test workflow
harshitha-cstk May 28, 2026
6d983dc
resolve tsc issues
harshitha-cstk May 28, 2026
2006547
upgrade dependencies
harshitha-cstk May 28, 2026
9b7e88c
update tsconfig for bulk operations
harshitha-cstk May 28, 2026
325247f
chore: upgrade oclif dependency
harshitha-cstk May 28, 2026
cf9f4f1
remove override minimatch
harshitha-cstk May 29, 2026
ea1040e
update pnpm lock
harshitha-cstk May 29, 2026
c7b7fff
update package json
harshitha-cstk May 29, 2026
0e41414
upgrade package json files
harshitha-cstk May 29, 2026
85d3ec7
upgrade package json files
harshitha-cstk May 29, 2026
11a3df3
remove the .snyk file
harshitha-cstk May 29, 2026
30bc77c
remove the .snyk file
harshitha-cstk May 29, 2026
046900c
Merge branch 'development' into feat/migrate-external-cli-plugins-v1
harshitha-cstk May 29, 2026
46ff9e5
update pnpm lock
harshitha-cstk May 29, 2026
5c1e158
Merge branch 'feat/migrate-external-cli-plugins-v1' into fix/v1-build…
harshitha-cstk May 29, 2026
4d9f0f3
update pnpm lock
harshitha-cstk May 29, 2026
37fcda3
Merge pull request #162 from contentstack/feat/migrate-external-cli-p…
harshitha-cstk May 29, 2026
11a66c4
version bump
harshitha-cstk May 29, 2026
8e6608e
update bin folder
harshitha-cstk May 29, 2026
8827f57
fix: minor fixes in bulk delete and move ops
May 30, 2026
2108c79
feat: implement BaseAmCommand and related functionality for asset man…
May 30, 2026
b564102
feat(import): add --skip-taxonomy-publish flag, default true
cs-raj May 30, 2026
8bef867
fixed the auth error in clone command
cs-raj May 31, 2026
428ec06
Merge pull request #194 from contentstack/fix/version-bump-1
harshitha-cstk Jun 1, 2026
d72c15c
fixed am support in export query
shafeeqd959 Jun 1, 2026
0689218
fixed am support in export query
shafeeqd959 Jun 1, 2026
926ebd4
fix: fixed log disappearing in clone process
cs-raj Jun 1, 2026
c87d22e
upgrade dependencies
harshitha-cstk Jun 1, 2026
42d2fd2
Merge branch 'v2-dev' into fix/DX-5341
cs-raj Jun 1, 2026
fa78370
Merge pull request #201 from contentstack/fix/version-bump-v1
harshitha-cstk Jun 1, 2026
ca52111
Merge pull request #204 from contentstack/v2-beta
harshitha-cstk Jun 1, 2026
eafc85b
Merge branch 'development' into fix/back-merge-development
harshitha-cstk Jun 1, 2026
0f2ed59
Merge pull request #207 from contentstack/fix/back-merge-development
harshitha-cstk Jun 1, 2026
cf2396d
Merge branch 'v2-dev' into fix/DX-5341
cs-raj Jun 2, 2026
a1bdfea
Merge branch 'v2-dev' into fix/DX-5636
cs-raj Jun 2, 2026
4fb06fd
Merge pull request #200 from contentstack/fix/DX-5341
cs-raj Jun 2, 2026
ed23500
Merge branch 'v2-dev' into fix/DX-5636
cs-raj Jun 2, 2026
7e67139
fix: changed default value to false for skip-taxonomy flag
cs-raj Jun 2, 2026
f9d90df
Merge pull request #198 from contentstack/fix/DX-5636
cs-raj Jun 2, 2026
60c08d5
Merge branch 'v2-dev' into fix/DX-8552
Jun 2, 2026
3d82c99
fixed asset management test cases
shafeeqd959 Jun 2, 2026
c16270e
fix: add success url in bulk delete and move
Jun 2, 2026
b589b1b
chore: fix lint issues
Jun 2, 2026
346f945
fixed test
cs-raj Jun 3, 2026
012175e
Merge pull request #197 from contentstack/enhc/DX-8556
cs-raj Jun 3, 2026
760c8ce
chore: update func/var names according to convention
Jun 3, 2026
25a45be
Merge branch 'v2-dev' into feat/taxonomy-publishing
cs-raj Jun 3, 2026
8f53ebf
Merge pull request #143 from contentstack/feat/taxonomy-publishing
cs-raj Jun 3, 2026
56da1a3
chore: update test cases
Jun 4, 2026
651fb20
Merge branch 'v2-dev' into fix/DX-8552
naman-contentstack Jun 4, 2026
baecb48
Merge pull request #196 from contentstack/fix/DX-8552
naman-contentstack Jun 4, 2026
54a2ce2
merged latest changes
shafeeqd959 Jun 4, 2026
e8b9a0a
Merge pull request #149 from contentstack/DX-7332
shafeeqd959 Jun 4, 2026
f8ac0d1
chore: fixed unit test cases
cs-raj Jun 4, 2026
e6631ba
Merge branch 'v2-dev' into DX-8739
cs-raj Jun 4, 2026
c250459
lock file update
cs-raj Jun 4, 2026
a53e075
chore fixed test
cs-raj Jun 4, 2026
afd1e17
Merge pull request #208 from contentstack/DX-8739
cs-raj Jun 4, 2026
6c4fbdd
chore: version bumps
Jun 7, 2026
205d991
Merge pull request #213 from contentstack/version_bumps
naman-contentstack Jun 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/config/release.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"releaseAll": true,
"plugins": {
"asset-management": false,
"variants": false,
"query-export": false,
"export": false,
"import": false,
"clone": false,
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/unit-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Prune pnpm store
run: pnpm store prune
- name: Install Dependencies
run: pnpm install --frozen-lockfile
run: pnpm install --no-frozen-lockfile

- name: Build all plugins
run: |
Expand Down Expand Up @@ -86,3 +86,7 @@ jobs:
- name: Run tests for Contentstack Bulk Operations
working-directory: ./packages/contentstack-bulk-operations
run: npm test

- name: Run tests for Contentstack Variants
working-directory: ./packages/contentstack-variants
run: npm run test
8 changes: 1 addition & 7 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
fileignoreconfig:
- filename: packages/contentstack-bulk-operations/src/services/am-asset-service.ts
checksum: 5f6c0ecba74e27399a7079ca15e65e77ef692697093c9fb1d57213728c4fe985
- filename: packages/contentstack-bulk-operations/src/utils/asset-uids-from-file.ts
checksum: 580932f192dd3fdd8bb2c55b7a7a78f1694f646ef5c5041f86c75668778f7ecb
- filename: packages/contentstack-bulk-operations/test/unit/utils/asset-uids-from-file.test.ts
checksum: 8123f7a675a0275795b59b15d0f2d5f8f1e57ccbecf3f97249a0dc5a037b9203
- filename: pnpm-lock.yaml
checksum: f3d6c28f120dd0f2a6abcdaf033734e979996e462a29b1b5b350228c61f62c87
checksum: cdead0797199d22bbc55b9e5b6b86983f28eb760fabe5e1f2d5139c4456a9131
version: '1.0'
2 changes: 1 addition & 1 deletion packages/contentstack-apps-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@
"app:deploy": "APDP"
}
}
}
}
2 changes: 1 addition & 1 deletion packages/contentstack-asset-management/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-asset-management",
"version": "1.0.0-beta.3",
"version": "1.0.0-beta.4",
"description": "Contentstack Assets API adapter for export and import",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/contentstack-asset-management/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './types';
export * from './utils';
export * from './export';
export * from './import';
export * from './query-export';
export * from './import-setup';
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { resolve as pResolve } from 'node:path';
import { mkdir, writeFile } from 'node:fs/promises';
import { Readable } from 'node:stream';
import { log, handleAndLogError, configHandler } from '@contentstack/cli-utilities';

import type { CsAssetsQueryExportOptions, CSAssetsAPIConfig, LinkedWorkspace } from '../types/cs-assets-api';
import type { ExportContext } from '../types/export-types';
import ExportAssetTypes from '../export/asset-types';
import ExportFields from '../export/fields';
import { CSAssetsExportAdapter } from '../export/base';
import { getAssetItems, writeStreamToFile } from '../utils/export-helpers';
import { runInBatches } from '../utils/concurrent-batch';

const DEFAULT_ASSET_BATCH_SIZE = 100;
const SEARCH_PAGE_LIMIT = 100;

/**
* Query-based Contentstack Assets exporter.
* Exports only referenced asset UIDs from entries into the `spaces/` directory layout.
*/
export class CsAssetsQueryExporter {
private readonly options: CsAssetsQueryExportOptions;

constructor(options: CsAssetsQueryExportOptions) {
this.options = options;
}

async export(assetUIDs: string[]): Promise<void> {
const { linkedWorkspaces, exportDir, context } = this.options;

if (!assetUIDs.length) {
log.info('No asset UIDs to export for Contentstack Assets query export', context);
return;
}

if (!linkedWorkspaces.length) {
log.warn('No linked workspaces configured for Contentstack Assets query export', context);
return;
}

log.info(
`Starting Contentstack Assets query export (${assetUIDs.length} UID(s), ${linkedWorkspaces.length} space(s))`,
context,
);

const spacesRootPath = pResolve(exportDir, 'spaces');
await mkdir(spacesRootPath, { recursive: true });

const apiConfig: CSAssetsAPIConfig = {
baseURL: this.options.csAssetsUrl,
headers: { organization_uid: this.options.org_uid },
context,
};

const exportContext: ExportContext = {
spacesRootPath,
context,
securedAssets: this.options.securedAssets,
chunkFileSizeMb: this.options.chunkFileSizeMb,
apiConcurrency: this.options.apiConcurrency,
downloadAssetsConcurrency: this.options.downloadAssetsConcurrency,
};

const batchSize = this.options.assetBatchSize ?? DEFAULT_ASSET_BATCH_SIZE;

try {
await this.bootstrapSharedModules(apiConfig, exportContext, linkedWorkspaces[0].space_uid);

for (const workspace of linkedWorkspaces) {
try {
await this.exportWorkspaceAssets(apiConfig, exportContext, workspace, assetUIDs, batchSize);
} catch (err) {
handleAndLogError(
err,
{ ...(context as Record<string, unknown>), spaceUid: workspace.space_uid },
`Failed Contentstack Assets query export for space ${workspace.space_uid}`,
);
}
}

log.success('Contentstack Assets query export completed', context);
} catch (err) {
handleAndLogError(err, context as Record<string, unknown>, 'Contentstack Assets query export failed');
throw err;
}
}

private async bootstrapSharedModules(
apiConfig: CSAssetsAPIConfig,
exportContext: ExportContext,
firstSpaceUid: string,
): Promise<void> {
const sharedFieldsDir = pResolve(exportContext.spacesRootPath, 'fields');
const sharedAssetTypesDir = pResolve(exportContext.spacesRootPath, 'asset_types');
await mkdir(sharedFieldsDir, { recursive: true });
await mkdir(sharedAssetTypesDir, { recursive: true });

const exportAssetTypes = new ExportAssetTypes(apiConfig, exportContext);
const exportFields = new ExportFields(apiConfig, exportContext);
await Promise.all([exportAssetTypes.start(firstSpaceUid), exportFields.start(firstSpaceUid)]);
}

private async exportWorkspaceAssets(
apiConfig: CSAssetsAPIConfig,
exportContext: ExportContext,
workspace: LinkedWorkspace,
assetUIDs: string[],
batchSize: number,
): Promise<void> {
const { branchName, context } = this.options;
const workspaceExporter = new QueryExportWorkspaceAdapter(apiConfig, exportContext);
await workspaceExporter.start(workspace, assetUIDs, branchName || 'main', batchSize);
log.debug(`Contentstack Assets query export finished for space ${workspace.space_uid}`, context);
}
}

/**
* Per-space export: search by UID, write metadata/files, download binaries.
*/
class QueryExportWorkspaceAdapter extends CSAssetsExportAdapter {
async start(
workspace: LinkedWorkspace,
assetUIDs: string[],
branchName: string,
uidBatchSize: number,
): Promise<void> {
await this.init();

const spaceDir = pResolve(this.exportContext.spacesRootPath, workspace.space_uid);
await mkdir(spaceDir, { recursive: true });

const spaceResponse = await this.getSpace(workspace.space_uid);
const space = spaceResponse.space;
const metadata = {
...space,
workspace_uid: workspace.uid,
is_default: workspace.is_default,
branch: branchName,
};
await writeFile(pResolve(spaceDir, 'metadata.json'), JSON.stringify(metadata, null, 2));

const assetsDir = pResolve(spaceDir, 'assets');
await mkdir(assetsDir, { recursive: true });

const spaceRef = { space_uid: workspace.space_uid, workspace: workspace.uid };
const assetItems = await this.searchAllAssets(assetUIDs, spaceRef, uidBatchSize);

const folders = assetItems.filter((item) => (item as { is_dir?: boolean }).is_dir === true);
const files = assetItems.filter((item) => (item as { is_dir?: boolean }).is_dir !== true);

await writeFile(pResolve(assetsDir, 'folders.json'), JSON.stringify(folders, null, 2));

await this.writeItemsToChunkedJson(
assetsDir,
'assets.json',
'assets',
['uid', 'url', 'filename', 'file_name', 'parent_uid'],
files,
);

await this.downloadAssets(files, assetsDir, workspace.space_uid);
}

private async searchAllAssets(
assetUIDs: string[],
spaceRef: { space_uid: string; workspace: string },
uidBatchSize: number,
): Promise<Array<Record<string, unknown>>> {
const seen = new Set<string>();
const results: Array<Record<string, unknown>> = [];

for (let i = 0; i < assetUIDs.length; i += uidBatchSize) {
const uidBatch = assetUIDs.slice(i, i + uidBatchSize);
let skip = 0;
let pageItems: unknown[];

do {
const response = await this.searchAssets({
assetUIDs: uidBatch,
spaces: [spaceRef],
skip,
limit: SEARCH_PAGE_LIMIT,
});
pageItems = getAssetItems(response);

if (pageItems.length === 0 && skip === 0) {
log.warn(
`Search returned 0 assets in space ${spaceRef.space_uid} for UID(s): [${uidBatch.join(', ')}]`,
this.exportContext.context,
);
}

for (const item of pageItems) {
const record = item as Record<string, unknown>;
const key = String(record.uid ?? record.asset_id ?? record._uid ?? '');
if (key && !seen.has(key)) {
seen.add(key);
results.push(record);
}
}

skip += pageItems.length;
} while (pageItems.length === SEARCH_PAGE_LIMIT);
}

return results;
}

private async downloadAssets(
items: Array<Record<string, unknown>>,
assetsDir: string,
spaceUid: string,
): Promise<void> {
const downloadable = items.filter((asset) => Boolean(asset.url && (asset.uid ?? asset._uid)));
if (downloadable.length === 0) {
log.debug(`No downloadable assets for space ${spaceUid}`, this.exportContext.context);
return;
}

const filesDir = pResolve(assetsDir, 'files');
await mkdir(filesDir, { recursive: true });

const securedAssets = this.exportContext.securedAssets ?? false;
const authtoken = securedAssets ? configHandler.get('authtoken') : null;

await runInBatches(downloadable, this.downloadAssetsBatchConcurrency, async (asset) => {
const uid = String(asset.uid ?? asset._uid);
const url = String(asset.url);
const filename = String(asset.filename ?? asset.file_name ?? 'asset');
try {
const separator = url.includes('?') ? '&' : '?';
const downloadUrl = securedAssets && authtoken ? `${url}${separator}authtoken=${authtoken}` : url;
const response = await fetch(downloadUrl);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const body = response.body;
if (!body) throw new Error('No response body');
const nodeStream = Readable.fromWeb(body as Parameters<typeof Readable.fromWeb>[0]);
const assetFolderPath = pResolve(filesDir, uid);
await mkdir(assetFolderPath, { recursive: true });
await writeStreamToFile(nodeStream, pResolve(assetFolderPath, filename));
} catch (e) {
log.debug(`Failed to download asset ${uid} in space ${spaceUid}: ${e}`, this.exportContext.context);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CsAssetsQueryExporter } from './cs-assets-query-exporter';
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,30 @@ export type BulkMoveAssetsResponse = {
* Adapter interface for Contentstack Assets API calls.
* Used by export and (future) import.
*/
/** Space + workspace pair for Contentstack Assets search API. */
export type SearchSpaceRef = {
space_uid: string;
workspace: string;
};

/** Parameters for POST /api/search (asset query export). */
export type SearchAssetsParams = {
assetUIDs: string[];
spaces: SearchSpaceRef[];
skip?: number;
limit?: number;
};

/** Response shape from POST /api/search for assets. */
export type SearchAssetsResponse = {
count?: number;
relation?: string;
assets?: unknown[];
items?: unknown[];
results?: unknown[];
folders?: unknown[];
};

export interface ICSAssetsAdapter {
init(): Promise<void>;
listSpaces(): Promise<SpacesListResponse>;
Expand All @@ -149,6 +173,7 @@ export interface ICSAssetsAdapter {
getWorkspaceAssets(spaceUid: string, workspaceUid?: string): Promise<unknown>;
getWorkspaceFolders(spaceUid: string, workspaceUid?: string): Promise<unknown>;
getWorkspaceAssetTypes(spaceUid: string): Promise<AssetTypesResponse>;
searchAssets(params: SearchAssetsParams): Promise<SearchAssetsResponse>;
bulkDeleteAssets(
spaceUid: string,
workspaceUid: string | undefined,
Expand All @@ -161,6 +186,23 @@ export interface ICSAssetsAdapter {
): Promise<BulkMoveAssetsResponse>;
}

/** Options for query-based Contentstack Assets export (referenced assets from entries). */
export type CsAssetsQueryExportOptions = {
linkedWorkspaces: LinkedWorkspace[];
exportDir: string;
branchName: string;
csAssetsUrl: string;
org_uid: string;
apiKey?: string;
context?: Record<string, unknown>;
securedAssets?: boolean;
chunkFileSizeMb?: number;
apiConcurrency?: number;
downloadAssetsConcurrency?: number;
/** Max UIDs per search request ($in batch). */
assetBatchSize?: number;
};

/**
* Options for exporting space structure (used by export app after fetching linked workspaces).
*/
Expand Down
Loading
Loading