create-slotkit-app 0.1.0

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.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/index.js'
4
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-slotkit-app - Initialize a new SlotKit project using Vite
4
+ */
5
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,423 @@
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 } from 'child_process';
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } 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
+ * Create SlotKit-specific files
33
+ */
34
+ function createSlotKitFiles(projectDir) {
35
+ // Create src/app directory
36
+ const appDir = join(projectDir, 'src', 'app');
37
+ mkdirSync(appDir, { recursive: true });
38
+ // Create App.tsx
39
+ const appTsx = `import React, { useEffect, useState } from 'react'
40
+ import { AppLayout } from './AppLayout'
41
+ import { pluginRegistry, pluginLoader } from '@slotkitjs/core/core'
42
+
43
+ /**
44
+ * Load all plugins
45
+ */
46
+ const loadPlugins = async () => {
47
+ try {
48
+ console.log('[INFO] Starting to load plugins...')
49
+
50
+ // Load all plugins using plugin loader
51
+ const plugins = await pluginLoader.loadAllPlugins()
52
+
53
+ // Register all plugins to registry
54
+ plugins.forEach(plugin => {
55
+ pluginRegistry.register(plugin)
56
+ console.log(\`[INFO] Plugin "\${plugin.name}" registered to slots:\`, plugin.slots)
57
+ })
58
+
59
+ console.log('[OK] Plugin loading completed!')
60
+ console.log(\`[INFO] Total \${plugins.length} plugins loaded\`)
61
+
62
+ return plugins
63
+ } catch (error) {
64
+ console.error('[ERROR] Failed to load plugins:', error)
65
+ return []
66
+ }
67
+ }
68
+
69
+ export const App: React.FC = () => {
70
+ const [pluginsLoaded, setPluginsLoaded] = useState(false)
71
+ const [loadedPlugins, setLoadedPlugins] = useState<any[]>([])
72
+
73
+ useEffect(() => {
74
+ loadPlugins().then((plugins) => {
75
+ setPluginsLoaded(true)
76
+ setLoadedPlugins(plugins)
77
+ }).catch((error) => {
78
+ console.error('[ERROR] Failed to load plugins:', error)
79
+ })
80
+ }, [])
81
+
82
+ return (
83
+ <div className="app">
84
+ <AppLayout />
85
+
86
+ {/* Plugin loading status indicator */}
87
+ <div style={{
88
+ position: 'fixed',
89
+ top: '10px',
90
+ right: '10px',
91
+ background: pluginsLoaded ? '#e8f5e8' : '#fff3cd',
92
+ padding: '10px',
93
+ borderRadius: '4px',
94
+ fontSize: '12px',
95
+ border: \`1px solid \${pluginsLoaded ? '#4caf50' : '#ffc107'}\`,
96
+ zIndex: 1000
97
+ }}>
98
+ {pluginsLoaded ? '[OK] Plugins loaded' : '[INFO] Loading plugins...'}
99
+ </div>
100
+
101
+ {/* Loaded plugins list */}
102
+ {loadedPlugins.length > 0 && (
103
+ <div style={{
104
+ position: 'fixed',
105
+ top: '50px',
106
+ right: '10px',
107
+ background: '#f3e5f5',
108
+ padding: '10px',
109
+ borderRadius: '4px',
110
+ fontSize: '11px',
111
+ border: '1px solid #9c27b0',
112
+ zIndex: 1000,
113
+ maxWidth: '200px'
114
+ }}>
115
+ <div style={{ fontWeight: 'bold', marginBottom: '5px' }}>Loaded Plugins:</div>
116
+ {loadedPlugins.map(plugin => (
117
+ <div key={plugin.id} style={{ marginBottom: '2px' }}>
118
+ • {plugin.name} v{plugin.version}
119
+ </div>
120
+ ))}
121
+ </div>
122
+ )}
123
+ </div>
124
+ )
125
+ }
126
+ `;
127
+ writeFileSync(join(appDir, 'App.tsx'), appTsx, 'utf-8');
128
+ // Create AppLayout.tsx
129
+ const appLayoutTsx = `import React from 'react'
130
+ import { Slot } from '@slotkitjs/react'
131
+ import './AppLayout.css'
132
+
133
+ export const AppLayout: React.FC = () => {
134
+ return (
135
+ <div className="app-layout">
136
+ <header className="app-header">
137
+ <h1>SlotKit App</h1>
138
+ <Slot name="header" />
139
+ </header>
140
+
141
+ <main className="app-main">
142
+ <aside className="app-sidebar">
143
+ <h3>Sidebar</h3>
144
+ <Slot name="sidebar">
145
+ <p style={{ color: '#666', fontSize: '14px' }}>Waiting for plugins...</p>
146
+ </Slot>
147
+ </aside>
148
+
149
+ <section className="app-content">
150
+ <h3>Main Content</h3>
151
+ <Slot name="content">
152
+ <p style={{ color: '#666', fontSize: '14px' }}>Waiting for plugins...</p>
153
+ </Slot>
154
+ </section>
155
+ </main>
156
+
157
+ <footer className="app-footer">
158
+ <Slot name="footer" />
159
+ </footer>
160
+ </div>
161
+ )
162
+ }
163
+ `;
164
+ writeFileSync(join(appDir, 'AppLayout.tsx'), appLayoutTsx, 'utf-8');
165
+ // Create AppLayout.css
166
+ const appLayoutCss = `.app-layout {
167
+ display: flex;
168
+ flex-direction: column;
169
+ min-height: 100vh;
170
+ }
171
+
172
+ .app-header {
173
+ background: #2196f3;
174
+ color: white;
175
+ padding: 1rem 2rem;
176
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
177
+ }
178
+
179
+ .app-header h1 {
180
+ margin: 0;
181
+ font-size: 1.5rem;
182
+ }
183
+
184
+ .app-main {
185
+ display: flex;
186
+ flex: 1;
187
+ gap: 1rem;
188
+ padding: 1rem;
189
+ }
190
+
191
+ .app-sidebar {
192
+ width: 250px;
193
+ background: #f5f5f5;
194
+ padding: 1rem;
195
+ border-radius: 8px;
196
+ border: 1px solid #ddd;
197
+ }
198
+
199
+ .app-sidebar h3 {
200
+ margin: 0 0 1rem 0;
201
+ font-size: 1.1rem;
202
+ color: #333;
203
+ }
204
+
205
+ .app-content {
206
+ flex: 1;
207
+ background: white;
208
+ padding: 1rem;
209
+ border-radius: 8px;
210
+ border: 1px solid #ddd;
211
+ }
212
+
213
+ .app-content h3 {
214
+ margin: 0 0 1rem 0;
215
+ font-size: 1.1rem;
216
+ color: #333;
217
+ }
218
+
219
+ .app-footer {
220
+ background: #f5f5f5;
221
+ padding: 1rem 2rem;
222
+ border-top: 1px solid #ddd;
223
+ text-align: center;
224
+ color: #666;
225
+ }
226
+ `;
227
+ writeFileSync(join(appDir, 'AppLayout.css'), appLayoutCss, 'utf-8');
228
+ // Update main.tsx or main.ts to use SlotKit App
229
+ // Check for both .tsx and .ts extensions, and rename .ts to .tsx if needed
230
+ const mainTsxPath = join(projectDir, 'src', 'main.tsx');
231
+ const mainTsPath = join(projectDir, 'src', 'main.ts');
232
+ let mainPath = mainTsxPath;
233
+ if (existsSync(mainTsPath)) {
234
+ // If main.ts exists, we need to rename it to .tsx since it will contain JSX
235
+ // But first check if main.tsx already exists
236
+ if (!existsSync(mainTsxPath)) {
237
+ // Rename main.ts to main.tsx
238
+ const mainTsContent = readFileSync(mainTsPath, 'utf-8');
239
+ writeFileSync(mainTsxPath, mainTsContent, 'utf-8');
240
+ // Delete the old main.ts
241
+ unlinkSync(mainTsPath);
242
+ }
243
+ mainPath = mainTsxPath;
244
+ }
245
+ if (existsSync(mainPath)) {
246
+ // Update existing main file
247
+ const mainContent = `import React from 'react'
248
+ import ReactDOM from 'react-dom/client'
249
+ import { App } from './app/App'
250
+ import './style.css'
251
+
252
+ ReactDOM.createRoot(document.getElementById('root')!).render(
253
+ <React.StrictMode>
254
+ <App />
255
+ </React.StrictMode>,
256
+ )
257
+ `;
258
+ writeFileSync(mainPath, mainContent, 'utf-8');
259
+ }
260
+ // Create slotkit.config.ts
261
+ const slotkitConfig = `import { defineConfig } from '@slotkitjs/core'
262
+
263
+ export default defineConfig({
264
+ pluginsDir: './plugins',
265
+ // Add more configuration here
266
+ })
267
+ `;
268
+ writeFileSync(join(projectDir, 'slotkit.config.ts'), slotkitConfig, 'utf-8');
269
+ // Create plugins directory
270
+ mkdirSync(join(projectDir, 'plugins'), { recursive: true });
271
+ // Create or update vite.config.ts with SlotKit-specific configuration
272
+ const viteConfigPath = join(projectDir, 'vite.config.ts');
273
+ const viteConfigJsPath = join(projectDir, 'vite.config.js');
274
+ // Check if vite.config.ts or vite.config.js exists
275
+ const existingConfigPath = existsSync(viteConfigPath) ? viteConfigPath :
276
+ (existsSync(viteConfigJsPath) ? viteConfigJsPath : null);
277
+ if (existingConfigPath) {
278
+ // Update existing config
279
+ let viteConfig = readFileSync(existingConfigPath, 'utf-8');
280
+ // Add server configuration if not present
281
+ if (!viteConfig.includes('server:')) {
282
+ // Find the defineConfig call and add server config inside it
283
+ if (viteConfig.includes('export default defineConfig')) {
284
+ viteConfig = viteConfig.replace(/export default defineConfig\(\{/, `export default defineConfig({
285
+ server: {
286
+ fs: {
287
+ // Allow access to plugin directory
288
+ allow: ['..', '../..', '../../..']
289
+ }
290
+ },`);
291
+ writeFileSync(existingConfigPath, viteConfig, 'utf-8');
292
+ }
293
+ }
294
+ }
295
+ else {
296
+ // Create new vite.config.ts
297
+ const viteConfigContent = `import { defineConfig } from 'vite'
298
+ import react from '@vitejs/plugin-react'
299
+
300
+ export default defineConfig({
301
+ plugins: [react()],
302
+ server: {
303
+ fs: {
304
+ // Allow access to plugin directory
305
+ allow: ['..', '../..', '../../..']
306
+ }
307
+ }
308
+ })
309
+ `;
310
+ writeFileSync(viteConfigPath, viteConfigContent, 'utf-8');
311
+ }
312
+ }
313
+ program
314
+ .name('create-slotkit-app')
315
+ .description('Create a new SlotKit application')
316
+ .argument('<project-name>', 'Project name')
317
+ .option('--template <template>', 'Vite template to use', 'react-ts')
318
+ .action(async (projectName, options) => {
319
+ const projectDir = resolve(process.cwd(), projectName);
320
+ if (existsSync(projectDir)) {
321
+ console.error(`[ERROR] Directory "${projectName}" already exists`);
322
+ process.exit(1);
323
+ }
324
+ // Extract just the project name (last part of path)
325
+ const actualProjectName = projectName.split(/[/\\]/).pop() || projectName;
326
+ const packageName = actualProjectName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
327
+ console.log(`[INFO] Creating SlotKit app: ${actualProjectName}`);
328
+ console.log(`[INFO] Using Vite template: ${options.template || 'react'}`);
329
+ try {
330
+ // Step 1: Use Vite to create the base project
331
+ // Vite creates the project in a subdirectory, so we need to extract just the name
332
+ console.log('[INFO] Step 1: Creating Vite project...');
333
+ // Create parent directory if needed (e.g., examples/test-project -> create examples first)
334
+ const parentDir = resolve(projectDir, '..');
335
+ if (!existsSync(parentDir)) {
336
+ mkdirSync(parentDir, { recursive: true });
337
+ }
338
+ // Try pnpm first, fallback to npm if pnpm fails
339
+ // Use non-interactive flags to skip prompts
340
+ const createCommand = 'pnpm';
341
+ const createArgs = [
342
+ 'create', 'vite', actualProjectName,
343
+ '--template', options.template || 'react-ts',
344
+ '--yes' // Skip prompts
345
+ ];
346
+ try {
347
+ // Run in the parent directory so Vite creates the project in the right place
348
+ await execCommand(createCommand, createArgs, parentDir);
349
+ }
350
+ catch (error) {
351
+ // If pnpm fails, try npm
352
+ console.log('[INFO] pnpm create vite failed, trying npm...');
353
+ const npmArgs = [
354
+ 'create', 'vite@latest', actualProjectName,
355
+ '--', '--template', options.template || 'react-ts'
356
+ ];
357
+ await execCommand('npm', npmArgs, parentDir);
358
+ }
359
+ // Step 2: Update package.json with SlotKit dependencies
360
+ console.log('[INFO] Step 2: Adding SlotKit dependencies...');
361
+ const packageJsonPath = join(projectDir, 'package.json');
362
+ if (existsSync(packageJsonPath)) {
363
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
364
+ // Update package name
365
+ packageJson.name = packageName;
366
+ // Ensure dependencies object exists
367
+ if (!packageJson.dependencies) {
368
+ packageJson.dependencies = {};
369
+ }
370
+ // Add SlotKit dependencies (merge with existing)
371
+ packageJson.dependencies = {
372
+ ...packageJson.dependencies,
373
+ '@slotkitjs/core': '^0.1.0',
374
+ '@slotkitjs/react': '^0.1.0'
375
+ };
376
+ // Ensure React dependencies are present (for React templates)
377
+ if (options.template?.includes('react')) {
378
+ packageJson.dependencies['react'] = packageJson.dependencies['react'] || '^19.1.1';
379
+ packageJson.dependencies['react-dom'] = packageJson.dependencies['react-dom'] || '^19.1.1';
380
+ // Add React types to devDependencies if not present
381
+ if (!packageJson.devDependencies) {
382
+ packageJson.devDependencies = {};
383
+ }
384
+ packageJson.devDependencies['@types/react'] = packageJson.devDependencies['@types/react'] || '^19.1.16';
385
+ packageJson.devDependencies['@types/react-dom'] = packageJson.devDependencies['@types/react-dom'] || '^19.1.9';
386
+ // Ensure @vitejs/plugin-react is in devDependencies
387
+ packageJson.devDependencies['@vitejs/plugin-react'] = packageJson.devDependencies['@vitejs/plugin-react'] || '^5.0.4';
388
+ }
389
+ // Update scripts to use slotkit commands (merge with existing, don't overwrite)
390
+ packageJson.scripts = {
391
+ ...packageJson.scripts,
392
+ 'dev': 'slotkit dev',
393
+ 'build': 'slotkit build',
394
+ 'preview': 'slotkit preview'
395
+ };
396
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
397
+ }
398
+ // Step 3: Create SlotKit-specific files
399
+ console.log('[INFO] Step 3: Creating SlotKit-specific files...');
400
+ createSlotKitFiles(projectDir);
401
+ // Step 4: Create .gitignore entry for generated files
402
+ const gitignorePath = join(projectDir, '.gitignore');
403
+ if (existsSync(gitignorePath)) {
404
+ const gitignore = readFileSync(gitignorePath, 'utf-8');
405
+ if (!gitignore.includes('plugin-imports.generated.ts')) {
406
+ writeFileSync(gitignorePath, gitignore + '\n# Generated files\nsrc/core/plugin/loader/plugin-imports.generated.ts\n', 'utf-8');
407
+ }
408
+ }
409
+ console.log('[OK] Project created successfully!');
410
+ console.log(`[INFO] Project location: ${projectDir}`);
411
+ console.log('\n[INFO] Next steps:');
412
+ console.log(` cd ${projectName}`);
413
+ console.log(' pnpm install');
414
+ console.log(' slotkit create-plugin <plugin-name> --slots <slots>');
415
+ console.log(' slotkit generate-imports');
416
+ console.log(' slotkit dev');
417
+ }
418
+ catch (error) {
419
+ console.error('[ERROR] Failed to create project:', error.message);
420
+ process.exit(1);
421
+ }
422
+ });
423
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "create-slotkit-app",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Create a new SlotKit application",
6
+ "bin": {
7
+ "create-slotkit-app": "./bin/create-slotkit-app.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch"
16
+ },
17
+ "keywords": [
18
+ "slotkit",
19
+ "create-app",
20
+ "scaffold"
21
+ ],
22
+ "author": "liusnew <liushuinew@gmail.com>",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "commander": "^12.1.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^24.6.0",
29
+ "typescript": "~5.9.3"
30
+ }
31
+ }
32
+