Skip to content

Commit a0addbb

Browse files
committed
重构 CLI 命令,添加生成、检查、添加、医生和工作室功能,优化开发脚本和依赖管理
1 parent daa3271 commit a0addbb

File tree

11 files changed

+627
-36
lines changed

11 files changed

+627
-36
lines changed

package.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@
1414
"docs"
1515
],
1616
"scripts": {
17-
"dev": "node scripts/start-app.mjs examples/showcase",
18-
"runner": "pnpm --filter @object-ui/runner dev",
19-
"dev:dashboard": "node scripts/start-app.mjs examples/dashboard",
20-
"dev:crm": "node scripts/start-app.mjs examples/crm-app",
21-
"dev:showcase": "node scripts/start-app.mjs examples/showcase",
22-
"start:app": "node scripts/start-app.mjs",
17+
"dev": "pnpm dev:showcase",
18+
"dev:dashboard": "node packages/cli/dist/cli.js dev examples/dashboard/index.json",
19+
"dev:crm": "node packages/cli/dist/cli.js dev examples/crm-app/app.json",
20+
"dev:showcase": "node packages/cli/dist/cli.js dev examples/showcase/app.json",
21+
"showcase": "node packages/cli/dist/cli.js showcase",
2322
"start": "pnpm dev",
2423
"build": "pnpm --filter './packages/*' -r build && pnpm --filter './examples/*' -r build",
2524
"pretest": "pnpm --filter @object-ui/types build && pnpm --filter @object-ui/core build && pnpm --filter @object-ui/react build && pnpm --filter @object-ui/components build",
@@ -33,6 +32,11 @@
3332
"lint": "pnpm -r lint",
3433
"cli": "node packages/cli/dist/cli.js",
3534
"objectui": "node packages/cli/dist/cli.js",
35+
"doctor": "node packages/cli/dist/cli.js doctor",
36+
"studio": "node packages/cli/dist/cli.js studio",
37+
"check": "node packages/cli/dist/cli.js check",
38+
"generate": "node packages/cli/dist/cli.js generate",
39+
"g": "node packages/cli/dist/cli.js generate",
3640
"changeset": "changeset",
3741
"changeset:version": "changeset version",
3842
"changeset:publish": "changeset publish"
@@ -49,6 +53,7 @@
4953
"@types/react-dom": "18.3.1",
5054
"@vitest/coverage-v8": "^4.0.17",
5155
"@vitest/ui": "^4.0.17",
56+
"autoprefixer": "^10.4.23",
5257
"eslint": "^9.39.2",
5358
"eslint-plugin-react-hooks": "^7.0.1",
5459
"eslint-plugin-react-refresh": "^0.4.24",

packages/cli/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,22 @@
3131
"author": "ObjectQL Team",
3232
"license": "MIT",
3333
"dependencies": {
34-
"commander": "^14.0.2",
35-
"chalk": "^5.4.1",
36-
"vite": "^7.3.1",
34+
"@object-ui/components": "workspace:*",
35+
"@object-ui/react": "workspace:*",
36+
"@types/glob": "^9.0.0",
3737
"@vitejs/plugin-react": "^4.2.1",
38+
"chalk": "^5.4.1",
39+
"commander": "^14.0.2",
3840
"express": "^4.21.2",
3941
"express-rate-limit": "^7.4.1",
42+
"glob": "^13.0.0",
4043
"js-yaml": "^4.1.0",
41-
"@object-ui/react": "workspace:*",
42-
"@object-ui/components": "workspace:*"
44+
"vite": "^7.3.1"
4345
},
4446
"devDependencies": {
4547
"@types/express": "^4.17.21",
46-
"@types/node": "^25.0.8",
4748
"@types/js-yaml": "^4.0.9",
49+
"@types/node": "^25.0.8",
4850
"tsup": "^8.0.0",
4951
"typescript": "^5.9.3"
5052
}

packages/cli/src/cli.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { buildApp } from './commands/build.js';
88
import { start } from './commands/start.js';
99
import { lint } from './commands/lint.js';
1010
import { test } from './commands/test.js';
11+
import { generate } from './commands/generate.js';
12+
import { doctor } from './commands/doctor.js';
13+
import { add } from './commands/add.js';
14+
import { studio } from './commands/studio.js';
15+
import { check } from './commands/check.js';
1116
import { readFileSync } from 'fs';
1217
import { fileURLToPath } from 'url';
1318
import { dirname, join } from 'path';
@@ -130,4 +135,85 @@ program
130135
}
131136
});
132137

