@contentstorage/core 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function flattenJson(data: any, prefix?: string, result?: Record<string, any>): Record<string, any>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function flattenJson(data, prefix = '', result = {}) {
|
|
2
|
+
if (typeof data === 'object' && data !== null) {
|
|
3
|
+
if (Array.isArray(data)) {
|
|
4
|
+
if (data.length === 0 && prefix) {
|
|
5
|
+
// Handle empty arrays if prefix exists
|
|
6
|
+
result[prefix] = [];
|
|
7
|
+
}
|
|
8
|
+
data.forEach((item, index) => {
|
|
9
|
+
flattenJson(item, prefix ? `${prefix}.${index}` : `${index}`, result);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
// It's an object
|
|
14
|
+
let isEmptyObject = true;
|
|
15
|
+
for (const key in data) {
|
|
16
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
17
|
+
isEmptyObject = false;
|
|
18
|
+
const newPrefix = prefix ? `${prefix}.${key}` : key;
|
|
19
|
+
flattenJson(data[key], newPrefix, result);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (isEmptyObject && prefix) {
|
|
23
|
+
// Handle empty objects if prefix exists
|
|
24
|
+
result[prefix] = {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (prefix) {
|
|
29
|
+
// Primitive value (string, number, boolean, null)
|
|
30
|
+
result[prefix] = data;
|
|
31
|
+
}
|
|
32
|
+
// If the initial data itself is a primitive and prefix is empty, it means we can't flatten it into key-value pairs.
|
|
33
|
+
// This function is designed to take a root object or array.
|
|
34
|
+
// If the root `data` is a primitive, `result` would remain empty, which is fine; `pullContent` should validate the root.
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
@@ -2,76 +2,132 @@
|
|
|
2
2
|
// ^ Shebang ensures the script is executed with Node.js
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import axios from 'axios';
|
|
5
6
|
import jsonToTS from 'json-to-ts'; // Import the library
|
|
6
7
|
import chalk from 'chalk'; // Optional: for colored output
|
|
7
8
|
import { loadConfig } from '../lib/configLoader.js';
|
|
9
|
+
import { flattenJson } from '../helpers/flattenJson.js';
|
|
8
10
|
export async function generateTypes() {
|
|
9
11
|
console.log(chalk.blue('Starting type generation...'));
|
|
10
|
-
const config = await loadConfig();
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const config = await loadConfig();
|
|
13
|
+
if (!config.typesOutputFile) {
|
|
14
|
+
console.error(chalk.red.bold("Configuration error: 'typesOutputFile' is missing."));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!config.languageCodes ||
|
|
18
|
+
!Array.isArray(config.languageCodes) ||
|
|
19
|
+
config.languageCodes.length === 0) {
|
|
20
|
+
console.error(chalk.red.bold("Configuration error: 'languageCodes' must be a non-empty array."));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk.gray(`TypeScript types will be saved to: ${config.typesOutputFile}`));
|
|
24
|
+
let jsonObject; // To hold the JSON data from either local or remote source
|
|
25
|
+
let dataSourceDescription = ''; // For clearer logging
|
|
26
|
+
const firstLanguageCode = config.languageCodes[0];
|
|
13
27
|
try {
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
28
|
+
let attemptLocalLoad = false;
|
|
29
|
+
if (config.contentDir) {
|
|
30
|
+
try {
|
|
31
|
+
await fs.stat(config.contentDir); // Check if directory exists
|
|
32
|
+
attemptLocalLoad = true;
|
|
33
|
+
console.log(chalk.gray(`Local content directory found: ${config.contentDir}`));
|
|
34
|
+
}
|
|
35
|
+
catch (statError) {
|
|
36
|
+
if (statError.code === 'ENOENT') {
|
|
37
|
+
console.log(chalk.yellow(`Local content directory specified but not found: ${config.contentDir}. Will attempt to fetch from URL.`));
|
|
38
|
+
// attemptLocalLoad remains false
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Other errors accessing contentDir (e.g., permissions)
|
|
42
|
+
throw new Error(`Error accessing content directory ${config.contentDir}: ${statError.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
19
45
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const jsonFilePath = path.join(config.contentDir, targetFilename);
|
|
23
|
-
console.log(chalk.gray(`Attempting to generate types using the JSON file for the first configured language code ('${firstLanguageCode}').`));
|
|
24
|
-
console.log(chalk.gray(`Target file: ${jsonFilePath}`));
|
|
25
|
-
// Read the specific JSON file
|
|
26
|
-
let jsonContent;
|
|
27
|
-
try {
|
|
28
|
-
jsonContent = await fs.readFile(jsonFilePath, 'utf-8');
|
|
46
|
+
else {
|
|
47
|
+
console.log(chalk.yellow(`Local content directory (config.contentDir) not specified. Attempting to fetch from URL.`));
|
|
29
48
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
if (attemptLocalLoad && config.contentDir) {
|
|
50
|
+
// --- Load from local file system ---
|
|
51
|
+
const targetFilename = `${firstLanguageCode}.json`;
|
|
52
|
+
const jsonFilePath = path.join(config.contentDir, targetFilename);
|
|
53
|
+
dataSourceDescription = `local file (${jsonFilePath})`;
|
|
54
|
+
console.log(chalk.gray(`Attempting to read JSON from: ${jsonFilePath}`));
|
|
55
|
+
try {
|
|
56
|
+
const jsonContentString = await fs.readFile(jsonFilePath, 'utf-8');
|
|
57
|
+
console.log(chalk.gray('Parsing JSON'));
|
|
58
|
+
const parsendJsonObject = JSON.parse(jsonContentString);
|
|
59
|
+
console.log(chalk.gray('Flattening JSON for type generation'));
|
|
60
|
+
jsonObject = flattenJson(parsendJsonObject);
|
|
61
|
+
console.log(chalk.green(`Successfully read and parsed JSON from ${jsonFilePath}.`));
|
|
62
|
+
}
|
|
63
|
+
catch (fileError) {
|
|
64
|
+
if (fileError.code === 'ENOENT') {
|
|
65
|
+
throw new Error(`Target JSON file not found at ${jsonFilePath}. ` +
|
|
66
|
+
`Ensure content for language code '${firstLanguageCode}' has been pulled and exists locally, ` +
|
|
67
|
+
`or ensure 'contentDir' is not set if you intend to fetch from URL.`);
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Failed to read or parse JSON from ${jsonFilePath}: ${fileError.message}`);
|
|
33
70
|
}
|
|
34
|
-
// Re-throw other fs.readFile errors (e.g., permission issues)
|
|
35
|
-
throw new Error(`Failed to read file ${jsonFilePath}: ${err.message}`);
|
|
36
71
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
else {
|
|
73
|
+
// --- Fetch from URL ---
|
|
74
|
+
if (!config.contentUrl) {
|
|
75
|
+
throw new Error("Cannot generate types: 'contentDir' is not accessible or not specified, and 'contentUrl' is also missing in configuration.");
|
|
76
|
+
}
|
|
77
|
+
const fileUrl = `${config.contentUrl}/${firstLanguageCode}.json`; // Adjust URL construction if necessary
|
|
78
|
+
dataSourceDescription = `remote URL (${fileUrl})`;
|
|
79
|
+
console.log(chalk.gray(`Attempting to fetch JSON from: ${fileUrl}`));
|
|
80
|
+
try {
|
|
81
|
+
const response = await axios.get(fileUrl, { responseType: 'json' });
|
|
82
|
+
const jsonResponse = response.data;
|
|
83
|
+
console.log(chalk.gray('Flattening JSON for type generation'));
|
|
84
|
+
jsonObject = flattenJson(jsonResponse);
|
|
85
|
+
if (typeof jsonObject !== 'object' || jsonObject === null) {
|
|
86
|
+
throw new Error(`Workspaceed data from ${fileUrl} is not a valid JSON object. Received type: ${typeof jsonObject}`);
|
|
87
|
+
}
|
|
88
|
+
console.log(chalk.green(`Successfully fetched and parsed JSON from ${fileUrl}. This content will not be saved locally.`));
|
|
89
|
+
}
|
|
90
|
+
catch (fetchError) {
|
|
91
|
+
let errorDetail = fetchError.message;
|
|
92
|
+
if (axios.isAxiosError(fetchError)) {
|
|
93
|
+
errorDetail = `Status: ${fetchError.response?.status}, Response: ${JSON.stringify(fetchError.response?.data)}`;
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`Failed to fetch JSON from ${fileUrl}: ${errorDetail}`);
|
|
96
|
+
}
|
|
41
97
|
}
|
|
42
|
-
|
|
43
|
-
|
|
98
|
+
// Validate the obtained jsonObject (must be an object or array for json-to-ts)
|
|
99
|
+
if (typeof jsonObject !== 'object' || jsonObject === null) {
|
|
100
|
+
// jsonToTS can handle root arrays too, but if it's primitive it's an issue.
|
|
101
|
+
// Allowing arrays here explicitly based on jsonToTS capability.
|
|
102
|
+
if (!Array.isArray(jsonObject)) {
|
|
103
|
+
throw new Error(`The content obtained from ${dataSourceDescription} is not a JSON object or array (type: ${typeof jsonObject}). Cannot generate types.`);
|
|
104
|
+
}
|
|
44
105
|
}
|
|
45
106
|
// Generate TypeScript interfaces using json-to-ts
|
|
46
|
-
|
|
47
|
-
|
|
107
|
+
const rootTypeName = 'ContentRoot'; // As per your previous update
|
|
108
|
+
console.log(chalk.gray(`Generating TypeScript types with root name '${rootTypeName}'...`));
|
|
48
109
|
const typeDeclarations = jsonToTS.default(jsonObject, {
|
|
49
110
|
rootName: rootTypeName,
|
|
50
111
|
});
|
|
51
|
-
// If your previous code `jsonToTS.default(...)` was correct for your setup,
|
|
52
|
-
// please revert the line above to:
|
|
53
|
-
// const typeDeclarations: string[] = jsonToTS.default(jsonObject, {
|
|
54
|
-
// rootName: rootTypeName,
|
|
55
|
-
// });
|
|
56
112
|
if (!typeDeclarations || typeDeclarations.length === 0) {
|
|
57
|
-
throw new Error(`Could not generate types from ${
|
|
113
|
+
throw new Error(`Could not generate types from the content of ${dataSourceDescription}. 'json-to-ts' returned no declarations.`);
|
|
58
114
|
}
|
|
59
|
-
const outputContent = typeDeclarations.join('\n\n');
|
|
60
|
-
// Ensure the output directory exists
|
|
115
|
+
const outputContent = typeDeclarations.join('\n\n');
|
|
116
|
+
// Ensure the output directory exists for the types file
|
|
61
117
|
const outputDir = path.dirname(config.typesOutputFile);
|
|
62
118
|
await fs.mkdir(outputDir, { recursive: true });
|
|
63
119
|
// Write the generated types to the output file
|
|
64
120
|
await fs.writeFile(config.typesOutputFile, outputContent, 'utf-8');
|
|
65
|
-
console.log(chalk.green(`TypeScript types generated successfully at ${config.typesOutputFile}
|
|
121
|
+
console.log(chalk.green(`TypeScript types generated successfully at ${config.typesOutputFile} using data from ${dataSourceDescription}.`));
|
|
66
122
|
}
|
|
67
123
|
catch (error) {
|
|
68
124
|
console.error(chalk.red.bold('\nError generating TypeScript types:'));
|
|
69
125
|
console.error(chalk.red(error.message));
|
|
70
|
-
//
|
|
71
|
-
// if (error.stack) {
|
|
126
|
+
// Optionally log stack for more details during development
|
|
127
|
+
// if (error.stack && process.env.NODE_ENV === 'development') {
|
|
72
128
|
// console.error(chalk.gray(error.stack));
|
|
73
129
|
// }
|
|
74
|
-
process.exit(1);
|
|
130
|
+
process.exit(1);
|
|
75
131
|
}
|
|
76
132
|
}
|
|
77
133
|
generateTypes();
|
|
@@ -43,6 +43,9 @@ export async function pullContent() {
|
|
|
43
43
|
Array.isArray(jsonData) /* jsonData === null is already covered */) {
|
|
44
44
|
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.`);
|
|
45
45
|
}
|
|
46
|
+
console.log(chalk.green(`Received and json for ${languageCode}. Saving to ${outputPath}`));
|
|
47
|
+
await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
|
48
|
+
console.log(chalk.green(`Successfully saved ${outputPath}`));
|
|
46
49
|
console.log(chalk.green(`Received JSON for ${languageCode}. Saving to ${outputPath}`));
|
|
47
50
|
// Write the JSON data to the file
|
|
48
51
|
await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@contentstorage/core",
|
|
3
3
|
"author": "Kaido Hussar <kaidohus@gmail.com>",
|
|
4
4
|
"homepage": "https://contentstorage.app",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.16",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Fetch content from contentstorage and generate TypeScript types",
|
|
8
8
|
"module": "dist/index.js",
|