Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 104 additions & 4 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,59 @@ objectui init . --template dashboard
- **form**: A contact form with validation
- **dashboard**: A full dashboard with metrics and charts

### `objectui dev [schema]`

Start a development server with hot reload. Opens browser automatically.

```bash
objectui dev app.json
objectui dev my-schema.json --port 8080
objectui dev --no-open
```

**Arguments:**
- `[schema]` - Path to JSON/YAML schema file (default: `app.json`)

**Options:**
- `-p, --port <port>` - Port to run the server on (default: `3000`)
- `-h, --host <host>` - Host to bind the server to (default: `localhost`)
- `--no-open` - Do not open browser automatically

### `objectui build [schema]`

Build your application for production deployment.

```bash
objectui build app.json
objectui build --out-dir build
objectui build --clean
```

**Arguments:**
- `[schema]` - Path to JSON/YAML schema file (default: `app.json`)

**Options:**
- `-o, --out-dir <dir>` - Output directory (default: `dist`)
- `--clean` - Clean output directory before build

### `objectui start`

Serve the production build locally.

```bash
objectui start
objectui start --port 8080
objectui start --dir build
```

**Options:**
- `-p, --port <port>` - Port to run the server on (default: `3000`)
- `-h, --host <host>` - Host to bind the server to (default: `0.0.0.0`)
- `-d, --dir <dir>` - Directory to serve (default: `dist`)

### `objectui serve [schema]`

Start a development server to render your JSON schema.
Start a development server (legacy command, use `dev` instead).

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

### `objectui lint`

Lint the generated application code using ESLint.

```bash
objectui lint
objectui lint --fix
```

**Options:**
- `--fix` - Automatically fix linting issues

**Note:** Run `objectui dev` first to generate the application before linting.

### `objectui test`

Run tests for the application using Vitest.

```bash
objectui test
objectui test --watch
objectui test --coverage
objectui test --ui
```

**Options:**
- `-w, --watch` - Run tests in watch mode
- `-c, --coverage` - Generate test coverage report
- `--ui` - Run tests with Vitest UI

**Note:** Run `objectui dev` first to generate the application before testing.

## Quick Start

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

2. Start the development server:
```bash
objectui serve app.json
objectui dev app.json
```

3. Open http://localhost:3000 in your browser
3. Lint your code (optional):
```bash
objectui lint
```

4. Run tests (optional):
```bash
objectui test
```

5. Build for production:
```bash
objectui build app.json
```

6. Serve the production build:
```bash
objectui start
```

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

## Example Schema

Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"vite": "^5.0.0",
"@vitejs/plugin-react": "^4.2.1",
"express": "^4.21.2",
"express-rate-limit": "^7.4.1",
"js-yaml": "^4.1.0",
"@object-ui/react": "workspace:*",
"@object-ui/components": "workspace:*"
Expand Down
79 changes: 79 additions & 0 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { Command } from 'commander';
import chalk from 'chalk';
import { serve } from './commands/serve.js';
import { init } from './commands/init.js';
import { dev } from './commands/dev.js';
import { buildApp } from './commands/build.js';
import { start } from './commands/start.js';
import { lint } from './commands/lint.js';
import { test } from './commands/test.js';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Expand Down Expand Up @@ -37,6 +42,52 @@ program
}
});

program
.command('dev')
.description('Start development server (alias for serve)')
.argument('[schema]', 'Path to JSON/YAML schema file', 'app.json')
.option('-p, --port <port>', 'Port to run the server on', '3000')
.option('-h, --host <host>', 'Host to bind the server to', 'localhost')
.option('--no-open', 'Do not open browser automatically')
.action(async (schema, options) => {
try {
await dev(schema, options);
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});

program
.command('build')
.description('Build application for production')
.argument('[schema]', 'Path to JSON/YAML schema file', 'app.json')
.option('-o, --out-dir <dir>', 'Output directory', 'dist')
.option('--clean', 'Clean output directory before build', false)
.action(async (schema, options) => {
try {
await buildApp(schema, options);
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});

program
.command('start')
.description('Start production server')
.option('-p, --port <port>', 'Port to run the server on', '3000')
.option('-h, --host <host>', 'Host to bind the server to', '0.0.0.0')
.option('-d, --dir <dir>', 'Directory to serve', 'dist')
.action(async (options) => {
try {
await start(options);
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});

program
.command('init')
.description('初始化新的Object UI应用 / Initialize a new Object UI application with sample schema')
Expand All @@ -51,4 +102,32 @@ program
}
});

