@contentstorage/core 1.2.0 → 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.
package/README.md CHANGED
@@ -1,31 +1,33 @@
1
1
  # @contentstorage/core
2
2
 
3
- > A key-value based CMS core library that fetches content from ContentStorage and generates TypeScript types
3
+ > CLI tool for managing translations and generating TypeScript types from ContentStorage
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@contentstorage/core.svg)](https://www.npmjs.com/package/@contentstorage/core)
6
6
  [![License](https://img.shields.io/npm/l/@contentstorage/core.svg)](https://github.com/kaidohussar/contentstorage-core/blob/master/LICENSE)
7
7
 
8
+ ## Overview
9
+
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
+
8
12
  ## Features
9
13
 
10
- - **Key-Value Storage** - Organize content with hierarchical dot-notation paths
14
+ - **Translation Management** - Pull content from ContentStorage CDN
15
+ - **TypeScript Generation** - Automatic type generation from your content
16
+ - **Translation Statistics** - Analyze translation completeness across languages
11
17
  - **Multi-Language Support** - Built-in support for 40+ languages
12
- - **TypeScript First** - Automatic type generation from your content
13
- - **Type Safety** - Full autocompletion and type checking for content access
14
- - **Live Editor Integration** - Real-time content editing without page reload
15
- - **Special Content Types** - Support for text, images, and variations
16
- - **Variable Substitution** - Dynamic content with template variables
17
- - **CLI Tools** - Easy content management with professional CLI
18
+ - **CLI Tools** - Professional command-line interface
19
+ - **Plugin Ecosystem** - Integrate with i18next, react-intl, vue-i18n, and more
18
20
 
19
21
  ## Installation
20
22
 
21
23
  ```bash
22
- npm install @contentstorage/core
24
+ npm install -D @contentstorage/core
23
25
  ```
24
26
 
25
27
  or
26
28
 
27
29
  ```bash
28
- yarn add @contentstorage/core
30
+ yarn add -D @contentstorage/core
29
31
  ```
30
32
 
31
33
  ## Quick Start
@@ -53,30 +55,23 @@ npx contentstorage pull
53
55
  npx contentstorage generate-types
54
56
  ```
55
57
 
56
- ### 3. Initialize and Use in Your App
57
-
58
- ```typescript
59
- import { initContentStorage, fetchContent, getText, getImage } from '@contentstorage/core';
60
-
61
- // Initialize
62
- initContentStorage({
63
- contentKey: 'your-content-key',
64
- languageCodes: ['EN', 'FR', 'DE']
65
- });
66
-
67
- // Fetch content for a language
68
- await fetchContent('EN');
58
+ ### 3. Use with Your i18n Library
69
59
 
70
- // Access content with full type safety
71
- const title = getText('HomePage.title');
72
- // { contentId: 'HomePage.title', text: 'Welcome!' }
60
+ **With i18next:**
61
+ ```bash
62
+ npm install @contentstorage/plugin-i18next
63
+ npx contentstorage-i18next export
64
+ ```
73
65
 
74
- const heroImage = getImage('HomePage.hero');
75
- // → { contentId: 'HomePage.hero', data: { url: '...', altText: '...' } }
66
+ **With react-intl:**
67
+ ```bash
68
+ npm install @contentstorage/plugin-react-intl
69
+ npx contentstorage-react-intl export
70
+ ```
76
71
 
77
- // Use variables in text
78
- const greeting = getText('HomePage.greeting', { name: 'John' });
79
- // { contentId: 'HomePage.greeting', text: 'Hello John!' }
72
+ **With ContentStorage SDK (for advanced features like variations and images):**
73
+ ```bash
74
+ npm install @contentstorage/sdk
80
75
  ```
81
76
 
82
77
  ## CLI Commands
@@ -122,6 +117,39 @@ npx contentstorage generate-types --output src/types.ts
122
117
  npx contentstorage generate-types --content-key abc123 --lang EN
123
118
  ```
124
119
 
120
+ ### `contentstorage stats`
121
+
122
+ Show translation completeness statistics across all configured languages
123
+
124
+ ```bash
125
+ npx contentstorage stats [options]
126
+ ```
127
+
128
+ **Options:**
129
+ - `--content-key <key>` - Content key for your project
130
+ - `--content-dir <dir>` - Directory with content files
131
+ - `--pending-changes` - Analyze pending/draft content
132
+
133
+ **Examples:**
134
+ ```bash
135
+ npx contentstorage stats
136
+ npx contentstorage stats --content-key abc123
137
+ npx contentstorage stats --pending-changes
138
+ ```
139
+
140
+ **What it shows:**
141
+ - Total number of content items per language
142
+ - Number of translated vs untranslated items
143
+ - Completion percentage for each language
144
+ - Detailed list of untranslated item IDs grouped by language
145
+ - Overall translation completion across all languages
146
+
147
+ **What counts as "untranslated":**
148
+ - Empty strings (`""`)
149
+ - Keys that exist in the reference language but are missing in target languages
150
+
151
+ The stats command uses the first language in your `languageCodes` array as the reference/baseline for comparison.
152
+
125
153
  ### `contentstorage --help`
126
154
 
127
155
  Show all available commands and options
@@ -130,6 +158,7 @@ Show all available commands and options
130
158
  npx contentstorage --help
131
159
  npx contentstorage pull --help
132
160
  npx contentstorage generate-types --help
161
+ npx contentstorage stats --help
133
162
  ```
134
163
 
135
164
  ## Configuration
@@ -157,106 +186,47 @@ export default {
157
186
 
158
187
  ### Supported Languages
159
188
 
160
- The library supports 40+ languages including:
189
+ The CLI supports 40+ languages including:
161
190
  EN, FR, DE, ES, IT, PT, NL, PL, RU, TR, SV, NO, DA, FI, CS, SK, HU, RO, BG, HR, SL, SR, and more.
162
191
 
163
- ## API Reference
164
-
165
- ### Initialization
192
+ ## Integration Options
166
193
 
167
- #### `initContentStorage(config)`
194
+ ### Option 1: Use with i18n Libraries (Recommended for most projects)
168
195
 
169
- Initialize the content storage system.
196
+ For standard i18n needs, use ContentStorage CLI with popular i18n libraries:
170
197
 
171
- ```typescript
172
- initContentStorage({
173
- contentKey: string,
174
- languageCodes: LanguageCode[]
175
- });
176
- ```
198
+ - **[@contentstorage/plugin-i18next](https://www.npmjs.com/package/@contentstorage/plugin-i18next)** - i18next integration
199
+ - **[@contentstorage/plugin-react-intl](https://www.npmjs.com/package/@contentstorage/plugin-react-intl)** - react-intl (FormatJS) integration
200
+ - **[@contentstorage/plugin-vue-i18n](https://www.npmjs.com/package/@contentstorage/plugin-vue-i18n)** - Vue i18n integration
177
201
 
178
- #### `setContentLanguage(config)`
202
+ ### Option 2: Use with ContentStorage SDK (Advanced features)
179
203
 
180
- Set content for a specific language.
204
+ If you need advanced features like variations (A/B testing) and image management:
181
205
 
182
- ```typescript
183
- setContentLanguage({
184
- languageCode: LanguageCode,
185
- contentJson: object
186
- });
187
- ```
188
-
189
- ### Content Retrieval
190
-
191
- #### `getText<Path>(contentId, variables?)`
192
-
193
- Get localized text with optional variable substitution.
194
-
195
- ```typescript
196
- const result = getText('HomePage.title');
197
- // → { contentId: 'HomePage.title', text: 'Welcome!' }
198
-
199
- const greeting = getText('HomePage.greeting', { name: 'John', count: 5 });
200
- // → { contentId: 'HomePage.greeting', text: 'Hello John, you have 5 items' }
201
- ```
202
-
203
- **Returns:** `{ contentId: string, text: string }`
204
-
205
- #### `getImage(contentId)`
206
-
207
- Get image content with CDN URL.
208
-
209
- ```typescript
210
- const image = getImage('HomePage.hero');
211
- // → {
212
- // contentId: 'HomePage.hero',
213
- // data: {
214
- // contentstorage_type: 'image',
215
- // url: 'https://cdn.contentstorage.app/...',
216
- // altText: 'Hero image'
217
- // }
218
- // }
219
- ```
220
-
221
- **Returns:** `{ contentId: string, data: ImageObject } | undefined`
222
-
223
- #### `getVariation(contentId, variationKey?, variables?)`
224
-
225
- Get content variation for A/B testing.
226
-
227
- ```typescript
228
- const cta = getVariation('HomePage.cta', 'mobile');
229
- // → { contentId: 'HomePage.cta', text: 'Tap Now' }
230
-
231
- // Defaults to 'default' variation if not specified
232
- const ctaDefault = getVariation('HomePage.cta');
206
+ ```bash
207
+ npm install @contentstorage/sdk
233
208
  ```
234
209
 
235
- **Returns:** `{ contentId: string, text: string }`
210
+ See [@contentstorage/sdk](https://www.npmjs.com/package/@contentstorage/sdk) for documentation on:
211
+ - Content variations (A/B testing)
212
+ - Image management with CDN URLs
213
+ - Live editor integration
214
+ - Runtime content fetching
236
215
 
237
- ### Content Fetching
216
+ ## TypeScript Support
238
217
 
239
- #### `fetchContent(languageCode)`
240
-
241
- Fetch content from ContentStorage CDN.
218
+ After running `generate-types`, you get full TypeScript support:
242
219
 
243
220
  ```typescript
244
- await fetchContent('EN');
245
- ```
221
+ // Generated types augment the ContentStructure interface
222
+ import type { ContentStructure } from '@contentstorage/core';
246
223
 
247
- ## TypeScript Integration
248
-
249
- The library uses interface augmentation for type-safe content access:
250
-
251
- ```typescript
252
- // After running: npx contentstorage generate-types
253
- // The generated types augment the ContentStructure interface
254
-
255
- import { getText } from '@contentstorage/core';
224
+ // Use with your i18n library
225
+ import i18next from 'i18next';
256
226
 
257
227
  // TypeScript knows all available content paths
258
- const title = getText('HomePage.title'); // ✅ Autocomplete works
259
- const invalid = getText('Invalid.path'); // ❌ TypeScript error
228
+ i18next.t('HomePage.title'); // ✅ Autocomplete works
229
+ i18next.t('Invalid.path'); // ❌ TypeScript error
260
230
  ```
261
231
 
262
232
  ## Content Structure
@@ -268,28 +238,20 @@ Content is organized in a hierarchical key-value structure:
268
238
  "HomePage": {
269
239
  "title": "Welcome to Our App",
270
240
  "greeting": "Hello {name}, you have {count} items",
271
- "hero": {
272
- "contentstorage_type": "image",
273
- "url": "hero.jpg",
274
- "altText": "Hero image"
275
- },
276
- "cta": {
277
- "contentstorage_type": "variation",
278
- "data": {
279
- "default": "Click Here",
280
- "mobile": "Tap Now",
281
- "desktop": "Click to Continue"
282
- }
283
- }
241
+ "description": "Get started with our platform"
242
+ },
243
+ "Navigation": {
244
+ "home": "Home",
245
+ "about": "About",
246
+ "contact": "Contact"
284
247
  }
285
248
  }
286
249
  ```
287
250
 
288
251
  **Access with dot notation:**
289
- - `getText('HomePage.title')` → "Welcome to Our App"
290
- - `getText('HomePage.greeting', { name: 'John', count: 5 })` → "Hello John, you have 5 items"
291
- - `getImage('HomePage.hero')` → Image object with CDN URL
292
- - `getVariation('HomePage.cta', 'mobile')` → "Tap Now"
252
+ - `HomePage.title` → "Welcome to Our App"
253
+ - `HomePage.greeting` → "Hello {name}, you have {count} items"
254
+ - `Navigation.home` → "Home"
293
255
 
294
256
  ## Package.json Scripts
295
257
 
@@ -314,16 +276,43 @@ npm run content:types # Generate types
314
276
  npm run content:sync # Pull and generate in one command
315
277
  ```
316
278
 
317
- ## Integration with React
279
+ ## Workflow Example
280
+
281
+ 1. **Pull latest content:**
282
+ ```bash
283
+ npx contentstorage pull
284
+ ```
285
+
286
+ 2. **Generate TypeScript types:**
287
+ ```bash
288
+ npx contentstorage generate-types
289
+ ```
290
+
291
+ 3. **Use in your app with i18next:**
292
+ ```typescript
293
+ import i18next from 'i18next';
294
+ import enContent from './content/json/EN.json';
295
+
296
+ i18next.init({
297
+ lng: 'EN',
298
+ resources: {
299
+ EN: { translation: enContent }
300
+ }
301
+ });
302
+
303
+ // Use translations
304
+ const title = i18next.t('HomePage.title');
305
+ ```
318
306
 
319
- For React integration, use [@contentstorage/react](https://www.npmjs.com/package/@contentstorage/react) which builds on top of this core library and provides React-specific components and hooks.
307
+ ## SDK Extract
320
308
 
321
- ## Live Editor Mode
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
+ - getText/getImage/getVariation functions
311
+ - Content variations (A/B testing)
312
+ - Image management
313
+ - Live editor integration
322
314
 
323
- When running in an iframe with live editor parameters, the library automatically:
324
- - Loads the live editor script from CDN
325
- - Tracks content usage via `window.memoryMap`
326
- - Enables real-time content updates without page reload
315
+ To use the SDK, it will be published as `@contentstorage/sdk` in a separate package.
327
316
 
328
317
  ## Requirements
329
318
 
@@ -346,4 +335,4 @@ Kaido Hussar - [kaidohus@gmail.com](mailto:kaidohus@gmail.com)
346
335
 
347
336
  ## Support
348
337
 
349
- 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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import { pullContent } from './pull.js';
4
+ import { generateTypes } from './generate-types.js';
5
+ import { showStats } from './stats.js';
6
+ const COMMANDS = {
7
+ pull: {
8
+ name: 'pull',
9
+ description: 'Pull content from Contentstorage CDN',
10
+ usage: 'contentstorage pull [options]',
11
+ options: [
12
+ ' --content-key <key> Content key for your project',
13
+ ' --content-dir <dir> Directory to save content files',
14
+ ' --lang <code> Language code (e.g., EN, FR)',
15
+ ' --pending-changes Fetch pending/draft content',
16
+ ],
17
+ },
18
+ 'generate-types': {
19
+ name: 'generate-types',
20
+ description: 'Generate TypeScript type definitions from content',
21
+ usage: 'contentstorage generate-types [options]',
22
+ options: [
23
+ ' --output <file> Output file for generated types',
24
+ ' --content-key <key> Content key for your project',
25
+ ' --lang <code> Language code (e.g., EN, FR)',
26
+ ' --pending-changes Use pending/draft content',
27
+ ],
28
+ },
29
+ stats: {
30
+ name: 'stats',
31
+ description: 'Show translation completeness statistics',
32
+ usage: 'contentstorage stats [options]',
33
+ options: [
34
+ ' --content-key <key> Content key for your project',
35
+ ' --content-dir <dir> Directory with content files',
36
+ ' --pending-changes Analyze pending/draft content',
37
+ ],
38
+ },
39
+ };
40
+ function showHelp() {
41
+ console.log(chalk.bold('\nContentStorage CLI'));
42
+ console.log(chalk.dim('Manage content and generate TypeScript types\n'));
43
+ console.log(chalk.bold('Usage:'));
44
+ console.log(' contentstorage <command> [options]\n');
45
+ console.log(chalk.bold('Commands:'));
46
+ Object.values(COMMANDS).forEach((cmd) => {
47
+ console.log(` ${chalk.cyan(cmd.name.padEnd(20))} ${cmd.description}`);
48
+ });
49
+ console.log(chalk.bold('\nOptions:'));
50
+ console.log(' --help Show help for a command\n');
51
+ console.log(chalk.dim('Examples:'));
52
+ console.log(chalk.dim(' contentstorage pull --content-key abc123'));
53
+ console.log(chalk.dim(' contentstorage generate-types --output types.ts'));
54
+ console.log(chalk.dim(' contentstorage pull --help # Show help for pull command\n'));
55
+ }
56
+ function showCommandHelp(commandName) {
57
+ const cmd = COMMANDS[commandName];
58
+ if (!cmd) {
59
+ console.error(chalk.red(`Unknown command: ${commandName}`));
60
+ process.exit(1);
61
+ }
62
+ console.log(chalk.bold(`\n${cmd.name}`));
63
+ console.log(chalk.dim(cmd.description + '\n'));
64
+ console.log(chalk.bold('Usage:'));
65
+ console.log(` ${cmd.usage}\n`);
66
+ if (cmd.options.length > 0) {
67
+ console.log(chalk.bold('Options:'));
68
+ cmd.options.forEach((opt) => console.log(opt));
69
+ console.log('');
70
+ }
71
+ }
72
+ async function main() {
73
+ const args = process.argv.slice(2);
74
+ // No arguments - show help
75
+ if (args.length === 0) {
76
+ showHelp();
77
+ process.exit(0);
78
+ }
79
+ const command = args[0];
80
+ // Global --help flag
81
+ if (command === '--help' || command === '-h') {
82
+ showHelp();
83
+ process.exit(0);
84
+ }
85
+ // Command-specific --help
86
+ if (args.includes('--help') || args.includes('-h')) {
87
+ showCommandHelp(command);
88
+ process.exit(0);
89
+ }
90
+ // Route to commands
91
+ switch (command) {
92
+ case 'pull':
93
+ await pullContent();
94
+ break;
95
+ case 'generate-types':
96
+ await generateTypes();
97
+ break;
98
+ case 'stats':
99
+ await showStats();
100
+ break;
101
+ default:
102
+ console.error(chalk.red(`Unknown command: ${command}\n`));
103
+ console.log(chalk.dim('Run "contentstorage --help" for usage'));
104
+ process.exit(1);
105
+ }
106
+ }
107
+ main().catch((error) => {
108
+ console.error(chalk.red('Unexpected error:'), error);
109
+ process.exit(1);
110
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function generateTypes(): Promise<void>;
@@ -0,0 +1,177 @@
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 '../core/config-loader.js';
8
+ import { flattenJson } from '../utils/flatten-json.js';
9
+ import { CONTENTSTORAGE_CONFIG } from '../utils/constants.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 &&
127
+ jsonResponse &&
128
+ typeof jsonResponse === 'object' &&
129
+ 'data' in jsonResponse) {
130
+ jsonResponse = jsonResponse.data;
131
+ }
132
+ console.log(chalk.blue('Flattening JSON for type generation'));
133
+ jsonObject = flattenJson(jsonResponse);
134
+ if (typeof jsonObject !== 'object' || jsonObject === null) {
135
+ throw new Error(`Workspaceed data from ${fileUrl} is not a valid JSON object. Received type: ${typeof jsonObject}`);
136
+ }
137
+ console.log(chalk.green(`Successfully fetched and parsed JSON from ${fileUrl}. This content will not be saved locally.`));
138
+ }
139
+ catch (fetchError) {
140
+ let errorDetail = fetchError.message;
141
+ if (axios.isAxiosError(fetchError)) {
142
+ errorDetail = `Status: ${fetchError.response?.status}, Response: ${JSON.stringify(fetchError.response?.data)}`;
143
+ }
144
+ throw new Error(`Failed to fetch JSON from ${fileUrl}: ${errorDetail}`);
145
+ }
146
+ }
147
+ // Validate the obtained jsonObject (must be an object or array for json-to-ts)
148
+ if (typeof jsonObject !== 'object' || jsonObject === null) {
149
+ // jsonToTS can handle root arrays too, but if it's primitive it's an issue.
150
+ // Allowing arrays here explicitly based on jsonToTS capability.
151
+ if (!Array.isArray(jsonObject)) {
152
+ throw new Error(`The content obtained from ${dataSourceDescription} is not a JSON object or array (type: ${typeof jsonObject}). Cannot generate types.`);
153
+ }
154
+ }
155
+ // Generate TypeScript interfaces using json-to-ts
156
+ const rootTypeName = 'ContentRoot';
157
+ console.log(chalk.blue(`Generating TypeScript types with root name '${rootTypeName}'...`));
158
+ const typeDeclarations = jsonToTS(jsonObject, {
159
+ rootName: rootTypeName,
160
+ });
161
+ if (!typeDeclarations || typeDeclarations.length === 0) {
162
+ throw new Error(`Could not generate types from the content of ${dataSourceDescription}. 'json-to-ts' returned no declarations.`);
163
+ }
164
+ const outputContent = typeDeclarations.join('\n\n');
165
+ // Ensure the output directory exists for the types file
166
+ const outputDir = path.dirname(config.typesOutputFile);
167
+ await fs.mkdir(outputDir, { recursive: true });
168
+ // Write the generated types to the output file
169
+ await fs.writeFile(config.typesOutputFile, outputContent, 'utf-8');
170
+ console.log(chalk.green(`TypeScript types generated successfully at ${config.typesOutputFile} using data from ${dataSourceDescription}.`));
171
+ }
172
+ catch (error) {
173
+ console.error(chalk.red.bold('\nError generating TypeScript types:'));
174
+ console.error(chalk.red(error.message));
175
+ process.exit(1);
176
+ }
177
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function pullContent(): Promise<void>;