138+
program
139+
.command('generate')
140+
.alias('g')
141+
.description('Generate new resources (objects, pages, plugins)')
142+
.argument('<type>', 'Type of resource to generate (resource/object, page, plugin)')
143+
.argument('<name>', 'Name of the resource')
144+
.action(async (type, name) => {
145+
try {
146+
await generate(type, name);
147+
} catch (error) {
148+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
149+
process.exit(1);
150+
}
151+
});
152+
153+
program
154+
.command('doctor')
155+
.description('Diagnose and fix common issues')
156+
.action(async () => {
157+
try {
158+
await doctor();
159+
} catch (error) {
160+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
161+
process.exit(1);
162+
}
163+
});
164+
165+
program
166+
.command('add')
167+
.description('Add a new component renderer to your project')
168+
.argument('<component>', 'Component name (e.g. Input, Grid)')
169+
.action(async (component) => {
170+
try {
171+
await add(component);
172+
} catch (error) {
173+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
174+
process.exit(1);
175+
}
176+
});
177+
178+
program
179+
.command('studio')
180+
.description('Start the visual designer')
181+
.action(async () => {
182+
try {
183+
await studio();
184+
} catch (error) {
185+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
186+
process.exit(1);
187+
}
188+
});
189+
190+
program
191+
.command('check')
192+
.description('Validate schema files')
193+
.action(async () => {
194+
try {
195+
await check();
196+
} catch (error) {
197+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
198+
process.exit(1);
199+
}
200+
});
201+
202+
program
203+
.command('showcase')
204+
.description('Start the built-in showcase example')
205+
.option('-p, --port <port>', 'Port to run the server on', '3000')
206+
.option('-h, --host <host>', 'Host to bind the server to', 'localhost')
207+
.option('--no-open', 'Do not open browser automatically')
208+
.action(async (options) => {
209+
try {
210+
// Locate repository root relative to this file and point to examples/showcase/app.json
211+
const showcaseSchema = join(__dirname, '../../..', 'examples', 'showcase', 'app.json');
212+
await dev(showcaseSchema, options);
213+
} catch (error) {
214+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : error);
215+
process.exit(1);
216+
}
217+
});
218+
133219
program.parse();

packages/cli/src/commands/add.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import chalk from 'chalk';
2+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
3+
import { join } from 'path';
4+
5+
export async function add(component: string) {
6+
console.log(chalk.bold(`Object UI Add: ${component}`));
7+
console.log(chalk.yellow('Feature not implemented yet.'));
8+
console.log(`This command will download the source code for ${component}Renderer to your project.`);
9+
}

packages/cli/src/commands/check.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import chalk from 'chalk';
2+
import { globSync } from 'glob';
3+
import { readFileSync } from 'fs';
4+
import { join } from 'path';
5+
6+
export async function check() {
7+
console.log(chalk.bold('Object UI Schema Check'));
8+
const cwd = process.cwd();
9+
10+
// 1. Find all JSON/YAML files
11+
const files = globSync('**/*.{json,yaml,yml}', {
12+
cwd,
13+
ignore: ['node_modules/**', 'dist/**', '.git/**']
14+
});
15+
16+
console.log(`Analyzing ${files.length} files...`);
17+
18+
let errors = 0;
19+
20+
for (const file of files) {
21+
try {
22+
// Basic JSON parsing check
23+
if (file.endsWith('.json')) {
24+
JSON.parse(readFileSync(join(cwd, file), 'utf-8'));
25+
}
26+
// TODO: Schema validation logic
27+
} catch (e) {
28+
console.log(chalk.red(`x Invalid JSON in ${file}: ${(e as Error).message}`));
29+
errors++;
30+
}
31+
}
32+
33+
if (errors === 0) {
34+
console.log(chalk.green('✓ All checks passed'));
35+
} else {
36+
console.log(chalk.red(`Found ${errors} errors`));
37+
process.exit(1);
38+
}
39+
}

