@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 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
@@ -0,0 +1,4 @@
1
+ export { loadConfig } from './lib/configLoader';
2
+ export type { AppConfig } from './lib/configLoader';
3
+ export { generateTypes } from './scripts/generate-types';
4
+ export { pullContent } from './scripts/pull-content';
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,6 @@
1
+ export interface AppConfig {
2
+ contentUrl: string;
3
+ contentDir: string;
4
+ typesOutputFile: string;
5
+ }
6
+ export declare function loadConfig(): AppConfig;
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function generateTypes(): Promise<void>;
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function pullContent(): Promise<void>;
@@ -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
+ }