@contentstack/cli-cm-import 2.0.0-beta.2 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,7 +47,7 @@ $ npm install -g @contentstack/cli-cm-import
47
47
  $ csdx COMMAND
48
48
  running command...
49
49
  $ csdx (--version)
50
- @contentstack/cli-cm-import/2.0.0-beta.2 linux-x64 node-v22.21.1
50
+ @contentstack/cli-cm-import/2.0.0-beta.3 linux-x64 node-v22.21.1
51
51
  $ csdx --help [COMMAND]
52
52
  USAGE
53
53
  $ csdx COMMAND
@@ -71,39 +71,68 @@ USAGE
71
71
  [--backup-dir <value>] [--branch <value>] [--import-webhook-status disable|current]
72
72
 
73
73
  FLAGS
74
- -B, --branch=<value> The name of the branch where you want to import your content. If you don't
75
- mention the branch name, then by default the content will be imported to the
76
- main branch.
77
- -a, --alias=<value> The management token of the destination stack where you will import the
78
- content.
79
- -b, --backup-dir=<value> [optional] Backup directory name when using specific module.
80
- -c, --config=<value> [optional] The path of the configuration JSON file containing all the options
81
- for a single run.
82
- -d, --data-dir=<value> The path or the location in your file system where the content, you intend to
83
- import, is stored. For example, -d "C:\Users\Name\Desktop\cli\content". If the
84
- export folder has branches involved, then the path should point till the
85
- particular branch. For example, “-d
86
- "C:\Users\Name\Desktop\cli\content\branch_name"
87
- -k, --stack-api-key=<value> API Key of the target stack
88
- -m, --module=<value> [optional] Specify the module to import into the target stack. If not
89
- specified, the import command will import all the modules into the stack. The
90
- available modules are assets, content-types, entries, environments,
91
- extensions, marketplace-apps, global-fields, labels, locales, webhooks,
92
- workflows, custom-roles, personalize projects, and taxonomies.
93
- -y, --yes [optional] Force override all Marketplace prompts.
94
- --branch-alias=<value> Specify the branch alias where you want to import your content. If not
95
- specified, the content is imported into the main branch by default.
96
- --exclude-global-modules Excludes the branch-independent module from the import operation.
97
- --import-webhook-status=<option> [default: disable] [default: disable] (optional) This webhook state keeps the
98
- same state of webhooks as the source stack. <options: disable|current>
99
- <options: disable|current>
100
- --personalize-project-name=<value> (optional) Provide a unique name for the Personalize project.
101
- --replace-existing Replaces the existing module in the target stack.
102
- --skip-app-recreation (optional) Skips the recreation of private apps if they already exist.
103
- --skip-assets-publish Skips asset publishing during the import process.
104
- --skip-audit Skips the audit fix that occurs during an import operation.
105
- --skip-entries-publish Skips entry publishing during the import process
106
- --skip-existing Skips the module exists warning messages.
74
+ -B, --branch=<value>
75
+ The name of the branch where you want to import your content. If you don't mention the branch name, then by default
76
+ the content will be imported to the main branch.
77
+
78
+ -a, --alias=<value>
79
+ The management token of the destination stack where you will import the content.
80
+
81
+ -b, --backup-dir=<value>
82
+ [optional] Backup directory name when using specific module.
83
+
84
+ -c, --config=<value>
85
+ [optional] The path of the configuration JSON file containing all the options for a single run.
86
+
87
+ -d, --data-dir=<value>
88
+ The path or the location in your file system where the content, you intend to import, is stored. For example, -d
89
+ "C:\Users\Name\Desktop\cli\content". If the export folder has branches involved, then the path should point till the
90
+ particular branch. For example, “-d "C:\Users\Name\Desktop\cli\content\branch_name"
91
+
92
+ -k, --stack-api-key=<value>
93
+ API Key of the target stack
94
+
95
+ -m, --module=<value>
96
+ [optional] Specify the module to import into the target stack. If not specified, the import command will import all
97
+ the modules into the stack. The available modules are assets, content-types, entries, environments, extensions,
98
+ marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, personalize projects,
99
+ taxonomies, and composable-studio.
100
+
101
+ -y, --yes
102
+ [optional] Force override all Marketplace prompts.
103
+
104
+ --branch-alias=<value>
105
+ Specify the branch alias where you want to import your content. If not specified, the content is imported into the
106
+ main branch by default.
107
+
108
+ --exclude-global-modules
109
+ Excludes the branch-independent module from the import operation.
110
+
111
+ --import-webhook-status=<option>
112
+ [default: disable] [default: disable] (optional) This webhook state keeps the same state of webhooks as the source
113
+ stack. <options: disable|current>
114
+ <options: disable|current>
115
+
116
+ --personalize-project-name=<value>
117
+ (optional) Provide a unique name for the Personalize project.
118
+
119
+ --replace-existing
120
+ Replaces the existing module in the target stack.
121
+
122
+ --skip-app-recreation
123
+ (optional) Skips the recreation of private apps if they already exist.
124
+
125
+ --skip-assets-publish
126
+ Skips asset publishing during the import process.
127
+
128
+ --skip-audit
129
+ Skips the audit fix that occurs during an import operation.
130
+
131
+ --skip-entries-publish
132
+ Skips entry publishing during the import process
133
+
134
+ --skip-existing
135
+ Skips the module exists warning messages.
107
136
 
108
137
  DESCRIPTION
109
138
  Import content from a stack
@@ -139,39 +168,68 @@ USAGE
139
168
  <value>] [--branch <value>] [--import-webhook-status disable|current]
140
169
 
141
170
  FLAGS
