@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +156 -0
  3. package/__tests__/commands.test.ts +110 -0
  4. package/dist/commands/ai.js +4 -3
  5. package/dist/commands/ai.js.map +1 -1
  6. package/dist/commands/build.d.ts +12 -0
  7. package/dist/commands/build.js +119 -0
  8. package/dist/commands/build.js.map +1 -0
  9. package/dist/commands/dev.d.ts +9 -0
  10. package/dist/commands/dev.js +23 -0
  11. package/dist/commands/dev.js.map +1 -0
  12. package/dist/commands/format.d.ts +9 -0
  13. package/dist/commands/format.js +137 -0
  14. package/dist/commands/format.js.map +1 -0
  15. package/dist/commands/lint.d.ts +9 -0
  16. package/dist/commands/lint.js +120 -0
  17. package/dist/commands/lint.js.map +1 -0
  18. package/dist/commands/new.js +0 -52
  19. package/dist/commands/new.js.map +1 -1
  20. package/dist/commands/start.d.ts +11 -0
  21. package/dist/commands/start.js +119 -0
  22. package/dist/commands/start.js.map +1 -0
  23. package/dist/commands/test.d.ts +10 -0
  24. package/dist/commands/test.js +120 -0
  25. package/dist/commands/test.js.map +1 -0
  26. package/dist/index.js +91 -14
  27. package/dist/index.js.map +1 -1
  28. package/package.json +6 -7
  29. package/src/commands/ai.ts +4 -3
  30. package/src/commands/build.ts +98 -0
  31. package/src/commands/dev.ts +23 -0
  32. package/src/commands/format.ts +110 -0
  33. package/src/commands/lint.ts +98 -0
  34. package/src/commands/new.ts +0 -52
  35. package/src/commands/start.ts +100 -0
  36. package/src/commands/test.ts +98 -0
  37. package/src/index.ts +96 -14
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/dist/commands/studio.d.ts +0 -5
  40. package/dist/commands/studio.js +0 -364
  41. package/dist/commands/studio.js.map +0 -1
  42. 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 { startStudio } from './commands/studio';
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
- // Serve command
189
+ // Dev command - Start development server
185
190
  program
186
- .command('serve')
187
- .alias('s')
188
- .description('Start a development server')
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 serve({ port: parseInt(options.port), dir: options.dir });
198
+ await dev({
199
+ port: parseInt(options.port),
200
+ dir: options.dir,
201
+ watch: options.watch
202
+ });
193
203
  });
194
204
 
195
- // Studio command
205
+ // Start command - Production server
196
206
  program
197
- .command('studio')
198
- .alias('ui')
199
- .description('Start the ObjectQL Studio')
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('--no-open', 'Do not open browser automatically')
211
+ .option('-c, --config <path>', 'Path to objectql.config.ts/js')
203
212
  .action(async (options) => {
204
- await startStudio({
213
+ await start({
205
214
  port: parseInt(options.port),
206
215
  dir: options.dir,
207
- open: options.open
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')