@forgebase/cli 0.0.1

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/mock-server.js ADDED
@@ -0,0 +1,39 @@
1
+ import http from 'http';
2
+
3
+ const mockSchema = {
4
+ users: {
5
+ columns: [
6
+ { name: 'id', dataType: 'serial', isNullable: false },
7
+ { name: 'email', dataType: 'varchar', isNullable: false },
8
+ { name: 'age', dataType: 'integer', isNullable: true },
9
+ { name: 'is_active', dataType: 'boolean', isNullable: false },
10
+ { name: 'metadata', dataType: 'jsonb', isNullable: true },
11
+ { name: 'created_at', dataType: 'timestamptz', isNullable: false },
12
+ ],
13
+ },
14
+ posts: {
15
+ columns: [
16
+ { name: 'id', dataType: 'bigserial', isNullable: false },
17
+ { name: 'title', dataType: 'text', isNullable: false },
18
+ { name: 'content', dataType: 'text', isNullable: true },
19
+ { name: 'author_id', dataType: 'integer', isNullable: false },
20
+ ],
21
+ },
22
+ };
23
+
24
+ const server = http.createServer((req, res) => {
25
+ if (req.url === '/schema') {
26
+ // Simulate network latency (3 seconds)
27
+ setTimeout(() => {
28
+ res.writeHead(200, { 'Content-Type': 'application/json' });
29
+ res.end(JSON.stringify(mockSchema));
30
+ }, 3000);
31
+ } else {
32
+ res.writeHead(404);
33
+ res.end();
34
+ }
35
+ });
36
+
37
+ server.listen(3001, () => {
38
+ console.log('Mock schema server running at http://localhost:3001/schema');
39
+ });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@forgebase/cli",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "CLI tool for ForgeBase",
7
+ "main": "dist/index.js",
8
+ "bin": {
9
+ "forgebase-cli": "dist/index.js",
10
+ "dbcli": "dist/index.js"
11
+ },
12
+ "keywords": [
13
+ "forgebase",
14
+ "cli",
15
+ "database",
16
+ "schema"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "author": "SOG-web",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@types/figlet": "^1.7.0",
25
+ "axios": "^1.11.0",
26
+ "chalk": "^5.6.2",
27
+ "commander": "^14.0.3",
28
+ "figlet": "^1.10.0",
29
+ "inquirer": "^13.2.4",
30
+ "ora": "^9.3.0",
31
+ "prettier": "^3.6.2"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^20.0.0",
35
+ "tslib": "^2.3.0",
36
+ "typescript": "5.8.2",
37
+ "@forgebase/typescript-config": "0.0.0"
38
+ },
39
+ "scripts": {
40
+ "clean": "rm -rf dist",
41
+ "build:esm": "tsc -p tsconfig.esm.json",
42
+ "build:cjs": "tsc -p tsconfig.cjs.json",
43
+ "build": "pnpm clean && pnpm build:esm && pnpm build:cjs",
44
+ "dev": "tsc -p tsconfig.esm.json --watch",
45
+ "start": "node dist/index.js",
46
+ "test:mock-server": "node mock-server.js",
47
+ "test:generate": "node dist/esm/index.js database --url http://localhost:3001/schema --output test-schema.ts"
48
+ }
49
+ }
@@ -0,0 +1,204 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as prettier from 'prettier';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+
9
+ const KyselyTypeMapping: Record<string, string> = {
10
+ // Number types
11
+ integer: 'number',
12
+ int2: 'number',
13
+ int4: 'number',
14
+ int8: 'number',
15
+ smallint: 'number',
16
+ bigint: 'number',
17
+ real: 'number',
18
+ 'double precision': 'number',
19
+ float4: 'number',
20
+ float8: 'number',
21
+ decimal: 'number',
22
+ numeric: 'number',
23
+ serial: 'number',
24
+ bigserial: 'number',
25
+
26
+ // String types
27
+ varchar: 'string',
28
+ char: 'string',
29
+ text: 'string',
30
+ uuid: 'string',
31
+ date: 'string',
32
+ time: 'string',
33
+ timetz: 'string',
34
+ timestamp: 'string',
35
+ timestamptz: 'string',
36
+ datetime: 'string',
37
+ blob: 'string',
38
+ varbinary: 'string',
39
+
40
+ // Boolean
41
+ boolean: 'boolean',
42
+
43
+ // JSON/Any
44
+ json: 'any',
45
+ jsonb: 'any',
46
+ binary: 'any',
47
+ bytea: 'any',
48
+ };
49
+
50
+ // Default mapping for unknown types
51
+ const DEFAULT_TYPE = 'any';
52
+
53
+ interface ColumnInfo {
54
+ name: string;
55
+ dataType: string;
56
+ isNullable: boolean;
57
+ hasAutoIncrement: boolean;
58
+ }
59
+
60
+ interface TableInfo {
61
+ columns: ColumnInfo[];
62
+ }
63
+
64
+ interface DatabaseSchema {
65
+ [tableName: string]: TableInfo;
66
+ }
67
+
68
+ import { showBanner, confirmAction } from '../utils/ui.js';
69
+
70
+ export const databaseCommand = new Command('database')
71
+ .description('Database management commands')
72
+ .requiredOption('--url <url>', 'URL to the ForgeBase schema endpoint')
73
+ .requiredOption('--output <path>', 'Path to save the generated schema file')
74
+ .option('-y, --yes', 'Skip confirmation prompt')
75
+ .action(async (options) => {
76
+ showBanner(
77
+ 'Database Schema Generator',
78
+ 'Generates TypeScript definitions from your running database.',
79
+ );
80
+
81
+ const confirmed = await confirmAction(
82
+ `Do you want to generate the schema from ${chalk.bold(options.url)} to ${chalk.bold(options.output)}?`,
83
+ options.yes,
84
+ );
85
+
86
+ if (!confirmed) {
87
+ console.log(chalk.yellow('Operation cancelled.'));
88
+ return;
89
+ }
90
+
91
+ const spinner = ora('Fetching database schema...').start();
92
+ try {
93
+ // 1. Fetch Schema
94
+ const response = await axios.get<DatabaseSchema>(options.url);
95
+ const schema = response.data;
96
+
97
+ if (!schema || typeof schema !== 'object') {
98
+ throw new Error('Invalid schema response from server');
99
+ }
100
+
101
+ spinner.text = 'Generating TypeScript definitions...';
102
+
103
+ // 2. Generate TypeScript Interfaces
104
+ const lines: string[] = [];
105
+ const tableNames: string[] = [];
106
+
107
+ // Add file header
108
+ lines.push('/**');
109
+ lines.push(' * This file was auto-generated by @forgebase/cli.');
110
+ lines.push(' * Do not modify this file manually.');
111
+ lines.push(' */');
112
+ lines.push('');
113
+
114
+ for (const [tableName, tableInfo] of Object.entries(schema)) {
115
+ const interfaceName = toPascalCase(tableName);
116
+ tableNames.push(tableName); // Keep original table name for Schema interface keys
117
+
118
+ lines.push(`export interface ${interfaceName} {`);
119
+
120
+ for (const col of tableInfo.columns) {
121
+ // Map type
122
+ let tsType =
123
+ KyselyTypeMapping[col.dataType.toLowerCase()] || DEFAULT_TYPE;
124
+
125
+ // Handle nullability
126
+ if (col.isNullable) {
127
+ tsType += ' | null';
128
+ }
129
+
130
+ lines.push(` ${col.name}: ${tsType};`);
131
+ }
132
+
133
+ lines.push('}');
134
+ lines.push('');
135
+ }
136
+
137
+ // 3. Generate Schema Interface
138
+ lines.push('export interface Schema {');
139
+ for (const tableName of tableNames) {
140
+ // Map table name to interface name
141
+ // e.g. "users" -> "users: User" (assuming User interface is generated)
142
+ // But commonly tables are plural, interfaces are singular/pascal.
143
+ // Let's use the generated interface name.
144
+ const interfaceName = toPascalCase(tableName);
145
+ lines.push(` ${tableName}: ${interfaceName};`);
146
+ }
147
+ lines.push('}');
148
+ lines.push('');
149
+
150
+ // 4. Format with Prettier
151
+ const rawContent = lines.join('\n');
152
+ const formattedContent = await prettier.format(rawContent, {
153
+ parser: 'typescript',
154
+ singleQuote: true,
155
+ });
156
+
157
+ // 5. Write to file
158
+ const outputPath = path.resolve(process.cwd(), options.output);
159
+ const outputDir = path.dirname(outputPath);
160
+
161
+ if (!fs.existsSync(outputDir)) {
162
+ fs.mkdirSync(outputDir, { recursive: true });
163
+ }
164
+
165
+ fs.writeFileSync(outputPath, formattedContent);
166
+
167
+ spinner.succeed(chalk.green(`Schema generated successfully!`));
168
+
169
+ console.log('');
170
+ console.log(chalk.cyan(' → ') + chalk.bold('Source: ') + options.url);
171
+ console.log(chalk.cyan(' → ') + chalk.bold('Output: ') + outputPath);
172
+ console.log(
173
+ chalk.cyan(' → ') + chalk.bold('Tables: ') + tableNames.length,
174
+ );
175
+ console.log('');
176
+ console.log(chalk.gray(' You can now use this schema with the SDK:'));
177
+ console.log(chalk.blue(' const db = new DatabaseSDK<Schema>(...);'));
178
+ console.log('');
179
+ } catch (error: any) {
180
+ spinner.fail(chalk.red('Failed to generate schema'));
181
+ console.error(chalk.red(error.message));
182
+ if (axios.isAxiosError(error)) {
183
+ console.error(
184
+ chalk.red(
185
+ `Status: ${error.response?.status} ${error.response?.statusText}`,
186
+ ),
187
+ );
188
+ }
189
+ process.exit(1);
190
+ }
191
+ });
192
+
193
+ function toPascalCase(str: string): string {
194
+ // Handle snake_case, kebab-case, etc.
195
+ // e.g. user_profiles -> UserProfiles
196
+ return (
197
+ str
198
+ .match(/[a-z0-9]+/gi)
199
+ ?.map(
200
+ (word) => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(),
201
+ )
202
+ .join('') || str
203
+ );
204
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { databaseCommand } from './commands/database.js';
4
+
5
+ const program = new Command();
6
+
7
+ program
8
+ .name('forgebase-cli')
9
+ .description('CLI tool for ForgeBase')
10
+ .version('0.0.1');
11
+
12
+ program.addCommand(databaseCommand);
13
+
14
+ program.parse();
@@ -0,0 +1,37 @@
1
+ import figlet from 'figlet';
2
+ import inquirer from 'inquirer';
3
+ import chalk from 'chalk';
4
+
5
+ export function showBanner(commandName: string, description: string) {
6
+ console.log('');
7
+ console.log(
8
+ chalk.hex('#FF5733')(
9
+ figlet.textSync('ForgeBase CLI', { horizontalLayout: 'full' }),
10
+ ),
11
+ );
12
+ console.log('');
13
+ console.log(chalk.bold.cyan(`Command: ${commandName}`));
14
+ console.log(chalk.gray(description));
15
+ console.log(chalk.gray('--------------------------------------------------'));
16
+ console.log('');
17
+ }
18
+
19
+ export async function confirmAction(
20
+ message: string,
21
+ skip: boolean,
22
+ ): Promise<boolean> {
23
+ if (skip) {
24
+ return true;
25
+ }
26
+
27
+ const { proceed } = await inquirer.prompt([
28
+ {
29
+ type: 'confirm',
30
+ name: 'proceed',
31
+ message: message,
32
+ default: true,
33
+ },
34
+ ]);
35
+
36
+ return proceed;
37
+ }
package/test-schema.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * This file was auto-generated by @forgebase/cli.
3
+ * Do not modify this file manually.
4
+ */
5
+
6
+ export interface Users {
7
+ id: number;
8
+ email: string;
9
+ age: number | null;
10
+ is_active: boolean;
11
+ metadata: any | null;
12
+ created_at: string;
13
+ }
14
+
15
+ export interface Posts {
16
+ id: number;
17
+ title: string;
18
+ content: string | null;
19
+ author_id: number;
20
+ }
21
+
22
+ export interface Schema {
23
+ users: Users;
24
+ posts: Posts;
25
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist/cjs",
5
+ "module": "CommonJS",
6
+ "target": "ES2020",
7
+ "moduleResolution": "Node"
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist/esm",
5
+ "module": "ESNext",
6
+ "target": "ES2020",
7
+ "moduleResolution": "Bundler"
8
+ }
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "@forgebase/typescript-config/base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "composite": true,
7
+ "noImplicitAny": false,
8
+ "target": "ES2020",
9
+ "lib": ["ES2020", "DOM", "DOM.Iterable"]
10
+ },
11
+ "include": ["src"],
12
+ "exclude": [
13
+ "node_modules",
14
+ "dist",
15
+ "jest.config.ts",
16
+ "src/**/*.spec.ts",
17
+ "src/**/*.test.ts"
18
+ ]
19
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "moduleResolution": "node10",
7
+ "types": ["jest", "node"]
8
+ },
9
+ "include": [
10
+ "jest.config.ts",
11
+ "src/**/*.test.ts",
12
+ "src/**/*.spec.ts",
13
+ "src/**/*.d.ts"
14
+ ]
15
+ }