142
- -B, --branch=<value> The name of the branch where you want to import your content. If you don't
143
- mention the branch name, then by default the content will be imported to the
144
- main branch.
145
- -a, --alias=<value> The management token of the destination stack where you will import the
146
- content.
147
- -b, --backup-dir=<value> [optional] Backup directory name when using specific module.
148
- -c, --config=<value> [optional] The path of the configuration JSON file containing all the options
149
- for a single run.
150
- -d, --data-dir=<value> The path or the location in your file system where the content, you intend to
151
- import, is stored. For example, -d "C:\Users\Name\Desktop\cli\content". If the
152
- export folder has branches involved, then the path should point till the
153
- particular branch. For example, “-d
154
- "C:\Users\Name\Desktop\cli\content\branch_name"
155
- -k, --stack-api-key=<value> API Key of the target stack
156
- -m, --module=<value> [optional] Specify the module to import into the target stack. If not
157
- specified, the import command will import all the modules into the stack. The
158
- available modules are assets, content-types, entries, environments,
159
- extensions, marketplace-apps, global-fields, labels, locales, webhooks,
160
- workflows, custom-roles, personalize projects, and taxonomies.
161
- -y, --yes [optional] Force override all Marketplace prompts.
162
- --branch-alias=<value> Specify the branch alias where you want to import your content. If not
163
- specified, the content is imported into the main branch by default.
164
- --exclude-global-modules Excludes the branch-independent module from the import operation.
165
- --import-webhook-status=<option> [default: disable] [default: disable] (optional) This webhook state keeps the
166
- same state of webhooks as the source stack. <options: disable|current>
167
- <options: disable|current>
168
- --personalize-project-name=<value> (optional) Provide a unique name for the Personalize project.
169
- --replace-existing Replaces the existing module in the target stack.
170
- --skip-app-recreation (optional) Skips the recreation of private apps if they already exist.
171
- --skip-assets-publish Skips asset publishing during the import process.
172
- --skip-audit Skips the audit fix that occurs during an import operation.
173
- --skip-entries-publish Skips entry publishing during the import process
174
- --skip-existing Skips the module exists warning messages.
171
+ -B, --branch=<value>
172
+ The name of the branch where you want to import your content. If you don't mention the branch name, then by default
173
+ the content will be imported to the main branch.
174
+
175
+ -a, --alias=<value>
176
+ The management token of the destination stack where you will import the content.
177
+
178
+ -b, --backup-dir=<value>
179
+ [optional] Backup directory name when using specific module.
180
+
181
+ -c, --config=<value>
182
+ [optional] The path of the configuration JSON file containing all the options for a single run.
183
+
184
+ -d, --data-dir=<value>
185
+ The path or the location in your file system where the content, you intend to import, is stored. For example, -d
186
+ "C:\Users\Name\Desktop\cli\content". If the export folder has branches involved, then the path should point till the
187
+ particular branch. For example, “-d "C:\Users\Name\Desktop\cli\content\branch_name"
188
+
189
+ -k, --stack-api-key=<value>
190
+ API Key of the target stack
191
+
192
+ -m, --module=<value>
193
+ [optional] Specify the module to import into the target stack. If not specified, the import command will import all
194
+ the modules into the stack. The available modules are assets, content-types, entries, environments, extensions,
195
+ marketplace-apps, global-fields, labels, locales, webhooks, workflows, custom-roles, personalize projects,
196
+ taxonomies, and composable-studio.
197
+
198
+ -y, --yes
199
+ [optional] Force override all Marketplace prompts.
200
+
201
+ --branch-alias=<value>
202
+ Specify the branch alias where you want to import your content. If not specified, the content is imported into the
203
+ main branch by default.
204
+
205
+ --exclude-global-modules
206
+ Excludes the branch-independent module from the import operation.
207
+
208
+ --import-webhook-status=<option>
209
+ [default: disable] [default: disable] (optional) This webhook state keeps the same state of webhooks as the source
210
+ stack. <options: disable|current>
211
+ <options: disable|current>
212
+
213
+ --personalize-project-name=<value>
214
+ (optional) Provide a unique name for the Personalize project.
215
+
216
+ --replace-existing
217
+ Replaces the existing module in the target stack.
218
+
219
+ --skip-app-recreation
220
+ (optional) Skips the recreation of private apps if they already exist.
221
+
222
+ --skip-assets-publish
223
+ Skips asset publishing during the import process.
224
+
225
+ --skip-audit
226
+ Skips the audit fix that occurs during an import operation.
227
+
228
+ --skip-entries-publish
229
+ Skips entry publishing during the import process
230
+
231
+ --skip-existing
232
+ Skips the module exists warning messages.
175
233
 
176
234
  DESCRIPTION
177
235
  Import content from a stack
