Skip to content

Commit caa94aa

Browse files
authored
Merge pull request #53 from objectstack-ai/copilot/add-cli-development-commands
2 parents 36746a8 + 1d07c74 commit caa94aa

File tree

11 files changed

+1261
-627
lines changed

11 files changed

+1261
-627
lines changed

packages/cli/README.md

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,59 @@ objectui init . --template dashboard
3434
- **form**: A contact form with validation
3535
- **dashboard**: A full dashboard with metrics and charts
3636

37+
### `objectui dev [schema]`
38+
39+
Start a development server with hot reload. Opens browser automatically.
40+
41+
```bash
42+
objectui dev app.json
43+
objectui dev my-schema.json --port 8080
44+
objectui dev --no-open
45+
```
46+
47+
**Arguments:**
48+
- `[schema]` - Path to JSON/YAML schema file (default: `app.json`)
49+
50+
**Options:**
51+
- `-p, --port <port>` - Port to run the server on (default: `3000`)
52+
- `-h, --host <host>` - Host to bind the server to (default: `localhost`)
53+
- `--no-open` - Do not open browser automatically
54+
55+
### `objectui build [schema]`
56+
57+
Build your application for production deployment.
58+
59+
```bash
60+
objectui build app.json
61+
objectui build --out-dir build
62+
objectui build --clean
63+
```
64+
65+
**Arguments:**
66+
- `[schema]` - Path to JSON/YAML schema file (default: `app.json`)
67+
68+
**Options:**
69+
- `-o, --out-dir <dir>` - Output directory (default: `dist`)
70+
- `--clean` - Clean output directory before build
71+
72+
### `objectui start`
73+
74+
Serve the production build locally.
75+
76+
```bash
77+
objectui start
78+
objectui start --port 8080
79+
objectui start --dir build
80+
```
81+
82+
**Options:**
83+
- `-p, --port <port>` - Port to run the server on (default: `3000`)
84+
- `-h, --host <host>` - Host to bind the server to (default: `0.0.0.0`)
85+
- `-d, --dir <dir>` - Directory to serve (default: `dist`)
86+
3787
### `objectui serve [schema]`
3888

39-
Start a development server to render your JSON schema.
89+
Start a development server (legacy command, use `dev` instead).
4090

