Skip to content

Commit 77d207f

Browse files
committed
merged latest code
2 parents 80075d2 + 55b4401 commit 77d207f

11 files changed

Lines changed: 646 additions & 17 deletions

File tree

.talismanrc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ fileignoreconfig:
1616
- filename: packages/contentstack-import/src/import/modules/publishing-rules.ts
1717
checksum: 429a803bc18e691db93bae3df1714071d0face6441b82cb938a83e8bf94ae14c
1818
- filename: packages/contentstack-import/src/types/default-config.ts
19-
checksum: 84589b06580c88dc6722abd663c4b7bb352d1bcfa1f870f4237f6f97b2166f3d
19+
checksum: c117d060d6979540a1bb6ae20ad6ad6d43e9b15a6909291f76ed60b11e5f793d
2020
- filename: packages/contentstack-import/test/unit/import/modules/publishing-rules.test.ts
2121
checksum: 0fcbff5dab2f9e594fe2a316c3c96e8d86bcd5d72e7c1f9eb35c0e3458f87817
2222
- filename: packages/contentstack-export/src/types/default-config.ts
2323
checksum: 70c9ca7400c447f2b54f48a07965c7ba34706deee97d68951d6229d523c7e4b0
2424
- filename: packages/contentstack-export/src/types/index.ts
25-
checksum: 4d895dc8355b94847e3e1746d65eb51d5b9434de307ddcd91c0a33083b97b9ef
25+
checksum: 71f02ac11507c61222a661caf228dfd54ffdc6c57e4cc631f66598ecd617852b
26+
- filename: packages/contentstack-import/test/unit/utils/build-import-spaces-options.test.ts
27+
checksum: ecc39327ca93ca4ea749937ec853f5a4c9a609a96c4e4e15df1b596fb4f32c74
28+
- filename: packages/contentstack-asset-management/test/unit/import/workspaces.test.ts
29+
checksum: a0a17ff803fcf8f20538c8e158909d06705fc801f0bd3356b1eac1d92f006d83
30+
- filename: packages/contentstack-asset-management/test/unit/import/spaces.test.ts
31+
checksum: 163c7e519561f2c1f473f6f0c5303a2a47c9f5c231b638cd90782c37eaa4859e
2632
version: '1.0'

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,14 @@ export class ImportSpaces {
158158
try {
159159
const workspaceImporter = new ImportWorkspace(apiConfig, importContext);
160160
workspaceImporter.setParentProgressManager(progress);
161-
const result = await workspaceImporter.start(spaceUid, spaceDir, existingSpaceUids, spaceProcess);
161+
const result = await workspaceImporter.start(
162+
spaceUid,
163+
spaceDir,
164+
existingSpaceUids,
165+
spaceProcess,
166+
configOptions.targetDefaultSpaceUid,
167+
configOptions.targetDefaultWorkspaceUid,
168+
);
162169

163170
// Newly created spaces get a new uid — add so later iterations in this run see it.
164171
existingSpaceUids.add(result.newSpaceUid);

packages/contentstack-asset-management/src/import/workspaces.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export default class ImportWorkspace extends CSAssetsImportAdapter {
3535
spaceDir: string,
3636
existingSpaceUids: Set<string> = new Set(),
3737
spaceProcessName?: string,
38+
targetDefaultSpaceUid?: string,
39+
targetDefaultWorkspaceUid?: string,
3840
): Promise<WorkspaceResult> {
3941
await this.init();
4042

@@ -64,6 +66,20 @@ export default class ImportWorkspace extends CSAssetsImportAdapter {
6466
assetsImporter.setProcessName(spaceProcessName);
6567
}
6668

69+
// Map source default space → existing target default space (cross-org migration).
70+
// The caller supplies the uid of the pre-existing target default space; we upload
71+
// source assets into it instead of creating a new space.
72+
if (isDefault && targetDefaultSpaceUid) {
73+
const newSpaceUid = targetDefaultSpaceUid;
74+
const resolvedWorkspaceUid = targetDefaultWorkspaceUid ?? workspaceUid;
75+
log.info(
76+
`Source default space "${oldSpaceUid}" mapped to existing target default space "${newSpaceUid}".`,
77+
this.importContext.context,
78+
);
79+
const { uidMap, urlMap } = await assetsImporter.start(newSpaceUid, spaceDir);
80+
return { oldSpaceUid, newSpaceUid, workspaceUid: resolvedWorkspaceUid, isDefault: true, uidMap, urlMap };
81+
}
82+
6783
// Reuse: target org already has a space with the same uid as the export directory.
6884
if (existingSpaceUids.has(oldSpaceUid)) {
6985
log.info(

packages/contentstack-asset-management/src/types/cs-assets-api.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,16 @@ export type ImportSpacesOptions = {
243243
mapperUidFileName?: string;
244244
mapperUrlFileName?: string;
245245
mapperSpaceUidFileName?: string;
246+
/**
247+
* UID of the already-existing default space in the target org.
248+
* When set, the source default space is imported into this space instead of creating a new one.
249+
*/
250+
targetDefaultSpaceUid?: string;
251+
/**
252+
* Workspace link UID of the existing default workspace in the target branch's `am_v2.linked_workspaces`.
253+
* Returned in SpaceMapping.workspaceUid so downstream branch-linking logic can identify the entry correctly.
254+
*/
255+
targetDefaultWorkspaceUid?: string;
246256
};
247257

248258
/**
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { expect } from 'chai';
2+
import sinon from 'sinon';
3+
import { CLIProgressManager, configHandler } from '@contentstack/cli-utilities';
4+
5+
import { ImportSpaces } from '../../../src/import/spaces';
6+
import ImportWorkspace from '../../../src/import/workspaces';
7+
import ImportFields from '../../../src/import/fields';
8+
import ImportAssetTypes from '../../../src/import/asset-types';
9+
import { CSAssetsAdapter } from '../../../src/utils/cs-assets-api-adapter';
10+
import { CSAssetsImportAdapter } from '../../../src/import/base';
11+
import { PROCESS_NAMES } from '../../../src/constants/index';
12+
13+
import type { ImportSpacesOptions } from '../../../src/types/cs-assets-api';
14+
15+
describe('ImportSpaces', () => {
16+
const baseOptions: ImportSpacesOptions = {
17+
contentDir: '/tmp/import',
18+
csAssetsUrl: 'https://am.example.com',
19+
org_uid: 'org-1',
20+
apiKey: 'api-key-1',
21+
host: 'https://api.contentstack.io/v3',
22+
};
23+
24+
const fakeProgress = {
25+
addProcess: sinon.stub().returnsThis(),
26+
startProcess: sinon.stub().returnsThis(),
27+
updateStatus: sinon.stub().returnsThis(),
28+
tick: sinon.stub(),
29+
completeProcess: sinon.stub(),
30+
};
31+
32+
beforeEach(() => {
33+
sinon.stub(configHandler, 'get').returns({ showConsoleLogs: false });
34+
sinon.stub(CLIProgressManager, 'createNested').returns(fakeProgress as any);
35+
// init and listSpaces live on AssetManagementAdapter (the common base).
36+
// Stubbing the base once covers both the adapter used for listSpaces and ImportWorkspace.
37+
sinon.stub(CSAssetsAdapter.prototype, 'init' as any).resolves();
38+
sinon.stub(CSAssetsAdapter.prototype, 'listSpaces' as any).resolves({ spaces: [] });
39+
sinon.stub(ImportFields.prototype, 'start').resolves();
40+
sinon.stub(ImportFields.prototype, 'setParentProgressManager');
41+
sinon.stub(ImportAssetTypes.prototype, 'start').resolves();
42+
sinon.stub(ImportAssetTypes.prototype, 'setParentProgressManager');
43+
sinon.stub(ImportWorkspace.prototype, 'setParentProgressManager');
44+
45+
fakeProgress.addProcess.resetHistory();
46+
fakeProgress.addProcess.returnsThis();
47+
fakeProgress.startProcess.resetHistory();
48+
fakeProgress.startProcess.returnsThis();
49+
fakeProgress.completeProcess.reset();
50+
fakeProgress.tick.reset();
51+
});
52+
53+
afterEach(() => {
54+
sinon.restore();
55+
});
56+
57+
const stubSpaceDirs = (dirs: string[]) => {
58+
const fsMock = require('node:fs');
59+
sinon.stub(fsMock, 'readdirSync').returns(dirs as any);
60+
sinon.stub(fsMock, 'statSync').returns({ isDirectory: () => true } as any);
61+
};
62+
63+
describe('targetDefaultSpaceUid threading', () => {
64+
it('should pass targetDefaultSpaceUid and targetDefaultWorkspaceUid to ImportWorkspace.start()', async () => {
65+
stubSpaceDirs(['am-space-1']);
66+
const startStub = sinon.stub(ImportWorkspace.prototype, 'start').resolves({
67+
oldSpaceUid: 'am-space-1',
68+
newSpaceUid: 'target-space-3',
69+
workspaceUid: 'ws-3',
70+
isDefault: true,
71+
uidMap: {},
72+
urlMap: {},
73+
});
74+
75+
const options: ImportSpacesOptions = {
76+
...baseOptions,
77+
targetDefaultSpaceUid: 'target-space-3',
78+
targetDefaultWorkspaceUid: 'ws-3',
79+
};
80+
const importer = new ImportSpaces(options);
81+
await importer.start();
82+
83+
expect(startStub.callCount).to.equal(1);
84+
const args = startStub.firstCall.args;
85+
expect(args[4]).to.equal('target-space-3');
86+
expect(args[5]).to.equal('ws-3');
87+
});
88+
89+
it('should pass undefined to ImportWorkspace when targetDefaultSpaceUid is not set', async () => {
90+
stubSpaceDirs(['am-space-1']);
91+
const startStub = sinon.stub(ImportWorkspace.prototype, 'start').resolves({
92+
oldSpaceUid: 'am-space-1',
93+
newSpaceUid: 'new-space',
94+
workspaceUid: 'main',
95+
isDefault: false,
96+
uidMap: {},
97+
urlMap: {},
98+
});
99+
100+
const importer = new ImportSpaces(baseOptions);
101+
await importer.start();
102+
103+
expect(startStub.callCount).to.equal(1);
104+
expect(startStub.firstCall.args[4]).to.be.undefined;
105+
expect(startStub.firstCall.args[5]).to.be.undefined;
106+
});
107+
108+
it('should record the correct spaceUidMap entry when default space is remapped', async () => {
109+
stubSpaceDirs(['am-space-1']);
110+
sinon.stub(ImportWorkspace.prototype, 'start').resolves({
111+
oldSpaceUid: 'am-space-1',
112+
newSpaceUid: 'target-space-3',
113+
workspaceUid: 'ws-3',
114+
isDefault: true,
115+
uidMap: {},
116+
urlMap: {},
117+
});
118+
119+
const options: ImportSpacesOptions = {
120+
...baseOptions,
121+
targetDefaultSpaceUid: 'target-space-3',
122+
};
123+
const importer = new ImportSpaces(options);
124+
const result = await importer.start();
125+
126+
expect(result.spaceUidMap['am-space-1']).to.equal('target-space-3');
127+
expect(result.spaceMappings[0].newSpaceUid).to.equal('target-space-3');
128+
expect(result.spaceMappings[0].isDefault).to.equal(true);
129+
});
130+
131+
it('should process non-default spaces normally alongside the remapped default space', async () => {
132+
stubSpaceDirs(['am-space-1', 'am-space-2']);
133+
const startStub = sinon.stub(ImportWorkspace.prototype, 'start');
134+
startStub.onFirstCall().resolves({
135+
oldSpaceUid: 'am-space-1',
136+
newSpaceUid: 'target-space-3',
137+
workspaceUid: 'ws-3',
138+
isDefault: true,
139+
uidMap: {},
140+
urlMap: {},
141+
});
142+
startStub.onSecondCall().resolves({
143+
oldSpaceUid: 'am-space-2',
144+
newSpaceUid: 'brand-new-space',
145+
workspaceUid: 'main',
146+
isDefault: false,
147+
uidMap: {},
148+
urlMap: {},
149+
});
150+
151+
const options: ImportSpacesOptions = {
152+
...baseOptions,
153+
targetDefaultSpaceUid: 'target-space-3',
154+
targetDefaultWorkspaceUid: 'ws-3',
155+
};
156+
const importer = new ImportSpaces(options);
157+
const result = await importer.start();
158+
159+
expect(result.spaceMappings).to.have.lengthOf(2);
160+
expect(result.spaceUidMap['am-space-1']).to.equal('target-space-3');
161+
expect(result.spaceUidMap['am-space-2']).to.equal('brand-new-space');
162+
});
163+
});
164+
165+
describe('no spaces scenario', () => {
166+
it('should return empty maps when spaces directory has no am* dirs', async () => {
167+
stubSpaceDirs([]);
168+
169+
const importer = new ImportSpaces(baseOptions);
170+
const result = await importer.start();
171+
172+
expect(result.spaceMappings).to.deep.equal([]);
173+
expect(result.spaceUidMap).to.deep.equal({});
174+
});
175+
});
176+
});

0 commit comments

Comments
 (0)