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.
- package/bin/create-slotkit-app.js +4 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +423 -0
- package/package.json +32 -0
package/dist/index.d.ts
ADDED
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
|
+
|