@contentstorage/core 0.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/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -0
- package/dist/lib/configLoader.d.ts +6 -0
- package/dist/lib/configLoader.js +51 -0
- package/dist/scripts/generate-types.d.ts +2 -0
- package/dist/scripts/generate-types.js +74 -0
- package/dist/scripts/pull-content.d.ts +2 -0
- package/dist/scripts/pull-content.js +70 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Kaido Hussar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# contentstorage-core
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pullContent = exports.generateTypes = exports.loadConfig = void 0;
|
|
4
|
+
var configLoader_1 = require("./lib/configLoader");
|
|
5
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return configLoader_1.loadConfig; } });
|
|
6
|
+
var generate_types_1 = require("./scripts/generate-types");
|
|
7
|
+
Object.defineProperty(exports, "generateTypes", { enumerable: true, get: function () { return generate_types_1.generateTypes; } });
|
|
8
|
+
var pull_content_1 = require("./scripts/pull-content");
|
|
9
|
+
Object.defineProperty(exports, "pullContent", { enumerable: true, get: function () { return pull_content_1.pullContent; } });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadConfig = loadConfig;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
contentDir: path_1.default.join('src', 'assets', 'content'),
|
|
11
|
+
typesOutputFile: path_1.default.join('src', 'generated', 'content-types.ts'),
|
|
12
|
+
};
|
|
13
|
+
function loadConfig() {
|
|
14
|
+
const configPath = path_1.default.resolve(process.cwd(), 'contentstorage.config.ts'); // Look in user's current working dir
|
|
15
|
+
let userConfig = {};
|
|
16
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
17
|
+
try {
|
|
18
|
+
// Use require for JS config file
|
|
19
|
+
const loadedModule = require(configPath);
|
|
20
|
+
// Handle different export styles (module.exports = ... or export default ...)
|
|
21
|
+
userConfig = loadedModule.default || loadedModule;
|
|
22
|
+
console.log(`Loaded configuration from ${configPath}`);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(`Error loading configuration from ${configPath}:`, error);
|
|
26
|
+
// Decide if you want to proceed with defaults or exit
|
|
27
|
+
// For now, we'll proceed with defaults but warn
|
|
28
|
+
console.warn('Proceeding with default configuration.');
|
|
29
|
+
userConfig = {}; // Reset in case of partial load failure
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log('No content.config.js found. Using default configuration.');
|
|
34
|
+
}
|
|
35
|
+
const mergedConfig = {
|
|
36
|
+
...DEFAULT_CONFIG,
|
|
37
|
+
...userConfig,
|
|
38
|
+
};
|
|
39
|
+
// Validate required fields
|
|
40
|
+
if (!mergedConfig.contentUrl) {
|
|
41
|
+
console.error('Error: Configuration is missing the required "contentUrl" property.');
|
|
42
|
+
process.exit(1); // Exit if required URL is missing
|
|
43
|
+
}
|
|
44
|
+
// Resolve paths relative to the user's project root (process.cwd())
|
|
45
|
+
const finalConfig = {
|
|
46
|
+
contentUrl: mergedConfig.contentUrl,
|
|
47
|
+
contentDir: path_1.default.resolve(process.cwd(), mergedConfig.contentDir),
|
|
48
|
+
typesOutputFile: path_1.default.resolve(process.cwd(), mergedConfig.typesOutputFile),
|
|
49
|
+
};
|
|
50
|
+
return finalConfig;
|
|
51
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// ^ Shebang ensures the script is executed with Node.js
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.generateTypes = generateTypes;
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const json_to_ts_1 = __importDefault(require("json-to-ts")); // Import the library
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk")); // Optional: for colored output
|
|
13
|
+
const configLoader_1 = require("../lib/configLoader");
|
|
14
|
+
async function generateTypes() {
|
|
15
|
+
console.log(chalk_1.default.blue('Starting type generation...'));
|
|
16
|
+
const config = (0, configLoader_1.loadConfig)();
|
|
17
|
+
console.log(chalk_1.default.gray(`Reading JSON files from: ${config.contentDir}`));
|
|
18
|
+
console.log(chalk_1.default.gray(`Saving TypeScript types to: ${config.typesOutputFile}`));
|
|
19
|
+
try {
|
|
20
|
+
// Read the content directory
|
|
21
|
+
let files;
|
|
22
|
+
try {
|
|
23
|
+
files = await promises_1.default.readdir(config.contentDir);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err.code === 'ENOENT') {
|
|
27
|
+
throw new Error(`Content directory not found: ${config.contentDir}. Run 'pull-content' first?`);
|
|
28
|
+
}
|
|
29
|
+
throw err; // Re-throw other errors
|
|
30
|
+
}
|
|
31
|
+
// Filter for JSON files and sort them (optional, but good for consistency)
|
|
32
|
+
const jsonFiles = files
|
|
33
|
+
.filter((file) => file.toLowerCase().endsWith('.json'))
|
|
34
|
+
.sort();
|
|
35
|
+
if (jsonFiles.length === 0) {
|
|
36
|
+
throw new Error(`No JSON files found in ${config.contentDir}.`);
|
|
37
|
+
}
|
|
38
|
+
const firstJsonFile = jsonFiles[0];
|
|
39
|
+
const jsonFilePath = path_1.default.join(config.contentDir, firstJsonFile);
|
|
40
|
+
console.log(chalk_1.default.gray(`Using first JSON file for type generation: ${firstJsonFile}`));
|
|
41
|
+
// Read the first JSON file
|
|
42
|
+
const jsonContent = await promises_1.default.readFile(jsonFilePath, 'utf-8');
|
|
43
|
+
// Parse the JSON content
|
|
44
|
+
let jsonObject;
|
|
45
|
+
try {
|
|
46
|
+
jsonObject = JSON.parse(jsonContent);
|
|
47
|
+
}
|
|
48
|
+
catch (parseError) {
|
|
49
|
+
throw new Error(`Failed to parse JSON file ${firstJsonFile}: ${parseError.message}`);
|
|
50
|
+
}
|
|
51
|
+
// Generate TypeScript interfaces using json-to-ts
|
|
52
|
+
// jsonToTS returns an array of strings, each being a line of the interface/type
|
|
53
|
+
// We need to give the root type a name.
|
|
54
|
+
const rootTypeName = 'RootContentItem'; // Or derive from filename, or make configurable
|
|
55
|
+
const typeDeclarations = (0, json_to_ts_1.default)(jsonObject, {
|
|
56
|
+
rootName: rootTypeName,
|
|
57
|
+
});
|
|
58
|
+
if (!typeDeclarations || typeDeclarations.length === 0) {
|
|
59
|
+
throw new Error(`Could not generate types from ${firstJsonFile}. Is the file empty or malformed?`);
|
|
60
|
+
}
|
|
61
|
+
const outputContent = typeDeclarations.join('\n\n'); // Add extra newline between interfaces
|
|
62
|
+
// Ensure the output directory exists
|
|
63
|
+
const outputDir = path_1.default.dirname(config.typesOutputFile);
|
|
64
|
+
await promises_1.default.mkdir(outputDir, { recursive: true });
|
|
65
|
+
// Write the generated types to the output file
|
|
66
|
+
await promises_1.default.writeFile(config.typesOutputFile, outputContent, 'utf-8');
|
|
67
|
+
console.log(chalk_1.default.green(`TypeScript types generated successfully at ${config.typesOutputFile}`));
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(chalk_1.default.red('Error generating TypeScript types:'));
|
|
71
|
+
console.error(chalk_1.default.red(error.message));
|
|
72
|
+
process.exit(1); // Exit with error code
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.pullContent = pullContent;
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const configLoader_1 = require("../lib/configLoader");
|
|
13
|
+
async function pullContent() {
|
|
14
|
+
console.log(chalk_1.default.blue('Starting content pull...'));
|
|
15
|
+
const config = (0, configLoader_1.loadConfig)();
|
|
16
|
+
console.log(chalk_1.default.gray(`Getting content from: ${config.contentUrl}`));
|
|
17
|
+
console.log(chalk_1.default.gray(`Saving content to: ${config.contentDir}`));
|
|
18
|
+
try {
|
|
19
|
+
// Fetch data
|
|
20
|
+
const response = await axios_1.default.get(config.contentUrl);
|
|
21
|
+
const jsonData = response.data; // Assume axios parses JSON automatically
|
|
22
|
+
if (!jsonData) {
|
|
23
|
+
throw new Error('No data received from the URL.');
|
|
24
|
+
}
|
|
25
|
+
// Ensure the output directory exists
|
|
26
|
+
await promises_1.default.mkdir(config.contentDir, { recursive: true });
|
|
27
|
+
// --- How to save the data? ---
|
|
28
|
+
// Option 1: Save the entire response as one file (e.g., content.json)
|
|
29
|
+
// const outputPath = path.join(config.contentDir, 'content.json');
|
|
30
|
+
// await fs.writeFile(outputPath, JSON.stringify(jsonData, null, 2));
|
|
31
|
+
// console.log(chalk.green(`Content saved successfully to ${outputPath}`));
|
|
32
|
+
// Option 2: Assume the response is an ARRAY of objects, save each as a separate file
|
|
33
|
+
// This matches the requirement "use the first file from the directory" later
|
|
34
|
+
if (!Array.isArray(jsonData)) {
|
|
35
|
+
throw new Error(`Expected an array of objects from ${config.contentUrl}, but received type ${typeof jsonData}. Cannot save individual files.`);
|
|
36
|
+
}
|
|
37
|
+
if (jsonData.length === 0) {
|
|
38
|
+
console.log(chalk_1.default.yellow('Received empty array. No files to save.'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log(chalk_1.default.gray(`Received ${jsonData.length} items. Saving individually...`));
|
|
42
|
+
let filesSavedCount = 0;
|
|
43
|
+
for (let i = 0; i < jsonData.length; i++) {
|
|
44
|
+
const item = jsonData[i];
|
|
45
|
+
// Determine filename: use 'id' or 'slug' if available, otherwise use index
|
|
46
|
+
const filename = `${item.id || item.slug || i}.json`;
|
|
47
|
+
const outputPath = path_1.default.join(config.contentDir, filename);
|
|
48
|
+
try {
|
|
49
|
+
await promises_1.default.writeFile(outputPath, JSON.stringify(item, null, 2));
|
|
50
|
+
filesSavedCount++;
|
|
51
|
+
}
|
|
52
|
+
catch (writeError) {
|
|
53
|
+
console.error(chalk_1.default.red(`Error saving file ${filename}:`), writeError.message);
|
|
54
|
+
// Optionally decide whether to continue or stop on error
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk_1.default.green(`Successfully saved ${filesSavedCount} files to ${config.contentDir}`));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error(chalk_1.default.red('Error fetching or saving content:'));
|
|
61
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
62
|
+
console.error(chalk_1.default.red(`Status: ${error.response?.status}`));
|
|
63
|
+
console.error(chalk_1.default.red(`Data: ${JSON.stringify(error.response?.data)}`));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.error(chalk_1.default.red(error.message));
|
|
67
|
+
}
|
|
68
|
+
process.exit(1); // Exit with error code
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contentstorage/core",
|
|
3
|
+
"author": "Kaido Hussar <kaidohus@gmail.com>",
|
|
4
|
+
"homepage": "https://contentstorage.app",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"version": "0.2.0",
|
|
7
|
+
"description": "Fetch content from contentstorage and generate TypeScript types",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"pull-content": "dist/scripts/pull-content.js",
|
|
12
|
+
"generate-types": "dist/scripts/generate-types.js"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/kaidohussar/contentstorage-core.git"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"lint": "eslint src/**/*.ts",
|
|
22
|
+
"lint:fix": "eslint src/**/*.ts --fix",
|
|
23
|
+
"prettier:check": "prettier --check \"src/**/*.ts\"",
|
|
24
|
+
"prettier:write": "prettier --write \"src/**/*.ts\"",
|
|
25
|
+
"release": "npx release-it"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"axios": "^1.7.2",
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"json-to-ts": "^2.0.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/js": "^9.26.0",
|
|
34
|
+
"@types/node": "^20.12.12",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
|
36
|
+
"@typescript-eslint/parser": "^8.31.1",
|
|
37
|
+
"eslint": "^9.26.0",
|
|
38
|
+
"eslint-config-prettier": "^10.1.2",
|
|
39
|
+
"prettier": "^3.5.3",
|
|
40
|
+
"release-it": "^19.0.2",
|
|
41
|
+
"typescript": "^5.8.3",
|
|
42
|
+
"typescript-eslint": "^8.31.1"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18.0.0"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist/**/*",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"README.md"
|
|
54
|
+
]
|
|
55
|
+
}
|