Skip to content

Commit fa17076

Browse files
shafeeqd959harshithad0703
authored andcommitted
Merge pull request #2246 from contentstack/export-import/composable-studio
Export import support for composable studio
1 parent d05dac4 commit fa17076

File tree

16 files changed

+886
-251
lines changed

16 files changed

+886
-251
lines changed

packages/contentstack-export/messages/index.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@
5757
"MARKETPLACE_APP_CONFIG_EXPORT_FAILED": "Failed to export configuration for app '%s'",
5858
"MARKETPLACE_APP_MANIFEST_EXPORT_FAILED": "Failed to export manifest for app '%s'",
5959

60+
"COMPOSABLE_STUDIO_EXPORT_START": "Starting Composable Studio project export...",
61+
"COMPOSABLE_STUDIO_NOT_FOUND": "No Composable Studio project found for this stack",
62+
"COMPOSABLE_STUDIO_EXPORT_COMPLETE": "Successfully exported Composable Studio project '%s'",
63+
"COMPOSABLE_STUDIO_EXPORT_FAILED": "Failed to export Composable Studio project: %s",
64+
"COMPOSABLE_STUDIO_AUTH_REQUIRED": "To export Composable Studio projects, you must be logged in",
65+
6066
"ENTRIES_EXPORT_COMPLETE": "Successfully exported entries (Content Type: %s, Locale: %s)",
6167
"ENTRIES_EXPORT_SUCCESS": "All entries exported successfully",
6268
"ENTRIES_VERSIONED_EXPORT_SUCCESS": "Successfully exported versioned entry (Content Type: %s, UID: %s, Locale: %s)",