4191
```bash
4292
objectui serve app.json
@@ -50,6 +100,38 @@ objectui serve my-schema.json --port 8080
50100
- `-p, --port <port>` - Port to run the server on (default: `3000`)
51101
- `-h, --host <host>` - Host to bind the server to (default: `localhost`)
52102

103+
### `objectui lint`
104+
105+
Lint the generated application code using ESLint.
106+
107+
```bash
108+
objectui lint
109+
objectui lint --fix
110+
```
111+
112+
**Options:**
113+
- `--fix` - Automatically fix linting issues
114+
115+
**Note:** Run `objectui dev` first to generate the application before linting.
116+
117+
### `objectui test`
118+
119+
Run tests for the application using Vitest.
120+
121+
```bash
122+
objectui test
123+
objectui test --watch
124+
objectui test --coverage
125+
objectui test --ui
126+
```
127+
128+
**Options:**
129+
- `-w, --watch` - Run tests in watch mode
130+
- `-c, --coverage` - Generate test coverage report
131+
- `--ui` - Run tests with Vitest UI
132+
133+
**Note:** Run `objectui dev` first to generate the application before testing.
134+
53135
## Quick Start
54136

55137
1. Create a new application:
@@ -60,12 +142,30 @@ objectui serve my-schema.json --port 8080
60142

61143
2. Start the development server:
62144
```bash
63-
objectui serve app.json
145+
objectui dev app.json
64146
```
65147

66-
3. Open http://localhost:3000 in your browser
148+
3. Lint your code (optional):
149+
```bash
150+
objectui lint
151+
```
152+
153+
4. Run tests (optional):
154+
```bash
155+
objectui test
156+
```
157+
158+
5. Build for production:
159+
```bash
160+
objectui build app.json
161+
```
162+
163+
6. Serve the production build:
164+
```bash
165+
objectui start
166+
```
67167

68-
4. Edit `app.json` to customize your application
168+
Your app will be running at http://localhost:3000!
69169

70170
## Example Schema
71171

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"vite": "^5.0.0",
3737
"@vitejs/plugin-react": "^4.2.1",
3838
"express": "^4.21.2",
39+
"express-rate-limit": "^7.4.1",
3940
"js-yaml": "^4.1.0",
4041
"@object-ui/react": "workspace:*",
4142
"@object-ui/components": "workspace:*"

packages/cli/src/cli.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ import { Command } from 'commander';
33
import chalk from 'chalk';
44
import { serve } from './commands/serve.js';
55
import { init } from './commands/init.js';
6+
import { dev } from './commands/dev.js';
7+
import { buildApp } from './commands/build.js';
8+
import { start } from './commands/start.js';
9+
import { lint } from './commands/lint.js';
10+
import { test } from './commands/test.js';
611
import { readFileSync } from 'fs';
712
import { fileURLToPath } from 'url';
813
import { dirname, join } from 'path';
@@ -37,6 +42,52 @@ program
3742
}
3843
});
3944

45+
program
46+
.command('dev')
47+
.description('Start development server (alias for serve)')
48+
.argument('[schema]', 'Path to JSON/YAML schema file', 'app.json')
49+
.option('-p, --port <port>', 'Port to run the server on', '3000')
50+
.option('-h, --host <host>', 'Host to bind the server to', 'localhost')
51+
.option('--no-open', 'Do not open browser automatically')
52+
.action(async (schema, options) => {
53+
try {
54+
await dev(schema, options);
55+
} catch (error) {
56+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
57+
process.exit(1);
58+
}
59+
});
60+
61+
program
62+
.command('build')
63+
.description('Build application for production')
64+
.argument('[schema]', 'Path to JSON/YAML schema file', 'app.json')
65+
.option('-o, --out-dir <dir>', 'Output directory', 'dist')
66+
.option('--clean', 'Clean output directory before build', false)
67+
.action(async (schema, options) => {
68+
try {
69+
await buildApp(schema, options);
70+
} catch (error) {
71+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
72+
process.exit(1);
73+
}
74+
});
75+
76+
program
77+
.command('start')
78+
.description('Start production server')
79+
.option('-p, --port <port>', 'Port to run the server on', '3000')
80+
.option('-h, --host <host>', 'Host to bind the server to', '0.0.0.0')
81+
.option('-d, --dir <dir>', 'Directory to serve', 'dist')
82+
.action(async (options) => {
83+
try {
84+
await start(options);
85+
} catch (error) {
86+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
87+
process.exit(1);
88+
}
89+
});
90+
4091
program
4192
.command('init')
4293
.description('初始化新的Object UI应用 / Initialize a new Object UI application with sample schema')
@@ -51,4 +102,32 @@ program
51102
}
52103
});
53104

105+
program
106+
.command('lint')
107+
.description('Lint the generated application code')
108+
.option('--fix', 'Automatically fix linting issues')
109+
.action(async (options) => {
110+
try {
111+
await lint(options);
112+
} catch (error) {
113+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
114+
process.exit(1);
115+
}
116+
});
117+
118+
program
119+
.command('test')
120+
.description('Run tests for the application')
121+
.option('-w, --watch', 'Run tests in watch mode')
122+
.option('-c, --coverage', 'Generate test coverage report')
123+
.option('--ui', 'Run tests with Vitest UI')
124+
.action(async (options) => {
125+
try {
126+
await test(options);
127+
} catch (error) {
128+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
129+
process.exit(1);
130+
}
131+
});
132+
54133
program.parse();

packages/cli/src/commands/build.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { build as viteBuild } from 'vite';
2+
import react from '@vitejs/plugin-react';
3+
import { existsSync, mkdirSync, cpSync, rmSync } from 'fs';
4+
import { join, resolve } from 'path';
5+
import chalk from 'chalk';
6+
import { execSync } from 'child_process';
7+
import { scanPagesDirectory, createTempAppWithRouting, createTempApp, parseSchemaFile, type RouteInfo } from '../utils/app-generator.js';
8+
9+
interface BuildOptions {
10+
outDir?: string;
11+
clean?: boolean;
12+
}
13+
14+
export async function buildApp(schemaPath: string, options: BuildOptions) {
15+
const cwd = process.cwd();
16+
const outDir = options.outDir || 'dist';
17+
const outputPath = resolve(cwd, outDir);
18+
19+
console.log(chalk.blue('🔨 Building application for production...'));
20+
console.log();
21+
22+
// Check if pages directory exists for file-system routing
23+
const pagesDir = join(cwd, 'pages');
24+
const hasPagesDir = existsSync(pagesDir);
25+
26+
let routes: RouteInfo[] = [];
27+
let schema: unknown = null;
28+
let useFileSystemRouting = false;
29+
30+
if (hasPagesDir) {
31+
// File-system based routing
32+
console.log(chalk.blue('📁 Using file-system routing'));
33+
routes = scanPagesDirectory(pagesDir);
34+
useFileSystemRouting = true;
35+
36+
if (routes.length === 0) {
37+
throw new Error('No schema files found in pages/ directory');
38+
}
39+
40+
console.log(chalk.green(`✓ Found ${routes.length} route(s)`));
41+
} else {
42+
// Single schema file mode
43+
const fullSchemaPath = resolve(cwd, schemaPath);
44+
45+
// Check if schema file exists
46+
if (!existsSync(fullSchemaPath)) {
47+
throw new Error(`Schema file not found: ${schemaPath}\nRun 'objectui init' to create a sample schema.`);
48+
}
49+
50+
console.log(chalk.blue('📋 Loading schema:'), chalk.cyan(schemaPath));
51+
52+
// Read and validate schema
53+
try {
54+
schema = parseSchemaFile(fullSchemaPath);
55+
} catch (error) {
56+
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
57+
}
58+
}
59+
60+
// Create temporary app directory
61+
const tmpDir = join(cwd, '.objectui-tmp');
62+
mkdirSync(tmpDir, { recursive: true });
63+
64+
// Create temporary app files
65+
if (useFileSystemRouting) {
66+
createTempAppWithRouting(tmpDir, routes);
67+
} else {
68+
createTempApp(tmpDir, schema);
69+
}
70+
71+
// Install dependencies
72+
console.log(chalk.blue('📦 Installing dependencies...'));
73+
try {
74+
execSync('npm install --silent --prefer-offline', {
75+
cwd: tmpDir,
76+
stdio: 'pipe',
77+
});
78+
console.log(chalk.green('✓ Dependencies installed'));
79+
} catch {
80+
throw new Error('Failed to install dependencies. Please check your internet connection and try again.');
81+
}
82+
83+
console.log(chalk.blue('⚙️ Building with Vite...'));
84+
console.log();
85+
86+
// Clean output directory if requested
87+
if (options.clean && existsSync(outputPath)) {
88+
console.log(chalk.dim(` Cleaning ${outDir}/ directory...`));
89+
rmSync(outputPath, { recursive: true, force: true });
90+
}
91+
92+
// Build with Vite
93+
try {
94+
await viteBuild({
95+
root: tmpDir,
96+
build: {
97+
outDir: join(tmpDir, 'dist'),
98+
emptyOutDir: true,
99+
reportCompressedSize: true,
100+
},
101+
plugins: [react()],
102+
logLevel: 'info',
103+
});
104+
105+
// Copy built files to output directory
106+
mkdirSync(outputPath, { recursive: true });
107+
cpSync(join(tmpDir, 'dist'), outputPath, { recursive: true });
108+
109+
console.log();
110+
console.log(chalk.green('✓ Build completed successfully!'));
111+
console.log();
112+
console.log(chalk.bold(' Output: ') + chalk.cyan(outDir + '/'));
113+
console.log();
114+
console.log(chalk.dim(' To serve the production build, run:'));
115+
console.log(chalk.cyan(` objectui start --dir ${outDir}`));
116+
console.log();
117+
} catch (error) {
118+
throw new Error(`Build failed: ${error instanceof Error ? error.message : error}`);
119+
}
120+
}

0 commit comments

Comments
 (0)