@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 +128 -139
- package/dist/commands/cli.d.ts +2 -0
- package/dist/commands/cli.js +110 -0
- package/dist/commands/generate-types.d.ts +2 -0
- package/dist/commands/generate-types.js +177 -0
- package/dist/commands/pull.d.ts +2 -0
- package/dist/commands/pull.js +140 -0
- package/dist/commands/stats.d.ts +4 -0
- package/dist/commands/stats.js +268 -0
- package/dist/core/config-loader.d.ts +2 -0
- package/dist/core/config-loader.js +42 -0
- package/dist/lib/functions/fetchContent.js +77 -1
- package/dist/type-generation/get-names.js +14 -11
- package/dist/type-generation/util.js +7 -7
- package/dist/types.d.ts +0 -24
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/flatten-json.d.ts +1 -0
- package/dist/utils/flatten-json.js +56 -0
- package/package.json +5 -12
package/README.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
# @contentstorage/core
|
|
2
2
|
|
|
3
|
-
>
|
|
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
|
+
## 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
|
-
- **
|
|
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
|
-
- **
|
|
13
|
-
- **
|
|
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.
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
**With i18next:**
|
|
61
|
+
```bash
|
|
62
|
+
npm install @contentstorage/plugin-i18next
|
|
63
|
+
npx contentstorage-i18next export
|
|
64
|
+
```
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
**With react-intl:**
|
|
67
|
+
```bash
|
|
68
|
+
npm install @contentstorage/plugin-react-intl
|
|
69
|
+
npx contentstorage-react-intl export
|
|
70
|
+
```
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
-
##
|
|
164
|
-
|
|
165
|
-
### Initialization
|
|
192
|
+
## Integration Options
|
|
166
193
|
|
|
167
|
-
|
|
194
|
+
### Option 1: Use with i18n Libraries (Recommended for most projects)
|
|
168
195
|
|
|
169
|
-
|
|
196
|
+
For standard i18n needs, use ContentStorage CLI with popular i18n libraries:
|
|
170
197
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
202
|
+
### Option 2: Use with ContentStorage SDK (Advanced features)
|
|
179
203
|
|
|
180
|
-
|
|
204
|
+
If you need advanced features like variations (A/B testing) and image management:
|
|
181
205
|
|
|
182
|
-
```
|
|
183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
+
## TypeScript Support
|
|
238
217
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Fetch content from ContentStorage CDN.
|
|
218
|
+
After running `generate-types`, you get full TypeScript support:
|
|
242
219
|
|
|
243
220
|
```typescript
|
|
244
|
-
|
|
245
|
-
|
|
221
|
+
// Generated types augment the ContentStructure interface
|
|
222
|
+
import type { ContentStructure } from '@contentstorage/core';
|
|
246
223
|
|
|
247
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
"
|
|
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
|
-
- `
|
|
290
|
-
- `
|
|
291
|
-
- `
|
|
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
|
-
##
|
|
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
|
-
|
|
307
|
+
## SDK Extract
|
|
320
308
|
|
|
321
|
-
|
|
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
|
-
|
|
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,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,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
|
+
}
|