@contentstorage/core 1.2.1 → 2.1.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.
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import axios from 'axios';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import { loadConfig } from '../core/config-loader.js';
7
+ import { CONTENTSTORAGE_CONFIG } from '../utils/constants.js';
8
+ export async function pullContent() {
9
+ console.log(chalk.blue('Starting content pull...'));
10
+ const args = process.argv.slice(2);
11
+ const cliConfig = {};
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i];
14
+ if (arg.startsWith('--')) {
15
+ const key = arg.substring(2);
16
+ const value = args[i + 1];
17
+ if (key === 'pending-changes') {
18
+ cliConfig.pendingChanges = true;
19
+ }
20
+ else if (value && !value.startsWith('--')) {
21
+ if (key === 'lang') {
22
+ cliConfig.languageCodes = [value.toUpperCase()];
23
+ }
24
+ else if (key === 'content-key') {
25
+ cliConfig.contentKey = value;
26
+ }
27
+ else if (key === 'content-dir') {
28
+ cliConfig.contentDir = value;
29
+ }
30
+ // Skip the value in the next iteration
31
+ i++;
32
+ }
33
+ }
34
+ }
35
+ let fileConfig = {};
36
+ try {
37
+ fileConfig = await loadConfig();
38
+ }
39
+ catch {
40
+ console.log(chalk.yellow('Could not load a configuration file. Proceeding with CLI arguments.'));
41
+ }
42
+ const config = { ...fileConfig, ...cliConfig };
43
+ // Validate required fields
44
+ if (!config.contentKey) {
45
+ console.error(chalk.red('Error: Configuration is missing the required "contentKey" property.'));
46
+ process.exit(1);
47
+ }
48
+ if (!config.contentDir) {
49
+ console.error(chalk.red('Error: Configuration is missing the required "contentDir" property.'));
50
+ process.exit(1);
51
+ }
52
+ console.log(chalk.blue(`Content key: ${config.contentKey}`));
53
+ console.log(chalk.blue(`Saving content to: ${config.contentDir}`));
54
+ try {
55
+ // Validate languageCodes array
56
+ if (!Array.isArray(config.languageCodes)) {
57
+ console.log(chalk.red(`Expected array from config.languageCodes, but received type ${typeof config.languageCodes}. Cannot pull files.`));
58
+ return; // Exit if languageCodes is not an array
59
+ }
60
+ if (config.languageCodes.length === 0) {
61
+ console.log(chalk.yellow('config.languageCodes array is empty. No files to pull.'));
62
+ return; // Exit if languageCodes array is empty
63
+ }
64
+ // Ensure the output directory exists (create it once before the loop)
65
+ await fs.mkdir(config.contentDir, { recursive: true });
66
+ // Process each language code
67
+ for (const languageCode of config.languageCodes) {
68
+ let fileUrl;
69
+ const requestConfig = {};
70
+ if (config.pendingChanges) {
71
+ fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageCode}`;
72
+ requestConfig.headers = {
73
+ 'X-Content-Key': config.contentKey,
74
+ };
75
+ }
76
+ else {
77
+ fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${languageCode}.json`;
78
+ }
79
+ const filename = `${languageCode}.json`;
80
+ const outputPath = path.join(config.contentDir, filename);
81
+ console.log(chalk.blue(`\nProcessing language: ${languageCode}`));
82
+ console.log(chalk.blue(`Using following contentKey to fetch json: ${config.contentKey}`));
83
+ try {
84
+ // Fetch data for the current language
85
+ const response = await axios.get(fileUrl, requestConfig);
86
+ let jsonData = response.data;
87
+ // Handle API response structure - only for pending changes API
88
+ if (config.pendingChanges &&
89
+ jsonData &&
90
+ typeof jsonData === 'object' &&
91
+ 'data' in jsonData) {
92
+ jsonData = jsonData.data;
93
+ }
94
+ // Basic check for data existence, although axios usually throws for non-2xx responses
95
+ if (jsonData === undefined || jsonData === null) {
96
+ throw new Error(`No data received from ${fileUrl} for language ${languageCode}.`);
97
+ }
98
+ // Validate that jsonData is a single, non-null JSON object (not an array)
99
+ // This check mirrors the original code's expectation for the content of a JSON file.
100
+ if (typeof jsonData !== 'object' ||
101
+ Array.isArray(jsonData) /* jsonData === null is already covered */) {
102
+ throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageCode}, but received type ${Array.isArray(jsonData) ? 'array' : typeof jsonData}. Cannot save the file.`);
103
+ }
104
+ console.log(chalk.green(`Received JSON for ${languageCode}. Saving to ${outputPath}`));
105
+ await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
106
+ console.log(chalk.green(`Successfully saved ${outputPath}`));
107
+ }
108
+ catch (error) {
109
+ // Catch errors related to fetching or saving a single language file
110
+ console.error(chalk.red(`\nError processing language ${languageCode} from ${fileUrl}:`));
111
+ if (axios.isAxiosError(error)) {
112
+ console.error(chalk.red(` Status: ${error.response?.status}`));
113
+ console.error(chalk.red(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`));
114
+ console.error(chalk.red(` Message: ${error.message}`)); // Axios error message
115
+ }
116
+ else {
117
+ // For non-Axios errors (e.g., manually thrown errors, fs errors)
118
+ console.error(chalk.red(` Error: ${error.message}`));
119
+ }
120
+ // Re-throw the error to be caught by the outer try-catch block,
121
+ // which will then call process.exit(1), maintaining original exit behavior on error.
122
+ throw error;
123
+ }
124
+ }
125
+ console.log(chalk.green('\nAll content successfully pulled and saved.'));
126
+ }
127
+ catch {
128
+ // Outer catch for setup errors (like loadConfig) or re-thrown errors from the loop
129
+ // The specific error details for a file operation would have been logged by the inner catch.
130
+ // This block provides a general failure message and ensures the process exits with an error code.
131
+ console.error(chalk.red('\n-----------------------------------------------------'));
132
+ console.error(chalk.red('Content pull failed due to an error. See details above.'));
133
+ // error.message from the re-thrown error will be implicitly part of the error object logged by some environments,
134
+ // or if you add console.error(error) here.
135
+ // The original code logged error.message at this level:
136
+ // if (error.message) console.error(chalk.red(`Underlying error: ${error.message}`));
137
+ console.error(chalk.red('-----------------------------------------------------'));
138
+ process.exit(1); // Exit with error code
139
+ }
140
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Main stats command function
3
+ */
4
+ export declare function showStats(): Promise<void>;
@@ -0,0 +1,268 @@
1
+ import axios from 'axios';
2
+ import chalk from 'chalk';
3
+ import { promises as fs } from 'fs';
4
+ import path from 'path';
5
+ import { loadConfig } from '../core/config-loader.js';
6
+ import { flattenJson } from '../utils/flatten-json.js';
7
+ import { CONTENTSTORAGE_CONFIG } from '../utils/constants.js';
8
+ /**
9
+ * Check if a value is considered untranslated
10
+ */
11
+ function isUntranslated(value) {
12
+ return value === '' || value === null || value === undefined;
13
+ }
14
+ /**
15
+ * Load content for a specific language, trying local files first, then API
16
+ */
17
+ async function loadLanguageContent(languageCode, config) {
18
+ // Try local file first
19
+ try {
20
+ const filePath = path.join(config.contentDir, `${languageCode}.json`);
21
+ const content = await fs.readFile(filePath, 'utf-8');
22
+ return JSON.parse(content);
23
+ }
24
+ catch {
25
+ // Fallback to API
26
+ console.log(chalk.dim(`Local file not found for ${languageCode}, fetching from API...`));
27
+ let fileUrl;
28
+ const requestConfig = {
29
+ timeout: 30000,
30
+ };
31
+ if (config.pendingChanges) {
32
+ fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageCode}`;
33
+ requestConfig.headers = {
34
+ 'X-Content-Key': config.contentKey,
35
+ };
36
+ }
37
+ else {
38
+ fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${languageCode}.json`;
39
+ }
40
+ try {
41
+ const response = await axios.get(fileUrl, requestConfig);
42
+ let jsonData = response.data;
43
+ // Unwrap pending changes response
44
+ if (config.pendingChanges &&
45
+ jsonData &&
46
+ typeof jsonData === 'object' &&
47
+ 'data' in jsonData) {
48
+ jsonData = jsonData.data;
49
+ }
50
+ return jsonData;
51
+ }
52
+ catch (apiError) {
53
+ console.error(chalk.red(`Failed to load content for ${languageCode}:`));
54
+ if (apiError.response) {
55
+ console.error(chalk.red(` Status: ${apiError.response.status}`));
56
+ }
57
+ throw apiError;
58
+ }
59
+ }
60
+ }
61
+ /**
62
+ * Analyze a single language against a reference language
63
+ */
64
+ function analyzeLanguage(languageCode, content, referenceContent) {
65
+ const flatContent = flattenJson(content);
66
+ const flatReference = flattenJson(referenceContent);
67
+ const untranslatedItems = [];
68
+ // Get all keys from reference
69
+ const allKeys = Object.keys(flatReference);
70
+ const totalItems = allKeys.length;
71
+ // Check each key
72
+ for (const key of allKeys) {
73
+ // Check if key is missing in target language
74
+ if (!(key in flatContent)) {
75
+ untranslatedItems.push({ key, reason: 'missing' });
76
+ continue;
77
+ }
78
+ // Check if value is empty
79
+ if (isUntranslated(flatContent[key])) {
80
+ untranslatedItems.push({ key, reason: 'empty' });
81
+ }
82
+ }
83
+ const untranslatedCount = untranslatedItems.length;
84
+ const translatedCount = totalItems - untranslatedCount;
85
+ const completionPercentage = totalItems > 0 ? (translatedCount / totalItems) * 100 : 100;
86
+ return {
87
+ languageCode,
88
+ total: totalItems,
89
+ translated: translatedCount,
90
+ untranslated: untranslatedCount,
91
+ completionPercentage,
92
+ untranslatedItems,
93
+ };
94
+ }
95
+ /**
96
+ * Display statistics in a formatted table
97
+ */
98
+ function displayStats(result) {
99
+ console.log(chalk.bold('\n📊 Translation Statistics'));
100
+ console.log(chalk.dim('═'.repeat(70)));
101
+ // Reference language info
102
+ console.log(chalk.cyan(`\nReference Language: ${chalk.bold(result.referenceLanguage)} (baseline for comparison)`));
103
+ console.log(chalk.cyan(`Total unique content items: ${chalk.bold(result.totalItems)}`));
104
+ // Language statistics table header
105
+ console.log(chalk.bold('\n📋 Language Statistics:'));
106
+ console.log(chalk.dim('─'.repeat(70)));
107
+ // Table header
108
+ const headerFormat = (str, width) => str.padEnd(width, ' ');
109
+ console.log(chalk.bold(headerFormat('Language', 12) +
110
+ headerFormat('Total', 10) +
111
+ headerFormat('Translated', 13) +
112
+ headerFormat('Untranslated', 15) +
113
+ 'Complete'));
114
+ console.log(chalk.dim('─'.repeat(70)));
115
+ // Language rows
116
+ for (const stats of result.languageStats) {
117
+ const percentageColor = stats.completionPercentage === 100
118
+ ? chalk.green
119
+ : stats.completionPercentage >= 80
120
+ ? chalk.yellow
121
+ : chalk.red;
122
+ const percentage = percentageColor(stats.completionPercentage.toFixed(1) + '%');
123
+ console.log(chalk.white(headerFormat(stats.languageCode, 12)) +
124
+ chalk.white(headerFormat(stats.total.toString(), 10)) +
125
+ chalk.white(headerFormat(stats.translated.toString(), 13)) +
126
+ (stats.untranslated > 0
127
+ ? chalk.red(headerFormat(stats.untranslated.toString(), 15))
128
+ : chalk.green(headerFormat(stats.untranslated.toString(), 15))) +
129
+ percentage);
130
+ }
131
+ console.log(chalk.dim('─'.repeat(70)));
132
+ // Untranslated items details
133
+ const languagesWithIssues = result.languageStats.filter((s) => s.untranslated > 0);
134
+ if (languagesWithIssues.length > 0) {
135
+ console.log(chalk.bold('\n⚠️ Untranslated Items by Language:'));
136
+ console.log(chalk.dim('─'.repeat(70)));
137
+ for (const stats of languagesWithIssues) {
138
+ console.log(chalk.yellow(`\n${stats.languageCode} (${stats.untranslated} ${stats.untranslated === 1 ? 'item' : 'items'}):`));
139
+ // Group by reason
140
+ const emptyItems = stats.untranslatedItems
141
+ .filter((item) => item.reason === 'empty')
142
+ .map((item) => item.key);
143
+ const missingItems = stats.untranslatedItems
144
+ .filter((item) => item.reason === 'missing')
145
+ .map((item) => item.key);
146
+ if (emptyItems.length > 0) {
147
+ console.log(chalk.dim(' Empty values:'));
148
+ emptyItems.forEach((key) => {
149
+ console.log(chalk.red(` • ${key}`));
150
+ });
151
+ }
152
+ if (missingItems.length > 0) {
153
+ console.log(chalk.dim(' Missing keys:'));
154
+ missingItems.forEach((key) => {
155
+ console.log(chalk.red(` • ${key}`));
156
+ });
157
+ }
158
+ }
159
+ }
160
+ else {
161
+ console.log(chalk.green('\n✅ All languages are fully translated!'));
162
+ }
163
+ // Overall completion
164
+ console.log(chalk.bold('\n📈 Overall Summary:'));
165
+ console.log(chalk.dim('─'.repeat(70)));
166
+ const overallColor = result.overallCompletion === 100
167
+ ? chalk.green
168
+ : result.overallCompletion >= 80
169
+ ? chalk.yellow
170
+ : chalk.red;
171
+ console.log(overallColor(`Overall Completion: ${chalk.bold(result.overallCompletion.toFixed(1) + '%')}`));
172
+ console.log('');
173
+ }
174
+ /**
175
+ * Main stats command function
176
+ */
177
+ export async function showStats() {
178
+ try {
179
+ console.log(chalk.blue('Loading configuration...'));
180
+ // Parse CLI arguments
181
+ const args = process.argv.slice(2);
182
+ const cliConfig = {};
183
+ for (let i = 0; i < args.length; i++) {
184
+ const arg = args[i];
185
+ if (arg.startsWith('--')) {
186
+ const key = arg.substring(2);
187
+ if (key === 'pending-changes') {
188
+ cliConfig.pendingChanges = true;
189
+ }
190
+ else {
191
+ const value = args[i + 1];
192
+ if (value && !value.startsWith('--')) {
193
+ if (key === 'content-key') {
194
+ cliConfig.contentKey = value;
195
+ }
196
+ else if (key === 'content-dir') {
197
+ cliConfig.contentDir = value;
198
+ }
199
+ i++; // Skip the value in next iteration
200
+ }
201
+ }
202
+ }
203
+ }
204
+ // Load config from file
205
+ let fileConfig = {};
206
+ try {
207
+ fileConfig = await loadConfig();
208
+ }
209
+ catch {
210
+ console.log(chalk.yellow('⚠️ Could not load a configuration file, using CLI arguments only'));
211
+ }
212
+ // Merge configurations (CLI args override file config)
213
+ const config = { ...fileConfig, ...cliConfig };
214
+ // Validate required fields
215
+ if (!config.contentKey) {
216
+ console.error(chalk.red('\n❌ Error: Content key is required. Provide it via config file or --content-key argument.'));
217
+ process.exit(1);
218
+ }
219
+ if (!config.languageCodes || config.languageCodes.length === 0) {
220
+ console.error(chalk.red('\n❌ Error: At least one language code is required in configuration.'));
221
+ process.exit(1);
222
+ }
223
+ // Set defaults
224
+ if (!config.contentDir) {
225
+ config.contentDir = 'src/content/json';
226
+ }
227
+ const fullConfig = config;
228
+ console.log(chalk.blue(`Analyzing ${fullConfig.languageCodes.length} language(s)...`));
229
+ // Load content for all languages
230
+ const languageContents = {};
231
+ for (const languageCode of fullConfig.languageCodes) {
232
+ try {
233
+ languageContents[languageCode] = await loadLanguageContent(languageCode, fullConfig);
234
+ }
235
+ catch {
236
+ console.error(chalk.red(`\n❌ Failed to load content for ${languageCode}`));
237
+ process.exit(1);
238
+ }
239
+ }
240
+ console.log(chalk.green('✓ All content loaded successfully\n'));
241
+ // Use first language as reference
242
+ const referenceLanguage = fullConfig.languageCodes[0];
243
+ const referenceContent = languageContents[referenceLanguage];
244
+ // Analyze each language
245
+ const languageStats = [];
246
+ for (const languageCode of fullConfig.languageCodes) {
247
+ const stats = analyzeLanguage(languageCode, languageContents[languageCode], referenceContent);
248
+ languageStats.push(stats);
249
+ }
250
+ // Calculate overall completion
251
+ const totalTranslated = languageStats.reduce((sum, s) => sum + s.translated, 0);
252
+ const totalPossible = languageStats.reduce((sum, s) => sum + s.total, 0);
253
+ const overallCompletion = totalPossible > 0 ? (totalTranslated / totalPossible) * 100 : 100;
254
+ const result = {
255
+ referenceLanguage,
256
+ totalItems: Object.keys(flattenJson(referenceContent)).length,
257
+ languageStats,
258
+ overallCompletion,
259
+ };
260
+ // Display results
261
+ displayStats(result);
262
+ }
263
+ catch (error) {
264
+ console.error(chalk.red('\n❌ An error occurred:'));
265
+ console.error(chalk.red(error.message || error));
266
+ process.exit(1);
267
+ }
268
+ }
@@ -0,0 +1,2 @@
1
+ import { AppConfig } from '../types.js';
2
+ export declare function loadConfig(): Promise<AppConfig>;
@@ -0,0 +1,42 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import chalk from 'chalk';
4
+ const DEFAULT_CONFIG = {
5
+ languageCodes: [],
6
+ contentDir: path.join('src', 'content', 'json'),
7
+ typesOutputFile: path.join('src', 'content', 'content-types.ts'),
8
+ };
9
+ export async function loadConfig() {
10
+ const configPath = path.resolve(process.cwd(), 'contentstorage.config.js'); // Look in user's current working dir
11
+ let userConfig = {};
12
+ if (fs.existsSync(configPath)) {
13
+ try {
14
+ // Use require for JS config file
15
+ const loadedModule = await import(configPath);
16
+ userConfig = loadedModule.default || loadedModule;
17
+ console.log(chalk.blue('Loaded config', JSON.stringify(userConfig)));
18
+ console.log(chalk.blue(`Loaded configuration from ${configPath}`));
19
+ }
20
+ catch (error) {
21
+ console.error(chalk.red(`Error loading configuration from ${configPath}:`, error));
22
+ // Decide if you want to proceed with defaults or exit
23
+ // For now, we'll proceed with defaults but warn
24
+ console.warn(chalk.yellow('Proceeding with default configuration.'));
25
+ userConfig = {}; // Reset in case of partial load failure
26
+ }
27
+ }
28
+ else {
29
+ console.log(chalk.blue('No content.config.js found. Continuing.'));
30
+ }
31
+ const mergedConfig = {
32
+ ...DEFAULT_CONFIG,
33
+ ...userConfig,
34
+ };
35
+ const finalConfig = {
36
+ languageCodes: mergedConfig.languageCodes || [],
37
+ contentKey: mergedConfig.contentKey || '',
38
+ contentDir: path.resolve(process.cwd(), mergedConfig.contentDir),
39
+ typesOutputFile: path.resolve(process.cwd(), mergedConfig.typesOutputFile),
40
+ };
41
+ return finalConfig;
42
+ }
@@ -1,7 +1,7 @@
1
1
  // @ts-expect-error
