create-slotkit-app 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +494 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* create-slotkit-app - Initialize a new SlotKit project using Vite
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { spawn, execSync } from 'child_process';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { resolve, join } from 'path';
|
|
9
|
+
const program = new Command();
|
|
10
|
+
/**
|
|
11
|
+
* Execute command and wait for completion
|
|
12
|
+
*/
|
|
13
|
+
function execCommand(command, args, cwd) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = spawn(command, args, {
|
|
16
|
+
cwd,
|
|
17
|
+
stdio: 'inherit',
|
|
18
|
+
shell: true
|
|
19
|
+
});
|
|
20
|
+
child.on('error', reject);
|
|
21
|
+
child.on('exit', (code) => {
|
|
22
|
+
if (code === 0) {
|
|
23
|
+
resolve();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get latest version of a package from npm
|
|
33
|
+
*/
|
|
34
|
+
function getLatestVersion(packageName) {
|
|
35
|
+
try {
|
|
36
|
+
// Try to get latest version from npm
|
|
37
|
+
const result = execSync(`npm view ${packageName} version`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
38
|
+
return `^${result}`;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// If failed, use 'latest' tag to let npm resolve it
|
|
42
|
+
console.warn(`[WARN] Could not fetch latest version of ${packageName}, using 'latest' tag`);
|
|
43
|
+
return 'latest';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create SlotKit-specific files
|
|
48
|
+
*/
|
|
49
|
+
function createSlotKitFiles(projectDir) {
|
|
50
|
+
// Replace Vite's default App.tsx with SlotKit App
|
|
51
|
+
// This way we don't need to modify main.tsx
|
|
52
|
+
const appFilePath = join(projectDir, 'src', 'App.tsx');
|
|
53
|
+
// Create src/app directory for AppLayout
|
|
54
|
+
const appDir = join(projectDir, 'src', 'app');
|
|
55
|
+
mkdirSync(appDir, { recursive: true });
|
|
56
|
+
// Create App.tsx (replaces Vite's default)
|
|
57
|
+
const appTsx = `import React, { useEffect, useState } from 'react'
|
|
58
|
+
import { AppLayout } from './app/AppLayout'
|
|
59
|
+
import { pluginRegistry, pluginLoader, setPluginImportFunctions } from '@slotkitjs/core'
|
|
60
|
+
|
|
61
|
+
// Import generated plugin import mappings
|
|
62
|
+
// This file is generated by 'slotkit generate-imports'
|
|
63
|
+
import * as pluginImports from './core/plugin/loader/plugin-imports.generated'
|
|
64
|
+
|
|
65
|
+
// Initialize plugin import mappings
|
|
66
|
+
if (pluginImports.getPluginImport && pluginImports.getAvailablePluginIds) {
|
|
67
|
+
setPluginImportFunctions({
|
|
68
|
+
getPluginImport: (pluginId: string) => {
|
|
69
|
+
const importFn = pluginImports.getPluginImport(pluginId)
|
|
70
|
+
if (!importFn) return undefined
|
|
71
|
+
return () => Promise.resolve(importFn())
|
|
72
|
+
},
|
|
73
|
+
getAvailablePluginIds: pluginImports.getAvailablePluginIds,
|
|
74
|
+
getAllPluginManifests: pluginImports.getAllPluginManifests,
|
|
75
|
+
getPluginManifest: pluginImports.getPluginManifest
|
|
76
|
+
})
|
|
77
|
+
} else {
|
|
78
|
+
console.warn('[WARN] Plugin import functions not found in generated file')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load all plugins
|
|
82
|
+
const loadPlugins = async () => {
|
|
83
|
+
try {
|
|
84
|
+
const plugins = await pluginLoader.loadAllPlugins()
|
|
85
|
+
|
|
86
|
+
plugins.forEach(plugin => {
|
|
87
|
+
pluginRegistry.register(plugin)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return plugins
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('[ERROR] Failed to load plugins:', error)
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const App: React.FC = () => {
|
|
98
|
+
const [pluginsLoaded, setPluginsLoaded] = useState(false)
|
|
99
|
+
const [loadedPlugins, setLoadedPlugins] = useState<any[]>([])
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
loadPlugins()
|
|
103
|
+
.then((plugins) => {
|
|
104
|
+
setPluginsLoaded(true)
|
|
105
|
+
setLoadedPlugins(plugins)
|
|
106
|
+
})
|
|
107
|
+
.catch((error) => {
|
|
108
|
+
console.error('[ERROR] Failed to load plugins:', error)
|
|
109
|
+
})
|
|
110
|
+
}, [])
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="app">
|
|
114
|
+
<AppLayout />
|
|
115
|
+
|
|
116
|
+
{/* Plugin loading status indicator */}
|
|
117
|
+
<div style={{
|
|
118
|
+
position: 'fixed',
|
|
119
|
+
top: '10px',
|
|
120
|
+
right: '10px',
|
|
121
|
+
background: pluginsLoaded ? '#e8f5e8' : '#fff3cd',
|
|
122
|
+
padding: '10px',
|
|
123
|
+
borderRadius: '4px',
|
|
124
|
+
fontSize: '12px',
|
|
125
|
+
border: pluginsLoaded ? '1px solid #4caf50' : '1px solid #ffc107',
|
|
126
|
+
zIndex: 1000
|
|
127
|
+
}}>
|
|
128
|
+
{pluginsLoaded ? '[OK] Plugins loaded' : '[INFO] Loading plugins...'}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{/* Loaded plugins list */}
|
|
132
|
+
{loadedPlugins.length > 0 && (
|
|
133
|
+
<div style={{
|
|
134
|
+
position: 'fixed',
|
|
135
|
+
top: '50px',
|
|
136
|
+
right: '10px',
|
|
137
|
+
background: '#f3e5f5',
|
|
138
|
+
padding: '10px',
|
|
139
|
+
borderRadius: '4px',
|
|
140
|
+
fontSize: '11px',
|
|
141
|
+
border: '1px solid #9c27b0',
|
|
142
|
+
zIndex: 1000,
|
|
143
|
+
maxWidth: '200px'
|
|
144
|
+
}}>
|
|
145
|
+
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>Loaded Plugins:</div>
|
|
146
|
+
{loadedPlugins.map(plugin => (
|
|
147
|
+
<div key={plugin.id} style={{ marginBottom: '2px' }}>
|
|
148
|
+
• {plugin.name} v{plugin.version}
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export default App;
|
|
158
|
+
`;
|
|
159
|
+
writeFileSync(appFilePath, appTsx, 'utf-8');
|
|
160
|
+
// Create AppLayout.tsx
|
|
161
|
+
const appLayoutTsx = `import React from 'react'
|
|
162
|
+
import { Slot } from '@slotkitjs/react'
|
|
163
|
+
import './AppLayout.css'
|
|
164
|
+
|
|
165
|
+
export const AppLayout: React.FC = () => {
|
|
166
|
+
return (
|
|
167
|
+
<div className="app-layout">
|
|
168
|
+
<header className="app-header">
|
|
169
|
+
<h1>SlotKit App</h1>
|
|
170
|
+
<Slot name="header" />
|
|
171
|
+
</header>
|
|
172
|
+
|
|
173
|
+
<main className="app-main">
|
|
174
|
+
<aside className="app-sidebar">
|
|
175
|
+
<h3>Sidebar</h3>
|
|
176
|
+
<Slot name="sidebar">
|
|
177
|
+
<p style={{ color: '#666', fontSize: '14px' }}>Waiting for plugins...</p>
|
|
178
|
+
</Slot>
|
|
179
|
+
</aside>
|
|
180
|
+
|
|
181
|
+
<section className="app-content">
|
|
182
|
+
<h3>Main Content</h3>
|
|
183
|
+
<Slot name="content">
|
|
184
|
+
<p style={{ color: '#666', fontSize: '14px' }}>Waiting for plugins...</p>
|
|
185
|
+
</Slot>
|
|
186
|
+
</section>
|
|
187
|
+
</main>
|
|
188
|
+
|
|
189
|
+
<footer className="app-footer">
|
|
190
|
+
<Slot name="footer" />
|
|
191
|
+
</footer>
|
|
192
|
+
</div>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
`;
|
|
196
|
+
writeFileSync(join(appDir, 'AppLayout.tsx'), appLayoutTsx, 'utf-8');
|
|
197
|
+
// Create AppLayout.css
|
|
198
|
+
const appLayoutCss = `.app-layout {
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
min-height: 100vh;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.app-header {
|
|
205
|
+
background: #2196f3;
|
|
206
|
+
color: white;
|
|
207
|
+
padding: 1rem 2rem;
|
|
208
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.app-header h1 {
|
|
212
|
+
margin: 0;
|
|
213
|
+
font-size: 1.5rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.app-main {
|
|
217
|
+
display: flex;
|
|
218
|
+
flex: 1;
|
|
219
|
+
gap: 1rem;
|
|
220
|
+
padding: 1rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.app-sidebar {
|
|
224
|
+
width: 250px;
|
|
225
|
+
background: #f5f5f5;
|
|
226
|
+
padding: 1rem;
|
|
227
|
+
border-radius: 8px;
|
|
228
|
+
border: 1px solid #ddd;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.app-sidebar h3 {
|
|
232
|
+
margin: 0 0 1rem 0;
|
|
233
|
+
font-size: 1.1rem;
|
|
234
|
+
color: #333;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.app-content {
|
|
238
|
+
flex: 1;
|
|
239
|
+
background: white;
|
|
240
|
+
padding: 1rem;
|
|
241
|
+
border-radius: 8px;
|
|
242
|
+
border: 1px solid #ddd;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.app-content h3 {
|
|
246
|
+
margin: 0 0 1rem 0;
|
|
247
|
+
font-size: 1.1rem;
|
|
248
|
+
color: #333;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.app-footer {
|
|
252
|
+
background: #f5f5f5;
|
|
253
|
+
padding: 1rem 2rem;
|
|
254
|
+
border-top: 1px solid #ddd;
|
|
255
|
+
text-align: center;
|
|
256
|
+
color: #666;
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
writeFileSync(join(appDir, 'AppLayout.css'), appLayoutCss, 'utf-8');
|
|
260
|
+
// No need to modify main.tsx - Vite's default already imports './App'
|
|
261
|
+
// We replaced src/App.tsx above, so it will automatically work
|
|
262
|
+
// Create slotkit.config.ts
|
|
263
|
+
const slotkitConfig = `import { defineConfig } from '@slotkitjs/core'
|
|
264
|
+
|
|
265
|
+
export default defineConfig({
|
|
266
|
+
pluginsDir: './plugins',
|
|
267
|
+
// Add more configuration here
|
|
268
|
+
})
|
|
269
|
+
`;
|
|
270
|
+
writeFileSync(join(projectDir, 'slotkit.config.ts'), slotkitConfig, 'utf-8');
|
|
271
|
+
// Create plugins directory and default hello-world plugin
|
|
272
|
+
const pluginsDir = join(projectDir, 'plugins');
|
|
273
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
274
|
+
const helloWorldDir = join(pluginsDir, 'helloworld');
|
|
275
|
+
const helloWorldSrcDir = join(helloWorldDir, 'src');
|
|
276
|
+
mkdirSync(helloWorldSrcDir, { recursive: true });
|
|
277
|
+
const helloWorldManifest = `{
|
|
278
|
+
"id": "helloworld",
|
|
279
|
+
"name": "Hello-world Plugin",
|
|
280
|
+
"version": "1.0.0",
|
|
281
|
+
"description": "A built-in hello-world plugin",
|
|
282
|
+
"author": "SlotKit Team",
|
|
283
|
+
"entry": "./src/index.tsx",
|
|
284
|
+
"slots": [
|
|
285
|
+
"content"
|
|
286
|
+
],
|
|
287
|
+
"enabled": true
|
|
288
|
+
}`;
|
|
289
|
+
writeFileSync(join(helloWorldDir, 'manifest.json'), helloWorldManifest + '\n', 'utf-8');
|
|
290
|
+
const helloWorldPackageJson = `{
|
|
291
|
+
"name": "plugin-helloworld",
|
|
292
|
+
"version": "1.0.0",
|
|
293
|
+
"main": "src/index.tsx",
|
|
294
|
+
"private": true
|
|
295
|
+
}`;
|
|
296
|
+
writeFileSync(join(helloWorldDir, 'package.json'), helloWorldPackageJson + '\n', 'utf-8');
|
|
297
|
+
const helloWorldTsconfig = `{
|
|
298
|
+
"extends": "../../tsconfig.json",
|
|
299
|
+
"compilerOptions": {
|
|
300
|
+
"jsx": "react-jsx"
|
|
301
|
+
},
|
|
302
|
+
"include": [
|
|
303
|
+
"src/**/*"
|
|
304
|
+
]
|
|
305
|
+
}`;
|
|
306
|
+
writeFileSync(join(helloWorldDir, 'tsconfig.json'), helloWorldTsconfig + '\n', 'utf-8');
|
|
307
|
+
const helloWorldIndexTsx = `import React from 'react'
|
|
308
|
+
|
|
309
|
+
const HelloWorldComponent: React.FC = () => {
|
|
310
|
+
return (
|
|
311
|
+
<div
|
|
312
|
+
style={{
|
|
313
|
+
padding: '1rem',
|
|
314
|
+
background: '#f5f5f5',
|
|
315
|
+
border: '1px solid #ddd',
|
|
316
|
+
borderRadius: '8px',
|
|
317
|
+
margin: '0.5rem 0'
|
|
318
|
+
}}
|
|
319
|
+
>
|
|
320
|
+
<h3>Hello-world Plugin</h3>
|
|
321
|
+
<p>This is the default hello-world plugin generated by create-slotkit-app.</p>
|
|
322
|
+
</div>
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const helloworldPlugin = {
|
|
327
|
+
id: 'helloworld',
|
|
328
|
+
name: 'Hello-world Plugin',
|
|
329
|
+
version: '1.0.0',
|
|
330
|
+
component: HelloWorldComponent,
|
|
331
|
+
slots: ['content']
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export { helloworldPlugin }
|
|
335
|
+
export default helloworldPlugin
|
|
336
|
+
`;
|
|
337
|
+
writeFileSync(join(helloWorldSrcDir, 'index.tsx'), helloWorldIndexTsx, 'utf-8');
|
|
338
|
+
// Create or update vite.config.ts with SlotKit-specific configuration
|
|
339
|
+
const viteConfigPath = join(projectDir, 'vite.config.ts');
|
|
340
|
+
const viteConfigJsPath = join(projectDir, 'vite.config.js');
|
|
341
|
+
// Check if vite.config.ts or vite.config.js exists
|
|
342
|
+
const existingConfigPath = existsSync(viteConfigPath) ? viteConfigPath :
|
|
343
|
+
(existsSync(viteConfigJsPath) ? viteConfigJsPath : null);
|
|
344
|
+
if (existingConfigPath) {
|
|
345
|
+
// Update existing config
|
|
346
|
+
let viteConfig = readFileSync(existingConfigPath, 'utf-8');
|
|
347
|
+
// Add server configuration if not present
|
|
348
|
+
if (!viteConfig.includes('server:')) {
|
|
349
|
+
// Find the defineConfig call and add server config inside it
|
|
350
|
+
if (viteConfig.includes('export default defineConfig')) {
|
|
351
|
+
viteConfig = viteConfig.replace(/export default defineConfig\(\{/, `export default defineConfig({
|
|
352
|
+
server: {
|
|
353
|
+
fs: {
|
|
354
|
+
// Allow access to plugin directory
|
|
355
|
+
allow: ['..', '../..', '../../..']
|
|
356
|
+
}
|
|
357
|
+
},`);
|
|
358
|
+
writeFileSync(existingConfigPath, viteConfig, 'utf-8');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Create new vite.config.ts
|
|
364
|
+
const viteConfigContent = `import { defineConfig } from 'vite'
|
|
365
|
+
import react from '@vitejs/plugin-react'
|
|
366
|
+
|
|
367
|
+
export default defineConfig({
|
|
368
|
+
plugins: [react()],
|
|
369
|
+
server: {
|
|
370
|
+
fs: {
|
|
371
|
+
// Allow access to plugin directory
|
|
372
|
+
allow: ['..', '../..', '../../..']
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
`;
|
|
377
|
+
writeFileSync(viteConfigPath, viteConfigContent, 'utf-8');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
program
|
|
381
|
+
.name('create-slotkit-app')
|
|
382
|
+
.description('Create a new SlotKit application')
|
|
383
|
+
.argument('<project-name>', 'Project name')
|
|
384
|
+
.option('--template <template>', 'Vite template to use', 'react-ts')
|
|
385
|
+
.action(async (projectName, options) => {
|
|
386
|
+
const projectDir = resolve(process.cwd(), projectName);
|
|
387
|
+
if (existsSync(projectDir)) {
|
|
388
|
+
console.error(`[ERROR] Directory "${projectName}" already exists`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
// Extract just the project name (last part of path)
|
|
392
|
+
const actualProjectName = projectName.split(/[/\\]/).pop() || projectName;
|
|
393
|
+
const packageName = actualProjectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
394
|
+
console.log(`[INFO] Creating SlotKit app: ${actualProjectName}`);
|
|
395
|
+
console.log(`[INFO] Using Vite template: ${options.template || 'react'}`);
|
|
396
|
+
try {
|
|
397
|
+
// Step 1: Use Vite to create the base project
|
|
398
|
+
// Vite creates the project in a subdirectory, so we need to extract just the name
|
|
399
|
+
console.log('[INFO] Step 1: Creating Vite project...');
|
|
400
|
+
// Create parent directory if needed (e.g., examples/test-project -> create examples first)
|
|
401
|
+
const parentDir = resolve(projectDir, '..');
|
|
402
|
+
if (!existsSync(parentDir)) {
|
|
403
|
+
mkdirSync(parentDir, { recursive: true });
|
|
404
|
+
}
|
|
405
|
+
// Try pnpm first, fallback to npm if pnpm fails
|
|
406
|
+
// Use non-interactive flags to skip prompts
|
|
407
|
+
const createCommand = 'pnpm';
|
|
408
|
+
const createArgs = [
|
|
409
|
+
'create', 'vite', actualProjectName,
|
|
410
|
+
'--template', options.template || 'react-ts',
|
|
411
|
+
'--yes' // Skip prompts
|
|
412
|
+
];
|
|
413
|
+
try {
|
|
414
|
+
// Run in the parent directory so Vite creates the project in the right place
|
|
415
|
+
await execCommand(createCommand, createArgs, parentDir);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
// If pnpm fails, try npm
|
|
419
|
+
console.log('[INFO] pnpm create vite failed, trying npm...');
|
|
420
|
+
const npmArgs = [
|
|
421
|
+
'create', 'vite@latest', actualProjectName,
|
|
422
|
+
'--', '--template', options.template || 'react-ts'
|
|
423
|
+
];
|
|
424
|
+
await execCommand('npm', npmArgs, parentDir);
|
|
425
|
+
}
|
|
426
|
+
// Step 2: Update package.json with SlotKit dependencies
|
|
427
|
+
console.log('[INFO] Step 2: Adding SlotKit dependencies...');
|
|
428
|
+
const packageJsonPath = join(projectDir, 'package.json');
|
|
429
|
+
if (existsSync(packageJsonPath)) {
|
|
430
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
431
|
+
// Update package name
|
|
432
|
+
packageJson.name = packageName;
|
|
433
|
+
// Ensure dependencies object exists
|
|
434
|
+
if (!packageJson.dependencies) {
|
|
435
|
+
packageJson.dependencies = {};
|
|
436
|
+
}
|
|
437
|
+
// Add SlotKit dependencies (merge with existing)
|
|
438
|
+
// Use latest versions
|
|
439
|
+
const slotkitCoreVersion = getLatestVersion('@slotkitjs/core');
|
|
440
|
+
const slotkitReactVersion = getLatestVersion('@slotkitjs/react');
|
|
441
|
+
packageJson.dependencies = {
|
|
442
|
+
...packageJson.dependencies,
|
|
443
|
+
'@slotkitjs/core': slotkitCoreVersion,
|
|
444
|
+
'@slotkitjs/react': slotkitReactVersion
|
|
445
|
+
};
|
|
446
|
+
// Ensure React dependencies are present (for React templates)
|
|
447
|
+
if (options.template?.includes('react')) {
|
|
448
|
+
packageJson.dependencies['react'] = packageJson.dependencies['react'] || '^19.1.1';
|
|
449
|
+
packageJson.dependencies['react-dom'] = packageJson.dependencies['react-dom'] || '^19.1.1';
|
|
450
|
+
// Add React types to devDependencies if not present
|
|
451
|
+
if (!packageJson.devDependencies) {
|
|
452
|
+
packageJson.devDependencies = {};
|
|
453
|
+
}
|
|
454
|
+
packageJson.devDependencies['@types/react'] = packageJson.devDependencies['@types/react'] || '^19.1.16';
|
|
455
|
+
packageJson.devDependencies['@types/react-dom'] = packageJson.devDependencies['@types/react-dom'] || '^19.1.9';
|
|
456
|
+
// Ensure @vitejs/plugin-react is in devDependencies
|
|
457
|
+
packageJson.devDependencies['@vitejs/plugin-react'] = packageJson.devDependencies['@vitejs/plugin-react'] || '^5.0.4';
|
|
458
|
+
}
|
|
459
|
+
// Ensure scripts object exists
|
|
460
|
+
if (!packageJson.scripts) {
|
|
461
|
+
packageJson.scripts = {};
|
|
462
|
+
}
|
|
463
|
+
packageJson.scripts.dev = 'slotkit generate-imports && slotkit dev';
|
|
464
|
+
packageJson.scripts.build = 'slotkit generate-imports && slotkit build';
|
|
465
|
+
packageJson.scripts.preview = packageJson.scripts.preview || 'slotkit preview';
|
|
466
|
+
packageJson.scripts['generate-imports'] = packageJson.scripts['generate-imports'] || 'slotkit generate-imports';
|
|
467
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
|
468
|
+
}
|
|
469
|
+
// Step 3: Create SlotKit-specific files
|
|
470
|
+
console.log('[INFO] Step 3: Creating SlotKit-specific files...');
|
|
471
|
+
createSlotKitFiles(projectDir);
|
|
472
|
+
// Step 4: Create .gitignore entry for generated files
|
|
473
|
+
const gitignorePath = join(projectDir, '.gitignore');
|
|
474
|
+
if (existsSync(gitignorePath)) {
|
|
475
|
+
const gitignore = readFileSync(gitignorePath, 'utf-8');
|
|
476
|
+
if (!gitignore.includes('plugin-imports.generated.ts')) {
|
|
477
|
+
writeFileSync(gitignorePath, gitignore + '\n# Generated files\nsrc/core/plugin/loader/plugin-imports.generated.ts\n', 'utf-8');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
console.log('[OK] Project created successfully!');
|
|
481
|
+
console.log(`[INFO] Project location: ${projectDir}`);
|
|
482
|
+
console.log('\n[INFO] Next steps:');
|
|
483
|
+
console.log(` cd ${projectName}`);
|
|
484
|
+
console.log(' pnpm install');
|
|
485
|
+
console.log(' pnpm dev');
|
|
486
|
+
console.log(' # Optional: pnpm slotkit create-plugin <name> --slots <slots>');
|
|
487
|
+
console.log(' # predev/prebuild will automatically run slotkit generate-imports');
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
console.error('[ERROR] Failed to create project:', error.message);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
program.parse();
|