Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
446 changes: 223 additions & 223 deletions .talismanrc

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions packages/contentstack-export/messages/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
"MARKETPLACE_APP_CONFIG_EXPORT_FAILED": "Failed to export configuration for app '%s'",
"MARKETPLACE_APP_MANIFEST_EXPORT_FAILED": "Failed to export manifest for app '%s'",

"COMPOSABLE_STUDIO_EXPORT_START": "Starting Composable Studio project export...",
"COMPOSABLE_STUDIO_NOT_FOUND": "No Composable Studio project found for this stack",
"COMPOSABLE_STUDIO_EXPORT_COMPLETE": "Successfully exported Composable Studio project '%s'",
"COMPOSABLE_STUDIO_EXPORT_FAILED": "Failed to export Composable Studio project: %s",
"COMPOSABLE_STUDIO_AUTH_REQUIRED": "To export Composable Studio projects, you must be logged in",

"ENTRIES_EXPORT_COMPLETE": "Successfully exported entries (Content Type: %s, Locale: %s)",
"ENTRIES_EXPORT_SUCCESS": "All entries exported successfully",
"ENTRIES_VERSIONED_EXPORT_SUCCESS": "Successfully exported versioned entry (Content Type: %s, UID: %s, Locale: %s)",
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-export/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"bugs": "https://github.com/contentstack/cli/issues",
"dependencies": {
"@contentstack/cli-command": "~1.6.1",
"@contentstack/cli-utilities": "~1.14.4",
"@contentstack/cli-variants": "~1.3.4",
"@oclif/core": "^4.3.3",
"@contentstack/cli-utilities": "~1.14.4",
"async": "^3.2.6",
"big-json": "^3.2.0",
"bluebird": "^3.7.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class ExportCommand extends Command {
module: flags.string({
char: 'm',
description:
'[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.',
'[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.',
parse: printFlagDeprecation(['-m'], ['--module']),
}),
'content-types': flags.string({
Expand Down
6 changes: 6 additions & 0 deletions packages/contentstack-export/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const config: DefaultConfig = {
'entries',
'labels',
'marketplace-apps',
'composable-studio',
],
locales: {
dirName: 'locales',
Expand Down Expand Up @@ -213,6 +214,11 @@ const config: DefaultConfig = {
dirName: 'marketplace_apps',
fileName: 'marketplace_apps.json',
},
'composable-studio': {
dirName: 'composable_studio',
fileName: 'composable_studio.json',
apiBaseUrl: 'https://composable-studio-api.contentstack.com/v1',
},
taxonomies: {
dirName: 'taxonomies',
fileName: 'taxonomies.json',
Expand Down
5 changes: 2 additions & 3 deletions packages/contentstack-export/src/export/module-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,8 @@ class ModuleExporter {
}

if (!this.exportConfig.skipDependencies) {
const {
modules: { [moduleName]: { dependencies = [] } = {} },
} = this.exportConfig;
const moduleConfig = this.exportConfig.modules[moduleName as keyof typeof this.exportConfig.modules];
const dependencies = (moduleConfig as any)?.dependencies || [];

if (dependencies.length > 0) {
exportModules = exportModules.concat(dependencies);
Expand Down
138 changes: 138 additions & 0 deletions packages/contentstack-export/src/export/modules/composable-studio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { resolve as pResolve } from 'node:path';
import {
cliux,
isAuthenticated,
log,
messageHandler,
handleAndLogError,
HttpClient,
authenticationHandler,
} from '@contentstack/cli-utilities';

import { fsUtil, getOrgUid } from '../../utils';
import { ModuleClassParams, ComposableStudioConfig, ExportConfig, ComposableStudioProject } from '../../types';

export default class ExportComposableStudio {
protected composableStudioConfig: ComposableStudioConfig;
protected composableStudioProject: ComposableStudioProject | null = null;
protected apiClient: HttpClient;
public composableStudioPath: string;
public exportConfig: ExportConfig;

constructor({ exportConfig }: Omit<ModuleClassParams, 'stackAPIClient' | 'moduleName'>) {
this.exportConfig = exportConfig;
this.composableStudioConfig = exportConfig.modules['composable-studio'];
this.exportConfig.context.module = 'composable-studio';

// Initialize HttpClient with Composable Studio API base URL
this.apiClient = new HttpClient();
this.apiClient.baseUrl(this.composableStudioConfig.apiBaseUrl);
}

async start(): Promise<void> {
log.debug('Starting Composable Studio project export process...', this.exportConfig.context);

if (!isAuthenticated()) {
cliux.print(
'WARNING!!! To export Composable Studio projects, you must be logged in. Please check csdx auth:login --help to log in',
{ color: 'yellow' },
);
return Promise.resolve();
}

this.composableStudioPath = pResolve(
this.exportConfig.data,
this.exportConfig.branchName || '',
this.composableStudioConfig.dirName,
);
log.debug(`Composable Studio folder path: ${this.composableStudioPath}`, this.exportConfig.context);

await fsUtil.makeDirectory(this.composableStudioPath);
log.debug('Created Composable Studio directory', this.exportConfig.context);

this.exportConfig.org_uid = this.exportConfig.org_uid || (await getOrgUid(this.exportConfig));
log.debug(`Organization UID: ${this.exportConfig.org_uid}`, this.exportConfig.context);

await this.exportProjects();
log.debug('Composable Studio project export process completed', this.exportConfig.context);
}

/**
* Export Composable Studio projects connected to the current stack
*/
async exportProjects(): Promise<void> {
log.debug('Starting Composable Studio project export...', this.exportConfig.context);

try {
// Get authentication details - following personalization-api-adapter pattern
log.debug('Initializing Composable Studio API authentication...', this.exportConfig.context);
await authenticationHandler.getAuthDetails();
const token = authenticationHandler.accessToken;
log.debug(
`Authentication type: ${authenticationHandler.isOauthEnabled ? 'OAuth' : 'Token'}`,
this.exportConfig.context,
);

// Set authentication headers based on auth type
if (authenticationHandler.isOauthEnabled) {
log.debug('Setting OAuth authorization header', this.exportConfig.context);
this.apiClient.headers({ authorization: token });
} else {
log.debug('Setting authtoken header', this.exportConfig.context);
this.apiClient.headers({ authtoken: token });
}

// Set organization_uid header
this.apiClient.headers({
organization_uid: this.exportConfig.org_uid,
Accept: 'application/json',
});

const apiUrl = '/projects';
log.debug(
`Fetching projects from: ${this.composableStudioConfig.apiBaseUrl}${apiUrl}`,
this.exportConfig.context,
);

// Make API call to fetch projects using HttpClient
const response = await this.apiClient.get(apiUrl);

if (response.status < 200 || response.status >= 300) {
throw new Error(`API call failed with status ${response.status}: ${JSON.stringify(response.data)}`);
}

const data = response.data;
log.debug(`Fetched ${data.projects?.length || 0} total projects`, this.exportConfig.context);

// Filter projects connected to this stack
const connectedProject = data.projects?.filter(
(project: ComposableStudioProject) => project.connectedStackApiKey === this.exportConfig.apiKey,
);

if (!connectedProject || connectedProject.length === 0) {
log.info(messageHandler.parse('COMPOSABLE_STUDIO_NOT_FOUND'), this.exportConfig.context);
return;
}

// Use the first connected project (stacks should have only one project)
this.composableStudioProject = connectedProject[0];
log.debug(`Found Composable Studio project: ${this.composableStudioProject.name}`, this.exportConfig.context);

// Write the project to file
const composableStudioFilePath = pResolve(this.composableStudioPath, this.composableStudioConfig.fileName);
log.debug(`Writing Composable Studio project to: ${composableStudioFilePath}`, this.exportConfig.context);

fsUtil.writeFile(composableStudioFilePath, this.composableStudioProject as unknown as Record<string, unknown>);

log.success(
messageHandler.parse('COMPOSABLE_STUDIO_EXPORT_COMPLETE', this.composableStudioProject.name),
this.exportConfig.context,
);
} catch (error: any) {
log.debug('Error occurred while exporting Composable Studio project', this.exportConfig.context);
handleAndLogError(error, {
...this.exportConfig.context,
});
}
}
}
5 changes: 5 additions & 0 deletions packages/contentstack-export/src/types/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ export default interface DefaultConfig {
fileName: string;
dependencies?: Modules[];
};
'composable-studio': {
dirName: string;
fileName: string;
apiBaseUrl: string;
};
masterLocale: {
dirName: string;
fileName: string;
Expand Down
30 changes: 29 additions & 1 deletion packages/contentstack-export/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export type Modules =
| 'labels'
| 'marketplace-apps'
| 'taxonomies'
| 'personalize';
| 'personalize'
| 'composable-studio';

export type ModuleClassParams = {
stackAPIClient: ReturnType<ContentstackClient['stack']>;
Expand Down Expand Up @@ -129,6 +130,33 @@ export interface StackConfig {
dependencies?: Modules[];
limit?: number;
}

export interface ComposableStudioConfig {
dirName: string;
fileName: string;
apiBaseUrl: string;
}

export interface ComposableStudioProject {
name: string;
description: string;
canvasUrl: string;
connectedStackApiKey: string;
contentTypeUid: string;
organizationUid: string;
settings: {
configuration: {
environment: string;
locale: string;
};
};
createdBy: string;
updatedBy: string;
deletedAt: boolean;
createdAt: string;
updatedAt: string;
uid: string;
}
export interface Context {
command: string;
module: string;
Expand Down
Loading