@objectql/cli 1.8.3 → 1.8.4
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/CHANGELOG.md +12 -0
- package/README.md +156 -0
- package/__tests__/commands.test.ts +110 -0
- package/dist/commands/ai.js +4 -3
- package/dist/commands/ai.js.map +1 -1
- package/dist/commands/build.d.ts +12 -0
- package/dist/commands/build.js +119 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +9 -0
- package/dist/commands/dev.js +23 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/format.d.ts +9 -0
- package/dist/commands/format.js +137 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/lint.d.ts +9 -0
- package/dist/commands/lint.js +120 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/new.js +0 -52
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/start.d.ts +11 -0
- package/dist/commands/start.js +119 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/test.d.ts +10 -0
- package/dist/commands/test.js +120 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/index.js +91 -14
- package/dist/index.js.map +1 -1
- package/package.json +6 -7
- package/src/commands/ai.ts +4 -3
- package/src/commands/build.ts +98 -0
- package/src/commands/dev.ts +23 -0
- package/src/commands/format.ts +110 -0
- package/src/commands/lint.ts +98 -0
- package/src/commands/new.ts +0 -52
- package/src/commands/start.ts +100 -0
- package/src/commands/test.ts +98 -0
- package/src/index.ts +96 -14
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/commands/studio.d.ts +0 -5
- package/dist/commands/studio.js +0 -364
- package/dist/commands/studio.js.map +0 -1
- package/src/commands/studio.ts +0 -354
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ObjectQL } from '@objectql/core';
|
|
2
|
+
import { SqlDriver } from '@objectql/driver-sql';
|
|
3
|
+
import { ObjectLoader } from '@objectql/platform-node';
|
|
4
|
+
import { createNodeHandler } from '@objectql/server';
|
|
5
|
+
import { createServer } from 'http';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
interface StartOptions {
|
|
11
|
+
port: number;
|
|
12
|
+
dir: string;
|
|
13
|
+
config?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Flexible config type that handles both ObjectQLConfig and custom config formats
|
|
17
|
+
interface LoadedConfig {
|
|
18
|
+
datasources?: Record<string, any>;
|
|
19
|
+
datasource?: Record<string, any>;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Start production server
|
|
25
|
+
* Loads configuration from objectql.config.ts/js if available
|
|
26
|
+
*/
|
|
27
|
+
export async function start(options: StartOptions) {
|
|
28
|
+
console.log(chalk.blue('Starting ObjectQL Production Server...'));
|
|
29
|
+
|
|
30
|
+
const rootDir = path.resolve(process.cwd(), options.dir);
|
|
31
|
+
console.log(chalk.gray(`Loading schema from: ${rootDir}`));
|
|
32
|
+
|
|
33
|
+
// Try to load configuration
|
|
34
|
+
let config: LoadedConfig | null = null;
|
|
35
|
+
const configPath = options.config || path.join(process.cwd(), 'objectql.config.ts');
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(configPath)) {
|
|
38
|
+
try {
|
|
39
|
+
console.log(chalk.gray(`Loading config from: ${configPath}`));
|
|
40
|
+
// Use require for .js files or ts-node for .ts files
|
|
41
|
+
if (configPath.endsWith('.ts')) {
|
|
42
|
+
require('ts-node/register');
|
|
43
|
+
}
|
|
44
|
+
const loadedModule = require(configPath);
|
|
45
|
+
// Handle both default export and direct export
|
|
46
|
+
config = loadedModule.default || loadedModule;
|
|
47
|
+
} catch (e: any) {
|
|
48
|
+
console.warn(chalk.yellow(`⚠️ Failed to load config: ${e.message}`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Initialize datasource from config or use default SQLite
|
|
53
|
+
// Note: Config files may use 'datasource' (singular) while ObjectQLConfig uses 'datasources' (plural)
|
|
54
|
+
const datasourceConfig = config?.datasources?.default || config?.datasource?.default || {
|
|
55
|
+
client: 'sqlite3',
|
|
56
|
+
connection: {
|
|
57
|
+
filename: process.env.DATABASE_FILE || './objectql.db'
|
|
58
|
+
},
|
|
59
|
+
useNullAsDefault: true
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const driver = new SqlDriver(datasourceConfig);
|
|
63
|
+
const app = new ObjectQL({
|
|
64
|
+
datasources: { default: driver }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Load Schema
|
|
68
|
+
try {
|
|
69
|
+
const loader = new ObjectLoader(app.metadata);
|
|
70
|
+
loader.load(rootDir);
|
|
71
|
+
await app.init();
|
|
72
|
+
console.log(chalk.green('✅ Schema loaded successfully.'));
|
|
73
|
+
} catch (e: any) {
|
|
74
|
+
console.error(chalk.red('❌ Failed to load schema:'), e.message);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Create Handler
|
|
79
|
+
const handler = createNodeHandler(app);
|
|
80
|
+
|
|
81
|
+
// Start Server
|
|
82
|
+
const server = createServer(async (req, res) => {
|
|
83
|
+
await handler(req, res);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
server.listen(options.port, () => {
|
|
87
|
+
console.log(chalk.green(`\n✅ Server started in production mode`));
|
|
88
|
+
console.log(chalk.green(`🚀 API endpoint: http://localhost:${options.port}`));
|
|
89
|
+
console.log(chalk.blue(`📖 OpenAPI Spec: http://localhost:${options.port}/openapi.json`));
|
|
90
|
+
|
|
91
|
+
// Handle graceful shutdown
|
|
92
|
+
process.on('SIGTERM', () => {
|
|
93
|
+
console.log(chalk.yellow('\n⚠️ SIGTERM received, shutting down gracefully...'));
|
|
94
|
+
server.close(() => {
|
|
95
|
+
console.log(chalk.green('✅ Server closed'));
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
interface TestOptions {
|
|
7
|
+
dir?: string;
|
|
8
|
+
watch?: boolean;
|
|
9
|
+
coverage?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Test command - runs tests for the ObjectQL project
|
|
14
|
+
*/
|
|
15
|
+
export async function test(options: TestOptions) {
|
|
16
|
+
console.log(chalk.blue('🧪 Running tests...\n'));
|
|
17
|
+
|
|
18
|
+
const rootDir = path.resolve(process.cwd(), options.dir || '.');
|
|
19
|
+
|
|
20
|
+
// Look for package.json to determine test runner
|
|
21
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
22
|
+
let testCommand = 'npm test';
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
26
|
+
try {
|
|
27
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
28
|
+
|
|
29
|
+
// Check if jest is configured
|
|
30
|
+
if (packageJson.devDependencies?.jest || packageJson.dependencies?.jest || packageJson.jest) {
|
|
31
|
+
const jestArgs = ['jest'];
|
|
32
|
+
|
|
33
|
+
if (options.watch) {
|
|
34
|
+
jestArgs.push('--watch');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.coverage) {
|
|
38
|
+
jestArgs.push('--coverage');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(chalk.cyan(`Running: ${jestArgs.join(' ')}\n`));
|
|
42
|
+
|
|
43
|
+
const jestProcess = spawn('npx', jestArgs, {
|
|
44
|
+
cwd: rootDir,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
shell: true
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
jestProcess.on('exit', (code) => {
|
|
50
|
+
if (code !== 0) {
|
|
51
|
+
console.error(chalk.red(`\n❌ Tests failed with exit code ${code}`));
|
|
52
|
+
process.exit(code || 1);
|
|
53
|
+
} else {
|
|
54
|
+
console.log(chalk.green('\n✅ All tests passed!'));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fall back to package.json test script
|
|
62
|
+
if (packageJson.scripts?.test) {
|
|
63
|
+
console.log(chalk.cyan(`Running: npm test\n`));
|
|
64
|
+
|
|
65
|
+
const npmProcess = spawn('npm', ['test'], {
|
|
66
|
+
cwd: rootDir,
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
shell: true
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
npmProcess.on('exit', (code) => {
|
|
72
|
+
if (code !== 0) {
|
|
73
|
+
console.error(chalk.red(`\n❌ Tests failed with exit code ${code}`));
|
|
74
|
+
process.exit(code || 1);
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.green('\n✅ All tests passed!'));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
} catch (parseError: any) {
|
|
83
|
+
console.error(chalk.yellow(`⚠️ Failed to parse package.json: ${parseError.message}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// No test configuration found
|
|
88
|
+
console.log(chalk.yellow('⚠️ No test configuration found'));
|
|
89
|
+
console.log(chalk.gray('To add tests to your project:'));
|
|
90
|
+
console.log(chalk.gray(' 1. Install jest: npm install --save-dev jest @types/jest ts-jest'));
|
|
91
|
+
console.log(chalk.gray(' 2. Create a jest.config.js file'));
|
|
92
|
+
console.log(chalk.gray(' 3. Add a test script to package.json'));
|
|
93
|
+
|
|
94
|
+
} catch (e: any) {
|
|
95
|
+
console.error(chalk.red('❌ Test execution failed:'), e.message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { generateTypes } from './commands/generate';
|
|
3
3
|
import { startRepl } from './commands/repl';
|
|
4
4
|
import { serve } from './commands/serve';
|
|
5
|
-
import {
|
|
5
|
+
import { dev } from './commands/dev';
|
|
6
|
+
import { start } from './commands/start';
|
|
7
|
+
import { build } from './commands/build';
|
|
8
|
+
import { test } from './commands/test';
|
|
9
|
+
import { lint } from './commands/lint';
|
|
10
|
+
import { format } from './commands/format';
|
|
6
11
|
import { initProject } from './commands/init';
|
|
7
12
|
import { newMetadata } from './commands/new';
|
|
8
13
|
import { i18nExtract, i18nInit, i18nValidate } from './commands/i18n';
|
|
@@ -181,33 +186,110 @@ program
|
|
|
181
186
|
await startRepl(options.config);
|
|
182
187
|
});
|
|
183
188
|
|
|
184
|
-
//
|
|
189
|
+
// Dev command - Start development server
|
|
185
190
|
program
|
|
186
|
-
.command('
|
|
187
|
-
.alias('
|
|
188
|
-
.description('Start
|
|
191
|
+
.command('dev')
|
|
192
|
+
.alias('d')
|
|
193
|
+
.description('Start development server with hot reload')
|
|
189
194
|
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
190
195
|
.option('-d, --dir <path>', 'Directory containing schema', '.')
|
|
196
|
+
.option('--no-watch', 'Disable file watching')
|
|
191
197
|
.action(async (options) => {
|
|
192
|
-
await
|
|
198
|
+
await dev({
|
|
199
|
+
port: parseInt(options.port),
|
|
200
|
+
dir: options.dir,
|
|
201
|
+
watch: options.watch
|
|
202
|
+
});
|
|
193
203
|
});
|
|
194
204
|
|
|
195
|
-
//
|
|
205
|
+
// Start command - Production server
|
|
196
206
|
program
|
|
197
|
-
.command('
|
|
198
|
-
.
|
|
199
|
-
.
|
|
200
|
-
.option('-p, --port <number>', 'Port to listen on', '5555')
|
|
207
|
+
.command('start')
|
|
208
|
+
.description('Start production server')
|
|
209
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
201
210
|
.option('-d, --dir <path>', 'Directory containing schema', '.')
|
|
202
|
-
.option('--
|
|
211
|
+
.option('-c, --config <path>', 'Path to objectql.config.ts/js')
|
|
203
212
|
.action(async (options) => {
|
|
204
|
-
await
|
|
213
|
+
await start({
|
|
205
214
|
port: parseInt(options.port),
|
|
206
215
|
dir: options.dir,
|
|
207
|
-
|
|
216
|
+
config: options.config
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Build command - Build project for production
|
|
221
|
+
program
|
|
222
|
+
.command('build')
|
|
223
|
+
.alias('b')
|
|
224
|
+
.description('Build project and generate types')
|
|
225
|
+
.option('-d, --dir <path>', 'Source directory', '.')
|
|
226
|
+
.option('-o, --output <path>', 'Output directory', './dist')
|
|
227
|
+
.option('--no-types', 'Skip TypeScript type generation')
|
|
228
|
+
.option('--no-validate', 'Skip metadata validation')
|
|
229
|
+
.action(async (options) => {
|
|
230
|
+
await build({
|
|
231
|
+
dir: options.dir,
|
|
232
|
+
output: options.output,
|
|
233
|
+
types: options.types,
|
|
234
|
+
validate: options.validate
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Test command - Run tests
|
|
239
|
+
program
|
|
240
|
+
.command('test')
|
|
241
|
+
.alias('t')
|
|
242
|
+
.description('Run tests')
|
|
243
|
+
.option('-d, --dir <path>', 'Project directory', '.')
|
|
244
|
+
.option('-w, --watch', 'Watch mode')
|
|
245
|
+
.option('--coverage', 'Generate coverage report')
|
|
246
|
+
.action(async (options) => {
|
|
247
|
+
await test({
|
|
248
|
+
dir: options.dir,
|
|
249
|
+
watch: options.watch,
|
|
250
|
+
coverage: options.coverage
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Lint command - Validate metadata
|
|
255
|
+
program
|
|
256
|
+
.command('lint')
|
|
257
|
+
.alias('l')
|
|
258
|
+
.description('Validate metadata files')
|
|
259
|
+
.option('-d, --dir <path>', 'Directory to lint', '.')
|
|
260
|
+
.option('--fix', 'Automatically fix issues')
|
|
261
|
+
.action(async (options) => {
|
|
262
|
+
await lint({
|
|
263
|
+
dir: options.dir,
|
|
264
|
+
fix: options.fix
|
|
208
265
|
});
|
|
209
266
|
});
|
|
210
267
|
|
|
268
|
+
// Format command - Format metadata files
|
|
269
|
+
program
|
|
270
|
+
.command('format')
|
|
271
|
+
.alias('fmt')
|
|
272
|
+
.description('Format metadata files with Prettier')
|
|
273
|
+
.option('-d, --dir <path>', 'Directory to format', '.')
|
|
274
|
+
.option('--check', 'Check if files are formatted without modifying')
|
|
275
|
+
.action(async (options) => {
|
|
276
|
+
await format({
|
|
277
|
+
dir: options.dir,
|
|
278
|
+
check: options.check
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Serve command (kept for backwards compatibility)
|
|
283
|
+
program
|
|
284
|
+
.command('serve')
|
|
285
|
+
.alias('s')
|
|
286
|
+
.description('Start a development server (alias for dev)')
|
|
287
|
+
.option('-p, --port <number>', 'Port to listen on', '3000')
|
|
288
|
+
.option('-d, --dir <path>', 'Directory containing schema', '.')
|
|
289
|
+
.action(async (options) => {
|
|
290
|
+
await serve({ port: parseInt(options.port), dir: options.dir });
|
|
291
|
+
});
|
|
292
|
+
|
|
211
293
|
// AI command - Interactive by default, with specific subcommands for other modes
|
|
212
294
|
const aiCmd = program
|
|
213
295
|
.command('ai')
|