packages/cli/src/commands/dev.ts

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { createServer } from 'vite';
22
import react from '@vitejs/plugin-react';
3-
import { existsSync, mkdirSync } from 'fs';
4-
import { join, resolve } from 'path';
3+
import { existsSync, mkdirSync, unlinkSync, statSync } from 'fs';
4+
import { join, resolve, dirname } from 'path';
55
import chalk from 'chalk';
66
import { execSync } from 'child_process';
7+
import { createRequire } from 'module';
78
import { scanPagesDirectory, createTempAppWithRouting, createTempApp, parseSchemaFile, type RouteInfo } from '../utils/app-generator.js';
89

910
interface DevOptions {
@@ -15,22 +16,62 @@ interface DevOptions {
1516
export async function dev(schemaPath: string, options: DevOptions) {
1617
const cwd = process.cwd();
1718

18-
// Check if pages directory exists for file-system routing
19-
const pagesDir = join(cwd, 'pages');
20-
const hasPagesDir = existsSync(pagesDir);
19+
// Resolve the actual project root and schema file
20+
let projectRoot = cwd;
21+
let targetSchemaPath = schemaPath;
22+
let hasPagesDir = false;
23+
let pagesDir = '';
24+
let appConfig: unknown = null;
25+
26+
// 1. Determine Project Root & Mode
27+
const absoluteSchemaPath = resolve(cwd, schemaPath);
28+
29+
if (existsSync(absoluteSchemaPath) && statSync(absoluteSchemaPath).isFile()) {
30+
// If input is a file (e.g. examples/showcase/app.json)
31+
const fileDir = dirname(absoluteSchemaPath);
32+
const potentialPagesDir = join(fileDir, 'pages');
33+
34+
if (existsSync(potentialPagesDir)) {
35+
console.log(chalk.blue(`📂 Detected project structure at ${fileDir}`));
36+
projectRoot = fileDir;
37+
hasPagesDir = true;
38+
pagesDir = potentialPagesDir;
39+
40+
// Try to load app.json as config
41+
try {
42+
appConfig = parseSchemaFile(absoluteSchemaPath);
43+
console.log(chalk.blue('⚙️ Loaded App Config from app.json'));
44+
} catch (e) {
45+
console.warn('Failed to parse app config');
46+
}
47+
}
48+
}
49+
50+
// Fallback: Check detect pages dir in current cwd if not found above
51+
if (!hasPagesDir) {
52+
const localPagesDir = join(cwd, 'pages');
53+
if (existsSync(localPagesDir)) {
54+
hasPagesDir = true;
55+
pagesDir = localPagesDir;
56+
// Try to find app.json in cwd
57+
// TODO: Load app.json if exists
58+
}
59+
}
60+
61+
const require = createRequire(join(cwd, 'package.json'));
2162

2263
let routes: RouteInfo[] = [];
2364
let schema: unknown = null;
2465
let useFileSystemRouting = false;
2566

2667
if (hasPagesDir) {
2768
// File-system based routing
28-
console.log(chalk.blue('📁 Detected pages/ directory - using file-system routing'));
69+
console.log(chalk.blue(`📁 Using file-system routing from ${pagesDir}`));
2970
routes = scanPagesDirectory(pagesDir);
3071
useFileSystemRouting = true;
3172

3273
if (routes.length === 0) {
33-
throw new Error('No schema files found in pages/ directory');
74+
throw new Error(`No schema files found in ${pagesDir}`);
3475
}
3576

3677
console.log(chalk.green(`✓ Found ${routes.length} route(s)`));
@@ -40,33 +81,30 @@ export async function dev(schemaPath: string, options: DevOptions) {
4081
} else {
4182
// Single schema file mode
4283
const fullSchemaPath = resolve(cwd, schemaPath);
43-
44-
// Check if schema file exists
84+
// ... (rest of the logic)
4585
if (!existsSync(fullSchemaPath)) {
4686
throw new Error(`Schema file not found: ${schemaPath}\nRun 'objectui init' to create a sample schema.`);
4787
}
48-
4988
console.log(chalk.blue('📋 Loading schema:'), chalk.cyan(schemaPath));
50-
51-
// Read and validate schema
5289
try {
5390
schema = parseSchemaFile(fullSchemaPath);
5491
} catch (error) {
55-
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
92+
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
5693
}
5794
}
5895

59-
// Create temporary app directory
96+
// Create temporary app directory (always in cwd to keep node_modules access)
6097
const tmpDir = join(cwd, '.objectui-tmp');
6198
mkdirSync(tmpDir, { recursive: true });
6299

63100
// Create temporary app files
64101
if (useFileSystemRouting) {
65-
createTempAppWithRouting(tmpDir, routes);
102+
createTempAppWithRouting(tmpDir, routes, appConfig);
66103
} else {
67104
createTempApp(tmpDir, schema);
68105
}
69106

107+
70108
// Install dependencies
71109
const isMonorepo = existsSync(join(cwd, 'pnpm-workspace.yaml'));
72110

@@ -90,16 +128,61 @@ export async function dev(schemaPath: string, options: DevOptions) {
90128
console.log(chalk.blue('🚀 Starting development server...\n'));
91129

92130
// Create Vite config
93-
const viteConfig = {
131+
const viteConfig: any = {
94132
root: tmpDir,
95133
server: {
96134
port: parseInt(options.port),
97135
host: options.host,
98136
open: options.open !== false,
137+
fs: {
138+
// Allow serving files from workspace root
139+
allow: [cwd],
140+
}
141+
},
142+
resolve: {
143+
alias: {}
99144
},
100145
plugins: [react()],
101146
};
102147

148+
if (isMonorepo) {
149+
console.log(chalk.blue('📦 Detected monorepo - configuring workspace aliases'));
150+
151+
// Remove postcss.config.js to prevent interference with programmatic config
152+
const postcssPath = join(tmpDir, 'postcss.config.js');
153+
if (existsSync(postcssPath)) {
154+
unlinkSync(postcssPath);
155+
}
156+
157+
// Add aliases for workspace packages
158+
viteConfig.resolve.alias = {
159+
'@object-ui/react': join(cwd, 'packages/react/src/index.ts'),
160+
'@object-ui/components': join(cwd, 'packages/components/src/index.ts'),
161+
'@object-ui/core': join(cwd, 'packages/core/src/index.ts'),
162+
'@object-ui/types': join(cwd, 'packages/types/src/index.ts'),
163+
};
164+
165+
// Debug aliases
166+
// console.log('Aliases:', viteConfig.resolve.alias);
167+
168+
// Configure PostCSS programmatically reusing root dependencies
169+
try {
170+
const tailwindcss = require('tailwindcss');
171+
const autoprefixer = require('autoprefixer');
172+
173+
viteConfig.css = {
174+
postcss: {
175+
plugins: [
176+
tailwindcss(join(tmpDir, 'tailwind.config.js')),
177+
autoprefixer(),
178+
],
179+
},
180+
};
181+
} catch (e) {
182+
console.warn(chalk.yellow('⚠️ Failed to load PostCSS plugins from root node_modules. Styles might not work correctly.'));
183+
}
184+
}
185+
103186
// Create Vite server
104187
const server = await createServer(viteConfig);
105188

0 commit comments

Comments
 (0)