@contentstorage/core 2.1.1 → 2.2.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 +10 -10
- package/dist/commands/cli.js +2 -1
- package/dist/commands/pull.js +6 -1
- package/dist/types.d.ts +1 -0
- package/package.json +2 -2
- package/dist/contentstorage-config.d.ts +0 -4
- package/dist/contentstorage-config.js +0 -4
- package/dist/helpers/defineConfig.d.ts +0 -6
- package/dist/helpers/defineConfig.js +0 -19
- package/dist/helpers/flattenJson.d.ts +0 -1
- package/dist/helpers/flattenJson.js +0 -56
- package/dist/helpers/populateTextWithVariables.d.ts +0 -1
- package/dist/helpers/populateTextWithVariables.js +0 -10
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -62
- package/dist/lib/configLoader.d.ts +0 -2
- package/dist/lib/configLoader.js +0 -42
- package/dist/lib/contentManagement.d.ts +0 -32
- package/dist/lib/contentManagement.js +0 -257
- package/dist/lib/functions/fetchContent.d.ts +0 -7
- package/dist/lib/functions/fetchContent.js +0 -141
- package/dist/scripts/cli.d.ts +0 -2
- package/dist/scripts/cli.js +0 -96
- package/dist/scripts/generate-types.d.ts +0 -2
- package/dist/scripts/generate-types.js +0 -174
- package/dist/scripts/pull-content.d.ts +0 -2
- package/dist/scripts/pull-content.js +0 -137
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# @contentstorage/core
|
|
2
2
|
|
|
3
|
-
> CLI tool for managing translations and generating TypeScript types from
|
|
3
|
+
> CLI tool for managing translations and generating TypeScript types from Contentstorage
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@contentstorage/core)
|
|
6
6
|
[](https://github.com/kaidohussar/contentstorage-core/blob/master/LICENSE)
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Contentstorage Core is a powerful CLI tool for managing translations and content. It pulls content from Contentstorage CDN, generates TypeScript types, and integrates seamlessly with popular i18n libraries.
|
|
11
11
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
|
-
- **Translation Management** - Pull content from
|
|
14
|
+
- **Translation Management** - Pull content from Contentstorage CDN
|
|
15
15
|
- **TypeScript Generation** - Automatic type generation from your content
|
|
16
16
|
- **Translation Statistics** - Analyze translation completeness across languages
|
|
17
17
|
- **Multi-Language Support** - Built-in support for 40+ languages
|
|
@@ -48,7 +48,7 @@ export default {
|
|
|
48
48
|
### 2. Pull Content & Generate Types
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
# Pull content from
|
|
51
|
+
# Pull content from Contentstorage CDN
|
|
52
52
|
npx contentstorage pull
|
|
53
53
|
|
|
54
54
|
# Generate TypeScript type definitions
|
|
@@ -69,7 +69,7 @@ npm install @contentstorage/plugin-react-intl
|
|
|
69
69
|
npx contentstorage-react-intl export
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
**With
|
|
72
|
+
**With Contentstorage SDK (for advanced features like variations and images):**
|
|
73
73
|
```bash
|
|
74
74
|
npm install @contentstorage/sdk
|
|
75
75
|
```
|
|
@@ -78,7 +78,7 @@ npm install @contentstorage/sdk
|
|
|
78
78
|
|
|
79
79
|
### `contentstorage pull`
|
|
80
80
|
|
|
81
|
-
Pull content from
|
|
81
|
+
Pull content from Contentstorage CDN
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
npx contentstorage pull [options]
|
|
@@ -193,13 +193,13 @@ EN, FR, DE, ES, IT, PT, NL, PL, RU, TR, SV, NO, DA, FI, CS, SK, HU, RO, BG, HR,
|
|
|
193
193
|
|
|
194
194
|
### Option 1: Use with i18n Libraries (Recommended for most projects)
|
|
195
195
|
|
|
196
|
-
For standard i18n needs, use
|
|
196
|
+
For standard i18n needs, use Contentstorage CLI with popular i18n libraries:
|
|
197
197
|
|
|
198
198
|
- **[@contentstorage/plugin-i18next](https://www.npmjs.com/package/@contentstorage/plugin-i18next)** - i18next integration
|
|
199
199
|
- **[@contentstorage/plugin-react-intl](https://www.npmjs.com/package/@contentstorage/plugin-react-intl)** - react-intl (FormatJS) integration
|
|
200
200
|
- **[@contentstorage/plugin-vue-i18n](https://www.npmjs.com/package/@contentstorage/plugin-vue-i18n)** - Vue i18n integration
|
|
201
201
|
|
|
202
|
-
### Option 2: Use with
|
|
202
|
+
### Option 2: Use with Contentstorage SDK (Advanced features)
|
|
203
203
|
|
|
204
204
|
If you need advanced features like variations (A/B testing) and image management:
|
|
205
205
|
|
|
@@ -306,7 +306,7 @@ npm run content:sync # Pull and generate in one command
|
|
|
306
306
|
|
|
307
307
|
## SDK Extract
|
|
308
308
|
|
|
309
|
-
The `/sdk-extract` folder contains the
|
|
309
|
+
The `/sdk-extract` folder contains the Contentstorage SDK code ready to be moved to a separate repository. This SDK provides runtime features like:
|
|
310
310
|
- getText/getImage/getVariation functions
|
|
311
311
|
- Content variations (A/B testing)
|
|
312
312
|
- Image management
|
|
@@ -335,4 +335,4 @@ Kaido Hussar - [kaidohus@gmail.com](mailto:kaidohus@gmail.com)
|
|
|
335
335
|
|
|
336
336
|
## Support
|
|
337
337
|
|
|
338
|
-
For issues and questions, please [open an issue](https://github.com/kaidohussar/contentstorage-core/issues) on GitHub.
|
|
338
|
+
For issues and questions, please [open an issue](https://github.com/kaidohussar/contentstorage-core/issues) on GitHub.
|
package/dist/commands/cli.js
CHANGED
|
@@ -13,6 +13,7 @@ const COMMANDS = {
|
|
|
13
13
|
' --content-dir <dir> Directory to save content files',
|
|
14
14
|
' --lang <code> Language code (e.g., EN, FR)',
|
|
15
15
|
' --pending-changes Fetch pending/draft content',
|
|
16
|
+
' --flatten Output flattened key-value pairs',
|
|
16
17
|
],
|
|
17
18
|
},
|
|
18
19
|
'generate-types': {
|
|
@@ -38,7 +39,7 @@ const COMMANDS = {
|
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
function showHelp() {
|
|
41
|
-
console.log(chalk.bold('\
|
|
42
|
+
console.log(chalk.bold('\nContentstorage CLI'));
|
|
42
43
|
console.log(chalk.dim('Manage content and generate TypeScript types\n'));
|
|
43
44
|
console.log(chalk.bold('Usage:'));
|
|
44
45
|
console.log(' contentstorage <command> [options]\n');
|
package/dist/commands/pull.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { loadConfig } from '../core/config-loader.js';
|
|
7
7
|
import { CONTENTSTORAGE_CONFIG } from '../utils/constants.js';
|
|
8
|
+
import { flattenJson } from '../utils/flatten-json.js';
|
|
8
9
|
export async function pullContent() {
|
|
9
10
|
console.log(chalk.blue('Starting content pull...'));
|
|
10
11
|
const args = process.argv.slice(2);
|
|
@@ -17,6 +18,9 @@ export async function pullContent() {
|
|
|
17
18
|
if (key === 'pending-changes') {
|
|
18
19
|
cliConfig.pendingChanges = true;
|
|
19
20
|
}
|
|
21
|
+
else if (key === 'flatten') {
|
|
22
|
+
cliConfig.flatten = true;
|
|
23
|
+
}
|
|
20
24
|
else if (value && !value.startsWith('--')) {
|
|
21
25
|
if (key === 'lang') {
|
|
22
26
|
cliConfig.languageCodes = [value.toUpperCase()];
|
|
@@ -102,7 +106,8 @@ export async function pullContent() {
|
|
|
102
106
|
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
107
|
}
|
|
104
108
|
console.log(chalk.green(`Received JSON for ${languageCode}. Saving to ${outputPath}`));
|
|
105
|
-
|
|
109
|
+
const outputData = config.flatten ? flattenJson(jsonData) : jsonData;
|
|
110
|
+
await fs.writeFile(outputPath, JSON.stringify(outputData, null, 2));
|
|
106
111
|
console.log(chalk.green(`Successfully saved ${outputPath}`));
|
|
107
112
|
}
|
|
108
113
|
catch (error) {
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type AppConfig = {
|
|
|
4
4
|
contentDir: string;
|
|
5
5
|
typesOutputFile: string;
|
|
6
6
|
pendingChanges?: boolean;
|
|
7
|
+
flatten?: boolean;
|
|
7
8
|
};
|
|
8
9
|
export type LanguageCode = 'SQ' | 'BE' | 'BS' | 'BG' | 'HR' | 'CS' | 'DA' | 'NL' | 'EN' | 'ET' | 'FI' | 'FR' | 'DE' | 'EL' | 'HU' | 'GA' | 'IT' | 'LV' | 'LT' | 'MK' | 'NO' | 'PL' | 'PT' | 'RO' | 'RU' | 'SR' | 'SK' | 'SL' | 'ES' | 'SV' | 'TR' | 'UK';
|
|
9
10
|
/**
|
package/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"name": "@contentstorage/core",
|
|
3
3
|
"author": "Kaido Hussar <kaido@contentstorage.app>",
|
|
4
4
|
"homepage": "https://contentstorage.app",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.2.0",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Contentstorage CLI for managing translations and generating TypeScript types",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"bin": {
|
|
10
10
|
"contentstorage": "dist/commands/cli.js"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Helper function to define your application configuration.
|
|
3
|
-
* Provides autocompletion and type-checking for contentstorage.config.js files.
|
|
4
|
-
*/
|
|
5
|
-
export function defineConfig(config) {
|
|
6
|
-
// You can add basic runtime validation here if desired,
|
|
7
|
-
// e.g., check if contentUrl is a valid URL format,
|
|
8
|
-
// or if languageCodes is not empty.
|
|
9
|
-
if (!config.languageCodes || config.languageCodes.length === 0) {
|
|
10
|
-
console.warn('Warning: languageCodes array is empty or missing in the configuration.');
|
|
11
|
-
}
|
|
12
|
-
if (!config.contentDir) {
|
|
13
|
-
// This would typically be a hard error, but defineConfig is more for type safety at edit time.
|
|
14
|
-
// Runtime validation (see point 3) is better for hard errors.
|
|
15
|
-
console.warn('Warning: contentDir is missing in the configuration.');
|
|
16
|
-
}
|
|
17
|
-
// ... other checks
|
|
18
|
-
return config;
|
|
19
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function flattenJson(data: any, prefix?: string, result?: Record<string, any>): Record<string, any>;
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function populateTextWithVariables(text: string, variables: Record<string, any>, textKey: string): string;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function populateTextWithVariables(text, variables, textKey) {
|
|
2
|
-
return text.replace(/\{(\w+)}/g, (placeholder, variableName) => {
|
|
3
|
-
if (Object.prototype.hasOwnProperty.call(variables, variableName)) {
|
|
4
|
-
const value = variables[variableName];
|
|
5
|
-
return String(value);
|
|
6
|
-
}
|
|
7
|
-
console.warn(`[getText] Variable "${variableName}" for text id "${textKey}" not found in provided variables. Placeholder "${placeholder}" will be replaced with an empty string.`);
|
|
8
|
-
return placeholder;
|
|
9
|
-
});
|
|
10
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { AppConfig, LanguageCode, ContentStructure } from './types.js';
|
|
2
|
-
import { setContentLanguage, getText, getImage, getVariation, initContentStorage } from './lib/contentManagement.js';
|
|
3
|
-
import { fetchContent } from './lib/functions/fetchContent.js';
|
|
4
|
-
export { AppConfig, LanguageCode, ContentStructure };
|
|
5
|
-
export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, liveEditorReady, };
|
|
6
|
-
declare function liveEditorReady(retries?: number, delay?: number): Promise<boolean>;
|
package/dist/index.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { CONTENTSTORAGE_CONFIG } from './contentstorage-config.js';
|
|
2
|
-
import { setContentLanguage, getText, getImage, getVariation, initContentStorage, } from './lib/contentManagement.js';
|
|
3
|
-
import { fetchContent } from './lib/functions/fetchContent.js';
|
|
4
|
-
export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, liveEditorReady, };
|
|
5
|
-
let liveEditorReadyPromise = null;
|
|
6
|
-
async function isLiveEditorMode() {
|
|
7
|
-
try {
|
|
8
|
-
const inIframe = window.self !== window.top;
|
|
9
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
10
|
-
const iframeMarkerFromParent = urlParams.get('contentstorage_live_editor');
|
|
11
|
-
return !!(inIframe && iframeMarkerFromParent);
|
|
12
|
-
}
|
|
13
|
-
catch (e) {
|
|
14
|
-
console.warn('Error accessing window.top:', e);
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function liveEditorReady(retries = 2, delay = 3000) {
|
|
19
|
-
if (liveEditorReadyPromise) {
|
|
20
|
-
return liveEditorReadyPromise;
|
|
21
|
-
}
|
|
22
|
-
liveEditorReadyPromise = new Promise(async (resolve) => {
|
|
23
|
-
const isLiveMode = await isLiveEditorMode();
|
|
24
|
-
if (!isLiveMode) {
|
|
25
|
-
resolve(false);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const cdnScriptUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/live-editor.js?contentstorage-live-editor=true`;
|
|
29
|
-
const loadScript = (attempt = 1) => {
|
|
30
|
-
console.log(`Attempting to load Contentstorage live editor script (attempt ${attempt}/${retries})`);
|
|
31
|
-
const scriptElement = document.createElement('script');
|
|
32
|
-
scriptElement.type = 'text/javascript';
|
|
33
|
-
scriptElement.src = cdnScriptUrl;
|
|
34
|
-
scriptElement.onload = () => {
|
|
35
|
-
console.log(`Script loaded successfully from: ${cdnScriptUrl}`);
|
|
36
|
-
resolve(true);
|
|
37
|
-
};
|
|
38
|
-
scriptElement.onerror = (error) => {
|
|
39
|
-
// Clean up the failed script element to avoid clutter
|
|
40
|
-
scriptElement.remove();
|
|
41
|
-
console.error(`Failed to load script (attempt ${attempt}/${retries})`, error);
|
|
42
|
-
if (attempt < retries) {
|
|
43
|
-
setTimeout(() => loadScript(attempt + 1), delay);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
console.error(`All ${retries} attempts to load the script failed.`);
|
|
47
|
-
resolve(false);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
document.head.appendChild(scriptElement);
|
|
51
|
-
};
|
|
52
|
-
loadScript();
|
|
53
|
-
});
|
|
54
|
-
return liveEditorReadyPromise;
|
|
55
|
-
}
|
|
56
|
-
if (typeof window !== 'undefined') {
|
|
57
|
-
liveEditorReady().then((result) => {
|
|
58
|
-
if (result === true) {
|
|
59
|
-
console.log('Contentstorage live editor script loaded!');
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
package/dist/lib/configLoader.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
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,32 +0,0 @@
|
|
|
1
|
-
import { AppConfig, ContentStructure, GetImageReturn, GetTextReturn, GetVariationReturn, LanguageCode } from '../types.js';
|
|
2
|
-
export declare let activeContent: object | null;
|
|
3
|
-
export declare let appConfig: Pick<AppConfig, 'contentKey' | 'languageCodes' | 'pendingChanges'> | null;
|
|
4
|
-
/**
|
|
5
|
-
* Loads and sets the content for a specific language.
|
|
6
|
-
* It will internally ensure the application configuration (for contentDir) is loaded.
|
|
7
|
-
* @param contentJson
|
|
8
|
-
* Language code which is used for live editor to manage pending changes
|
|
9
|
-
* @param languageCode
|
|
10
|
-
*/
|
|
11
|
-
export declare function setContentLanguage({ languageCode, contentJson, }: {
|
|
12
|
-
languageCode: LanguageCode;
|
|
13
|
-
contentJson: object;
|
|
14
|
-
}): void;
|
|
15
|
-
export declare function initContentStorage(config: Pick<AppConfig, 'contentKey' | 'languageCodes'>): void;
|
|
16
|
-
/**
|
|
17
|
-
* Retrieves the text string from the loaded JSON content for the given path.
|
|
18
|
-
* Autocompletion for pathString is enabled via module augmentation of CustomContentStructure.
|
|
19
|
-
* `setContentLanguage()` must be called successfully before using this.
|
|
20
|
-
*
|
|
21
|
-
* @param contentId A dot-notation path string (e.g., 'HomePage.Login'). Autocompletion is provided.
|
|
22
|
-
* @param variables Variables help to render dynamic content inside text strings
|
|
23
|
-
* If not provided, and path is not found/value not string, undefined is returned.
|
|
24
|
-
* @returns The text string from the JSON, or the fallbackValue, or undefined.
|
|
25
|
-
*/
|
|
26
|
-
export declare function getText<Path extends keyof ContentStructure>(contentId: Path, variables?: ContentStructure[Path] extends {
|
|
27
|
-
variables: infer Vars;
|
|
28
|
-
} ? keyof Vars : Record<string, any>): GetTextReturn;
|
|
29
|
-
export declare function getImage(contentId: keyof ContentStructure): GetImageReturn | undefined;
|
|
30
|
-
export declare function getVariation<Path extends keyof ContentStructure>(contentId: Path, variationKey?: ContentStructure[Path] extends {
|
|
31
|
-
data: infer D;
|
|
32
|
-
} ? keyof D : string, variables?: Record<string, any>): GetVariationReturn;
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { populateTextWithVariables } from '../helpers/populateTextWithVariables.js';
|
|
2
|
-
export let activeContent = null;
|
|
3
|
-
export let appConfig = null;
|
|
4
|
-
/**
|
|
5
|
-
* NB! Only used when live editor mode is on
|
|
6
|
-
*/
|
|
7
|
-
window.memoryMap = new Map();
|
|
8
|
-
window.currentLanguageCode = null;
|
|
9
|
-
/**
|
|
10
|
-
* Loads and sets the content for a specific language.
|
|
11
|
-
* It will internally ensure the application configuration (for contentDir) is loaded.
|
|
12
|
-
* @param contentJson
|
|
13
|
-
* Language code which is used for live editor to manage pending changes
|
|
14
|
-
* @param languageCode
|
|
15
|
-
*/
|
|
16
|
-
export function setContentLanguage({ languageCode, contentJson, }) {
|
|
17
|
-
if (!contentJson || typeof contentJson !== 'object') {
|
|
18
|
-
throw new Error('[Contentstorage] Invalid contentJson might be provided which caused setContentLanguage to fail.');
|
|
19
|
-
}
|
|
20
|
-
try {
|
|
21
|
-
activeContent = contentJson; // Relies on augmentation
|
|
22
|
-
if (typeof window !== 'undefined') {
|
|
23
|
-
window.currentLanguageCode = languageCode;
|
|
24
|
-
}
|
|
25
|
-
console.log(`[Contentstorage] Content loaded.`);
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
activeContent = null; // Reset on failure
|
|
29
|
-
window.currentLanguageCode = null;
|
|
30
|
-
console.error(`[Contentstorage] Failed to load content. Error: ${error.message}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export function initContentStorage(config) {
|
|
34
|
-
if (!config ||
|
|
35
|
-
typeof config !== 'object' ||
|
|
36
|
-
!config.contentKey ||
|
|
37
|
-
!Array.isArray(config.languageCodes)) {
|
|
38
|
-
if (!config.contentKey) {
|
|
39
|
-
throw new Error('[Contentstorage] No contentKey provided in initContentStorage function.');
|
|
40
|
-
}
|
|
41
|
-
if (!Array.isArray(config.languageCodes)) {
|
|
42
|
-
throw new Error('[Contentstorage] No languageCodes provided in initContentStorage function.');
|
|
43
|
-
}
|
|
44
|
-
throw new Error('[Contentstorage] Invalid config.');
|
|
45
|
-
}
|
|
46
|
-
appConfig = config;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Retrieves the text string from the loaded JSON content for the given path.
|
|
50
|
-
* Autocompletion for pathString is enabled via module augmentation of CustomContentStructure.
|
|
51
|
-
* `setContentLanguage()` must be called successfully before using this.
|
|
52
|
-
*
|
|
53
|
-
* @param contentId A dot-notation path string (e.g., 'HomePage.Login'). Autocompletion is provided.
|
|
54
|
-
* @param variables Variables help to render dynamic content inside text strings
|
|
55
|
-
* If not provided, and path is not found/value not string, undefined is returned.
|
|
56
|
-
* @returns The text string from the JSON, or the fallbackValue, or undefined.
|
|
57
|
-
*/
|
|
58
|
-
export function getText(contentId, variables) {
|
|
59
|
-
const defaultVal = {
|
|
60
|
-
contentId,
|
|
61
|
-
text: '',
|
|
62
|
-
};
|
|
63
|
-
if (!activeContent) {
|
|
64
|
-
const msg = `[Contentstorage] getText: Content not loaded (Key: "${String(contentId)}"). Ensure setContentLanguage() was called and completed successfully.`;
|
|
65
|
-
console.warn(msg);
|
|
66
|
-
return defaultVal;
|
|
67
|
-
}
|
|
68
|
-
const keys = contentId.split('.');
|
|
69
|
-
let current = activeContent;
|
|
70
|
-
for (const key of keys) {
|
|
71
|
-
if (current && typeof current === 'object' && key in current) {
|
|
72
|
-
current = current[key];
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
const msg = `[Contentstorage] getText: Path "${String(contentId)}" not found in loaded content.`;
|
|
76
|
-
console.warn(msg);
|
|
77
|
-
return defaultVal;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (typeof current === 'string') {
|
|
81
|
-
if (window.parent && window.parent !== window) {
|
|
82
|
-
const key = current;
|
|
83
|
-
const existingEntry = window.memoryMap.get(key);
|
|
84
|
-
const idSet = existingEntry ? existingEntry.ids : new Set();
|
|
85
|
-
idSet.add(contentId); // Add the current ID to the set.
|
|
86
|
-
window.memoryMap.set(key, {
|
|
87
|
-
ids: idSet,
|
|
88
|
-
type: 'text',
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (!variables || Object.keys(variables).length === 0) {
|
|
92
|
-
return {
|
|
93
|
-
contentId,
|
|
94
|
-
text: current,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
contentId,
|
|
99
|
-
text: populateTextWithVariables(current, variables, contentId),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
const msg = `[Contentstorage] getText: Value at path "${String(contentId)}" is not a string (actual type: ${typeof current}).`;
|
|
104
|
-
console.warn(msg);
|
|
105
|
-
return defaultVal;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
export function getImage(contentId) {
|
|
109
|
-
const defaultVal = {
|
|
110
|
-
contentId,
|
|
111
|
-
data: { url: '', altText: '', contentstorage_type: 'image' },
|
|
112
|
-
};
|
|
113
|
-
if (!activeContent) {
|
|
114
|
-
const msg = `[Contentstorage] getImage: Content not loaded (Content Id: "${contentId}"). Ensure setContentLanguage() was called and completed successfully.`;
|
|
115
|
-
console.warn(msg);
|
|
116
|
-
return {
|
|
117
|
-
contentId,
|
|
118
|
-
data: { url: '', altText: '', contentstorage_type: 'image' },
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
const keys = contentId.split('.');
|
|
122
|
-
let current = activeContent;
|
|
123
|
-
for (const key of keys) {
|
|
124
|
-
if (current && typeof current === 'object' && key in current) {
|
|
125
|
-
current = current[key];
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
const msg = `[Contentstorage] getImage: Path "${contentId}" not found in loaded content.`;
|
|
129
|
-
console.warn(msg);
|
|
130
|
-
return defaultVal;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (current &&
|
|
134
|
-
typeof current === 'object' &&
|
|
135
|
-
current.contentstorage_type === 'image' &&
|
|
136
|
-
typeof current.url === 'string') {
|
|
137
|
-
const currentData = current;
|
|
138
|
-
const key = `https://cdn.contentstorage.app/${currentData.url}`;
|
|
139
|
-
if (window.parent && window.parent !== window) {
|
|
140
|
-
const existingEntry = window.memoryMap.get(key);
|
|
141
|
-
const idSet = existingEntry ? existingEntry.ids : new Set();
|
|
142
|
-
idSet.add(contentId); // Add the current ID to the set.
|
|
143
|
-
window.memoryMap.set(key, {
|
|
144
|
-
ids: idSet,
|
|
145
|
-
type: 'image',
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
console.log('currentData.url', currentData.url);
|
|
149
|
-
return {
|
|
150
|
-
contentId,
|
|
151
|
-
data: {
|
|
152
|
-
...currentData,
|
|
153
|
-
url: key,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
const msg = `[Contentstorage] getImage: Value at path "${contentId}" is not a valid image object (actual value: ${JSON.stringify(current)}).`;
|
|
159
|
-
console.warn(msg);
|
|
160
|
-
return defaultVal;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
export function getVariation(contentId, variationKey, variables) {
|
|
164
|
-
const defaultVal = {
|
|
165
|
-
contentId,
|
|
166
|
-
text: '',
|
|
167
|
-
};
|
|
168
|
-
if (!activeContent) {
|
|
169
|
-
const msg = `[Contentstorage] getVariation: Content not loaded (Content Id: "${contentId}", Variation: "${variationKey?.toString()}"). Ensure setContentLanguage() was called and completed successfully.`;
|
|
170
|
-
console.warn(msg);
|
|
171
|
-
return defaultVal;
|
|
172
|
-
}
|
|
173
|
-
const keys = contentId.split('.');
|
|
174
|
-
let current = activeContent;
|
|
175
|
-
for (const key of keys) {
|
|
176
|
-
if (current && typeof current === 'object' && key in current) {
|
|
177
|
-
current = current[key];
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
const msg = `[Contentstorage] getVariation: Path "${contentId}" for variation object not found in loaded content.`;
|
|
181
|
-
console.warn(msg);
|
|
182
|
-
return defaultVal;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (current &&
|
|
186
|
-
typeof current === 'object' &&
|
|
187
|
-
current.contentstorage_type === 'variation' &&
|
|
188
|
-
typeof current.data === 'object' &&
|
|
189
|
-
current.data !== null) {
|
|
190
|
-
const variationObject = current;
|
|
191
|
-
if (variationKey &&
|
|
192
|
-
typeof variationKey === 'string' &&
|
|
193
|
-
variationKey in variationObject.data) {
|
|
194
|
-
if (typeof variationObject.data[variationKey] === 'string') {
|
|
195
|
-
const current = variationObject.data[variationKey];
|
|
196
|
-
if (window.parent && window.parent !== window) {
|
|
197
|
-
const key = current;
|
|
198
|
-
const existingEntry = window.memoryMap.get(key);
|
|
199
|
-
const idSet = existingEntry ? existingEntry.ids : new Set();
|
|
200
|
-
idSet.add(contentId); // Add the current ID to the set.
|
|
201
|
-
window.memoryMap.set(key, {
|
|
202
|
-
ids: idSet,
|
|
203
|
-
type: 'variation',
|
|
204
|
-
variation: variationKey,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
if (!variables || Object.keys(variables).length === 0) {
|
|
208
|
-
return {
|
|
209
|
-
contentId,
|
|
210
|
-
text: current,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
contentId,
|
|
215
|
-
text: populateTextWithVariables(current, variables, contentId),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
const msg = `[Contentstorage] getVariation: Variation value for key "${variationKey}" at path "${contentId}" is not a string (actual type: ${typeof variationObject.data[variationKey]}).`;
|
|
220
|
-
console.warn(msg);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// If specific variationKey is not found or not provided, try to return the 'default' variation
|
|
224
|
-
if ('default' in variationObject.data && typeof variationKey === 'string') {
|
|
225
|
-
if (typeof variationObject.data.default === 'string') {
|
|
226
|
-
if (variationKey && variationKey !== 'default') {
|
|
227
|
-
console.warn(`[Contentstorage] getVariation: Variation key "${variationKey}" not found at path "${contentId}". Returning 'default' variation.`);
|
|
228
|
-
}
|
|
229
|
-
if (window.parent && window.parent !== window) {
|
|
230
|
-
const key = current;
|
|
231
|
-
const existingEntry = window.memoryMap.get(key);
|
|
232
|
-
const idSet = existingEntry ? existingEntry.ids : new Set();
|
|
233
|
-
idSet.add(contentId); // Add the current ID to the set.
|
|
234
|
-
window.memoryMap.set(key, {
|
|
235
|
-
ids: idSet,
|
|
236
|
-
type: 'variation',
|
|
237
|
-
variation: 'default',
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
contentId,
|
|
242
|
-
text: variationObject.data.default,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
console.warn(`[Contentstorage] getVariation: 'default' variation value at path "${contentId}" is not a string (actual type: ${typeof variationObject.data.default}).`);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// If neither specific key nor 'default' is found or valid
|
|
250
|
-
console.warn(`[Contentstorage] getVariation: Neither variation key "${variationKey?.toString()}" nor 'default' variation found or valid at path "${contentId}".`);
|
|
251
|
-
return defaultVal;
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
console.warn(`[Contentstorage] getVariation: Value at path "${contentId}" is not a valid variation object (actual value: ${JSON.stringify(current)}).`);
|
|
255
|
-
return defaultVal;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { CONTENTSTORAGE_CONFIG } from '../../contentstorage-config.js';
|
|
3
|
-
import { appConfig, setContentLanguage } from '../contentManagement.js';
|
|
4
|
-
// Map of request keys to their debounce timers
|
|
5
|
-
const debounceTimers = new Map();
|
|
6
|
-
// Map of request keys to their cancel token sources
|
|
7
|
-
const cancelTokenSources = new Map();
|
|
8
|
-
// Fixed debounce delay in milliseconds
|
|
9
|
-
const DEBOUNCE_MS = 200;
|
|
10
|
-
export async function fetchContent(language, options = {}) {
|
|
11
|
-
const { withPendingChanges, contentKey } = options;
|
|
12
|
-
const languageToFetch = language || appConfig?.languageCodes?.[0];
|
|
13
|
-
const usePendingChangesToFetch = withPendingChanges || appConfig?.pendingChanges;
|
|
14
|
-
console.log(`Starting content fetch for language ${language}...`);
|
|
15
|
-
if (!languageToFetch) {
|
|
16
|
-
throw Error(`No language found`);
|
|
17
|
-
}
|
|
18
|
-
if (!appConfig) {
|
|
19
|
-
throw Error(`No app config found`);
|
|
20
|
-
}
|
|
21
|
-
const effectiveContentKey = contentKey || appConfig.contentKey;
|
|
22
|
-
let fileUrl;
|
|
23
|
-
let requestConfig = {};
|
|
24
|
-
if (usePendingChangesToFetch) {
|
|
25
|
-
if (!effectiveContentKey) {
|
|
26
|
-
throw Error(`No contentKey found`);
|
|
27
|
-
}
|
|
28
|
-
fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageToFetch}`;
|
|
29
|
-
requestConfig.headers = {
|
|
30
|
-
'X-Content-Key': effectiveContentKey,
|
|
31
|
-
};
|
|
32
|
-
// Apply debouncing and cancellation only for pending-changes API endpoint
|
|
33
|
-
const requestKey = `${languageToFetch}:${effectiveContentKey}`;
|
|
34
|
-
// Cancel any existing in-flight request for the same parameters
|
|
35
|
-
const existingCancelSource = cancelTokenSources.get(requestKey);
|
|
36
|
-
if (existingCancelSource) {
|
|
37
|
-
existingCancelSource.cancel('Request cancelled due to new request');
|
|
38
|
-
cancelTokenSources.delete(requestKey);
|
|
39
|
-
}
|
|
40
|
-
// Clear any existing debounce timer for the same parameters
|
|
41
|
-
const existingTimer = debounceTimers.get(requestKey);
|
|
42
|
-
if (existingTimer) {
|
|
43
|
-
clearTimeout(existingTimer);
|
|
44
|
-
debounceTimers.delete(requestKey);
|
|
45
|
-
}
|
|
46
|
-
// Return a promise that will resolve after debouncing
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
const timer = setTimeout(async () => {
|
|
49
|
-
debounceTimers.delete(requestKey);
|
|
50
|
-
// Create a cancel token for this request
|
|
51
|
-
const cancelTokenSource = axios.CancelToken.source();
|
|
52
|
-
requestConfig.cancelToken = cancelTokenSource.token;
|
|
53
|
-
cancelTokenSources.set(requestKey, cancelTokenSource);
|
|
54
|
-
try {
|
|
55
|
-
// Fetch data for the current language
|
|
56
|
-
const response = await axios.get(fileUrl, requestConfig);
|
|
57
|
-
let jsonData = response.data;
|
|
58
|
-
// Handle API response structure - only for pending changes API
|
|
59
|
-
if (jsonData && typeof jsonData === 'object' && 'data' in jsonData) {
|
|
60
|
-
jsonData = jsonData.data;
|
|
61
|
-
}
|
|
62
|
-
if (jsonData === undefined || jsonData === null) {
|
|
63
|
-
throw new Error(`No data received from ${fileUrl} for language ${languageToFetch}.`);
|
|
64
|
-
}
|
|
65
|
-
// Validate that jsonData is a single, non-null JSON object (not an array)
|
|
66
|
-
if (typeof jsonData !== 'object') {
|
|
67
|
-
throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageToFetch}, but received type ${typeof jsonData}. Cannot proceed.`);
|
|
68
|
-
}
|
|
69
|
-
console.log(`Received JSON for ${languageToFetch}`);
|
|
70
|
-
setContentLanguage({
|
|
71
|
-
languageCode: languageToFetch,
|
|
72
|
-
contentJson: jsonData,
|
|
73
|
-
});
|
|
74
|
-
// Clean up cancel token source
|
|
75
|
-
cancelTokenSources.delete(requestKey);
|
|
76
|
-
resolve();
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
// Clean up cancel token source
|
|
80
|
-
cancelTokenSources.delete(requestKey);
|
|
81
|
-
// Don't log cancelled requests as errors
|
|
82
|
-
if (axios.isCancel(error)) {
|
|
83
|
-
console.log(`Request cancelled for language ${languageToFetch}`);
|
|
84
|
-
resolve();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
// Catch errors related to fetching or saving a single language file
|
|
88
|
-
console.error(`\nError processing language ${languageToFetch} from ${fileUrl}:`);
|
|
89
|
-
if (axios.isAxiosError(error)) {
|
|
90
|
-
console.error(` Status: ${error.response?.status}`);
|
|
91
|
-
console.error(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`);
|
|
92
|
-
console.error(` Message: ${error.message}`);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
console.error(` Error: ${error.message}`);
|
|
96
|
-
}
|
|
97
|
-
reject(error);
|
|
98
|
-
}
|
|
99
|
-
}, DEBOUNCE_MS);
|
|
100
|
-
debounceTimers.set(requestKey, timer);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${effectiveContentKey}/content/${languageToFetch}.json`;
|
|
105
|
-
}
|
|
106
|
-
try {
|
|
107
|
-
// Fetch data for the current language (CDN endpoint - no debouncing)
|
|
108
|
-
const response = await axios.get(fileUrl, requestConfig);
|
|
109
|
-
let jsonData = response.data;
|
|
110
|
-
// Handle API response structure - only for pending changes API
|
|
111
|
-
if (usePendingChangesToFetch && jsonData && typeof jsonData === 'object' && 'data' in jsonData) {
|
|
112
|
-
jsonData = jsonData.data;
|
|
113
|
-
}
|
|
114
|
-
if (jsonData === undefined || jsonData === null) {
|
|
115
|
-
throw new Error(`No data received from ${fileUrl} for language ${languageToFetch}.`);
|
|
116
|
-
}
|
|
117
|
-
// Validate that jsonData is a single, non-null JSON object (not an array)
|
|
118
|
-
// This check mirrors the original code's expectation for the content of a JSON file.
|
|
119
|
-
if (typeof jsonData !== 'object') {
|
|
120
|
-
throw new Error(`Expected a single JSON object from ${fileUrl} for language ${languageToFetch}, but received type ${typeof jsonData}. Cannot proceed.`);
|
|
121
|
-
}
|
|
122
|
-
console.log(`Received JSON for ${languageToFetch}`);
|
|
123
|
-
setContentLanguage({
|
|
124
|
-
languageCode: languageToFetch,
|
|
125
|
-
contentJson: jsonData,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
catch (error) {
|
|
129
|
-
// Catch errors related to fetching or saving a single language file
|
|
130
|
-
console.error(`\nError processing language ${languageToFetch} from ${fileUrl}:`);
|
|
131
|
-
if (axios.isAxiosError(error)) {
|
|
132
|
-
console.error(` Status: ${error.response?.status}`);
|
|
133
|
-
console.error(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`);
|
|
134
|
-
console.error(` Message: ${error.message}`); // Axios error message
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
// For non-Axios errors (e.g., manually thrown errors, fs errors)
|
|
138
|
-
console.error(` Error: ${error.message}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
package/dist/scripts/cli.d.ts
DELETED
package/dist/scripts/cli.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { pullContent } from './pull-content.js';
|
|
4
|
-
import { generateTypes } from './generate-types.js';
|
|
5
|
-
const COMMANDS = {
|
|
6
|
-
pull: {
|
|
7
|
-
name: 'pull',
|
|
8
|
-
description: 'Pull content from Contentstorage CDN',
|
|
9
|
-
usage: 'contentstorage pull [options]',
|
|
10
|
-
options: [
|
|
11
|
-
' --content-key <key> Content key for your project',
|
|
12
|
-
' --content-dir <dir> Directory to save content files',
|
|
13
|
-
' --lang <code> Language code (e.g., EN, FR)',
|
|
14
|
-
' --pending-changes Fetch pending/draft content',
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
'generate-types': {
|
|
18
|
-
name: 'generate-types',
|
|
19
|
-
description: 'Generate TypeScript type definitions from content',
|
|
20
|
-
usage: 'contentstorage generate-types [options]',
|
|
21
|
-
options: [
|
|
22
|
-
' --output <file> Output file for generated types',
|
|
23
|
-
' --content-key <key> Content key for your project',
|
|
24
|
-
' --lang <code> Language code (e.g., EN, FR)',
|
|
25
|
-
' --pending-changes Use pending/draft content',
|
|
26
|
-
],
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
function showHelp() {
|
|
30
|
-
console.log(chalk.bold('\nContentStorage CLI'));
|
|
31
|
-
console.log(chalk.dim('Manage content and generate TypeScript types\n'));
|
|
32
|
-
console.log(chalk.bold('Usage:'));
|
|
33
|
-
console.log(' contentstorage <command> [options]\n');
|
|
34
|
-
console.log(chalk.bold('Commands:'));
|
|
35
|
-
Object.values(COMMANDS).forEach((cmd) => {
|
|
36
|
-
console.log(` ${chalk.cyan(cmd.name.padEnd(20))} ${cmd.description}`);
|
|
37
|
-
});
|
|
38
|
-
console.log(chalk.bold('\nOptions:'));
|
|
39
|
-
console.log(' --help Show help for a command\n');
|
|
40
|
-
console.log(chalk.dim('Examples:'));
|
|
41
|
-
console.log(chalk.dim(' contentstorage pull --content-key abc123'));
|
|
42
|
-
console.log(chalk.dim(' contentstorage generate-types --output types.ts'));
|
|
43
|
-
console.log(chalk.dim(' contentstorage pull --help # Show help for pull command\n'));
|
|
44
|
-
}
|
|
45
|
-
function showCommandHelp(commandName) {
|
|
46
|
-
const cmd = COMMANDS[commandName];
|
|
47
|
-
if (!cmd) {
|
|
48
|
-
console.error(chalk.red(`Unknown command: ${commandName}`));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
console.log(chalk.bold(`\n${cmd.name}`));
|
|
52
|
-
console.log(chalk.dim(cmd.description + '\n'));
|
|
53
|
-
console.log(chalk.bold('Usage:'));
|
|
54
|
-
console.log(` ${cmd.usage}\n`);
|
|
55
|
-
if (cmd.options.length > 0) {
|
|
56
|
-
console.log(chalk.bold('Options:'));
|
|
57
|
-
cmd.options.forEach((opt) => console.log(opt));
|
|
58
|
-
console.log('');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function main() {
|
|
62
|
-
const args = process.argv.slice(2);
|
|
63
|
-
// No arguments - show help
|
|
64
|
-
if (args.length === 0) {
|
|
65
|
-
showHelp();
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
68
|
-
const command = args[0];
|
|
69
|
-
// Global --help flag
|
|
70
|
-
if (command === '--help' || command === '-h') {
|
|
71
|
-
showHelp();
|
|
72
|
-
process.exit(0);
|
|
73
|
-
}
|
|
74
|
-
// Command-specific --help
|
|
75
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
76
|
-
showCommandHelp(command);
|
|
77
|
-
process.exit(0);
|
|
78
|
-
}
|
|
79
|
-
// Route to commands
|
|
80
|
-
switch (command) {
|
|
81
|
-
case 'pull':
|
|
82
|
-
await pullContent();
|
|
83
|
-
break;
|
|
84
|
-
case 'generate-types':
|
|
85
|
-
await generateTypes();
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
console.error(chalk.red(`Unknown command: ${command}\n`));
|
|
89
|
-
console.log(chalk.dim('Run "contentstorage --help" for usage'));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
main().catch((error) => {
|
|
94
|
-
console.error(chalk.red('Unexpected error:'), error);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// ^ Ensures the script is executed with Node.js
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import axios from 'axios';
|
|
6
|
-
import chalk from 'chalk'; // Optional: for colored output
|
|
7
|
-
import { loadConfig } from '../lib/configLoader.js';
|
|
8
|
-
import { flattenJson } from '../helpers/flattenJson.js';
|
|
9
|
-
import { CONTENTSTORAGE_CONFIG } from '../contentstorage-config.js';
|
|
10
|
-
import { jsonToTS } from '../type-generation/index.js';
|
|
11
|
-
export async function generateTypes() {
|
|
12
|
-
console.log(chalk.blue('Starting type generation...'));
|
|
13
|
-
const args = process.argv.slice(2);
|
|
14
|
-
const cliConfig = {};
|
|
15
|
-
for (let i = 0; i < args.length; i++) {
|
|
16
|
-
const arg = args[i];
|
|
17
|
-
if (arg.startsWith('--')) {
|
|
18
|
-
const key = arg.substring(2);
|
|
19
|
-
const value = args[i + 1];
|
|
20
|
-
if (key === 'pending-changes') {
|
|
21
|
-
cliConfig.pendingChanges = true;
|
|
22
|
-
}
|
|
23
|
-
else if (value && !value.startsWith('--')) {
|
|
24
|
-
if (key === 'lang') {
|
|
25
|
-
cliConfig.languageCodes = [value.toUpperCase()];
|
|
26
|
-
}
|
|
27
|
-
else if (key === 'content-key') {
|
|
28
|
-
cliConfig.contentKey = value;
|
|
29
|
-
}
|
|
30
|
-
else if (key === 'output') {
|
|
31
|
-
cliConfig.typesOutputFile = value;
|
|
32
|
-
}
|
|
33
|
-
// Skip the value in the next iteration
|
|
34
|
-
i++;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
let fileConfig = {};
|
|
39
|
-
try {
|
|
40
|
-
fileConfig = await loadConfig();
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
console.log(chalk.yellow('Could not load a configuration file. Proceeding with CLI arguments.'));
|
|
44
|
-
}
|
|
45
|
-
const config = { ...fileConfig, ...cliConfig };
|
|
46
|
-
if (!config.typesOutputFile) {
|
|
47
|
-
console.error(chalk.red.bold("Configuration error: 'typesOutputFile' is missing."));
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
if (!config.languageCodes ||
|
|
51
|
-
!Array.isArray(config.languageCodes) ||
|
|
52
|
-
config.languageCodes.length === 0) {
|
|
53
|
-
console.error(chalk.red.bold("Configuration error: 'languageCodes' must be a non-empty array."));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
console.log(chalk.blue(`TypeScript types will be saved to: ${config.typesOutputFile}`));
|
|
57
|
-
let jsonObject; // To hold the JSON data from either local or remote source
|
|
58
|
-
let dataSourceDescription = ''; // For clearer logging
|
|
59
|
-
const firstLanguageCode = config.languageCodes[0];
|
|
60
|
-
try {
|
|
61
|
-
let attemptLocalLoad = false;
|
|
62
|
-
if (config.contentDir) {
|
|
63
|
-
try {
|
|
64
|
-
await fs.stat(config.contentDir); // Check if directory exists
|
|
65
|
-
attemptLocalLoad = true;
|
|
66
|
-
console.log(chalk.blue(`Local content directory found: ${config.contentDir}`));
|
|
67
|
-
}
|
|
68
|
-
catch (statError) {
|
|
69
|
-
if (statError.code === 'ENOENT') {
|
|
70
|
-
console.log(chalk.yellow(`Local content directory specified but not found: ${config.contentDir}. Will attempt to fetch from URL.`));
|
|
71
|
-
// attemptLocalLoad remains false
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
// Other errors accessing contentDir (e.g., permissions)
|
|
75
|
-
throw new Error(`Error accessing content directory ${config.contentDir}: ${statError.message}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
console.log(chalk.yellow(`Local content directory (config.contentDir) not specified. Attempting to fetch from URL.`));
|
|
81
|
-
}
|
|
82
|
-
if (attemptLocalLoad && config.contentDir) {
|
|
83
|
-
// --- Load from local file system ---
|
|
84
|
-
const targetFilename = `${firstLanguageCode}.json`;
|
|
85
|
-
const jsonFilePath = path.join(config.contentDir, targetFilename);
|
|
86
|
-
dataSourceDescription = `local file (${jsonFilePath})`;
|
|
87
|
-
console.log(chalk.blue(`Attempting to read JSON from: ${jsonFilePath}`));
|
|
88
|
-
try {
|
|
89
|
-
const jsonContentString = await fs.readFile(jsonFilePath, 'utf-8');
|
|
90
|
-
console.log(chalk.blue('Parsing JSON'));
|
|
91
|
-
const parsendJsonObject = JSON.parse(jsonContentString);
|
|
92
|
-
console.log(chalk.blue('Flattening JSON for type generation'));
|
|
93
|
-
jsonObject = flattenJson(parsendJsonObject);
|
|
94
|
-
console.log(chalk.green(`Successfully read and parsed JSON from ${jsonFilePath}.`));
|
|
95
|
-
}
|
|
96
|
-
catch (fileError) {
|
|
97
|
-
if (fileError.code === 'ENOENT') {
|
|
98
|
-
throw new Error(`Target JSON file not found at ${jsonFilePath}. ` +
|
|
99
|
-
`Ensure content for language code '${firstLanguageCode}' has been pulled and exists locally, ` +
|
|
100
|
-
`or ensure 'contentDir' is not set if you intend to fetch from URL.`);
|
|
101
|
-
}
|
|
102
|
-
throw new Error(`Failed to read or parse JSON from ${jsonFilePath}: ${fileError.message}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
if (!config.contentKey) {
|
|
107
|
-
throw new Error('Cannot generate types: contentKey is missing');
|
|
108
|
-
}
|
|
109
|
-
let fileUrl;
|
|
110
|
-
const requestConfig = { responseType: 'json' };
|
|
111
|
-
if (config.pendingChanges) {
|
|
112
|
-
fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${firstLanguageCode}`;
|
|
113
|
-
requestConfig.headers = {
|
|
114
|
-
'X-Content-Key': config.contentKey,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${firstLanguageCode}.json`;
|
|
119
|
-
}
|
|
120
|
-
dataSourceDescription = `remote URL (${fileUrl})`;
|
|
121
|
-
console.log(chalk.blue(`Attempting to fetch JSON from: ${fileUrl}`));
|
|
122
|
-
try {
|
|
123
|
-
const response = await axios.get(fileUrl, requestConfig);
|
|
124
|
-
let jsonResponse = response.data;
|
|
125
|
-
// Handle API response structure - API returns { data: actualContent }
|
|
126
|
-
if (config.pendingChanges && jsonResponse && typeof jsonResponse === 'object' && 'data' in jsonResponse) {
|
|
127
|
-
jsonResponse = jsonResponse.data;
|
|
128
|
-
}
|
|
129
|
-
console.log(chalk.blue('Flattening JSON for type generation'));
|
|
130
|
-
jsonObject = flattenJson(jsonResponse);
|
|
131
|
-
if (typeof jsonObject !== 'object' || jsonObject === null) {
|
|
132
|
-
throw new Error(`Workspaceed data from ${fileUrl} is not a valid JSON object. Received type: ${typeof jsonObject}`);
|
|
133
|
-
}
|
|
134
|
-
console.log(chalk.green(`Successfully fetched and parsed JSON from ${fileUrl}. This content will not be saved locally.`));
|
|
135
|
-
}
|
|
136
|
-
catch (fetchError) {
|
|
137
|
-
let errorDetail = fetchError.message;
|
|
138
|
-
if (axios.isAxiosError(fetchError)) {
|
|
139
|
-
errorDetail = `Status: ${fetchError.response?.status}, Response: ${JSON.stringify(fetchError.response?.data)}`;
|
|
140
|
-
}
|
|
141
|
-
throw new Error(`Failed to fetch JSON from ${fileUrl}: ${errorDetail}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Validate the obtained jsonObject (must be an object or array for json-to-ts)
|
|
145
|
-
if (typeof jsonObject !== 'object' || jsonObject === null) {
|
|
146
|
-
// jsonToTS can handle root arrays too, but if it's primitive it's an issue.
|
|
147
|
-
// Allowing arrays here explicitly based on jsonToTS capability.
|
|
148
|
-
if (!Array.isArray(jsonObject)) {
|
|
149
|
-
throw new Error(`The content obtained from ${dataSourceDescription} is not a JSON object or array (type: ${typeof jsonObject}). Cannot generate types.`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// Generate TypeScript interfaces using json-to-ts
|
|
153
|
-
const rootTypeName = 'ContentRoot';
|
|
154
|
-
console.log(chalk.blue(`Generating TypeScript types with root name '${rootTypeName}'...`));
|
|
155
|
-
const typeDeclarations = jsonToTS(jsonObject, {
|
|
156
|
-
rootName: rootTypeName,
|
|
157
|
-
});
|
|
158
|
-
if (!typeDeclarations || typeDeclarations.length === 0) {
|
|
159
|
-
throw new Error(`Could not generate types from the content of ${dataSourceDescription}. 'json-to-ts' returned no declarations.`);
|
|
160
|
-
}
|
|
161
|
-
const outputContent = typeDeclarations.join('\n\n');
|
|
162
|
-
// Ensure the output directory exists for the types file
|
|
163
|
-
const outputDir = path.dirname(config.typesOutputFile);
|
|
164
|
-
await fs.mkdir(outputDir, { recursive: true });
|
|
165
|
-
// Write the generated types to the output file
|
|
166
|
-
await fs.writeFile(config.typesOutputFile, outputContent, 'utf-8');
|
|
167
|
-
console.log(chalk.green(`TypeScript types generated successfully at ${config.typesOutputFile} using data from ${dataSourceDescription}.`));
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
console.error(chalk.red.bold('\nError generating TypeScript types:'));
|
|
171
|
-
console.error(chalk.red(error.message));
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
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 '../lib/configLoader.js';
|
|
7
|
-
import { CONTENTSTORAGE_CONFIG } from '../contentstorage-config.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 && jsonData && typeof jsonData === 'object' && 'data' in jsonData) {
|
|
89
|
-
jsonData = jsonData.data;
|
|
90
|
-
}
|
|
91
|
-
// Basic check for data existence, although axios usually throws for non-2xx responses
|
|
92
|
-
if (jsonData === undefined || jsonData === null) {
|
|
93
|
-
throw new Error(`No data received from ${fileUrl} for language ${languageCode}.`);
|
|
94
|
-
}
|
|
95
|
-
// Validate that jsonData is a single, non-null JSON object (not an array)
|
|
96
|
-
// This check mirrors the original code's expectation for the content of a JSON file.
|
|
97
|
-
if (typeof jsonData !== 'object' ||
|
|
98
|
-
Array.isArray(jsonData) /* jsonData === null is already covered */) {
|
|
99
|
-
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.`);
|
|
100
|
-
}
|
|
101
|
-
console.log(chalk.green(`Received JSON for ${languageCode}. Saving to ${outputPath}`));
|
|
102
|
-
await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
|
103
|
-
console.log(chalk.green(`Successfully saved ${outputPath}`));
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
// Catch errors related to fetching or saving a single language file
|
|
107
|
-
console.error(chalk.red(`\nError processing language ${languageCode} from ${fileUrl}:`));
|
|
108
|
-
if (axios.isAxiosError(error)) {
|
|
109
|
-
console.error(chalk.red(` Status: ${error.response?.status}`));
|
|
110
|
-
console.error(chalk.red(`Response Data: ${error.response?.data ? JSON.stringify(error.response.data) : 'N/A'}`));
|
|
111
|
-
console.error(chalk.red(` Message: ${error.message}`)); // Axios error message
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
// For non-Axios errors (e.g., manually thrown errors, fs errors)
|
|
115
|
-
console.error(chalk.red(` Error: ${error.message}`));
|
|
116
|
-
}
|
|
117
|
-
// Re-throw the error to be caught by the outer try-catch block,
|
|
118
|
-
// which will then call process.exit(1), maintaining original exit behavior on error.
|
|
119
|
-
throw error;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
console.log(chalk.green('\nAll content successfully pulled and saved.'));
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
// Outer catch for setup errors (like loadConfig) or re-thrown errors from the loop
|
|
126
|
-
// The specific error details for a file operation would have been logged by the inner catch.
|
|
127
|
-
// This block provides a general failure message and ensures the process exits with an error code.
|
|
128
|
-
console.error(chalk.red('\n-----------------------------------------------------'));
|
|
129
|
-
console.error(chalk.red('Content pull failed due to an error. See details above.'));
|
|
130
|
-
// error.message from the re-thrown error will be implicitly part of the error object logged by some environments,
|
|
131
|
-
// or if you add console.error(error) here.
|
|
132
|
-
// The original code logged error.message at this level:
|
|
133
|
-
// if (error.message) console.error(chalk.red(`Underlying error: ${error.message}`));
|
|
134
|
-
console.error(chalk.red('-----------------------------------------------------'));
|
|
135
|
-
process.exit(1); // Exit with error code
|
|
136
|
-
}
|
|
137
|
-
}
|