Skip to content

Commit 64c6e36

Browse files
author
naman-contentstack
committed
feat: add support for AM in import module
1 parent 20c2ab2 commit 64c6e36

24 files changed

Lines changed: 1230 additions & 65 deletions

File tree

.talismanrc

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
fileignoreconfig:
2-
- filename: packages/contentstack-audit/test/unit/mock/contents/environments/environments.json
3-
checksum: d983bc17ab56937c66a6d25f449ecbe285d00c807923ed56deacb3a571aa3448
4-
- filename: package-lock.json
5-
checksum: 8fd93d7b01c7d064fa797fba81d09da612b5639468c02003e11b2f3b599e49a2
6-
- filename: pnpm-lock.yaml
7-
checksum: 458cb8d135905583080023055430cf8344f46e92dd1c9e90cdb6f78bbd02eecb
8-
- filename: packages/contentstack-seed/src/commands/cm/stacks/seed.ts
9-
checksum: 5c59296f3d5ba078f16bca23a47c920dc2180cff3b8250a341176185a4dabc39
10-
- filename: packages/contentstack-seed/README.md
11-
checksum: d2a017a8206aae1058d4a91d445a7f7d50e919a0d2dd69605a66529c4f4ebe2e
12-
- filename: packages/contentstack-seed/src/seed/github/client.ts
13-
checksum: 44d491ab5253ebb6c24bb0ac5c8d985320bdc4cc3711de77adfe79f7fb1874a1
14-
- filename: packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts
15-
checksum: 999b1afe970452691318c76d5e9abd8852384fcf2d826cecda19f156de75fb59
16-
- filename: packages/contentstack-seed/src/seed/index.ts
17-
checksum: 2bd73b2562618a37e02247459141432515471277ee5b85f5053f169891a54eb5
18-
- filename: packages/contentstack-export-to-csv/src/utils/interactive.ts
19-
checksum: 8aa3870a6694e404f4f8df3ed884dd69521099fe66851c4f8c9860276254da9d
20-
- filename: packages/contentstack-import/test/unit/import/modules/marketplace-apps.test.ts
21-
checksum: e927e670f70374bfa09a4866faf2af0a65476709412882122ea2811717e528aa
22-
- filename: packages/contentstack-export/test/unit/export/modules/marketplace-apps.test.ts
23-
checksum: 6cb665ee2ea09372b3f80c3d2c38d1c3c3889ce0a1ba7d8488614a73aedf17b7
24-
- filename: packages/contentstack-import/test/unit/import/module-importer.test.ts
25-
checksum: baf0ffc77d2afe9084da2d1fecac4a3a6ef875739677ef6186cd7de278886406
2+
- filename: packages/contentstack-export/src/export/modules/assets.ts
3+
checksum: 1eacc8e86cb50fe283febe6688965854f420e02cf1b49555a15661fa0c3e3c7a
4+
- filename: packages/contentstack-import/src/utils/import-config-handler.ts
5+
checksum: f831cef1b7c3bd97bdbc170cff452350cee0f448d97df02e25aa41d6c4d64ad3
6+
- filename: packages/contentstack-asset-management/src/import/asset-types.ts
7+
checksum: a39caa373b2a736d1e57063326cfb2073ae78376efa931b27d2c7110997708a5
8+
- filename: packages/contentstack-asset-management/src/export/spaces.ts
9+
checksum: 3ec11c8f710b60ae495c69344025587df2e6195c872a0c82feaf04ac044ecefa
10+
- filename: packages/contentstack-import/src/import/modules/assets.ts
11+
checksum: d9f4a29a29e8b8a2a36e498f2380d39e1c5c0ec13ff894ef450abd817f2a646e
12+
- filename: packages/contentstack-asset-management/src/import/fields.ts
13+
checksum: bbae69c28ec69bf67c2c7b4df3620380ef3fca488b3288e137b65a60ee738b9e
14+
- filename: packages/contentstack-asset-management/src/import/spaces.ts
15+
checksum: 79cf2f1b55523d28c218d970155f887255a00dc095a941556b709d1f19c6a8a0
16+
- filename: packages/contentstack-asset-management/src/types/asset-management-api.ts
17+
checksum: 716df03dcba70b2cc0f77b1f6338524553ba740080d7087a8699147c3ce8f0ba
18+
- filename: packages/contentstack-asset-management/src/utils/asset-management-api-adapter.ts
19+
checksum: 92bcad2feabc1954ead89b370d284b7af5f38ec1dca60a41752371977ef106ff
20+
- filename: packages/contentstack-asset-management/src/import/base.ts
21+
checksum: 513b55cf7e92bdbe8b815141822ba10579b6a728bec4424fc664794246ad33bb
22+
- filename: packages/contentstack-asset-management/src/import/assets.ts
23+
checksum: 2d34fa57f5ab269f6c535dff3242cc1135dbe1decd84fa0bc8997d0410d520b2
2624
version: '1.0'