2
- import * as pluralize from "pluralize";
3
- import { TypeGroup } from './model.js';
4
- import { findTypeById, getTypeDescriptionGroup, isHash, parseKeyMetaData } from './util.js';
2
+ import * as pluralize from 'pluralize';
3
+ import { TypeGroup, } from './model.js';
4
+ import { findTypeById, getTypeDescriptionGroup, isHash, parseKeyMetaData, } from './util.js';
5
5
  function getName({ rootTypeId, types }, keyName, names, isInsideArray
6
6
  // @ts-ignore
7
7
  ) {
@@ -34,7 +34,7 @@ function getName({ rootTypeId, types }, keyName, names, isInsideArray
34
34
  };
35
35
  }
36
36
  }
37
- export function getNames(typeStructure, rootName = "RootObject") {
37
+ export function getNames(typeStructure, rootName = 'RootObject') {
38
38
  return getName(typeStructure, rootName, [], false).names.reverse();
39
39
  }
40
40
  function getNameById(id, keyName, isInsideArray, types, nameMap) {
@@ -47,7 +47,9 @@ function getNameById(id, keyName, isInsideArray, types, nameMap) {
47
47
  let name;
48
48
  switch (group) {
49
49
  case TypeGroup.Array:
50
- name = typeDesc.isUnion ? getArrayName(typeDesc, types, nameMap) : formatArrayName(typeDesc, types, nameMap);
50
+ name = typeDesc.isUnion
51
+ ? getArrayName(typeDesc, types, nameMap)
52
+ : formatArrayName(typeDesc, types, nameMap);
51
53
  break;
52
54
  case TypeGroup.Object:
53
55
  /**
@@ -72,9 +74,9 @@ function getNameById(id, keyName, isInsideArray, types, nameMap) {
72
74
  function pascalCase(name) {
73
75
  return name
74
76
  .split(/\s+/g)
75
- .filter((_) => _ !== "")
77
+ .filter((_) => _ !== '')
76
78
  .map(capitalize)
77
- .reduce((a, b) => a + b, "");
79
+ .reduce((a, b) => a + b, '');
78
80
  }
79
81
  function capitalize(name) {
80
82
  return name.charAt(0).toUpperCase() + name.slice(1);
@@ -84,7 +86,7 @@ function normalizeInvalidTypeName(name) {
84
86
  return name;
85
87
  }
86
88
  else {
87
- const noSymbolsName = name.replace(/[^a-zA-Z0-9]/g, "");
89
+ const noSymbolsName = name.replace(/[^a-zA-Z0-9]/g, '');
88
90
  const startsWithWordCharacter = /^[a-zA-Z]/.test(noSymbolsName);
89
91
  return startsWithWordCharacter ? noSymbolsName : `_${noSymbolsName}`;
90
92
  }
@@ -100,9 +102,10 @@ function uniqueByIncrement(name, names) {
100
102
  function getArrayName(typeDesc, types, nameMap) {
101
103
  // @ts-ignore
102
104
  if (typeDesc.arrayOfTypes.length === 0) {
103
- return "any";
105
+ return 'any';
104
106
  }
105
- else { // @ts-ignore
107
+ else {
108
+ // @ts-ignore
106
109
  if (typeDesc.arrayOfTypes.length === 1) {
107
110
  // @ts-ignore
108
111
  const [idOrPrimitive] = typeDesc.arrayOfTypes;
@@ -125,7 +128,7 @@ function unionToString(typeDesc, types, nameMap) {
125
128
  return typeDesc.arrayOfTypes.reduce((acc, type, i) => {
126
129
  const readableTypeName = convertToReadableType(type, types, nameMap);
127
130
  return i === 0 ? readableTypeName : `${acc} | ${readableTypeName}`;
128
- }, "");
131
+ }, '');
129
132
  }
130
133
  function formatArrayName(typeDesc, types, nameMap) {
131
134
  // @ts-ignore
@@ -6,30 +6,30 @@ export function onlyUnique(value, index, self) {
6
6
  return self.indexOf(value) === index;
7
7
  }
8
8
  export function isArray(x) {
9
- return Object.prototype.toString.call(x) === "[object Array]";
9
+ return Object.prototype.toString.call(x) === '[object Array]';
10
10
  }
11
11
  export function isNonArrayUnion(typeName) {
12
12
  const arrayUnionRegex = /^\(.*\)\[\]$/;
13
- return typeName.includes(" | ") && !arrayUnionRegex.test(typeName);
13
+ return typeName.includes(' | ') && !arrayUnionRegex.test(typeName);
14
14
  }
15
15
  export function isObject(x) {
16
- return Object.prototype.toString.call(x) === "[object Object]" && x !== null;
16
+ return Object.prototype.toString.call(x) === '[object Object]' && x !== null;
17
17
  }
18
18
  export function isDate(x) {
19
19
  return x instanceof Date;
20
20
  }
21
21
  export function parseKeyMetaData(key) {
22
- const isOptional = key.endsWith("--?");
22
+ const isOptional = key.endsWith('--?');
23
23
  if (isOptional) {
24
24
  return {
25
25
  isOptional,
26
- keyValue: key.slice(0, -3)
26
+ keyValue: key.slice(0, -3),
27
27
  };
28
28
  }
29
29
  else {
30
30
  return {
31
31
  isOptional,
32
- keyValue: key
32
+ keyValue: key,
33
33
  };
34
34
  }
35
35
  }
@@ -45,5 +45,5 @@ export function getTypeDescriptionGroup(desc) {
45
45
  }
46
46
  }
47
47
  export function findTypeById(id, types) {
48
- return types.find(_ => _.id === id);
48
+ return types.find((_) => _.id === id);
49
49
  }
package/dist/types.d.ts CHANGED
@@ -15,27 +15,3 @@ export type LanguageCode = 'SQ' | 'BE' | 'BS' | 'BG' | 'HR' | 'CS' | 'DA' | 'NL'
15
15
  */
16
16
  export interface ContentStructure {
17
17
  }
18
- export type GetTextReturn = {
19
- contentId: string;
20
- text: string;
21
- };
22
- export type GetImageReturn = {
23
- contentId: string;
24
- data: ImageObject;
25
- };
26
- export type GetVariationReturn = {
27
- contentId: string;
28
- text: string;
29
- };
30
- export interface ImageObject {
31
- contentstorage_type: 'image';
32
- url: string;
33
- altText: string;
34
- }
35
- export interface VariationData {
36
- [key: string]: string;
37
- }
38
- export interface VariationObject {
39
- contentstorage_type: 'variation';
40
- data: VariationData;
41
- }
@@ -0,0 +1,4 @@
1
+ export declare const CONTENTSTORAGE_CONFIG: {
2
+ BASE_URL: string;
3
+ API_URL: string;
4
+ };
@@ -0,0 +1,4 @@
1
+ export const CONTENTSTORAGE_CONFIG = {
2
+ BASE_URL: 'https://cdn.contentstorage.app',
3
+ API_URL: 'https://api.contentstorage.app',
4
+ };
@@ -0,0 +1 @@
1
+ export declare function flattenJson(data: any, prefix?: string, result?: Record<string, any>): Record<string, any>;
@@ -0,0 +1,56 @@
1
+ export function flattenJson(data, prefix = '', result = {}) {
2
+ const stopFlatteningIfKeyExists = 'contentstorage_type';
3
+ // Check if the current data is an object that should not be flattened further
4
+ if (typeof data === 'object' &&
5
+ data !== null &&
6
+ !Array.isArray(data) && // Must be an object, not an array
7
+ Object.prototype.hasOwnProperty.call(data, stopFlatteningIfKeyExists)) {
8
+ if (prefix) {
9
+ // If there's a prefix, this object is nested. Assign it directly.
10
+ result[prefix] = data;
11
+ }
12
+ else {
13
+ // This is the root object itself having the 'stopFlatteningIfKeyExists' key.
14
+ // Consistent with how root primitives are handled (result remains empty),
15
+ // we don't add it to the result if there's no prefix. The function's
16
+ // purpose is to flatten *into* key-value pairs. If the root itself
17
+ // is one of these "don't flatten" types, 'result' will remain empty,
18
+ // which is consistent with the original function's behavior for root primitives.
19
+ }
20
+ }
21
+ else if (typeof data === 'object' && data !== null) {
22
+ // It's an object or array that should be processed further
23
+ if (Array.isArray(data)) {
24
+ if (data.length === 0 && prefix) {
25
+ // Handle empty arrays if prefix exists
26
+ result[prefix] = [];
27
+ }
28
+ data.forEach((item, index) => {
29
+ // Recursively call, the check for 'stopFlatteningIfKeyExists' will apply to 'item'
30
+ flattenJson(item, prefix ? `${prefix}.${index}` : `${index}`, result);
31
+ });
32
+ }
33
+ else {
34
+ let isEmptyObject = true;
35
+ for (const key in data) {
36
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
37
+ isEmptyObject = false;
38
+ const newPrefix = prefix ? `${prefix}.${key}` : key;
39
+ // Recursively call, the check for 'stopFlatteningIfKeyExists' will apply to 'data[key]'
40
+ flattenJson(data[key], newPrefix, result);
41
+ }
42
+ }
43
+ if (isEmptyObject && prefix) {
44
+ // Handle empty objects (that were not 'special') if prefix exists
45
+ result[prefix] = {};
46
+ }
47
+ }
48
+ }
49
+ else if (prefix) {
50
+ result[prefix] = data;
51
+ }
52
+ // If the initial data is a primitive and prefix is empty, result remains empty.
53
+ // If the initial data is a 'special' object (contains 'stopFlatteningIfKeyExists')
54
+ // and prefix is empty, result also remains empty based on the logic above.
55
+ return result;
56
+ }