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.
Files changed (2) hide show
  1. package/dist/index.js +494 -0
  2. 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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-slotkit-app",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "description": "Create a new SlotKit application",
6
6
  "bin": {