packages/contentstack-asset-management/src/constants/index.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@ export const PROCESS_NAMES = {
1717
AM_FIELDS: 'Fields',
1818
AM_ASSET_TYPES: 'Asset types',
1919
AM_DOWNLOADS: 'Asset downloads',
20+
// Import process names
21+
AM_IMPORT_FIELDS: 'Import fields',
22+
AM_IMPORT_ASSET_TYPES: 'Import asset types',
23+
AM_IMPORT_FOLDERS: 'Import folders',
24+
AM_IMPORT_ASSETS: 'Import assets',
2025
} as const;
2126

2227
/**
23-
* Status messages for each process (exporting, fetching, failed).
28+
* Status messages for each process (exporting, fetching, importing, failed).
2429
*/
2530
export const PROCESS_STATUS = {
2631
[PROCESS_NAMES.AM_SPACE_METADATA]: {
@@ -47,4 +52,20 @@ export const PROCESS_STATUS = {
4752
DOWNLOADING: 'Downloading asset files...',
4853
FAILED: 'Failed to download assets.',
4954
},
55+
[PROCESS_NAMES.AM_IMPORT_FIELDS]: {
56+
IMPORTING: 'Importing shared fields...',
57+
FAILED: 'Failed to import fields.',
58+
},
59+
[PROCESS_NAMES.AM_IMPORT_ASSET_TYPES]: {
60+
IMPORTING: 'Importing shared asset types...',
61+
FAILED: 'Failed to import asset types.',
62+
},
63+
[PROCESS_NAMES.AM_IMPORT_FOLDERS]: {
64+
IMPORTING: 'Importing folders...',
65+
FAILED: 'Failed to import folders.',
66+
},
67+
[PROCESS_NAMES.AM_IMPORT_ASSETS]: {
68+
IMPORTING: 'Importing assets...',
69+
FAILED: 'Failed to import assets.',
70+
},
5071
} as const;

packages/contentstack-asset-management/src/export/assets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export default class ExportAssets extends AssetManagementExportAdapter {
2121
log.debug(`Fetching folders and assets for space ${workspace.space_uid}`, this.exportContext.context);
2222

2323
const [folders, assetsData] = await Promise.all([
24-
this.getWorkspaceFolders(workspace.space_uid),
25-
this.getWorkspaceAssets(workspace.space_uid),
24+
this.getWorkspaceFolders(workspace.space_uid, workspace.uid),
25+
this.getWorkspaceAssets(workspace.space_uid, workspace.uid),
2626
]);
2727

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

packages/contentstack-asset-management/src/export/spaces.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,8 @@ export class ExportSpaces {
2929
}
3030

3131
async start(): Promise<void> {
32-
const {
33-
linkedWorkspaces,
34-
exportDir,
35-
branchName,
36-
assetManagementUrl,
37-
org_uid,
38-
context,
39-
securedAssets,
40-
} = this.options;
32+
const { linkedWorkspaces, exportDir, branchName, assetManagementUrl, org_uid, apiKey, context, securedAssets } =
33+
this.options;
4134

4235
if (!linkedWorkspaces.length) {
4336
log.debug('No linked workspaces to export', context);
@@ -54,7 +47,9 @@ export class ExportSpaces {
5447
const totalSteps = 2 + linkedWorkspaces.length * 4;
5548
const progress = this.createProgress();
5649
progress.addProcess(AM_MAIN_PROCESS_NAME, totalSteps);
57-
progress.startProcess(AM_MAIN_PROCESS_NAME).updateStatus(PROCESS_STATUS[PROCESS_NAMES.AM_FIELDS].FETCHING, AM_MAIN_PROCESS_NAME);
50+
progress
51+
.startProcess(AM_MAIN_PROCESS_NAME)
52+
.updateStatus(PROCESS_STATUS[PROCESS_NAMES.AM_FIELDS].FETCHING, AM_MAIN_PROCESS_NAME);
5853

5954
const apiConfig: AssetManagementAPIConfig = {
6055
baseURL: assetManagementUrl,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import omit from 'lodash/omit';
2+
import isEqual from 'lodash/isEqual';
3+
import { log } from '@contentstack/cli-utilities';
4+
5+
import type { AssetManagementAPIConfig, ImportContext } from '../types/asset-management-api';
6+
import { AssetManagementImportAdapter } from './base';
7+
import { PROCESS_NAMES, PROCESS_STATUS } from '../constants/index';
8+
9+
const STRIP_KEYS = ['created_at', 'created_by', 'updated_at', 'updated_by', 'is_system', 'category', 'preview_image_url', 'category_detail'];
10+
11+
/**
12+
* Reads shared asset types from `spaces/asset_types/asset-types.json` and POSTs
13+
* each to the target org-level AM endpoint (`POST /api/asset_types`).
14+
*
15+
* Strategy: Fetch → Diff → Create only missing, warn on conflict
16+
* 1. Fetch asset types that already exist in the target org.
17+
* 2. Skip entries where is_system=true (platform-owned, cannot be created via API).
18+
* 3. If uid already exists and definition differs → warn and skip.
19+
* 4. If uid already exists and definition matches → silently skip.
20+
* 5. Strip read-only/computed keys from the POST body before creating new asset types.
21+
*/
22+
export default class ImportAssetTypes extends AssetManagementImportAdapter {
23+
constructor(apiConfig: AssetManagementAPIConfig, importContext: ImportContext) {
24+
super(apiConfig, importContext);
25+
}
26+
27+
async start(): Promise<void> {
28+
await this.init();
29+
30+
const dir = this.getAssetTypesDir();
31+
const items = await this.readAllChunkedJson<Record<string, unknown>>(dir, 'asset-types.json');
32+
33+
if (items.length === 0) {
34+
log.debug('No shared asset types to import', this.importContext.context);
35+
return;
36+
}
37+
38+
// Fetch existing asset types from the target org keyed by uid for diff comparison.
39+
// Asset types are org-level; the spaceUid param in getWorkspaceAssetTypes is unused in the path.
40+
const existingByUid = new Map<string, Record<string, unknown>>();
41+
try {
42+
const existing = await this.getWorkspaceAssetTypes('');
43+
for (const at of existing.asset_types ?? []) {
44+
existingByUid.set(at.uid, at as Record<string, unknown>);
45+
}
46+
log.debug(`Target org has ${existingByUid.size} existing asset type(s)`, this.importContext.context);
47+
} catch (e) {
48+
log.debug(`Could not fetch existing asset types, will attempt to create all: ${e}`, this.importContext.context);
49+
}
50+
51+
this.updateStatus(PROCESS_STATUS[PROCESS_NAMES.AM_IMPORT_ASSET_TYPES].IMPORTING, PROCESS_NAMES.AM_IMPORT_ASSET_TYPES);
52+
53+
for (const assetType of items) {
54+
const uid = assetType.uid as string;
55+
56+
if (assetType.is_system) {
57+
log.debug(`Skipping system asset type: ${uid}`, this.importContext.context);
58+
continue;
59+
}
60+
61+
const existing = existingByUid.get(uid);
62+
if (existing) {
63+
const exportedClean = omit(assetType, STRIP_KEYS);
64+
const existingClean = omit(existing, STRIP_KEYS);
65+
if (!isEqual(exportedClean, existingClean)) {
66+
log.warn(
67+
`Asset type "${uid}" already exists in the target org with a different definition. Skipping — to apply the exported definition, delete the asset type from the target org first.`,
68+
this.importContext.context,
69+
);
70+
} else {
71+
log.debug(`Asset type "${uid}" already exists with matching definition, skipping`, this.importContext.context);
72+
}
73+
this.tick(true, `asset-type: ${uid} (skipped, already exists)`, null, PROCESS_NAMES.AM_IMPORT_ASSET_TYPES);
74+
continue;
75+
}
76+
77+
const payload = omit(assetType, STRIP_KEYS);
78+
try {
79+
await this.createAssetType(payload as any);
80+
this.tick(true, `asset-type: ${uid}`, null, PROCESS_NAMES.AM_IMPORT_ASSET_TYPES);
81+
log.debug(`Imported asset type: ${uid}`, this.importContext.context);
82+
} catch (e) {
83+
this.tick(false, `asset-type: ${uid}`, (e as Error)?.message ?? PROCESS_STATUS[PROCESS_NAMES.AM_IMPORT_ASSET_TYPES].FAILED, PROCESS_NAMES.AM_IMPORT_ASSET_TYPES);
84+
log.debug(`Failed to import asset type ${uid}: ${e}`, this.importContext.context);
85+
}
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)