@contentstack/cli-cm-import-setup 1.6.0 → 1.7.0

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-setup
47
47
  $ csdx COMMAND
48
48
  running command...
49
49
  $ csdx (--version)
50
- @contentstack/cli-cm-import-setup/1.6.0 linux-x64 node-v22.20.0
50
+ @contentstack/cli-cm-import-setup/1.7.0 linux-x64 node-v22.21.1
51
51
  $ csdx --help [COMMAND]
52
52
  USAGE
53
53
  $ csdx COMMAND
@@ -22,6 +22,11 @@ const config = {
22
22
  fileName: 'custom-roles.json',
23
23
  dependencies: ['environments', 'entries'],
24
24
  },
25
+ locales: {
26
+ dirName: 'locales',
27
+ fileName: 'locales.json',
28
+ dependencies: [],
29
+ },
25
30
  environments: {
26
31
  dirName: 'environments',
27
32
  fileName: 'environments.json',
@@ -43,7 +48,7 @@ const config = {
43
48
  entries: {
44
49
  dirName: 'entries',
45
50
  fileName: 'entries.json',
46
- dependencies: ['assets', 'marketplace-apps', 'taxonomies'],
51
+ dependencies: ['assets', 'extensions', 'marketplace-apps', 'taxonomies'],
47
52
  },
48
53
  'global-fields': {
49
54
  dirName: 'global_fields',
@@ -3,6 +3,7 @@ import { ModuleClassParams } from '../../types';
3
3
  export default class TaxonomiesImportSetup {
4
4
  private config;
5
5
  private taxonomiesFilePath;
6
+ private taxonomiesFolderPath;
6
7
  private stackAPIClient;
7
8
  private dependencies;
8
9
  private taxonomiesConfig;
@@ -10,8 +11,11 @@ export default class TaxonomiesImportSetup {
10
11
  private taxSuccessPath;
11
12
  private taxonomiesMapperDirPath;
12
13
  private termsMapperDirPath;
14
+ private localesFilePath;
15
+ private isLocaleBasedStructure;
13
16
  taxonomiesMapper: Record<string, unknown>;
14
17
  termsMapper: Record<string, unknown>;
18
+ masterLocaleFilePath: string;
15
19
  constructor({ config, stackAPIClient }: ModuleClassParams);
16
20
  /**
17
21
  * Start the taxonomies import setup
@@ -19,13 +23,39 @@ export default class TaxonomiesImportSetup {
19
23
  * @returns {Promise<void>}
20
24
  */
21
25
  start(): Promise<void>;
26
+ /**
27
+ * Setup taxonomies using legacy format (root-level taxonomy files)
28
+ */
29
+ setupTaxonomiesLegacy(taxonomies: any): Promise<void>;
30
+ /**
31
+ * Setup taxonomies using locale-based format (taxonomies organized by locale)
32
+ * For locale-based structure, we query the target stack for each taxonomy+locale combination
33
+ */
34
+ setupTaxonomiesByLocale(taxonomies: any): Promise<void>;
35
+ /**
36
+ * Detect if locale-based folder structure exists
37
+ * @returns {boolean} true if locale-based structure detected, false otherwise
38
+ */
39
+ detectLocaleBasedStructure(): boolean;
40
+ /**
41
+ * Get the master locale code
42
+ * First tries to read from master-locale.json, then falls back to config, then 'en-us'
43
+ * @returns {string} The master locale code
44
+ */
45
+ getMasterLocaleCode(): string;
46
+ /**
47
+ * Load available locales from locales file
48
+ * @returns {Record<string, string>} Map of locale codes
49
+ */
50
+ loadAvailableLocales(): Record<string, string>;
22
51
  /**
23
52
  * Retrieves the taxonomies based on the provided taxonomy UID.
24
53
  *
25
54
  * @param taxonomy - The UID of the taxonomy to retrieve.
55
+ * @param locale - Optional locale code to query taxonomy in specific locale
26
56
  * @returns A promise that resolves to the retrieved taxonomies.
27
57
  */
28
- getTaxonomies(taxonomy: any): Promise<any>;
58
+ getTaxonomies(taxonomy: any, locale?: string): Promise<any>;
29
59
  /**
30
60
  * Sanitizes the attributes of a taxonomy object.
31
61
  *
@@ -37,11 +67,12 @@ export default class TaxonomiesImportSetup {
37
67
  * Retrieves all terms of a taxonomy.
38
68
  *
39
69
  * @param taxonomy - The taxonomy object.
70
+ * @param locale - Optional locale code to query terms in specific locale
40
71
  * @param skip - The number of terms to skip (default: 0).
41
72
  * @param terms - An array to store the retrieved terms (default: []).
42
73
  * @returns A promise that resolves to an array of terms.
43
74
  */
44
- getAllTermsOfTaxonomy(taxonomy: any, skip?: number, terms?: any[]): Promise<any>;
75
+ getAllTermsOfTaxonomy(taxonomy: any, locale?: string, skip?: number, terms?: any[]): Promise<any>;
45
76
  /**
46
77
  * Sanitizes the attributes of the given terms.
47
78
  *
@@ -49,5 +80,5 @@ export default class TaxonomiesImportSetup {
49
80
  * @returns The sanitized terms.
50
81
  */
51
82
  sanitizeTermsAttribs(terms: Record<string, unknown>[]): Record<string, unknown>[];
52
- handleTaxonomyErrorMsg(err: any): void;
83
+ handleTaxonomyErrorMsg(err: any, taxonomyUid?: string, locale?: string): void;
53
84
  }
@@ -8,16 +8,21 @@ const utils_1 = require("../../utils");
8
8
  const cli_utilities_1 = require("@contentstack/cli-utilities");
9
9
  class TaxonomiesImportSetup {
10
10
  constructor({ config, stackAPIClient }) {
11
+ var _a, _b, _c;
12
+ this.isLocaleBasedStructure = false;
11
13
  this.taxonomiesMapper = {};
12
14
  this.termsMapper = {};
13
15
  this.config = config;
14
16
  this.stackAPIClient = stackAPIClient;
15
- this.taxonomiesFilePath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.config.contentDir), 'taxonomies', 'taxonomies.json');
17
+ this.taxonomiesFolderPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.config.contentDir), 'taxonomies');
18
+ this.taxonomiesFilePath = (0, path_1.join)(this.taxonomiesFolderPath, 'taxonomies.json');
16
19
  this.taxonomiesConfig = config.modules.taxonomies;
17
20
  this.taxonomiesMapperDirPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.config.backupDir), 'mapper', 'taxonomies');
