@dbcube/cli 5.1.4 → 5.2.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/README.md +59 -0
- package/package.json +24 -8
- package/src/commands/dev.js +83 -0
- package/src/commands/doctor.js +99 -0
- package/src/commands/generate.js +160 -0
- package/src/commands/help.js +24 -8
- package/src/commands/init.js +82 -0
- package/src/commands/migrate/lib.js +125 -0
- package/src/commands/migrate/rollback.js +72 -0
- package/src/commands/migrate/status.js +55 -0
- package/src/commands/run/database/create/index.js +10 -12
- package/src/commands/run/pull.js +249 -0
- package/src/commands/run/seeder/add.js +4 -3
- package/src/commands/run/table/alter.js +55 -3
- package/src/commands/run/table/fresh.js +81 -35
- package/src/commands/run/table/refresh.js +4 -4
- package/src/commands/run/trigger/fresh.js +4 -3
- package/src/commands/validate.js +70 -0
- package/src/commands/version.js +66 -29
- package/src/index.js +162 -92
- package/src/utils/ConfigFileUtils.js +3 -2
- package/.npmignore +0 -58
- package/bun.lock +0 -192
- package/dbcube.config.js +0 -15
- package/pnpm-workspace.yaml +0 -3
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# @dbcube/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [DBCube](https://www.npmjs.com/package/dbcube): schema management with `.cube` files, tracked migrations with rollback, seeders, triggers, TypeScript type generation and database introspection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D @dbcube/cli
|
|
9
|
+
npx dbcube init
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
| Command | Description |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `npx dbcube init` | Scaffold a new project (config + `dbcube/` + example schema) |
|
|
17
|
+
| `npx dbcube generate` | Generate TypeScript types from `.table.cube` files |
|
|
18
|
+
| `npx dbcube validate` | Validate every `.cube` file without executing (exit 1 on errors — CI-friendly) |
|
|
19
|
+
| `npx dbcube doctor` | Diagnose config, binaries and database connectivity |
|
|
20
|
+
| `npx dbcube dev` | Watch mode: saving a `.cube` file applies it automatically |
|
|
21
|
+
| `npx dbcube run database:create` | Interactive database setup (config + physical creation) |
|
|
22
|
+
| `npx dbcube run table:fresh` | Drop and recreate all tables (asks for written confirmation; `--force` for CI) |
|
|
23
|
+
| `npx dbcube run table:refresh` | Apply schema changes **without** dropping data |
|
|
24
|
+
| `npx dbcube run table:alter` | Apply pending `.alter.cube` migrations (`--dry-run`, `--all`) |
|
|
25
|
+
| `npx dbcube migrate:status` | Show applied / pending / modified migrations |
|
|
26
|
+
| `npx dbcube migrate:rollback` | Revert the last migration batch |
|
|
27
|
+
| `npx dbcube run pull [db]` | Generate `.table.cube` files FROM an existing database (MySQL, PostgreSQL, SQLite) |
|
|
28
|
+
| `npx dbcube run seeder:add` | Run `.seeder.cube` data seeders |
|
|
29
|
+
| `npx dbcube run trigger:fresh` | Recreate triggers from `.trigger.cube` files |
|
|
30
|
+
| `npx dbcube update` | Check and update the Rust engine binaries |
|
|
31
|
+
| `npx dbcube run download <engine> [ver]` | Download a specific engine binary |
|
|
32
|
+
| `npx dbcube -v` | Versions of CLI, packages, engines, Node and platform |
|
|
33
|
+
|
|
34
|
+
Run `npx dbcube help` for the full reference.
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
`dbcube.config.js` (created by `init`):
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
module.exports = function (config) {
|
|
42
|
+
config.set({
|
|
43
|
+
databases: {
|
|
44
|
+
myapp: {
|
|
45
|
+
type: "sqlite", // mysql | postgres | sqlite | mongodb
|
|
46
|
+
config: { DATABASE: "myapp" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Documentation
|
|
54
|
+
|
|
55
|
+
https://dbcube.org
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbcube/cli",
|
|
3
|
-
"version": "5.1
|
|
4
|
-
"main": "index.js",
|
|
3
|
+
"version": "5.2.1",
|
|
4
|
+
"main": "src/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dbcube": "node src/index.js"
|
|
7
7
|
},
|
|
8
8
|
"bin": {
|
|
9
9
|
"dbcube": "./src/index.js"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
11
|
+
"files": [
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"dbcube",
|
|
16
|
+
"cli",
|
|
17
|
+
"database",
|
|
18
|
+
"migrations",
|
|
19
|
+
"mysql",
|
|
20
|
+
"postgresql",
|
|
21
|
+
"sqlite",
|
|
22
|
+
"orm"
|
|
23
|
+
],
|
|
24
|
+
"author": "Albert Araya",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"description": "Command-line interface for DBCube: schema management (.cube files), migrations with rollback, seeders, triggers, TypeScript type generation and database introspection.",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
15
30
|
"dependencies": {
|
|
16
|
-
"@dbcube/schema-builder": "^5.1
|
|
17
|
-
"@inquirer/prompts": "^8.
|
|
31
|
+
"@dbcube/schema-builder": "^5.2.1",
|
|
32
|
+
"@inquirer/prompts": "^8.5.2",
|
|
18
33
|
"alwait": "^1.0.0",
|
|
19
34
|
"chalk": "4.1.2",
|
|
35
|
+
"dbcube": "^5.2.1",
|
|
20
36
|
"dotenv": "^17.4.2",
|
|
21
37
|
"fs-extra": "^11.3.5",
|
|
22
38
|
"glob": "^13.0.6",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* dbcube dev — Watch mode: edits to .cube files apply automatically.
|
|
8
|
+
* *.table.cube → table:refresh
|
|
9
|
+
* *.trigger.cube → trigger:fresh
|
|
10
|
+
* *.seeder.cube → hint (seeders insert data; never auto-run)
|
|
11
|
+
* *.alter.cube → hint (migrations are explicit; run table:alter)
|
|
12
|
+
* Regenerates dbcube/types.ts when it exists.
|
|
13
|
+
*/
|
|
14
|
+
const DEBOUNCE_MS = 600;
|
|
15
|
+
let timer = null;
|
|
16
|
+
const pendingKinds = new Set();
|
|
17
|
+
|
|
18
|
+
function runCli(args) {
|
|
19
|
+
try {
|
|
20
|
+
execSync(`node "${path.join(__dirname, '..', 'index.js')}" ${args}`, {
|
|
21
|
+
stdio: 'inherit',
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
});
|
|
24
|
+
} catch { /* errors already printed by the subcommand */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function flush() {
|
|
28
|
+
timer = null;
|
|
29
|
+
const kinds = [...pendingKinds];
|
|
30
|
+
pendingKinds.clear();
|
|
31
|
+
|
|
32
|
+
if (kinds.includes('table')) {
|
|
33
|
+
console.log(`\n${chalk.cyan('↻')} ${chalk.bold('table change detected')} → table:refresh`);
|
|
34
|
+
runCli('run table:refresh');
|
|
35
|
+
if (fs.existsSync(path.join(process.cwd(), 'dbcube', 'types.ts'))) {
|
|
36
|
+
runCli('generate');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (kinds.includes('trigger')) {
|
|
40
|
+
console.log(`\n${chalk.cyan('↻')} ${chalk.bold('trigger change detected')} → trigger:fresh`);
|
|
41
|
+
runCli('run trigger:fresh');
|
|
42
|
+
}
|
|
43
|
+
if (kinds.includes('seeder')) {
|
|
44
|
+
console.log(`\n${chalk.gray('ℹ seeder changed — run when ready:')} ${chalk.white('npx dbcube run seeder:add')}`);
|
|
45
|
+
}
|
|
46
|
+
if (kinds.includes('alter')) {
|
|
47
|
+
console.log(`\n${chalk.gray('ℹ alter file changed — apply explicitly:')} ${chalk.white('npx dbcube run table:alter')}`);
|
|
48
|
+
}
|
|
49
|
+
console.log(`\n${chalk.green('👀 watching dbcube/ ...')} ${chalk.gray('(Ctrl+C to stop)')}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function onChange(filename) {
|
|
53
|
+
if (!filename || !filename.endsWith('.cube')) return;
|
|
54
|
+
if (filename.includes('triggers' + path.sep) || filename.includes('logs' + path.sep)) return;
|
|
55
|
+
|
|
56
|
+
if (filename.endsWith('.table.cube')) pendingKinds.add('table');
|
|
57
|
+
else if (filename.endsWith('.trigger.cube')) pendingKinds.add('trigger');
|
|
58
|
+
else if (filename.endsWith('.seeder.cube')) pendingKinds.add('seeder');
|
|
59
|
+
else if (filename.endsWith('.alter.cube')) pendingKinds.add('alter');
|
|
60
|
+
else return;
|
|
61
|
+
|
|
62
|
+
if (timer) clearTimeout(timer);
|
|
63
|
+
timer = setTimeout(flush, DEBOUNCE_MS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const cubesDir = path.join(process.cwd(), 'dbcube');
|
|
68
|
+
if (!fs.existsSync(cubesDir)) {
|
|
69
|
+
console.error(`${chalk.red('❌')} dbcube/ directory not found — run: npx dbcube init`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(`\n${chalk.green.bold('🚀 DBCube dev mode')}`);
|
|
74
|
+
console.log(`${chalk.gray('Save a .cube file and the change applies automatically.')}\n`);
|
|
75
|
+
console.log(`${chalk.green('👀 watching dbcube/ ...')} ${chalk.gray('(Ctrl+C to stop)')}`);
|
|
76
|
+
|
|
77
|
+
fs.watch(cubesDir, { recursive: true }, (_event, filename) => onChange(filename));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch(error => {
|
|
81
|
+
console.error('Error fatal:', error);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ConfigFileUtils = require('../utils/ConfigFileUtils');
|
|
5
|
+
const { Engine } = require('@dbcube/schema-builder');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* dbcube doctor — Diagnoses the project setup: config file, databases,
|
|
9
|
+
* connectivity, cube files, and environment. Kills 80% of support issues.
|
|
10
|
+
*/
|
|
11
|
+
const ok = (msg) => console.log(` ${chalk.green('✓')} ${msg}`);
|
|
12
|
+
const warn = (msg) => console.log(` ${chalk.yellow('⚠')} ${msg}`);
|
|
13
|
+
const fail = (msg) => console.log(` ${chalk.red('✗')} ${msg}`);
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
console.log(`\n🩺 ${chalk.green.bold('DBCube Doctor')}\n`);
|
|
17
|
+
let problems = 0;
|
|
18
|
+
|
|
19
|
+
// 1. Node version
|
|
20
|
+
const major = parseInt(process.versions.node.split('.')[0], 10);
|
|
21
|
+
if (major >= 16) ok(`Node.js ${process.versions.node}`);
|
|
22
|
+
else { fail(`Node.js ${process.versions.node} — DBCube requires >= 16`); problems++; }
|
|
23
|
+
|
|
24
|
+
// 2. Config file
|
|
25
|
+
const configPath = path.join(process.cwd(), 'dbcube.config.js');
|
|
26
|
+
if (!fs.existsSync(configPath)) {
|
|
27
|
+
fail('dbcube.config.js not found — run: npx dbcube init');
|
|
28
|
+
problems++;
|
|
29
|
+
} else {
|
|
30
|
+
ok('dbcube.config.js found');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Configured databases
|
|
34
|
+
let databases = [];
|
|
35
|
+
try {
|
|
36
|
+
databases = await ConfigFileUtils.getConfiguredDatabases();
|
|
37
|
+
if (databases.length === 0) { warn('No databases configured — run: npx dbcube run database:create'); }
|
|
38
|
+
else ok(`${databases.length} database(s) configured: ${databases.map(d => `${d.name} (${d.type})`).join(', ')}`);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
fail(`dbcube.config.js could not be loaded: ${e.message}`);
|
|
41
|
+
problems++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 4. Cube files
|
|
45
|
+
const cubesDir = path.join(process.cwd(), 'dbcube');
|
|
46
|
+
if (fs.existsSync(cubesDir)) {
|
|
47
|
+
const count = (dir) => fs.readdirSync(dir, { withFileTypes: true }).reduce((acc, e) => {
|
|
48
|
+
const full = path.join(dir, e.name);
|
|
49
|
+
if (e.isDirectory() && !['triggers', 'logs'].includes(e.name)) return acc + count(full);
|
|
50
|
+
return acc + (e.name.endsWith('.cube') ? 1 : 0);
|
|
51
|
+
}, 0);
|
|
52
|
+
const n = count(cubesDir);
|
|
53
|
+
if (n > 0) ok(`${n} .cube file(s) in dbcube/`);
|
|
54
|
+
else warn('dbcube/ exists but has no .cube files');
|
|
55
|
+
} else {
|
|
56
|
+
warn('dbcube/ directory not found — run: npx dbcube init');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 5. Connectivity per database (also verifies engine binaries)
|
|
60
|
+
for (const db of databases) {
|
|
61
|
+
try {
|
|
62
|
+
const engine = new Engine(db.name);
|
|
63
|
+
const response = await engine.run('query_engine', ['--action', 'connect']);
|
|
64
|
+
if (response.status === 200 || /persistente|Listo|created/i.test(response.message || '')) {
|
|
65
|
+
ok(`Connection OK: ${db.name} (${db.type})`);
|
|
66
|
+
} else {
|
|
67
|
+
fail(`Connection failed: ${db.name} — ${response.message}`);
|
|
68
|
+
problems++;
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
fail(`Connection failed: ${db.name} — ${e.message}`);
|
|
72
|
+
problems++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 6. Daemon status
|
|
77
|
+
const daemonDir = path.join(process.cwd(), '.dbcube', 'daemon');
|
|
78
|
+
if (fs.existsSync(daemonDir)) {
|
|
79
|
+
const portfiles = fs.readdirSync(daemonDir).filter(f => f.endsWith('.json'));
|
|
80
|
+
if (portfiles.length > 0) ok(`Daemon mode active for: ${portfiles.map(f => path.basename(f, '.json')).join(', ')}`);
|
|
81
|
+
else warn('No active daemons (first query will start one automatically)');
|
|
82
|
+
} else {
|
|
83
|
+
warn('No active daemons (first query will start one automatically)');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('');
|
|
87
|
+
if (problems === 0) {
|
|
88
|
+
console.log(`${chalk.green.bold('✅ Everything looks healthy!')}\n`);
|
|
89
|
+
} else {
|
|
90
|
+
console.log(`${chalk.red.bold(`❌ ${problems} problem(s) found`)}\n`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main().catch(error => {
|
|
97
|
+
console.error('Error fatal:', error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* dbcube generate — Generates TypeScript interfaces from .table.cube files.
|
|
7
|
+
* Output: dbcube/types.ts (one interface per table + DbSchema map).
|
|
8
|
+
*
|
|
9
|
+
* Usage with the query builder:
|
|
10
|
+
* import type { User } from './dbcube/types';
|
|
11
|
+
* const users = await db.table('users').get() as User[];
|
|
12
|
+
*/
|
|
13
|
+
const TYPE_MAP = {
|
|
14
|
+
int: 'number', integer: 'number', bigint: 'number', tinyint: 'number',
|
|
15
|
+
float: 'number', double: 'number', decimal: 'number',
|
|
16
|
+
varchar: 'string', string: 'string', text: 'string', uuid: 'string',
|
|
17
|
+
date: 'string', datetime: 'string', timestamp: 'string',
|
|
18
|
+
boolean: 'boolean', bool: 'boolean',
|
|
19
|
+
json: 'any',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function collectTableCubes(dir, acc = []) {
|
|
23
|
+
if (!fs.existsSync(dir)) return acc;
|
|
24
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
25
|
+
const full = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
if (!['triggers', 'logs', 'node_modules'].includes(entry.name)) collectTableCubes(full, acc);
|
|
28
|
+
} else if (entry.name.endsWith('.table.cube')) {
|
|
29
|
+
acc.push(full);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return acc;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pascalCase(name) {
|
|
36
|
+
return name.split(/[_\-\s]+/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function singularize(word) {
|
|
40
|
+
if (word.endsWith('ies')) return word.slice(0, -3) + 'y';
|
|
41
|
+
if (word.endsWith('ses')) return word.slice(0, -2);
|
|
42
|
+
if (word.endsWith('s') && !word.endsWith('ss')) return word.slice(0, -1);
|
|
43
|
+
return word;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Parses the @columns block of a .table.cube into [{name, tsType, optional, comment}] */
|
|
47
|
+
function parseCube(content) {
|
|
48
|
+
const nameMatch = content.match(/@meta\s*\(\s*\{[\s\S]*?name\s*:\s*"([^"]+)"/);
|
|
49
|
+
const dbMatch = content.match(/@database\s*\(\s*"([^"]+)"\s*\)/);
|
|
50
|
+
const columnsMatch = content.match(/@columns\s*\(\s*\{([\s\S]*)\}\s*\)\s*;?\s*$/m)
|
|
51
|
+
|| content.match(/@columns\s*\(\s*\{([\s\S]*?)\}\s*\)\s*;/);
|
|
52
|
+
if (!columnsMatch) return null;
|
|
53
|
+
|
|
54
|
+
const body = columnsMatch[1];
|
|
55
|
+
const columns = [];
|
|
56
|
+
// Match each top-level "col: { ... };" block (tolerates one nesting level: foreign/@compute braces)
|
|
57
|
+
const colRegex = /(\w+)\s*:\s*\{((?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*?)\}\s*;/g;
|
|
58
|
+
let m;
|
|
59
|
+
while ((m = colRegex.exec(body)) !== null) {
|
|
60
|
+
const colName = m[1];
|
|
61
|
+
const def = m[2];
|
|
62
|
+
const typeMatch = def.match(/type\s*:\s*"([^"]+)"/);
|
|
63
|
+
if (!typeMatch) continue;
|
|
64
|
+
const cubeType = typeMatch[1].toLowerCase();
|
|
65
|
+
|
|
66
|
+
let tsType = TYPE_MAP[cubeType] ?? 'any';
|
|
67
|
+
if (cubeType === 'enum') {
|
|
68
|
+
const enumMatch = def.match(/enumValues\s*:\s*\[([^\]]*)\]/);
|
|
69
|
+
if (enumMatch) {
|
|
70
|
+
const values = enumMatch[1].match(/"([^"]*)"/g);
|
|
71
|
+
if (values && values.length > 0) tsType = values.join(' | ');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const options = (def.match(/options\s*:\s*\[([^\]]*)\]/) || [])[1] || '';
|
|
76
|
+
const notNull = /"not null"/.test(options) || /"primary"/.test(options);
|
|
77
|
+
const hasDefault = /defaultValue\s*:/.test(def) || /"autoincrement"/.test(options);
|
|
78
|
+
const isComputed = /@compute/.test(def);
|
|
79
|
+
|
|
80
|
+
columns.push({
|
|
81
|
+
name: colName,
|
|
82
|
+
tsType: notNull ? tsType : `${tsType} | null`,
|
|
83
|
+
optional: !notNull || hasDefault || isComputed,
|
|
84
|
+
comment: isComputed ? 'computed field (read-only, requires useComputes())' : null,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
table: nameMatch ? nameMatch[1] : null,
|
|
90
|
+
database: dbMatch ? dbMatch[1] : null,
|
|
91
|
+
columns,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function main() {
|
|
96
|
+
const cubesDir = path.join(process.cwd(), 'dbcube');
|
|
97
|
+
const files = collectTableCubes(cubesDir);
|
|
98
|
+
|
|
99
|
+
if (files.length === 0) {
|
|
100
|
+
console.log(`${chalk.yellow('⚠')} No .table.cube files found in dbcube/`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`\n🧬 ${chalk.green('Generating TypeScript types from')} ${files.length} cube(s)...\n`);
|
|
105
|
+
|
|
106
|
+
let out = `// ⚠ AUTO-GENERATED by \`dbcube generate\` — do not edit by hand.\n`;
|
|
107
|
+
out += `// Source of truth: your .table.cube files. Re-run after schema changes.\n\n`;
|
|
108
|
+
|
|
109
|
+
const schemaEntries = [];
|
|
110
|
+
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
113
|
+
const parsed = parseCube(content);
|
|
114
|
+
const tableName = (parsed && parsed.table) || path.basename(file, '.table.cube');
|
|
115
|
+
|
|
116
|
+
if (!parsed || parsed.columns.length === 0) {
|
|
117
|
+
console.log(` ${chalk.yellow('⚠')} ${path.relative(process.cwd(), file)} — could not parse columns, skipped`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const ifaceName = pascalCase(singularize(tableName));
|
|
122
|
+
out += `/** Table \`${tableName}\`${parsed.database ? ` · database \`${parsed.database}\`` : ''} */\n`;
|
|
123
|
+
out += `export interface ${ifaceName} {\n`;
|
|
124
|
+
for (const col of parsed.columns) {
|
|
125
|
+
if (col.comment) out += ` /** ${col.comment} */\n`;
|
|
126
|
+
out += ` ${col.name}: ${col.tsType};\n`;
|
|
127
|
+
}
|
|
128
|
+
out += `}\n\n`;
|
|
129
|
+
|
|
130
|
+
out += `/** Shape accepted by insert()/update() for \`${tableName}\` */\n`;
|
|
131
|
+
out += `export interface New${ifaceName} {\n`;
|
|
132
|
+
for (const col of parsed.columns) {
|
|
133
|
+
if (col.comment) continue; // computed fields are not writable
|
|
134
|
+
out += ` ${col.name}${col.optional ? '?' : ''}: ${col.tsType};\n`;
|
|
135
|
+
}
|
|
136
|
+
out += `}\n\n`;
|
|
137
|
+
|
|
138
|
+
schemaEntries.push({ tableName, ifaceName });
|
|
139
|
+
console.log(` ${chalk.green('✓')} ${tableName} → ${ifaceName}, New${ifaceName}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
out += `/** Map of table name → row type. */\n`;
|
|
143
|
+
out += `export interface DbSchema {\n`;
|
|
144
|
+
for (const { tableName, ifaceName } of schemaEntries) {
|
|
145
|
+
out += ` ${tableName}: ${ifaceName};\n`;
|
|
146
|
+
}
|
|
147
|
+
out += `}\n`;
|
|
148
|
+
|
|
149
|
+
const outPath = path.join(cubesDir, 'types.ts');
|
|
150
|
+
fs.writeFileSync(outPath, out, 'utf8');
|
|
151
|
+
console.log(`\n${chalk.green('✓')} Types written to ${chalk.bold('dbcube/types.ts')}\n`);
|
|
152
|
+
console.log(`${chalk.cyan('Usage:')}`);
|
|
153
|
+
console.log(` import type { ${schemaEntries[0] ? schemaEntries[0].ifaceName : 'User'} } from './dbcube/types';`);
|
|
154
|
+
console.log(` const rows = await db.table('${schemaEntries[0] ? schemaEntries[0].tableName : 'users'}').get() as ${schemaEntries[0] ? schemaEntries[0].ifaceName : 'User'}[];\n`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main().catch(error => {
|
|
158
|
+
console.error('Error fatal:', error);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
package/src/commands/help.js
CHANGED
|
@@ -10,7 +10,14 @@ async function showHelp() {
|
|
|
10
10
|
|
|
11
11
|
console.log(`${chalk.cyan.bold('USAGE:')}`);
|
|
12
12
|
console.log(` ${chalk.white('npx dbcube')} ${chalk.yellow('<command>')} ${chalk.gray('[options]')}\n`);
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
console.log(`${chalk.cyan.bold('PROJECT COMMANDS:')}`);
|
|
15
|
+
console.log(` ${chalk.yellow('init')} ${chalk.white('Scaffold a new DBCube project (config + dbcube/ + example)')}`);
|
|
16
|
+
console.log(` ${chalk.yellow('generate')} ${chalk.white('Generate TypeScript types from .table.cube files')}`);
|
|
17
|
+
console.log(` ${chalk.yellow('validate')} ${chalk.white('Validate every .cube file without executing (CI-friendly)')}`);
|
|
18
|
+
console.log(` ${chalk.yellow('doctor')} ${chalk.white('Diagnose config, binaries, and database connectivity')}`);
|
|
19
|
+
console.log(` ${chalk.yellow('dev')} ${chalk.white('Watch mode: .cube changes apply automatically')}\n`);
|
|
20
|
+
|
|
14
21
|
console.log(`${chalk.cyan.bold('DATABASE COMMANDS:')}`);
|
|
15
22
|
console.log(` ${chalk.yellow('database:create')} ${chalk.white('Create a new database with interactive setup')}`);
|
|
16
23
|
console.log(` ${chalk.yellow('run database:create')} ${chalk.white('Alternative syntax for database creation')}`);
|
|
@@ -20,7 +27,13 @@ async function showHelp() {
|
|
|
20
27
|
console.log(`${chalk.cyan.bold('TABLE COMMANDS:')}`);
|
|
21
28
|
console.log(` ${chalk.yellow('run table:fresh')} ${chalk.white('Drop and recreate all tables from .cube files')}`);
|
|
22
29
|
console.log(` ${chalk.yellow('run table:refresh')} ${chalk.white('Refresh table structures without dropping data')}`);
|
|
23
|
-
console.log(` ${chalk.yellow('run table:alter')} ${chalk.white('Apply
|
|
30
|
+
console.log(` ${chalk.yellow('run table:alter')} ${chalk.white('Apply PENDING .alter.cube migrations (tracked)')}`);
|
|
31
|
+
console.log(` ${chalk.gray(' Options:')} --dry-run ${chalk.gray('(print SQL only)')} · --all ${chalk.gray('(ignore history)')}\n`);
|
|
32
|
+
|
|
33
|
+
console.log(`${chalk.cyan.bold('MIGRATION COMMANDS:')}`);
|
|
34
|
+
console.log(` ${chalk.yellow('migrate:status')} ${chalk.white('Show applied and pending migrations')}`);
|
|
35
|
+
console.log(` ${chalk.yellow('migrate:rollback')} ${chalk.white('Revert the last applied migration batch')}`);
|
|
36
|
+
console.log(` ${chalk.yellow('run pull [database]')} ${chalk.white('Generate .table.cube files FROM an existing database')}\n`);
|
|
24
37
|
|
|
25
38
|
console.log(`${chalk.cyan.bold('TRIGGER COMMANDS:')}`);
|
|
26
39
|
console.log(` ${chalk.yellow('run trigger:fresh')} ${chalk.white('Recreate all database triggers from .cube files')}\n`);
|
|
@@ -67,12 +80,15 @@ async function showHelp() {
|
|
|
67
80
|
console.log(` ${chalk.white('npx dbcube update')}\n`);
|
|
68
81
|
|
|
69
82
|
console.log(`${chalk.cyan.bold('CONFIGURATION:')}`);
|
|
70
|
-
console.log(` ${chalk.white('Create a')} ${chalk.yellow('dbcube.config.js')} ${chalk.white('file in your project root:')}`);
|
|
71
|
-
console.log(` ${chalk.gray(
|
|
72
|
-
console.log(` ${chalk.gray(' config.
|
|
73
|
-
console.log(` ${chalk.gray('
|
|
74
|
-
console.log(` ${chalk.gray('
|
|
75
|
-
console.log(` ${chalk.gray('
|
|
83
|
+
console.log(` ${chalk.white('Create a')} ${chalk.yellow('dbcube.config.js')} ${chalk.white('file in your project root (or run')} ${chalk.yellow('npx dbcube init')}${chalk.white('):')}`);
|
|
84
|
+
console.log(` ${chalk.gray("module.exports = function (config) {")}`);
|
|
85
|
+
console.log(` ${chalk.gray(' config.set({')}`);
|
|
86
|
+
console.log(` ${chalk.gray(' databases: {')}`);
|
|
87
|
+
console.log(` ${chalk.gray(' myapp: {')}`);
|
|
88
|
+
console.log(` ${chalk.gray(' type: "sqlite", // mysql, postgres, sqlite, mongodb')}`);
|
|
89
|
+
console.log(` ${chalk.gray(' config: { DATABASE: process.env.DBCUBE_MYAPP_DATABASE }')}`);
|
|
90
|
+
console.log(` ${chalk.gray(' }')}`);
|
|
91
|
+
console.log(` ${chalk.gray(' }')}`);
|
|
76
92
|
console.log(` ${chalk.gray(' });')}`);
|
|
77
93
|
console.log(` ${chalk.gray('};')}\n`);
|
|
78
94
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const ConfigFileUtils = require('../utils/ConfigFileUtils');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* dbcube init — Scaffolds a new DBCube project:
|
|
8
|
+
* dbcube.config.js + .env + dbcube/ with an example .table.cube
|
|
9
|
+
*/
|
|
10
|
+
async function main() {
|
|
11
|
+
const root = process.cwd();
|
|
12
|
+
console.log(`\n💚 ${chalk.green('Initializing DBCube project...')}\n`);
|
|
13
|
+
|
|
14
|
+
// 1. dbcube.config.js
|
|
15
|
+
const configPath = path.join(root, 'dbcube.config.js');
|
|
16
|
+
if (fs.existsSync(configPath)) {
|
|
17
|
+
console.log(` ${chalk.yellow('•')} dbcube.config.js already exists — skipped`);
|
|
18
|
+
} else {
|
|
19
|
+
ConfigFileUtils.crearArchivoConfig(configPath);
|
|
20
|
+
console.log(` ${chalk.green('✓')} dbcube.config.js created`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. dbcube/ directory
|
|
24
|
+
const cubesDir = path.join(root, 'dbcube');
|
|
25
|
+
if (!fs.existsSync(cubesDir)) {
|
|
26
|
+
fs.mkdirSync(cubesDir, { recursive: true });
|
|
27
|
+
console.log(` ${chalk.green('✓')} dbcube/ directory created`);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(` ${chalk.yellow('•')} dbcube/ already exists — skipped`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 3. Example table cube
|
|
33
|
+
const examplePath = path.join(cubesDir, 'example.table.cube');
|
|
34
|
+
if (!fs.existsSync(examplePath) && fs.readdirSync(cubesDir).filter(f => f.endsWith('.cube')).length === 0) {
|
|
35
|
+
fs.writeFileSync(examplePath, `@database("local");
|
|
36
|
+
|
|
37
|
+
@meta({
|
|
38
|
+
name: "example";
|
|
39
|
+
description: "Example table - rename or delete this file";
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
@columns({
|
|
43
|
+
id: {
|
|
44
|
+
type: "int";
|
|
45
|
+
options: ["primary", "autoincrement"];
|
|
46
|
+
};
|
|
47
|
+
name: {
|
|
48
|
+
type: "varchar";
|
|
49
|
+
length: "255";
|
|
50
|
+
options: ["not null"];
|
|
51
|
+
};
|
|
52
|
+
created_at: {
|
|
53
|
+
type: "timestamp";
|
|
54
|
+
options: ["not null"];
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
`, 'utf8');
|
|
58
|
+
console.log(` ${chalk.green('✓')} dbcube/example.table.cube created`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 4. .gitignore entries
|
|
62
|
+
const gitignorePath = path.join(root, '.gitignore');
|
|
63
|
+
const entries = ['.dbcube/', 'dbcube/triggers/', 'dbcube/logs/', '.env'];
|
|
64
|
+
let gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
65
|
+
const missing = entries.filter(e => !gitignore.split('\n').some(l => l.trim() === e));
|
|
66
|
+
if (missing.length > 0) {
|
|
67
|
+
gitignore += (gitignore.endsWith('\n') || gitignore === '' ? '' : '\n') + '\n# DBCube\n' + missing.join('\n') + '\n';
|
|
68
|
+
fs.writeFileSync(gitignorePath, gitignore, 'utf8');
|
|
69
|
+
console.log(` ${chalk.green('✓')} .gitignore updated (${missing.join(', ')})`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`\n${chalk.cyan.bold('Next steps:')}`);
|
|
73
|
+
console.log(` 1. ${chalk.white('npx dbcube run database:create')} ${chalk.gray('— configure your first database')}`);
|
|
74
|
+
console.log(` 2. ${chalk.white('Edit dbcube/example.table.cube')} ${chalk.gray('— define your schema')}`);
|
|
75
|
+
console.log(` 3. ${chalk.white('npx dbcube run table:fresh')} ${chalk.gray('— create the tables')}`);
|
|
76
|
+
console.log(` 4. ${chalk.white('npx dbcube generate')} ${chalk.gray('— generate TypeScript types')}\n`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch(error => {
|
|
80
|
+
console.error('Error fatal:', error);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|