-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdev.ts
More file actions
216 lines (185 loc) · 7.21 KB
/
dev.ts
File metadata and controls
216 lines (185 loc) · 7.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import { createServer } from 'vite';
import react from '@vitejs/plugin-react';
import { existsSync, mkdirSync, unlinkSync, statSync } from 'fs';
import { join, resolve, dirname } from 'path';
import chalk from 'chalk';
import { execSync } from 'child_process';
import { createRequire } from 'module';
import { scanPagesDirectory, createTempAppWithRouting, createTempApp, parseSchemaFile, type RouteInfo } from '../utils/app-generator.js';
interface DevOptions {
port: string;
host: string;
open?: boolean;
}
export async function dev(schemaPath: string, options: DevOptions) {
const cwd = process.cwd();
// Resolve the actual project root and schema file
let _projectRoot = cwd;
const targetSchemaPath = schemaPath;
let hasPagesDir = false;
let pagesDir = '';
let appConfig: unknown = null;
// 1. Determine Project Root & Mode
const absoluteSchemaPath = resolve(cwd, schemaPath);
if (existsSync(absoluteSchemaPath) && statSync(absoluteSchemaPath).isFile()) {
// If input is a file (e.g. examples/showcase/app.json)
const fileDir = dirname(absoluteSchemaPath);
const potentialPagesDir = join(fileDir, 'pages');
if (existsSync(potentialPagesDir)) {
console.log(chalk.blue(`📂 Detected project structure at ${fileDir}`));
_projectRoot = fileDir;
hasPagesDir = true;
pagesDir = potentialPagesDir;
// Try to load app.json as config
try {
appConfig = parseSchemaFile(absoluteSchemaPath);
console.log(chalk.blue('⚙️ Loaded App Config from app.json'));
} catch (_e) {
console.warn('Failed to parse app config');
}
}
}
// Fallback: Check detect pages dir in current cwd if not found above
if (!hasPagesDir) {
const localPagesDir = join(cwd, 'pages');
if (existsSync(localPagesDir)) {
hasPagesDir = true;
pagesDir = localPagesDir;
// Try to find app.json in cwd
// TODO: Load app.json if exists
}
}
const require = createRequire(join(cwd, 'package.json'));
let routes: RouteInfo[] = [];
let schema: unknown = null;
let useFileSystemRouting = false;
if (hasPagesDir) {
// File-system based routing
console.log(chalk.blue(`📁 Using file-system routing from ${pagesDir}`));
routes = scanPagesDirectory(pagesDir);
useFileSystemRouting = true;
if (routes.length === 0) {
throw new Error(`No schema files found in ${pagesDir}`);
}
console.log(chalk.green(`✓ Found ${routes.length} route(s)`));
routes.forEach(route => {
console.log(chalk.dim(` ${route.path} → ${route.filePath.replace(cwd, '.')}`));
});
} else {
// Single schema file mode
const fullSchemaPath = resolve(cwd, schemaPath);
// ... (rest of the logic)
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));
try {
schema = parseSchemaFile(fullSchemaPath);
} catch (error) {
throw new Error(`Invalid schema file: ${error instanceof Error ? error.message : error}`);
}
}
// Create temporary app directory (always in cwd to keep node_modules access)
const tmpDir = join(cwd, '.objectui-tmp');
mkdirSync(tmpDir, { recursive: true });
// Create temporary app files
if (useFileSystemRouting) {
createTempAppWithRouting(tmpDir, routes, appConfig);
} else {
createTempApp(tmpDir, schema);
}
// Install dependencies
const isMonorepo = existsSync(join(cwd, 'pnpm-workspace.yaml'));
if (isMonorepo) {
console.log(chalk.blue('📦 Detected monorepo - using root node_modules'));
} else {
console.log(chalk.blue('📦 Installing dependencies...'));
console.log(chalk.dim(' This may take a moment on first run...'));
try {
execSync('npm install --silent --prefer-offline', {
cwd: tmpDir,
stdio: 'inherit',
});
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.green('✓ Schema loaded successfully'));
console.log(chalk.blue('🚀 Starting development server...\n'));
// Create Vite config
const viteConfig: any = {
root: tmpDir,
server: {
port: parseInt(options.port),
host: options.host,
open: options.open !== false,
fs: {
// Allow serving files from workspace root
allow: [cwd],
}
},
resolve: {
alias: {}
},
plugins: [react()],
};
if (isMonorepo) {
console.log(chalk.blue('📦 Detected monorepo - configuring workspace aliases'));
// Remove postcss.config.js to prevent interference with programmatic config
const postcssPath = join(tmpDir, 'postcss.config.js');
if (existsSync(postcssPath)) {
unlinkSync(postcssPath);
}
// Add aliases for workspace packages
viteConfig.resolve.alias = {
'@object-ui/react': join(cwd, 'packages/react/src/index.ts'),
'@object-ui/components': join(cwd, 'packages/components/src/index.ts'),
'@object-ui/core': join(cwd, 'packages/core/src/index.ts'),
'@object-ui/types': join(cwd, 'packages/types/src/index.ts'),
};
// Fix: Resolve lucide-react from components package to avoid "dependency not found" in temp app
try {
// Trying to find lucide-react in the components' node_modules or hoist
// checking specifically in packages/components context
const lucidePath = require.resolve('lucide-react', { paths: [join(cwd, 'packages/components')] });
// We might get the cjs entry, but for aliasing usually fine.
// Better yet, if we can find the package root, but require.resolve gives file.
// Let's just use what require.resolve gives.
// @ts-expect-error - lucidePath is dynamically resolved
viteConfig.resolve.alias['lucide-react'] = lucidePath;
} catch (e) {
console.warn('⚠️ Could not resolve lucide-react automatically:', e);
}
// Debug aliases
// console.log('Aliases:', viteConfig.resolve.alias);
// Configure PostCSS programmatically reusing root dependencies
try {
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
viteConfig.css = {
postcss: {
plugins: [
tailwindcss(join(tmpDir, 'tailwind.config.js')),
autoprefixer(),
],
},
};
} catch (_e) {
console.warn(chalk.yellow('⚠️ Failed to load PostCSS plugins from root node_modules. Styles might not work correctly.'));
}
}
// Create Vite server
const server = await createServer(viteConfig);
await server.listen();
const { port, host } = server.config.server;
const protocol = server.config.server.https ? 'https' : 'http';
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
console.log();
console.log(chalk.green('✓ Development server started successfully!'));
console.log();
console.log(chalk.bold(' Local: ') + chalk.cyan(`${protocol}://${displayHost}:${port}`));
console.log();
console.log(chalk.dim(' Press Ctrl+C to stop the server'));
console.log();
}