@@ -17,7 +17,7 @@ class ImportCommand extends cli_command_1.Command {
17
17
  // Prepare the context object
18
18
  const context = this.createImportContext(importConfig.apiKey, importConfig.authenticationMethod);
19
19
  importConfig.context = Object.assign({}, context);
20
- //log.info(`Using Cli Version: ${this.context?.cliVersion}`, importConfig.context);
20
+ // log.info(`Using CLI version: ${this.context?.cliVersion}`, importConfig.context);
21
21
  // Note setting host to create cma client
22
22
  importConfig.host = this.cmaHost;
23
23
  importConfig.region = this.region;
@@ -25,6 +25,8 @@ class ImportCommand extends cli_command_1.Command {
25
25
  importConfig.developerHubBaseUrl = this.developerHubUrl;
26
26
  if (this.personalizeUrl)
27
27
  importConfig.modules.personalize.baseURL[importConfig.region.name] = this.personalizeUrl;
28
+ if (this.composableStudioUrl)
29
+ importConfig.modules['composable-studio'].apiBaseUrl = this.composableStudioUrl;
28
30
  const managementAPIClient = await (0, cli_utilities_1.managementSDKClient)(importConfig);
29
31
  if (flags.branch) {
30
32
  cli_utilities_1.CLIProgressManager.initializeGlobalSummary(`IMPORT-${flags.branch}`, flags.branch, `Importing content into "${flags.branch}" branch...`);
@@ -92,6 +94,7 @@ class ImportCommand extends cli_command_1.Command {
92
94
  command: ((_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.info) === null || _b === void 0 ? void 0 : _b.command) || 'cm:stacks:import',
93
95
  module: '',
94
96
  userId: cli_utilities_1.configHandler.get('userUid') || '',
97
+ email: cli_utilities_1.configHandler.get('email') || '',
95
98
  sessionId: (_c = this.context) === null || _c === void 0 ? void 0 : _c.sessionId,
96
99
  apiKey: apiKey || '',
97
100
  orgId: cli_utilities_1.configHandler.get('oauthOrgUid') || '',
@@ -153,7 +156,7 @@ ImportCommand.flags = {
153
156
  module: cli_utilities_1.flags.string({
154
157
  required: false,
155
158
  char: 'm',
156
- description: '[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.',
159
+ description: '[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.',
157
160
  parse: (0, cli_utilities_1.printFlagDeprecation)(['-m'], ['--module']),
158
161
  }),
159
162
  'backup-dir': cli_utilities_1.flags.string({
@@ -168,7 +171,7 @@ ImportCommand.flags = {
168
171
  exclusive: ['branch-alias'],
169
172
  }),
170
173
  'branch-alias': cli_utilities_1.flags.string({
171
- description: "Specify the branch alias where you want to import your content. If not specified, the content is imported into the main branch by default.",
174
+ description: 'Specify the branch alias where you want to import your content. If not specified, the content is imported into the main branch by default.',
172
175
  exclusive: ['branch'],
173
176
  }),
174
177
  'import-webhook-status': cli_utilities_1.flags.string({
@@ -44,6 +44,7 @@ const config = {
44
44
  'variant-entries',
45
45
  'labels',
46
46
  'webhooks',
47
+ 'composable-studio',
47
48
  ],
48
49
  locales: {
49
50
  dirName: 'locales',
@@ -199,6 +200,12 @@ const config = {
199
200
  locale: 'en-us',
200
201
  },
201
202
  },
203
+ 'composable-studio': {
204
+ dirName: 'composable_studio',
205
+ fileName: 'composable_studio.json',
206
+ apiBaseUrl: 'https://composable-studio-api.contentstack.com',
207
+ apiVersion: 'v1',
208
+ },
202
209
  },
203
210
  languagesCode: [
204
211
  'af-za',
@@ -66,8 +66,15 @@ class ModuleImporter {
66
66
  }
67
67
  async importByModuleByName(moduleName) {
68
68
  cli_utilities_1.log.info(`Starting import of ${moduleName} module`, this.importConfig.context);
69
- // import the modules by name
70
- // calls the module runner which inturn calls the module itself
69
+ // Check if module should be skipped for legacy contentVersion
70
+ if (this.importConfig.contentVersion !== 2) {
71
+ const onlyTSModules = this.importConfig.onlyTSModules || [];
72
+ if (onlyTSModules.includes(moduleName)) {
73
+ // Module is in onlyTSModules list, skip import for legacy contentVersion
74
+ return undefined;
75
+ }
76
+ }
77
+ // Use module import (same for both contentVersion 1 and 2)
71
78
  return (0, modules_1.default)({
72
79
  stackAPIClient: this.stackAPIClient,
73
80
  importConfig: this.importConfig,
@@ -0,0 +1,43 @@
1
+ import { ModuleClassParams, ComposableStudioProject } from '../../types';
2
+ export default class ImportComposableStudio {
3
+ private importConfig;
4
+ private composableStudioConfig;
5
+ private composableStudioPath;
6
+ private composableStudioFilePath;
7
+ private apiClient;
8
+ private envUidMapperPath;
9
+ private envUidMapper;
10
+ constructor({ importConfig }: ModuleClassParams);
11
+ /**
12
+ * Entry point for Studio import
13
+ */
14
+ start(): Promise<void>;
15
+ /**
16
+ * Initialize authentication headers for API calls
17
+ */
18
+ addAuthHeaders(): Promise<boolean>;
19
+ /**
20
+ * Load environment UID mapper from backup directory
21
+ */
22
+ loadEnvironmentMapper(): Promise<void>;
23
+ /**
24
+ * Read exported project from file system
25
+ */
26
+ readExportedProject(): Promise<ComposableStudioProject | null>;
27
+ /**
28
+ * Check if target stack already has a connected project
29
+ */
30
+ getExistingProject(): Promise<ComposableStudioProject | null>;
31
+ /**
32
+ * Import project with name conflict handling
33
+ */
34
+ importProject(exportedProject: ComposableStudioProject): Promise<void>;
35
+ /**
36
+ * Map environment UID from source to target
37
+ */
38
+ mapEnvironmentUid(sourceEnvUid: string): string;
39
+ /**
40
+ * Prompt user for a new project name when conflict occurs
41
+ */
42
+ promptForNewProjectName(currentName: string): Promise<string>;
43
+ }
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const node_path_1 = require("node:path");
5
+ const cli_utilities_1 = require("@contentstack/cli-utilities");
6
+ const isEmpty_1 = tslib_1.__importDefault(require("lodash/isEmpty"));
7
+ const utils_1 = require("../../utils");
8
+ class ImportComposableStudio {
9
+ constructor({ importConfig }) {
10
+ this.importConfig = importConfig;
11
+ this.importConfig.context.module = 'composable-studio';
12
+ this.composableStudioConfig = importConfig.modules['composable-studio'];
13
+ // Setup paths
14
+ this.composableStudioPath = (0, node_path_1.join)(this.importConfig.backupDir, this.composableStudioConfig.dirName);
15
+ this.composableStudioFilePath = (0, node_path_1.join)(this.composableStudioPath, this.composableStudioConfig.fileName);
16
+ this.envUidMapperPath = (0, node_path_1.join)(this.importConfig.backupDir, 'mapper', 'environments', 'uid-mapping.json');
17
+ this.envUidMapper = {};
18
+ // Initialize HttpClient with Studio API base URL
19
+ this.apiClient = new cli_utilities_1.HttpClient();
20
+ this.apiClient.baseUrl(`${this.composableStudioConfig.apiBaseUrl}/${this.composableStudioConfig.apiVersion}`);
21
+ }
22
+ /**
23
+ * Entry point for Studio import
24
+ */
25
+ async start() {
26
+ if (this.importConfig.management_token) {
27
+ cli_utilities_1.log.warn('Skipping Studio project import when using management token', this.importConfig.context);
28
+ return;
29
+ }
30
+ cli_utilities_1.log.debug('Starting Studio project import process...', this.importConfig.context);
31
+ try {
32
+ // Initialize authentication
33
+ const authInitialized = await this.addAuthHeaders();
34
+ if (!authInitialized) {
35
+ cli_utilities_1.log.warn('Skipping Studio project import when using OAuth authentication', this.importConfig.context);
36
+ return;
37
+ }
38
+ // Load environment UID mapper
39
+ await this.loadEnvironmentMapper();
40
+ // Read exported project data
41
+ const exportedProject = await this.readExportedProject();
42
+ if (!exportedProject) {
43
+ cli_utilities_1.log.warn(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_NOT_FOUND'), this.importConfig.context);
44
+ return;
45
+ }
46
+ cli_utilities_1.log.debug(`Exported project found: ${exportedProject.name}`, this.importConfig.context);
47
+ // Check if target stack already has a connected project
48
+ const existingProject = await this.getExistingProject();
49
+ if (existingProject) {
50
+ cli_utilities_1.log.warn(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_SKIP_EXISTING'), this.importConfig.context);
51
+ return;
52
+ }
53
+ // Import the project with name conflict handling
54
+ await this.importProject(exportedProject);
55
+ cli_utilities_1.log.success(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_IMPORT_COMPLETE', exportedProject.name), this.importConfig.context);
56
+ }
57
+ catch (error) {
58
+ (0, cli_utilities_1.handleAndLogError)(error, Object.assign({}, this.importConfig.context));
59
+ }
60
+ }
61
+ /**
62
+ * Initialize authentication headers for API calls
63
+ */
64
+ async addAuthHeaders() {
65
+ cli_utilities_1.log.debug('Initializing Studio API authentication...', this.importConfig.context);
66
+ // Get authentication details - following personalization-api-adapter pattern
67
+ await cli_utilities_1.authenticationHandler.getAuthDetails();
68
+ const token = cli_utilities_1.authenticationHandler.accessToken;
69
+ cli_utilities_1.log.debug(`Authentication type: ${cli_utilities_1.authenticationHandler.isOauthEnabled ? 'OAuth' : 'Token'}`, this.importConfig.context);
70
+ // Set authentication headers based on auth type
71
+ if (cli_utilities_1.authenticationHandler.isOauthEnabled) {
72
+ cli_utilities_1.log.debug('Skipping setting OAuth authorization header when using OAuth authentication', this.importConfig.context);
73
+ return false;
74
+ }
75
+ else {
76
+ // TODO: Currenlty assuming if auth type is not OAuth, it is Basic Auth and we are setting authtoken header
77
+ cli_utilities_1.log.debug('Setting authtoken header', this.importConfig.context);
78
+ this.apiClient.headers({ authtoken: token });
79
+ }
80
+ // Set organization_uid header
81
+ this.apiClient.headers({
82
+ organization_uid: this.importConfig.org_uid,
83
+ 'Content-Type': 'application/json',
84
+ Accept: 'application/json',
85
+ });
86
+ cli_utilities_1.log.debug('Studio API authentication initialized', this.importConfig.context);
87
+ return true;
88
+ }
89
+ /**
90
+ * Load environment UID mapper from backup directory
91
+ */
92
+ async loadEnvironmentMapper() {
93
+ cli_utilities_1.log.debug('Loading environment UID mapper...', this.importConfig.context);
94
+ if (utils_1.fileHelper.fileExistsSync(this.envUidMapperPath)) {
95
+ this.envUidMapper = utils_1.fileHelper.readFileSync(this.envUidMapperPath);
96
+ cli_utilities_1.log.debug(`Environment mapper loaded with ${Object.keys(this.envUidMapper).length} mappings`, this.importConfig.context);
97
+ }
98
+ else {
99
+ cli_utilities_1.log.debug('No environment UID mapper found', this.importConfig.context);
100
+ }
101
+ }
102
+ /**
103
+ * Read exported project from file system
104
+ */
105
+ async readExportedProject() {
106
+ cli_utilities_1.log.debug(`Reading exported project from: ${this.composableStudioFilePath}`, this.importConfig.context);
107
+ if (!utils_1.fileHelper.fileExistsSync(this.composableStudioFilePath)) {
108
+ cli_utilities_1.log.debug('Studio project file does not exist', this.importConfig.context);
109
+ return null;
110
+ }
111
+ const projectData = utils_1.fileHelper.readFileSync(this.composableStudioFilePath);
112
+ if (!projectData || (0, isEmpty_1.default)(projectData)) {
113
+ cli_utilities_1.log.debug('Studio project file is empty', this.importConfig.context);
114
+ return null;
115
+ }
116
+ return projectData;
117
+ }
118
+ /**
119
+ * Check if target stack already has a connected project
120
+ */
121
+ async getExistingProject() {
122
+ var _a;
123
+ cli_utilities_1.log.debug('Checking if target stack already has a connected project...', this.importConfig.context);
124
+ try {
125
+ const apiUrl = '/projects';
126
+ cli_utilities_1.log.debug(`Fetching projects from: ${this.composableStudioConfig.apiBaseUrl}${apiUrl}`, this.importConfig.context);
127
+ const response = await this.apiClient.get(apiUrl);
128
+ if (response.status < 200 || response.status >= 300) {
129
+ throw new Error(`API call failed with status ${response.status}: ${JSON.stringify(response.data)}`);
130
+ }
131
+ const projects = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.projects) || [];
132
+ cli_utilities_1.log.debug(`Found ${projects.length} projects in organization`, this.importConfig.context);
133
+ // Filter projects by connected stack API key
134
+ const connectedProject = projects.find((project) => project.connectedStackApiKey === this.importConfig.apiKey);
135
+ if (connectedProject) {
136
+ cli_utilities_1.log.debug(`Target stack already has connected project: ${connectedProject.name}`, this.importConfig.context);
137
+ return connectedProject;
138
+ }
139
+ cli_utilities_1.log.debug('Target stack does not have a connected project', this.importConfig.context);
140
+ return null;
141
+ }
142
+ catch (error) {
143
+ cli_utilities_1.log.debug(`Error checking for existing project: ${error.message}`, this.importConfig.context);
144
+ throw error;
145
+ }
146
+ }
147
+ /**
148
+ * Import project with name conflict handling
149
+ */
150
+ async importProject(exportedProject) {
151
+ var _a, _b, _c;
152
+ cli_utilities_1.log.debug('Starting project import...', this.importConfig.context);
153
+ // Map environment UID
154
+ const mappedEnvironmentUid = this.mapEnvironmentUid(exportedProject.settings.configuration.environment);
155
+ // Prepare project data for import
156
+ const projectData = {
157
+ name: exportedProject.name,
158
+ connectedStackApiKey: this.importConfig.apiKey,
159
+ contentTypeUid: exportedProject.contentTypeUid,
160
+ description: exportedProject.description || '',
161
+ canvasUrl: exportedProject.canvasUrl || '/',
162
+ settings: {
163
+ configuration: {
164
+ environment: mappedEnvironmentUid,
165
+ locale: ((_b = (_a = exportedProject === null || exportedProject === void 0 ? void 0 : exportedProject.settings) === null || _a === void 0 ? void 0 : _a.configuration) === null || _b === void 0 ? void 0 : _b.locale) || '',
166
+ },
167
+ },
168
+ };
169
+ cli_utilities_1.log.debug(`Project data prepared: ${JSON.stringify(projectData, null, 2)}`, this.importConfig.context);
170
+ // Try to create project with name conflict retry loop
171
+ let projectCreated = false;
172
+ let currentName = projectData.name;
173
+ let attemptCount = 0;
174
+ while (!projectCreated) {
175
+ attemptCount++;
176
+ cli_utilities_1.log.debug(`Attempt ${attemptCount} to create project with name: ${currentName}`, this.importConfig.context);
177
+ projectData.name = currentName;
178
+ const response = await this.apiClient.post('/projects', projectData);
179
+ if (response.status >= 200 && response.status < 300) {
180
+ projectCreated = true;
181
+ cli_utilities_1.log.debug(`Project created successfully with UID: ${(_c = response.data) === null || _c === void 0 ? void 0 : _c.uid}`, this.importConfig.context);
182
+ }
183
+ else {
184
+ throw new Error(`API call failed with status ${response.status}: ${JSON.stringify(response.data)}`);
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * Map environment UID from source to target
190
+ */
191
+ mapEnvironmentUid(sourceEnvUid) {
192
+ if (!sourceEnvUid) {
193
+ cli_utilities_1.log.debug('Source environment UID is empty', this.importConfig.context);
194
+ return '';
195
+ }
196
+ cli_utilities_1.log.debug(`Mapping source environment UID: ${sourceEnvUid}`, this.importConfig.context);
197
+ if ((0, isEmpty_1.default)(this.envUidMapper)) {
198
+ cli_utilities_1.log.warn(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_ENV_MAPPING_FAILED', sourceEnvUid), this.importConfig.context);
199
+ return '';
200
+ }
201
+ const mappedUid = this.envUidMapper[sourceEnvUid];
202
+ if (!mappedUid) {
203
+ cli_utilities_1.log.warn(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_ENV_MAPPING_FAILED', sourceEnvUid), this.importConfig.context);
204
+ return '';
205
+ }
206
+ cli_utilities_1.log.debug(`Mapped environment UID: ${sourceEnvUid} → ${mappedUid}`, this.importConfig.context);
207
+ return mappedUid;
208
+ }
209
+ /**
210
+ * Prompt user for a new project name when conflict occurs
211
+ */
212
+ async promptForNewProjectName(currentName) {
213
+ const suggestedName = `Copy of ${currentName}`;
214
+ cli_utilities_1.log.warn(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_NAME_CONFLICT', currentName), this.importConfig.context);
215
+ cli_utilities_1.log.info(cli_utilities_1.messageHandler.parse('COMPOSABLE_STUDIO_SUGGEST_NAME', suggestedName), this.importConfig.context);
216
+ const response = await cli_utilities_1.cliux.inquire({
217
+ type: 'input',
218
+ name: 'projectName',
219
+ message: 'Enter new project name:',
220
+ default: suggestedName,
221
+ });
222
+ const newName = response.projectName || suggestedName;
223
+ cli_utilities_1.log.debug(`User provided new project name: ${newName}`, this.importConfig.context);
224
+ return newName;
225
+ }
226
+ }
227
+ exports.default = ImportComposableStudio;
@@ -47,7 +47,7 @@ class ImportStack extends base_class_1.default {
47
47
  var _a, _b, _c;
48
48
  cli_utilities_1.log.debug('Processing stack settings for import', this.importConfig.context);
49
49
  // Update environment UID mapping if live preview is configured
50
- if (((_a = this.stackSettings) === null || _a === void 0 ? void 0 : _a.live_preview) && ((_b = this.stackSettings) === null || _b === void 0 ? void 0 : _b.live_preview['default-env'])) {
50
+ if (((_a = this.stackSettings) === null || _a === void 0 ? void 0 : _a.live_preview) && ((_b = this.stackSettings) === null || _b === void 0 ? void 0 : _b.live_preview['default-env']) !== undefined) {
51
51
  const oldEnvUid = this.stackSettings.live_preview['default-env'];
52
52
  const mappedEnvUid = this.envUidMapper[oldEnvUid];
53
53
  if (mappedEnvUid) {
@@ -142,7 +142,7 @@ class ImportWorkflows extends base_class_1.default {
142
142
  else {
143
143
  this.failedWebhooks.push(apiData);
144
144
  (_d = this.progressManager) === null || _d === void 0 ? void 0 : _d.tick(false, `workflow: ${name || uid}`, (error === null || error === void 0 ? void 0 : error.message) || 'Failed to import workflow', utils_1.PROCESS_NAMES.WORKFLOWS_CREATE);
145
- if (error.errors['workflow_stages.0.users']) {
145
+ if ((error === null || error === void 0 ? void 0 : error.errors) && error.errors['workflow_stages.0.users']) {
146
146
  cli_utilities_1.log.error("Failed to import Workflows as you've specified certain roles in the Stage transition and access rules section. We currently don't import roles to the stack.", this.importConfig.context);
147
147
  }
148
148
  else {
@@ -156,6 +156,12 @@ export default interface DefaultConfig {
156
156
  locale: string;
157
157
  } & AnyProperty;
158
158
  } & AnyProperty;
159
+ 'composable-studio': {
160
+ dirName: string;
161
+ fileName: string;
162
+ apiBaseUrl: string;
163
+ apiVersion: string;
164
+ };
159
165
  };
160
166
  languagesCode: string[];
161
167
  apis: {
@@ -57,6 +57,7 @@ export default interface ImportConfig extends DefaultConfig, ExternalConfig {
57
57
  personalizeProjectName?: string;
58
58
  'exclude-global-modules': false;
59
59
  context: Context;
60
+ onlyTSModules?: Modules[];
60
61
  }
61
62
  type branch = {
62
63
  uid: string;
@@ -26,7 +26,7 @@ export interface User {
26
26
  email: string;
27
27
  authtoken: string;
28
28
  }
29
- export type Modules = 'stack' | 'assets' | 'locales' | 'environments' | 'extensions' | 'webhooks' | 'global-fields' | 'entries' | 'content-types' | 'custom-roles' | 'workflows' | 'labels' | 'marketplace-apps' | 'taxonomies' | 'personalize' | 'variant-entries';
29
+ export type Modules = 'stack' | 'assets' | 'locales' | 'environments' | 'extensions' | 'webhooks' | 'global-fields' | 'entries' | 'content-types' | 'custom-roles' | 'workflows' | 'labels' | 'marketplace-apps' | 'taxonomies' | 'personalize' | 'variant-entries' | 'composable-studio';
30
30
  export type ModuleClassParams = {
31
31
  stackAPIClient: ReturnType<ContentstackClient['stack']>;
32
32
  importConfig: ImportConfig;
@@ -71,23 +71,49 @@ export interface TaxonomiesConfig {
71
71
  fileName: string;
72
72
  dependencies?: Modules[];
73
73
  }
74
- export { default as DefaultConfig } from './default-config';
75
- export { default as ImportConfig } from './import-config';
76
- export * from './entries';
77
- export * from './marketplace-app';
78
- export type ExtensionType = {
79
- uid: string;
80
- scope: Record<string, unknown>;
81
- title: string;
82
- };
74
+ export interface ComposableStudioConfig {
75
+ dirName: string;
76
+ fileName: string;
77
+ apiBaseUrl: string;
78
+ apiVersion: string;
79
+ }
80
+ export interface ComposableStudioProject {
81
+ name: string;
82
+ description: string;
83
+ canvasUrl: string;
84
+ connectedStackApiKey: string;
85
+ contentTypeUid: string;
86
+ organizationUid: string;
87
+ settings: {
88
+ configuration: {
89
+ environment: string;
90
+ locale: string;
91
+ };
92
+ };
93
+ uid?: string;
94
+ createdBy?: string;
95
+ updatedBy?: string;
96
+ deletedAt?: boolean;
97
+ createdAt?: string;
98
+ updatedAt?: string;
99
+ }
83
100
  export interface Context {
84
101
  command: string;
85
102
  module: string;
86
103
  userId: string | undefined;
87
- email?: string | undefined;
104
+ email: string | undefined;
88
105
  sessionId: string | undefined;
89
106
  clientId?: string | undefined;
90
107
  apiKey: string;
91
108
  orgId: string;
92
109
  authenticationMethod?: string;
93
110
  }
111
+ export { default as DefaultConfig } from './default-config';
112
+ export { default as ImportConfig } from './import-config';
113
+ export * from './entries';
114
+ export * from './marketplace-app';
115
+ export type ExtensionType = {
116
+ uid: string;
117
+ scope: Record<string, unknown>;
118
+ title: string;
119
+ };
@@ -128,7 +128,7 @@ const removeReferenceFields = async function (schema, flag = { supressed: false
128
128
  catch (error) {
129
129
  // Else warn and modify the schema object.
130
130
  isContentTypeError = true;
131
- cli_utilities_1.log.warn(`Content-type ${schema[i].reference_to[j]} does not exist. Removing the field from schema`);
131
+ cli_utilities_1.log.warn(`Content type ${schema[i].reference_to[j]} does not exist. Removing the field from schema...`);
132
132
  }
133
133
  }
134
134
  if (isContentTypeError) {
@@ -200,7 +200,7 @@ const updateFieldRules = function (contentType) {
200
200
  const field = contentType.schema[i];
201
201
  fieldDataTypeMap[field.uid] = field.data_type;
202
202
  }
203
- cli_utilities_1.log.debug(`Created field data type mapping for ${Object.keys(fieldDataTypeMap).length} fields`);
203
+ cli_utilities_1.log.debug(`Created field data type mapping for ${Object.keys(fieldDataTypeMap).length} fields.`);
204
204
  const fieldRules = [...contentType.field_rules];
205
205
  let len = fieldRules.length;
206
206
  let removedRules = 0;
@@ -58,7 +58,7 @@ const readLargeFile = function (filePath, opts) {
58
58
  resolve(data);
59
59
  });
60
60
  parseStream.on('error', function (error) {
61
- console.log('error', error);
61
+ console.log('Error', error);
62
62
  reject(error);
63
63
  });
64
64
  readStream.pipe(parseStream);
@@ -26,7 +26,7 @@ const setupConfig = async (importCmdFlags) => {
26
26
  config.contentDir = (0, cli_utilities_1.sanitizePath)(importCmdFlags['data'] || importCmdFlags['data-dir'] || config.data || (await (0, interactive_1.askContentDir)()));
27
27
  const pattern = /[*$%#<>{}!&?]/g;
28
28
  if (pattern.test(config.contentDir)) {
29
- cli_utilities_1.cliux.print(`\nPlease add a directory path without any of the special characters: (*,&,{,},[,],$,%,<,>,?,!)`, {
29
+ cli_utilities_1.cliux.print(`\nPlease enter a directory path without any special characters: (*,&,{,},[,],$,%,<,>,?,!)`, {
30
30
  color: 'yellow',
31
31
  });
32
32
  config.contentDir = (0, cli_utilities_1.sanitizePath)(await (0, interactive_1.askContentDir)());
@@ -117,7 +117,7 @@ const setupConfig = async (importCmdFlags) => {
117
117
  cli_utilities_1.configHandler.set('log.progressSupportedModule', 'import');
118
118
  // Add authentication details to config for context tracking
119
119
  config.authenticationMethod = authenticationMethod;
120
- cli_utilities_1.log.debug('Import configuration setup completed', Object.assign({}, config));
120
+ cli_utilities_1.log.debug('Import configuration setup completed.', Object.assign({}, config));
121
121
  return config;
122
122
  };
123
123
  exports.default = setupConfig;
@@ -116,7 +116,30 @@ const updateImportConfigWithResolvedPath = async (importConfig, resolvedPath) =>
116
116
  importConfig.branchDir = resolvedPath;
117
117
  importConfig.contentDir = resolvedPath;
118
118
  importConfig.data = resolvedPath;
119
- cli_utilities_1.log.debug(`Import config updated - contentDir: ${importConfig.contentDir}, branchDir: ${importConfig.branchDir}, data: ${importConfig.data},`);
119
+ // Check if export-info.json exists to determine contentVersion
120
+ const exportInfoPath = path.join(resolvedPath, 'export-info.json');
121
+ if ((0, file_helper_1.fileExistsSync)(exportInfoPath)) {
122
+ try {
123
+ const exportInfo = await (0, file_helper_1.readFile)(exportInfoPath);
124
+ // If export-info.json exists, set contentVersion to 2 (or use value from file if present)
125
+ if (exportInfo && exportInfo.contentVersion) {
126
+ importConfig.contentVersion = exportInfo.contentVersion;
127
+ }
128
+ else {
129
+ // If export-info.json exists but contentVersion is missing, default to 2
130
+ importConfig.contentVersion = 2;
131
+ }
132
+ }
133
+ catch (error) {
134
+ // If export-info.json exists but is null or can't be read, default to 2
135
+ importConfig.contentVersion = 2;
136
+ }
137
+ }
138
+ else {
139
+ // If export-info.json doesn't exist, default to 1 (legacy format)
140
+ importConfig.contentVersion = 1;
141
+ }
142
+ cli_utilities_1.log.debug(`Import config updated - contentDir: ${importConfig.contentDir}, branchDir: ${importConfig.branchDir}, data: ${importConfig.data}, contentVersion: ${importConfig.contentVersion}`);
120
143
  };
121
144
  exports.updateImportConfigWithResolvedPath = updateImportConfigWithResolvedPath;
122
145
  /**
@@ -44,7 +44,7 @@ const login = async (config) => {
44
44
  let errorstack_key = (_a = error === null || error === void 0 ? void 0 : error.errors) === null || _a === void 0 ? void 0 : _a.api_key;
45
45
  if (errorstack_key) {
46
46
  const keyError = errorstack_key[0];
47
- cli_utilities_1.log.error(`Invalid stack API token: ${keyError} Please enter valid stack API token.`);
47
+ cli_utilities_1.log.error(`Invalid stack API token: ${keyError}. Please enter a valid stack API token.`);
48
48
  throw error;
49
49
  }
50
50
  cli_utilities_1.log.error(`Stack fetch error: ${error === null || error === void 0 ? void 0 : error.errorMessage}`);
@@ -21,6 +21,7 @@ const getAllStackSpecificApps = async (config, skip = 0, listOfApps = []) => {
21
21
  .fetchAll({ target_uids: config.target_stack, skip })
22
22
  .catch((error) => {
23
23
  (0, cli_utilities_1.handleAndLogError)(error);
24
+ cli_utilities_1.log.error(error, config === null || config === void 0 ? void 0 : config.context);
24
25
  });
25
26
  if (collection) {
26
27
  const { items: apps, count } = collection;
@@ -56,7 +57,8 @@ const getOrgUid = async (config) => {
56
57
  .stack({ api_key: config.target_stack })
57
58
  .fetch()
58
59
  .catch((error) => {
59
- throw error;
60
+ (0, cli_utilities_1.handleAndLogError)(error);
61
+ cli_utilities_1.log.error(error, config === null || config === void 0 ? void 0 : config.context);
60
62
  });
61
63
  const orgUid = (tempStackData === null || tempStackData === void 0 ? void 0 : tempStackData.org_uid) || '';
62
64
  cli_utilities_1.log.debug(`Organization UID: ${orgUid}`);
@@ -78,7 +80,7 @@ const getConfirmationToCreateApps = async (privateApps, config) => {
78
80
  return Promise.resolve(true);
79
81
  }
80
82
  else {
81
- cli_utilities_1.log.warn('User declined to create private apps (second prompt)');
83
+ cli_utilities_1.log.debug('User declined to create private apps (second prompt).');
82
84
  return Promise.resolve(false);
83
85
  }
84
86
  }
@@ -89,7 +91,7 @@ const getConfirmationToCreateApps = async (privateApps, config) => {
89
91
  }
90
92
  }
91
93
  else {
92
- cli_utilities_1.log.info('Force prompt disabled, automatically creating private apps');
94
+ cli_utilities_1.log.debug('Force prompt disabled, automatically creating private apps');
93
95
  return Promise.resolve(true);
94
96
  }
95
97
  };
@@ -111,7 +113,7 @@ const makeRedirectUrlCall = async (response, appName, config) => {
111
113
  .get(response.redirect_url)
112
114
  .then(async ({ response }) => {
113
115
  if ((0, includes_1.default)([501, 403], response.status)) {
114
- cli_utilities_1.log.error(`OAuth API call failed for ${appName}: ${response.statusText}`);
116
+ cli_utilities_1.log.error(`OAuth API call failed for ${appName}: ${response.statusText}`, config === null || config === void 0 ? void 0 : config.context);
115
117
  await (0, exports.confirmToCloseProcess)(response.data, config);
116
118
  }
117
119
  else {
@@ -119,6 +121,7 @@ const makeRedirectUrlCall = async (response, appName, config) => {
119
121
  }
120
122
  })
121
123
  .catch((error) => {
124
+ cli_utilities_1.log.error(error, config === null || config === void 0 ? void 0 : config.context);
122
125
  if ((0, includes_1.default)([501, 403], error.status)) {
123
126
  (0, cli_utilities_1.handleAndLogError)(error);
124
127
  }
@@ -14,7 +14,7 @@ const cli_utilities_1 = require("@contentstack/cli-utilities");
14
14
  * @param {ImportConfig} importConfig
15
15
  */
16
16
  const lookUpTaxonomy = function (importConfig, schema, taxonomies) {
17
- cli_utilities_1.log.debug(`Starting taxonomy lookup for schema with ${Object.keys(schema).length} fields`);
17
+ cli_utilities_1.log.debug(`Starting taxonomy lookup for schema with ${Object.keys(schema).length} fields.`);
18
18
  for (let i in schema) {
19
19
  if (schema[i].data_type === 'taxonomy') {
20
20
  cli_utilities_1.log.debug(`Processing taxonomy field: ${schema[i].uid}`);
@@ -1 +1,10 @@
1
- {}
1
+ {
2
+ "COMPOSABLE_STUDIO_IMPORT_START": "Starting Studio project import...",
3
+ "COMPOSABLE_STUDIO_NOT_FOUND": "No Studio project found in exported data",
4
+ "COMPOSABLE_STUDIO_SKIP_EXISTING": "Skipping Studio import - target stack already has a connected project",
5
+ "COMPOSABLE_STUDIO_IMPORT_COMPLETE": "Successfully imported Studio project '%s'",
6
+ "COMPOSABLE_STUDIO_IMPORT_FAILED": "Failed to import 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
+ }
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "module": {
86
86
  "char": "m",
87
- "description": "[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.",
87
+ "description": "[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.",
88
88
  "name": "module",
89
89
  "required": false,
90
90
  "hasDynamicHelp": false,
@@ -212,5 +212,5 @@
212
212
  ]
213
213
  }
214
214
  },
215
- "version": "2.0.0-beta.2"
215
+ "version": "2.0.0-beta.3"
216
216
  }
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-import",
3
3
  "description": "Contentstack CLI plugin to import content into stack",
4
- "version": "2.0.0-beta.2",
4
+ "version": "2.0.0-beta.3",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/contentstack/cli/issues",
7
7
  "dependencies": {
8
- "@contentstack/cli-audit": "~1.16.0",
9
- "@contentstack/cli-command": "~1.6.1",
8
+ "@contentstack/cli-audit": "2.0.0-beta",
9
+ "@contentstack/cli-command": "~1.7.0",
10
10
  "@contentstack/cli-utilities": "~1.15.0",
11
11
  "@contentstack/management": "~1.22.0",
12
- "@contentstack/cli-variants": "~2.0.0-beta.2",
12
+ "@contentstack/cli-variants": "~2.0.0-beta.3",
13
13
  "@oclif/core": "^4.3.0",
14
14
  "big-json": "^3.2.0",
15
15
  "bluebird": "^3.7.2",
@@ -60,7 +60,7 @@
60
60
  "format": "eslint src/**/*.ts --fix",
61
61
  "test:integration": "mocha --forbid-only \"test/run.test.js\" --integration-test --timeout 60000",
62
62
  "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"",
63
- "test:unit": "mocha --forbid-only \"test/**/*.test.ts\""
63
+ "test:unit": "mocha --forbid-only \"test/**/*.test.ts\" --exit"
64
64
  },
65
65
  "engines": {
66
66
  "node": ">=14.0.0"