packages/contentstack-export/src/commands/cm/stacks/export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export default class ExportCommand extends Command {
7979
module: flags.string({
8080
char: 'm',
8181
description:
82-
'[optional] Specific module name. If not specified, the export command will export all the modules to the stack. The available modules are assets, content-types, entries, environments, extensions, marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, and taxonomies.',
82+
'[optional] Specific module name. If not specified, the export command will export all the modules to the stack. The available modules are assets, content-types, entries, environments, extensions, marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, taxonomies, and composable-studio.',
8383
parse: printFlagDeprecation(['-m'], ['--module']),
8484
}),
8585
'content-types': flags.string({

packages/contentstack-export/src/config/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const config: DefaultConfig = {
3939
'entries',
4040
'labels',
4141
'marketplace-apps',
42+
'composable-studio',
4243
],
4344
locales: {
4445
dirName: 'locales',
@@ -212,6 +213,11 @@ const config: DefaultConfig = {
212213
dirName: 'marketplace_apps',
213214
fileName: 'marketplace_apps.json',
214215
},
216+
'composable-studio': {
217+
dirName: 'composable_studio',
218+
fileName: 'composable_studio.json',
219+
apiBaseUrl: 'https://composable-studio-api.contentstack.com/v1',
220+
},
215221
taxonomies: {
216222
dirName: 'taxonomies',
217223
fileName: 'taxonomies.json',

packages/contentstack-export/src/export/module-exporter.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,8 @@ class ModuleExporter {
122122
}
123123

124124
if (!this.exportConfig.skipDependencies) {
125-
const {
126-
modules: { [moduleName]: { dependencies = [] } = {} },
127-
} = this.exportConfig;
125+
const moduleConfig = this.exportConfig.modules[moduleName as keyof typeof this.exportConfig.modules];
126+
const dependencies = (moduleConfig as any)?.dependencies || [];
128127

129128
if (dependencies.length > 0) {
130129
exportModules = exportModules.concat(dependencies);
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { resolve as pResolve } from 'node:path';
2+
import {
3+
cliux,
4+
isAuthenticated,
5+
log,
6+
messageHandler,
7+
handleAndLogError,
8+
HttpClient,
9+
authenticationHandler,
10+
} from '@contentstack/cli-utilities';
11+
12+
import { fsUtil, getOrgUid } from '../../utils';
13+
import { ModuleClassParams, ComposableStudioConfig, ExportConfig, ComposableStudioProject } from '../../types';
14+
15+
export default class ExportComposableStudio {
16+
protected composableStudioConfig: ComposableStudioConfig;
17+
protected composableStudioProject: ComposableStudioProject | null = null;
18+
protected apiClient: HttpClient;
19+
public composableStudioPath: string;
20+
public exportConfig: ExportConfig;
21+
22+
constructor({ exportConfig }: Omit<ModuleClassParams, 'stackAPIClient' | 'moduleName'>) {
23+
this.exportConfig = exportConfig;
24+
this.composableStudioConfig = exportConfig.modules['composable-studio'];
25+
this.exportConfig.context.module = 'composable-studio';
26+
27+
// Initialize HttpClient with Composable Studio API base URL
28+
this.apiClient = new HttpClient();
29+
this.apiClient.baseUrl(this.composableStudioConfig.apiBaseUrl);
30+
}
31+
32+
async start(): Promise<void> {
33+
log.debug('Starting Composable Studio project export process...', this.exportConfig.context);
34+
35+
if (!isAuthenticated()) {
36+
cliux.print(
37+
'WARNING!!! To export Composable Studio projects, you must be logged in. Please check csdx auth:login --help to log in',
38+
{ color: 'yellow' },
39+
);
40+
return Promise.resolve();
41+
}
42+
43+
this.composableStudioPath = pResolve(
44+
this.exportConfig.data,
45+
this.exportConfig.branchName || '',
46+
this.composableStudioConfig.dirName,
47+
);
48+
log.debug(`Composable Studio folder path: ${this.composableStudioPath}`, this.exportConfig.context);
49+
50+
await fsUtil.makeDirectory(this.composableStudioPath);
51+
log.debug('Created Composable Studio directory', this.exportConfig.context);
52+
53+
this.exportConfig.org_uid = this.exportConfig.org_uid || (await getOrgUid(this.exportConfig));
54+
log.debug(`Organization UID: ${this.exportConfig.org_uid}`, this.exportConfig.context);
55+
56+
await this.exportProjects();
57+
log.debug('Composable Studio project export process completed', this.exportConfig.context);
58+
}
59+
60+
/**
61+
* Export Composable Studio projects connected to the current stack
62+
*/
63+
async exportProjects(): Promise<void> {
64+
log.debug('Starting Composable Studio project export...', this.exportConfig.context);
65+
66+
try {
67+
// Get authentication details - following personalization-api-adapter pattern
68+
log.debug('Initializing Composable Studio API authentication...', this.exportConfig.context);
69+
await authenticationHandler.getAuthDetails();
70+
const token = authenticationHandler.accessToken;
71+
log.debug(
72+
`Authentication type: ${authenticationHandler.isOauthEnabled ? 'OAuth' : 'Token'}`,
73+
this.exportConfig.context,
74+
);
75+
76+
// Set authentication headers based on auth type
77+
if (authenticationHandler.isOauthEnabled) {
78+
log.debug('Setting OAuth authorization header', this.exportConfig.context);
79+
this.apiClient.headers({ authorization: token });
80+
} else {
81+
log.debug('Setting authtoken header', this.exportConfig.context);
82+
this.apiClient.headers({ authtoken: token });
83+
}
84+
85+
// Set organization_uid header
86+
this.apiClient.headers({
87+
organization_uid: this.exportConfig.org_uid,
88+
Accept: 'application/json',
89+
});
90+
91+
const apiUrl = '/projects';
92+
log.debug(
93+
`Fetching projects from: ${this.composableStudioConfig.apiBaseUrl}${apiUrl}`,
94+
this.exportConfig.context,
95+
);
96+
97+
// Make API call to fetch projects using HttpClient
98+
const response = await this.apiClient.get(apiUrl);
99+
100+
if (response.status < 200 || response.status >= 300) {
101+
throw new Error(`API call failed with status ${response.status}: ${JSON.stringify(response.data)}`);
102+
}
103+
104+
const data = response.data;
105+
log.debug(`Fetched ${data.projects?.length || 0} total projects`, this.exportConfig.context);
106+
107+
// Filter projects connected to this stack
108+
const connectedProject = data.projects?.filter(
109+
(project: ComposableStudioProject) => project.connectedStackApiKey === this.exportConfig.apiKey,
110+
);
111+
112+
if (!connectedProject || connectedProject.length === 0) {
113+
log.info(messageHandler.parse('COMPOSABLE_STUDIO_NOT_FOUND'), this.exportConfig.context);
114+
return;
115+
}
116+
117+
// Use the first connected project (stacks should have only one project)
118+
this.composableStudioProject = connectedProject[0];
119+
log.debug(`Found Composable Studio project: ${this.composableStudioProject.name}`, this.exportConfig.context);
120+
121+
// Write the project to file
122+
const composableStudioFilePath = pResolve(this.composableStudioPath, this.composableStudioConfig.fileName);
123+
log.debug(`Writing Composable Studio project to: ${composableStudioFilePath}`, this.exportConfig.context);
124+
125+
fsUtil.writeFile(composableStudioFilePath, this.composableStudioProject as unknown as Record<string, unknown>);
126+
127+
log.success(
128+
messageHandler.parse('COMPOSABLE_STUDIO_EXPORT_COMPLETE', this.composableStudioProject.name),
129+
this.exportConfig.context,
130+
);
131+
} catch (error: any) {
132+
log.debug('Error occurred while exporting Composable Studio project', this.exportConfig.context);
133+
handleAndLogError(error, {
134+
...this.exportConfig.context,
135+
});
136+
}
137+
}
138+
}

packages/contentstack-export/src/types/default-config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ export default interface DefaultConfig {
162162
fileName: string;
163163
dependencies?: Modules[];
164164
};
165+
'composable-studio': {
166+
dirName: string;
167+
fileName: string;
168+
apiBaseUrl: string;
169+
};
165170
masterLocale: {
166171
dirName: string;
167172
fileName: string;

packages/contentstack-export/src/types/index.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export type Modules =
4949
| 'labels'
5050
| 'marketplace-apps'
5151
| 'taxonomies'
52-
| 'personalize';
52+
| 'personalize'
53+
| 'composable-studio';
5354

5455
export type ModuleClassParams = {
5556
stackAPIClient: ReturnType<ContentstackClient['stack']>;
@@ -129,6 +130,33 @@ export interface StackConfig {
129130
dependencies?: Modules[];
130131
limit?: number;
131132
}
133+
134+
export interface ComposableStudioConfig {
135+
dirName: string;
136+
fileName: string;
137+
apiBaseUrl: string;
138+
}
139+
140+
export interface ComposableStudioProject {
141+
name: string;
142+
description: string;
143+
canvasUrl: string;
144+
connectedStackApiKey: string;
145+
contentTypeUid: string;
146+
organizationUid: string;
147+
settings: {
148+
configuration: {
149+
environment: string;
150+
locale: string;
151+
};
152+
};
153+
createdBy: string;
154+
updatedBy: string;
155+
deletedAt: boolean;
156+
createdAt: string;
157+
updatedAt: string;
158+
uid: string;
159+
}
132160
export interface Context {
133161
command: string;
134162
module: string;
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
{}
1+
{
2+
"COMPOSABLE_STUDIO_IMPORT_START": "Starting Composable Studio project import...",
3+
"COMPOSABLE_STUDIO_NOT_FOUND": "No Composable Studio project found in exported data",
4+
"COMPOSABLE_STUDIO_SKIP_EXISTING": "Skipping Composable Studio import - target stack already has a connected project",
5+
"COMPOSABLE_STUDIO_IMPORT_COMPLETE": "Successfully imported Composable Studio project '%s'",
6+
"COMPOSABLE_STUDIO_IMPORT_FAILED": "Failed to import Composable Studio project: %s",
7+
"COMPOSABLE_STUDIO_NAME_CONFLICT": "Project name '%s' already exists. Please provide a new name:",
8+
"COMPOSABLE_STUDIO_SUGGEST_NAME": "Suggested name: %s",
9+
"COMPOSABLE_STUDIO_ENV_MAPPING_FAILED": "Warning: Could not map environment '%s', using empty environment"
10+
}

packages/contentstack-import/src/commands/cm/stacks/import.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default class ImportCommand extends Command {
7676
required: false,
7777
char: 'm',
7878
description:
79-
'[optional] Specify the module to import into the target stack. If not specified, the import command will import all the modules into the stack. The available modules are assets, content-types, entries, environments, extensions, marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, personalize projects, and taxonomies.',
79+
'[optional] Specify the module to import into the target stack. If not specified, the import command will import all the modules into the stack. The available modules are assets, content-types, entries, environments, extensions, marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, personalize projects, taxonomies, and composable-studio.',
8080
parse: printFlagDeprecation(['-m'], ['--module']),
8181
}),
8282
'backup-dir': flags.string({
@@ -93,7 +93,7 @@ export default class ImportCommand extends Command {
9393
}),
9494
'branch-alias': flags.string({
9595
description:
96-
"Specify the branch alias where you want to import your content. If not specified, the content is imported into the main branch by default.",
96+
'Specify the branch alias where you want to import your content. If not specified, the content is imported into the main branch by default.',
9797
exclusive: ['branch'],
9898
}),
9999
'import-webhook-status': flags.string({

packages/contentstack-import/src/config/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const config: DefaultConfig = {
4444
'variant-entries',
4545
'labels',
4646
'webhooks',
47+
'composable-studio',
4748
],
4849
locales: {
4950
dirName: 'locales',
@@ -199,6 +200,11 @@ const config: DefaultConfig = {
199200
locale: 'en-us',
200201
},
201202
},
203+
'composable-studio': {
204+
dirName: 'composable_studio',
205+
fileName: 'composable_studio.json',
206+
apiBaseUrl: 'https://composable-studio-api.contentstack.com/v1',
207+
},
202208
},
203209
languagesCode: [
204210
'af-za',

0 commit comments

Comments
 (0)