18
21
  this.taxSuccessPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.taxonomiesMapperDirPath), 'success.json');
19
22
  this.termsMapperDirPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.taxonomiesMapperDirPath), 'terms');
20
23
  this.termsSuccessPath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.termsMapperDirPath), 'success.json');
24
+ this.localesFilePath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.config.contentDir), ((_a = config.modules.locales) === null || _a === void 0 ? void 0 : _a.dirName) || 'locales', ((_b = config.modules.locales) === null || _b === void 0 ? void 0 : _b.fileName) || 'locales.json');
25
+ this.masterLocaleFilePath = (0, path_1.join)((0, cli_utilities_1.sanitizePath)(this.config.contentDir), ((_c = config.modules.locales) === null || _c === void 0 ? void 0 : _c.dirName) || 'locales', 'master-locale.json');
21
26
  this.taxonomiesMapper = {};
22
27
  this.termsMapper = {};
23
28
  }
@@ -30,20 +35,18 @@ class TaxonomiesImportSetup {
30
35
  try {
31
36
  const taxonomies = utils_1.fsUtil.readFile(this.taxonomiesFilePath);
32
37
  if (!(0, isEmpty_1.default)(taxonomies)) {
38
+ // 1. Detect locale-based structure
39
+ this.isLocaleBasedStructure = this.detectLocaleBasedStructure();
33
40
  // 2. Create mapper directory
34
41
  utils_1.fsUtil.makeDirectory(this.taxonomiesMapperDirPath);
35
42
  utils_1.fsUtil.makeDirectory(this.termsMapperDirPath);
36
- for (const taxonomy of Object.values(taxonomies)) {
37
- let targetTaxonomy = await this.getTaxonomies(taxonomy);
38
- if (!targetTaxonomy) {
39
- (0, utils_1.log)(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
40
- continue;
41
- }
42
- targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
43
- this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
44
- const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
45
- const sanitizedTerms = this.sanitizeTermsAttribs(terms);
46
- this.termsMapper[taxonomy.uid] = sanitizedTerms;
43
+ if (this.isLocaleBasedStructure) {
44
+ (0, utils_1.log)(this.config, 'Detected locale-based folder structure for taxonomies', 'info');
45
+ await this.setupTaxonomiesByLocale(taxonomies);
46
+ }
47
+ else {
48
+ (0, utils_1.log)(this.config, 'Using legacy folder structure for taxonomies', 'info');
49
+ await this.setupTaxonomiesLegacy(taxonomies);
47
50
  }
48
51
  if (this.taxonomiesMapper !== undefined && !(0, isEmpty_1.default)(this.taxonomiesMapper)) {
49
52
  utils_1.fsUtil.writeFile(this.taxSuccessPath, this.taxonomiesMapper);
@@ -61,18 +64,148 @@ class TaxonomiesImportSetup {
61
64
  (0, utils_1.log)(this.config, `Error generating taxonomies mapper: ${error.message}`, 'error');
62
65
  }
63
66
  }
67
+ /**
68
+ * Setup taxonomies using legacy format (root-level taxonomy files)
69
+ */
70
+ async setupTaxonomiesLegacy(taxonomies) {
71
+ for (const taxonomy of Object.values(taxonomies)) {
72
+ let targetTaxonomy = await this.getTaxonomies(taxonomy);
73
+ if (!targetTaxonomy) {
74
+ (0, utils_1.log)(this.config, `Taxonomies with uid '${taxonomy.uid}' not found in the stack!`, 'info');
75
+ continue;
76
+ }
77
+ targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
78
+ this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
79
+ const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy);
80
+ if (Array.isArray(terms) && terms.length > 0) {
81
+ (0, utils_1.log)(this.config, `Terms found for taxonomy '${taxonomy.uid}', processing...`, 'info');
82
+ const sanitizedTerms = this.sanitizeTermsAttribs(terms);
83
+ this.termsMapper[taxonomy.uid] = sanitizedTerms;
84
+ }
85
+ else {
86
+ (0, utils_1.log)(this.config, `No terms found for taxonomy '${taxonomy.uid}', skipping...`, 'info');
87
+ }
88
+ }
89
+ }
90
+ /**
91
+ * Setup taxonomies using locale-based format (taxonomies organized by locale)
92
+ * For locale-based structure, we query the target stack for each taxonomy+locale combination
93
+ */
94
+ async setupTaxonomiesByLocale(taxonomies) {
95
+ const locales = this.loadAvailableLocales();
96
+ for (const localeCode of Object.keys(locales)) {
97
+ (0, utils_1.log)(this.config, `Processing taxonomies for locale: ${localeCode}`, 'info');
98
+ for (const taxonomy of Object.values(taxonomies)) {
99
+ // Query target stack for this taxonomy in this locale
100
+ let targetTaxonomy = await this.getTaxonomies(taxonomy, localeCode);
101
+ if (!targetTaxonomy) {
102
+ (0, utils_1.log)(this.config, `Taxonomy '${taxonomy.uid}' not found in target stack for locale: ${localeCode}`, 'info');
103
+ continue;
104
+ }
105
+ targetTaxonomy = this.sanitizeTaxonomyAttribs(targetTaxonomy);
106
+ // Store with composite key: taxonomyUID_locale
107
+ // const mapperKey = `${taxonomy.uid}_${localeCode}`; // TODO: Unsure about this required or not
108
+ this.taxonomiesMapper[taxonomy.uid] = targetTaxonomy;
109
+ const terms = await this.getAllTermsOfTaxonomy(targetTaxonomy, localeCode);
110
+ if (Array.isArray(terms) && terms.length > 0) {
111
+ (0, utils_1.log)(this.config, `Terms found for taxonomy '${taxonomy.uid} for locale: ${localeCode}', processing...`, 'info');
112
+ const sanitizedTerms = this.sanitizeTermsAttribs(terms);
113
+ this.termsMapper[taxonomy.uid] = sanitizedTerms;
114
+ }
115
+ else {
116
+ (0, utils_1.log)(this.config, `No terms found for taxonomy '${taxonomy.uid} for locale: ${localeCode}', skipping...`, 'info');
117
+ }
118
+ }
119
+ }
120
+ }
121
+ /**
122
+ * Detect if locale-based folder structure exists
123
+ * @returns {boolean} true if locale-based structure detected, false otherwise
124
+ */
125
+ detectLocaleBasedStructure() {
126
+ const masterLocaleCode = this.getMasterLocaleCode();
127
+ const masterLocaleFolder = (0, path_1.join)(this.taxonomiesFolderPath, masterLocaleCode);
128
+ // Check if master locale folder exists (indicates new locale-based structure)
129
+ if (!utils_1.fileHelper.fileExistsSync(masterLocaleFolder)) {
130
+ (0, utils_1.log)(this.config, 'No locale-based folder structure detected', 'info');
131
+ return false;
132
+ }
133
+ (0, utils_1.log)(this.config, 'Locale-based folder structure detected', 'info');
134
+ return true;
135
+ }
136
+ /**
137
+ * Get the master locale code
138
+ * First tries to read from master-locale.json, then falls back to config, then 'en-us'
139
+ * @returns {string} The master locale code
140
+ */
141
+ getMasterLocaleCode() {
142
+ var _a;
143
+ // Try to read from master-locale.json file
144
+ if (utils_1.fileHelper.fileExistsSync(this.masterLocaleFilePath)) {
145
+ try {
146
+ const masterLocaleData = utils_1.fsUtil.readFile(this.masterLocaleFilePath, true);
147
+ // The file contains an object with UID as key, extract the code
148
+ const firstLocale = Object.values(masterLocaleData)[0];
149
+ if (firstLocale === null || firstLocale === void 0 ? void 0 : firstLocale.code) {
150
+ (0, utils_1.log)(this.config, `Master locale loaded from file: ${firstLocale.code}`, 'info');
151
+ return firstLocale.code;
152
+ }
153
+ }
154
+ catch (error) {
155
+ (0, utils_1.log)(this.config, 'Error reading master-locale.json, using fallback', 'warn');
156
+ }
157
+ }
158
+ // Fallback to config or default
159
+ const fallbackCode = ((_a = this.config.master_locale) === null || _a === void 0 ? void 0 : _a.code) || 'en-us';
160
+ (0, utils_1.log)(this.config, `Using fallback master locale: ${fallbackCode}`, 'info');
161
+ return fallbackCode;
162
+ }
163
+ /**
164
+ * Load available locales from locales file
165
+ * @returns {Record<string, string>} Map of locale codes
166
+ */
167
+ loadAvailableLocales() {
168
+ const locales = {};
169
+ // First, get the master locale
170
+ const masterLocaleCode = this.getMasterLocaleCode();
171
+ locales[masterLocaleCode] = masterLocaleCode;
172
+ // Then load additional locales from locales.json if it exists
173
+ if (!utils_1.fileHelper.fileExistsSync(this.localesFilePath)) {
174
+ (0, utils_1.log)(this.config, 'No locales file found, using only master locale', 'info');
175
+ return locales;
176
+ }
177
+ try {
178
+ const localesData = utils_1.fsUtil.readFile(this.localesFilePath, true);
179
+ for (const [uid, locale] of Object.entries(localesData)) {
180
+ if (locale === null || locale === void 0 ? void 0 : locale.code) {
181
+ locales[locale.code] = locale.code;
182
+ }
183
+ }
184
+ (0, utils_1.log)(this.config, `Loaded ${Object.keys(locales).length} locales (1 master + ${Object.keys(locales).length - 1} additional)`, 'info');
185
+ return locales;
186
+ }
187
+ catch (error) {
188
+ (0, utils_1.log)(this.config, 'Error loading locales file, using only master locale', 'error');
189
+ return locales;
190
+ }
191
+ }
64
192
  /**
65
193
  * Retrieves the taxonomies based on the provided taxonomy UID.
66
194
  *
67
195
  * @param taxonomy - The UID of the taxonomy to retrieve.
196
+ * @param locale - Optional locale code to query taxonomy in specific locale
68
197
  * @returns A promise that resolves to the retrieved taxonomies.
69
198
  */
70
- async getTaxonomies(taxonomy) {
199
+ async getTaxonomies(taxonomy, locale) {
200
+ const query = {};
201
+ if (locale) {
202
+ query.locale = locale;
203
+ }
71
204
  return await this.stackAPIClient
72
205
  .taxonomy(taxonomy.uid)
73
- .fetch()
206
+ .fetch(query)
74
207
  .then((data) => data)
75
- .catch((err) => this.handleTaxonomyErrorMsg(err));
208
+ .catch((err) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
76
209
  }
77
210
  /**
78
211
  * Sanitizes the attributes of a taxonomy object.
@@ -87,19 +220,21 @@ class TaxonomiesImportSetup {
87
220
  * Retrieves all terms of a taxonomy.
88
221
  *
89
222
  * @param taxonomy - The taxonomy object.
223
+ * @param locale - Optional locale code to query terms in specific locale
90
224
  * @param skip - The number of terms to skip (default: 0).
91
225
  * @param terms - An array to store the retrieved terms (default: []).
92
226
  * @returns A promise that resolves to an array of terms.
93
227
  */
94
- async getAllTermsOfTaxonomy(taxonomy, skip = 0, terms = []) {
228
+ async getAllTermsOfTaxonomy(taxonomy, locale, skip = 0, terms = []) {
95
229
  const queryParams = {
96
230
  include_count: true,
97
231
  limit: 100,
98
232
  skip,
233
+ depth: 0,
99
234
  };
100
- if (skip >= 0)
101
- queryParams['skip'] = skip || 0;
102
- queryParams['depth'] = 0;
235
+ if (locale) {
236
+ queryParams.locale = locale;
237
+ }
103
238
  await this.stackAPIClient
104
239
  .taxonomy(taxonomy.uid)
105
240
  .terms()
@@ -108,10 +243,10 @@ class TaxonomiesImportSetup {
108
243
  .then((data) => {
109
244
  terms = terms.concat(data.items);
110
245
  if (data.count >= skip + queryParams.limit) {
111
- return this.getAllTermsOfTaxonomy(taxonomy, skip + 100, terms);
246
+ return this.getAllTermsOfTaxonomy(taxonomy, locale, skip + 100, terms);
112
247
  }
113
248
  })
114
- .catch((err) => this.handleTaxonomyErrorMsg(err));
249
+ .catch((err) => this.handleTaxonomyErrorMsg(err, taxonomy.uid, locale));
115
250
  return terms;
116
251
  }
117
252
  /**
@@ -126,14 +261,16 @@ class TaxonomiesImportSetup {
126
261
  }
127
262
  return terms;
128
263
  }
129
- handleTaxonomyErrorMsg(err) {
264
+ handleTaxonomyErrorMsg(err, taxonomyUid, locale) {
130
265
  var _a, _b;
266
+ const context = locale ? ` for locale: ${locale}` : '';
267
+ const taxInfo = taxonomyUid ? ` (${taxonomyUid}${context})` : '';
131
268
  if ((err === null || err === void 0 ? void 0 : err.errorMessage) || (err === null || err === void 0 ? void 0 : err.message)) {
132
269
  const errorMsg = (err === null || err === void 0 ? void 0 : err.errorMessage) || ((_a = err === null || err === void 0 ? void 0 : err.errors) === null || _a === void 0 ? void 0 : _a.taxonomy) || ((_b = err === null || err === void 0 ? void 0 : err.errors) === null || _b === void 0 ? void 0 : _b.term) || (err === null || err === void 0 ? void 0 : err.message);
133
- (0, utils_1.log)(this.config, errorMsg, 'error');
270
+ (0, utils_1.log)(this.config, `${errorMsg}${taxInfo}`, 'error');
134
271
  }
135
272
  else {
136
- (0, utils_1.log)(this.config, 'Error fetching taxonomy data!', 'error');
273
+ (0, utils_1.log)(this.config, `Error fetching taxonomy data${taxInfo}!`, 'error');
137
274
  (0, utils_1.log)(this.config, err, 'error');
138
275
  }
139
276
  }
@@ -13,6 +13,11 @@ export default interface DefaultConfig {
13
13
  fileName: string;
14
14
  dependencies?: Modules[];
15
15
  };
16
+ locales: {
17
+ dirName: string;
18
+ fileName: string;
19
+ dependencies?: Modules[];
20
+ };
16
21
  extensions: {
17
22
  dirName: string;
18
23
  fileName: string;
@@ -112,4 +112,5 @@ export type TaxonomyQueryParams = {
112
112
  limit: number;
113
113
  skip: number;
114
114
  depth?: number;
115
+ locale?: string;
115
116
  };
@@ -88,5 +88,5 @@
88
88
  ]
89
89
  }
90
90
  },
91
- "version": "1.6.0"
91
+ "version": "1.7.0"
92
92
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@contentstack/cli-cm-import-setup",
3
3
  "description": "Contentstack CLI plugin to setup the mappers and configurations for the import command",
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
5
5
  "author": "Contentstack",
6
6
  "bugs": "https://github.com/contentstack/cli/issues",
7
7
  "dependencies": {
8
8
  "@contentstack/cli-command": "~1.6.1",
9
- "@contentstack/cli-utilities": "~1.14.1",
9
+ "@contentstack/cli-utilities": "~1.14.4",
10
10
  "@oclif/core": "^4.3.0",
11
11
  "big-json": "^3.2.0",
12
12
  "chalk": "^4.1.2",
@@ -24,7 +24,7 @@
24
24
  "@types/mkdirp": "^1.0.2",
25
25
  "@types/mocha": "^8.2.3",
26
26
  "@types/node": "^14.18.63",
27
- "@types/proxyquire": "^1.3.31",
27
+ "@types/rewire": "^2.5.30",
28
28
  "@types/tar": "^6.1.13",
29
29
  "@types/uuid": "^9.0.8",
30
30
  "@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -34,7 +34,7 @@
34
34
  "mocha": "^10.8.2",
35
35
  "nyc": "^15.1.0",
36
36
  "oclif": "^4.17.46",
37
- "proxyquire": "^2.1.3",
37
+ "rewire": "^9.0.1",
38
38
  "ts-node": "^10.9.2",
39
39
  "tsx": "^4.20.3",
40
40
  "typescript": "^4.9.5"