-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathdev.ts
More file actions
242 lines (210 loc) · 8.51 KB
/
Copy pathdev.ts
File metadata and controls
242 lines (210 loc) · 8.51 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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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 and load app.json in cwd
const appJsonPath = join(cwd, 'app.json');
if (existsSync(appJsonPath)) {
try {
appConfig = parseSchemaFile(appJsonPath);
console.log(chalk.blue('⚙️ Loaded App Config from app.json'));
} catch (_e) {
console.warn('Failed to parse app.json config');
}
}
}
}
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) {
// The caught error's message is inlined below. We can't pass it as the
// `Error` `cause` option because this package targets ES2020, whose lib
// types the 1-arg `Error` constructor only; hence the scoped disable.
// eslint-disable-next-line preserve-caught-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'),
'@object-ui/plugin-charts': join(cwd, 'packages/plugin-charts/src/index.tsx'),
'@object-ui/plugin-editor': join(cwd, 'packages/plugin-editor/src/index.tsx'),
'@object-ui/plugin-kanban': join(cwd, 'packages/plugin-kanban/src/index.tsx'),
'@object-ui/plugin-markdown': join(cwd, 'packages/plugin-markdown/src/index.tsx'),
'@object-ui/plugin-form': join(cwd, 'packages/plugin-form/src/index.tsx'),
'@object-ui/plugin-grid': join(cwd, 'packages/plugin-grid/src/index.tsx'),
'@object-ui/plugin-view': join(cwd, 'packages/plugin-view/src/index.tsx'),
};
// 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.
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();
}