program
.command('lint')
.description('Lint the generated application code')
.option('--fix', 'Automatically fix linting issues')
.action(async (options) => {
try {
await lint(options);
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});

program
.command('test')
.description('Run tests for the application')
.option('-w, --watch', 'Run tests in watch mode')
.option('-c, --coverage', 'Generate test coverage report')
.option('--ui', 'Run tests with Vitest UI')
.action(async (options) => {
try {
await test(options);
} catch (error) {
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});

program.parse();
120 changes: 120 additions & 0 deletions packages/cli/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { build as viteBuild } from 'vite';
import react from '@vitejs/plugin-react';
import { existsSync, mkdirSync, cpSync, rmSync } from 'fs';
import { join, resolve } from 'path';
import chalk from 'chalk';
import { execSync } from 'child_process';
import { scanPagesDirectory, createTempAppWithRouting, createTempApp, parseSchemaFile, type RouteInfo } from '../utils/app-generator.js';

interface BuildOptions {
outDir?: string;
clean?: boolean;
}

export async function buildApp(schemaPath: string, options: BuildOptions) {
const cwd = process.cwd();
const outDir = options.outDir || 'dist';
const outputPath = resolve(cwd, outDir);

console.log(chalk.blue('🔨 Building application for production...'));
console.log();

// Check if pages directory exists for file-system routing
const pagesDir = join(cwd, 'pages');
const hasPagesDir = existsSync(pagesDir);

let routes: RouteInfo[] = [];
let schema: unknown = null;
let useFileSystemRouting = false;

if (hasPagesDir) {
// File-system based routing
console.log(chalk.blue('📁 Using file-system routing'));
routes = scanPagesDirectory(pagesDir);
useFileSystemRouting = true;

if (routes.length === 0) {
throw new Error('No schema files found in pages/ directory');
}

console.log(chalk.green(`✓ Found ${routes.length} route(s)`));
} else {
// Single schema file mode
const fullSchemaPath = resolve(cwd, schemaPath);

// Check if schema file exists
if (!existsSync(fullSchemaPath)) {
throw new Error(`Schema file not found: ${schemaPath}\nRun 'objectui init' to create a sample schema.`);
}

console.log(chalk.blue('📋 Loading schema:'), chalk.cyan(schemaPath));

// Read and validate schema
try {
schema = parseSchemaFile(fullSchemaPath);
} catch (error) {
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
}
}

// Create temporary app directory
const tmpDir = join(cwd, '.objectui-tmp');
mkdirSync(tmpDir, { recursive: true });

// Create temporary app files
if (useFileSystemRouting) {
createTempAppWithRouting(tmpDir, routes);
} else {
createTempApp(tmpDir, schema);
}

// Install dependencies
console.log(chalk.blue('📦 Installing dependencies...'));
try {
execSync('npm install --silent --prefer-offline', {
cwd: tmpDir,
stdio: 'pipe',
});
console.log(chalk.green('✓ Dependencies installed'));
} catch {
throw new Error('Failed to install dependencies. Please check your internet connection and try again.');
}

console.log(chalk.blue('⚙️ Building with Vite...'));
console.log();

// Clean output directory if requested
if (options.clean && existsSync(outputPath)) {
console.log(chalk.dim(` Cleaning ${outDir}/ directory...`));
rmSync(outputPath, { recursive: true, force: true });
}

// Build with Vite
try {
await viteBuild({
root: tmpDir,
build: {
outDir: join(tmpDir, 'dist'),
emptyOutDir: true,
reportCompressedSize: true,
},
plugins: [react()],
logLevel: 'info',
});

// Copy built files to output directory
mkdirSync(outputPath, { recursive: true });
cpSync(join(tmpDir, 'dist'), outputPath, { recursive: true });

console.log();
console.log(chalk.green('✓ Build completed successfully!'));
console.log();
console.log(chalk.bold(' Output: ') + chalk.cyan(outDir + '/'));
console.log();
console.log(chalk.dim(' To serve the production build, run:'));
console.log(chalk.cyan(` objectui start --dir ${outDir}`));
console.log();
} catch (error) {
throw new Error(`Build failed: ${error instanceof Error ? error.message : error}`);
}
}
Loading
Loading