@iselect/select 1.0.0 → 1.0.2
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/select.js +4 -1
- package/lib/commands/build.js +328 -5
- package/lib/commands/deploy.js +3 -3
- package/lib/commands/init.js +219 -149
- package/package.json +1 -1
package/bin/select.js
CHANGED
|
@@ -33,7 +33,7 @@ program
|
|
|
33
33
|
.option('-t, --template <template>', 'Project template (react, vue, vanilla)', 'react')
|
|
34
34
|
.action(init);
|
|
35
35
|
|
|
36
|
-
// select - Interactive platform selection
|
|
36
|
+
// slct select - Interactive platform selection
|
|
37
37
|
program
|
|
38
38
|
.command('select')
|
|
39
39
|
.alias('s')
|
|
@@ -47,6 +47,8 @@ program
|
|
|
47
47
|
.option('--tauri', 'Use Tauri for desktop (default)')
|
|
48
48
|
.option('--electron', 'Use Electron for desktop')
|
|
49
49
|
.option('--release', 'Build for production release')
|
|
50
|
+
.option('--static', 'Copy files directly without bundling (vanilla HTML/CSS/JS)')
|
|
51
|
+
.option('--inline', 'Inline CSS and JS into a single HTML file')
|
|
50
52
|
.action(build);
|
|
51
53
|
|
|
52
54
|
// slct deploy - Deploy to Select marketplace
|
|
@@ -65,3 +67,4 @@ program
|
|
|
65
67
|
|
|
66
68
|
// Parse arguments
|
|
67
69
|
program.parse();
|
|
70
|
+
|
package/lib/commands/build.js
CHANGED
|
@@ -55,25 +55,348 @@ async function build(target, options) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// ==================== FRAMEWORK SIGNATURES (Vercel-style) ====================
|
|
59
|
+
const FRAMEWORK_SIGNATURES = {
|
|
60
|
+
// Next.js
|
|
61
|
+
'next.config.js': { name: 'Next.js', build: 'npm run build', output: '.next', icon: '▲' },
|
|
62
|
+
'next.config.mjs': { name: 'Next.js', build: 'npm run build', output: '.next', icon: '▲' },
|
|
63
|
+
'next.config.ts': { name: 'Next.js', build: 'npm run build', output: '.next', icon: '▲' },
|
|
64
|
+
|
|
65
|
+
// Astro
|
|
66
|
+
'astro.config.mjs': { name: 'Astro', build: 'npm run build', output: 'dist', icon: '🚀' },
|
|
67
|
+
'astro.config.js': { name: 'Astro', build: 'npm run build', output: 'dist', icon: '🚀' },
|
|
68
|
+
'astro.config.ts': { name: 'Astro', build: 'npm run build', output: 'dist', icon: '🚀' },
|
|
69
|
+
|
|
70
|
+
// SvelteKit
|
|
71
|
+
'svelte.config.js': { name: 'SvelteKit', build: 'npm run build', output: 'build', icon: '🔶' },
|
|
72
|
+
'svelte.config.ts': { name: 'SvelteKit', build: 'npm run build', output: 'build', icon: '🔶' },
|
|
73
|
+
|
|
74
|
+
// Nuxt
|
|
75
|
+
'nuxt.config.js': { name: 'Nuxt', build: 'npm run build', output: '.nuxt', icon: '💚' },
|
|
76
|
+
'nuxt.config.ts': { name: 'Nuxt', build: 'npm run build', output: '.nuxt', icon: '💚' },
|
|
77
|
+
|
|
78
|
+
// Remix
|
|
79
|
+
'remix.config.js': { name: 'Remix', build: 'npm run build', output: 'build', icon: '💿' },
|
|
80
|
+
|
|
81
|
+
// Gatsby
|
|
82
|
+
'gatsby-config.js': { name: 'Gatsby', build: 'npm run build', output: 'public', icon: '💜' },
|
|
83
|
+
'gatsby-config.ts': { name: 'Gatsby', build: 'npm run build', output: 'public', icon: '💜' },
|
|
84
|
+
|
|
85
|
+
// Vite (generic)
|
|
86
|
+
'vite.config.js': { name: 'Vite', build: 'npm run build', output: 'dist', icon: '⚡' },
|
|
87
|
+
'vite.config.ts': { name: 'Vite', build: 'npm run build', output: 'dist', icon: '⚡' },
|
|
88
|
+
'vite.config.mjs': { name: 'Vite', build: 'npm run build', output: 'dist', icon: '⚡' },
|
|
89
|
+
|
|
90
|
+
// Angular
|
|
91
|
+
'angular.json': { name: 'Angular', build: 'npm run build', output: 'dist', icon: '🅰️' },
|
|
92
|
+
|
|
93
|
+
// Vue CLI
|
|
94
|
+
'vue.config.js': { name: 'Vue CLI', build: 'npm run build', output: 'dist', icon: '💚' },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Package.json dependency-based detection
|
|
98
|
+
const DEPENDENCY_SIGNATURES = {
|
|
99
|
+
'next': { name: 'Next.js', build: 'npm run build', output: '.next', icon: '▲' },
|
|
100
|
+
'@remix-run/react': { name: 'Remix', build: 'npm run build', output: 'build', icon: '💿' },
|
|
101
|
+
'gatsby': { name: 'Gatsby', build: 'npm run build', output: 'public', icon: '💜' },
|
|
102
|
+
'@angular/core': { name: 'Angular', build: 'npm run build', output: 'dist', icon: '🅰️' },
|
|
103
|
+
'react-scripts': { name: 'Create React App', build: 'npm run build', output: 'build', icon: '⚛️' },
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ==================== FRAMEWORK DETECTION ====================
|
|
107
|
+
async function detectFramework(projectPath) {
|
|
108
|
+
// 1. Check for framework config files
|
|
109
|
+
for (const [configFile, framework] of Object.entries(FRAMEWORK_SIGNATURES)) {
|
|
110
|
+
if (await fs.pathExists(path.join(projectPath, configFile))) {
|
|
111
|
+
return { ...framework, detected: 'config', configFile };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. Check package.json dependencies
|
|
116
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
117
|
+
if (await fs.pathExists(pkgPath)) {
|
|
118
|
+
try {
|
|
119
|
+
const pkg = await fs.readJson(pkgPath);
|
|
120
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
121
|
+
|
|
122
|
+
for (const [dep, framework] of Object.entries(DEPENDENCY_SIGNATURES)) {
|
|
123
|
+
if (allDeps[dep]) {
|
|
124
|
+
return { ...framework, detected: 'dependency', dependency: dep };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for generic build script
|
|
129
|
+
if (pkg.scripts?.build) {
|
|
130
|
+
return {
|
|
131
|
+
name: 'Custom',
|
|
132
|
+
build: 'npm run build',
|
|
133
|
+
output: 'dist',
|
|
134
|
+
icon: '📦',
|
|
135
|
+
detected: 'build-script'
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
} catch { }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 3. Static site detection
|
|
142
|
+
const hasIndexHtml = await fs.pathExists(path.join(projectPath, 'index.html'));
|
|
143
|
+
if (hasIndexHtml) {
|
|
144
|
+
return {
|
|
145
|
+
name: 'Static',
|
|
146
|
+
build: null, // No build needed
|
|
147
|
+
output: '.',
|
|
148
|
+
icon: '📄',
|
|
149
|
+
detected: 'static'
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ==================== PROJECT TYPE DETECTION (legacy wrapper) ====================
|
|
157
|
+
async function detectProjectType(projectPath) {
|
|
158
|
+
const framework = await detectFramework(projectPath);
|
|
159
|
+
|
|
160
|
+
if (!framework) return 'unknown';
|
|
161
|
+
if (framework.detected === 'static') return 'static';
|
|
162
|
+
if (framework.name === 'Vite') return 'vite';
|
|
163
|
+
return 'bundled';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ==================== INLINE BUILD ====================
|
|
167
|
+
async function buildInline(projectPath, outputDir) {
|
|
168
|
+
const spinner = ora('Building inline HTML...').start();
|
|
169
|
+
|
|
170
|
+
// Ensure output dir exists
|
|
171
|
+
await fs.ensureDir(outputDir);
|
|
172
|
+
|
|
173
|
+
// Read index.html
|
|
174
|
+
const indexPath = path.join(projectPath, 'index.html');
|
|
175
|
+
if (!await fs.pathExists(indexPath)) {
|
|
176
|
+
spinner.fail('No index.html found');
|
|
177
|
+
throw new Error('index.html not found in project root');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let html = await fs.readFile(indexPath, 'utf8');
|
|
181
|
+
|
|
182
|
+
// Inline CSS files
|
|
183
|
+
const cssLinkRegex = /<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"']+)["'][^>]*>/gi;
|
|
184
|
+
const cssLinks = [];
|
|
185
|
+
let match;
|
|
186
|
+
while ((match = cssLinkRegex.exec(html)) !== null) {
|
|
187
|
+
cssLinks.push({ full: match[0], href: match[1] });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const link of cssLinks) {
|
|
191
|
+
const cssPath = path.join(projectPath, link.href);
|
|
192
|
+
if (await fs.pathExists(cssPath)) {
|
|
193
|
+
const cssContent = await fs.readFile(cssPath, 'utf8');
|
|
194
|
+
html = html.replace(link.full, `<style>\n${cssContent}\n</style>`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Inline JS files
|
|
199
|
+
const jsScriptRegex = /<script[^>]+src=["']([^"']+)["'][^>]*><\/script>/gi;
|
|
200
|
+
const jsScripts = [];
|
|
201
|
+
while ((match = jsScriptRegex.exec(html)) !== null) {
|
|
202
|
+
jsScripts.push({ full: match[0], src: match[1] });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const script of jsScripts) {
|
|
206
|
+
const jsPath = path.join(projectPath, script.src);
|
|
207
|
+
if (await fs.pathExists(jsPath)) {
|
|
208
|
+
const jsContent = await fs.readFile(jsPath, 'utf8');
|
|
209
|
+
html = html.replace(script.full, `<script>\n${jsContent}\n</script>`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Write output
|
|
214
|
+
await fs.writeFile(path.join(outputDir, 'index.html'), html);
|
|
215
|
+
|
|
216
|
+
spinner.succeed(`Created single-file HTML (${cssLinks.length} CSS, ${jsScripts.length} JS inlined)`);
|
|
217
|
+
|
|
218
|
+
return { cssInlined: cssLinks.length, jsInlined: jsScripts.length };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ==================== STATIC FILE COPY ====================
|
|
222
|
+
async function copyStaticFiles(projectPath, outputDir) {
|
|
223
|
+
const spinner = ora('Copying static files...').start();
|
|
224
|
+
|
|
225
|
+
// Ensure output dir exists and is clean
|
|
226
|
+
await fs.emptyDir(outputDir);
|
|
227
|
+
|
|
228
|
+
// Files and folders to copy
|
|
229
|
+
const filesToCopy = ['index.html', 'style.css', 'styles.css', 'main.css', 'app.css'];
|
|
230
|
+
const foldersToCheck = ['assets', 'css', 'js', 'images', 'fonts', 'src'];
|
|
231
|
+
|
|
232
|
+
let copiedFiles = 0;
|
|
233
|
+
let cssFound = false;
|
|
234
|
+
|
|
235
|
+
// Copy individual files
|
|
236
|
+
for (const file of filesToCopy) {
|
|
237
|
+
const srcPath = path.join(projectPath, file);
|
|
238
|
+
if (await fs.pathExists(srcPath)) {
|
|
239
|
+
await fs.copy(srcPath, path.join(outputDir, file));
|
|
240
|
+
copiedFiles++;
|
|
241
|
+
if (file.endsWith('.css')) cssFound = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Copy .js files from root
|
|
246
|
+
const rootFiles = await fs.readdir(projectPath);
|
|
247
|
+
for (const file of rootFiles) {
|
|
248
|
+
if (file.endsWith('.js') && !file.includes('.config.')) {
|
|
249
|
+
await fs.copy(path.join(projectPath, file), path.join(outputDir, file));
|
|
250
|
+
copiedFiles++;
|
|
251
|
+
}
|
|
252
|
+
if (file.endsWith('.css')) {
|
|
253
|
+
await fs.copy(path.join(projectPath, file), path.join(outputDir, file));
|
|
254
|
+
copiedFiles++;
|
|
255
|
+
cssFound = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Copy folders
|
|
260
|
+
for (const folder of foldersToCheck) {
|
|
261
|
+
const srcPath = path.join(projectPath, folder);
|
|
262
|
+
if (await fs.pathExists(srcPath)) {
|
|
263
|
+
await fs.copy(srcPath, path.join(outputDir, folder));
|
|
264
|
+
copiedFiles++;
|
|
265
|
+
|
|
266
|
+
// Check for CSS in folders
|
|
267
|
+
const folderFiles = await fs.readdir(srcPath);
|
|
268
|
+
if (folderFiles.some(f => f.endsWith('.css'))) {
|
|
269
|
+
cssFound = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
spinner.succeed(`Copied ${copiedFiles} files/folders to ${outputDir}`);
|
|
275
|
+
|
|
276
|
+
// Warning if no CSS found
|
|
277
|
+
if (!cssFound) {
|
|
278
|
+
console.log(chalk.yellow('\n⚠️ Warning: No CSS files found in build output'));
|
|
279
|
+
console.log(chalk.gray(' Ensure your HTML links to the CSS file correctly\n'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return { copiedFiles, cssFound };
|
|
283
|
+
}
|
|
284
|
+
|
|
58
285
|
// ==================== WEB BUILD ====================
|
|
59
286
|
async function buildWeb(config, options) {
|
|
60
287
|
console.log(chalk.blue('\n📦 Building for Web...\n'));
|
|
61
288
|
|
|
62
|
-
const
|
|
289
|
+
const projectPath = process.cwd();
|
|
290
|
+
const outputDir = path.resolve(projectPath, config.build?.outDir || 'dist');
|
|
291
|
+
|
|
292
|
+
// Check for --static flag
|
|
293
|
+
if (options.static) {
|
|
294
|
+
console.log(chalk.gray(' Mode: Static (--static flag)'));
|
|
295
|
+
await copyStaticFiles(projectPath, outputDir);
|
|
296
|
+
console.log(chalk.green('\n✅ Static web build complete!'));
|
|
297
|
+
console.log(chalk.gray(` Output: ${outputDir}/\n`));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Check for --inline flag
|
|
302
|
+
if (options.inline) {
|
|
303
|
+
console.log(chalk.gray(' Mode: Inline (--inline flag)'));
|
|
304
|
+
await buildInline(projectPath, outputDir);
|
|
305
|
+
console.log(chalk.green('\n✅ Inline web build complete!'));
|
|
306
|
+
console.log(chalk.gray(` Output: ${outputDir}/index.html (single file)\n`));
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Auto-detect framework (Vercel-style)
|
|
311
|
+
const framework = await detectFramework(projectPath);
|
|
312
|
+
|
|
313
|
+
if (!framework) {
|
|
314
|
+
console.log(chalk.yellow('\n⚠️ Could not detect project type'));
|
|
315
|
+
console.log(chalk.gray(' No index.html or package.json found'));
|
|
316
|
+
console.log(chalk.gray(' Try: slct build web --static\n'));
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Display detected framework
|
|
321
|
+
console.log(chalk.cyan(` ${framework.icon} Detected: ${chalk.bold(framework.name)}`));
|
|
322
|
+
if (framework.detected === 'config') {
|
|
323
|
+
console.log(chalk.gray(` via ${framework.configFile}`));
|
|
324
|
+
} else if (framework.detected === 'dependency') {
|
|
325
|
+
console.log(chalk.gray(` via ${framework.dependency} dependency`));
|
|
326
|
+
} else if (framework.detected === 'static') {
|
|
327
|
+
console.log(chalk.gray(' (static HTML/CSS/JS site)'));
|
|
328
|
+
}
|
|
329
|
+
console.log('');
|
|
330
|
+
|
|
331
|
+
// Handle static sites
|
|
332
|
+
if (framework.detected === 'static') {
|
|
333
|
+
await copyStaticFiles(projectPath, outputDir);
|
|
334
|
+
console.log(chalk.green('\n✅ Static web build complete!'));
|
|
335
|
+
console.log(chalk.gray(` Output: ${outputDir}/\n`));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Run build command for bundled projects
|
|
340
|
+
// Priority: select.json config > detected framework > default
|
|
341
|
+
const buildCommand = config.build?.command || framework.build || 'npm run build';
|
|
342
|
+
const finalOutputDir = config.build?.outDir
|
|
343
|
+
? path.resolve(projectPath, config.build.outDir)
|
|
344
|
+
: path.resolve(projectPath, framework.output || 'dist');
|
|
345
|
+
|
|
346
|
+
const spinner = ora(`Running: ${chalk.cyan(buildCommand)}`).start();
|
|
63
347
|
|
|
64
348
|
try {
|
|
65
|
-
execSync(
|
|
349
|
+
execSync(buildCommand, {
|
|
66
350
|
stdio: 'inherit',
|
|
67
|
-
cwd:
|
|
351
|
+
cwd: projectPath
|
|
68
352
|
});
|
|
69
|
-
spinner.succeed(
|
|
70
|
-
|
|
353
|
+
spinner.succeed(`${framework.icon} ${framework.name} build complete!`);
|
|
354
|
+
|
|
355
|
+
// Validate output
|
|
356
|
+
await validateBuildOutput(finalOutputDir);
|
|
357
|
+
|
|
358
|
+
console.log(chalk.gray(` Output: ${finalOutputDir}/\n`));
|
|
71
359
|
} catch (error) {
|
|
72
360
|
spinner.fail('Web build failed');
|
|
73
361
|
throw error;
|
|
74
362
|
}
|
|
75
363
|
}
|
|
76
364
|
|
|
365
|
+
// ==================== BUILD VALIDATION ====================
|
|
366
|
+
async function validateBuildOutput(outputDir) {
|
|
367
|
+
if (!await fs.pathExists(outputDir)) {
|
|
368
|
+
console.log(chalk.yellow('\n⚠️ Warning: Output directory not found'));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const files = await fs.readdir(outputDir);
|
|
373
|
+
|
|
374
|
+
// Check for index.html
|
|
375
|
+
if (!files.includes('index.html')) {
|
|
376
|
+
console.log(chalk.yellow('\n⚠️ Warning: No index.html found in build output'));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check for CSS
|
|
380
|
+
const hasCss = files.some(f => f.endsWith('.css'));
|
|
381
|
+
const hasAssetsFolder = files.includes('assets');
|
|
382
|
+
|
|
383
|
+
if (!hasCss && hasAssetsFolder) {
|
|
384
|
+
const assetsPath = path.join(outputDir, 'assets');
|
|
385
|
+
if (await fs.pathExists(assetsPath)) {
|
|
386
|
+
const assetFiles = await fs.readdir(assetsPath);
|
|
387
|
+
if (assetFiles.some(f => f.endsWith('.css'))) {
|
|
388
|
+
return; // CSS is in assets folder, all good
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!hasCss) {
|
|
394
|
+
console.log(chalk.yellow('\n⚠️ Warning: No CSS files found in build output'));
|
|
395
|
+
console.log(chalk.gray(' If using vanilla CSS, ensure it\'s imported in your JS entry point'));
|
|
396
|
+
console.log(chalk.gray(' Or use: slct build web --static\n'));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
77
400
|
// ==================== DESKTOP BUILD ====================
|
|
78
401
|
async function buildDesktop(config, options) {
|
|
79
402
|
const framework = options.electron ? 'electron' : (config.desktop?.framework || 'tauri');
|
package/lib/commands/deploy.js
CHANGED
|
@@ -39,8 +39,8 @@ async function deploy(options) {
|
|
|
39
39
|
type: 'list',
|
|
40
40
|
name: 'category',
|
|
41
41
|
message: 'Category:',
|
|
42
|
-
choices: ['
|
|
43
|
-
default: config.category || '
|
|
42
|
+
choices: ['productivity', 'finance', 'developer-tools', 'education', 'entertainment', 'utilities', 'games', 'ai-tools', 'experiments'],
|
|
43
|
+
default: config.category || 'productivity'
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
type: 'password',
|
|
@@ -66,7 +66,7 @@ async function deploy(options) {
|
|
|
66
66
|
...config,
|
|
67
67
|
name: answers.name,
|
|
68
68
|
tagline: answers.tagline,
|
|
69
|
-
category: answers.category
|
|
69
|
+
category: answers.category,
|
|
70
70
|
platforms: []
|
|
71
71
|
};
|
|
72
72
|
|
package/lib/commands/init.js
CHANGED
|
@@ -5,125 +5,125 @@ const inquirer = require('inquirer');
|
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
|
|
7
7
|
async function init(name, options) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// If no name provided, ask for it
|
|
11
|
-
if (!name) {
|
|
12
|
-
const answers = await inquirer.prompt([
|
|
13
|
-
{
|
|
14
|
-
type: 'input',
|
|
15
|
-
name: 'name',
|
|
16
|
-
message: 'Project name:',
|
|
17
|
-
default: 'my-select-app',
|
|
18
|
-
validate: (input) => {
|
|
19
|
-
if (/^[a-z0-9-]+$/.test(input)) return true;
|
|
20
|
-
return 'Project name must be lowercase with hyphens only';
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
]);
|
|
24
|
-
name = answers.name;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const projectDir = path.resolve(process.cwd(), name);
|
|
8
|
+
console.log(chalk.bold('\n📦 Initialize Select Project\n'));
|
|
28
9
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.log(chalk.red(`\n❌ Directory "${name}" already exists.\n`));
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Ask for project details
|
|
10
|
+
// If no name provided, ask for it
|
|
11
|
+
if (!name) {
|
|
36
12
|
const answers = await inquirer.prompt([
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
],
|
|
46
|
-
default: options.template
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
type: 'checkbox',
|
|
50
|
-
name: 'platforms',
|
|
51
|
-
message: 'Target platforms:',
|
|
52
|
-
choices: [
|
|
53
|
-
{ name: 'Web', value: 'web', checked: true },
|
|
54
|
-
{ name: 'Windows', value: 'windows' },
|
|
55
|
-
{ name: 'macOS', value: 'macos' },
|
|
56
|
-
{ name: 'Linux', value: 'linux' }
|
|
57
|
-
]
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
type: 'list',
|
|
61
|
-
name: 'desktopFramework',
|
|
62
|
-
message: 'Desktop framework:',
|
|
63
|
-
choices: [
|
|
64
|
-
{ name: 'Tauri (Recommended - smaller, faster)', value: 'tauri' },
|
|
65
|
-
{ name: 'Electron (Larger, more compatible)', value: 'electron' }
|
|
66
|
-
],
|
|
67
|
-
when: (ans) => ans.platforms.some(p => ['windows', 'macos', 'linux'].includes(p))
|
|
13
|
+
{
|
|
14
|
+
type: 'input',
|
|
15
|
+
name: 'name',
|
|
16
|
+
message: 'Project name:',
|
|
17
|
+
default: 'my-select-app',
|
|
18
|
+
validate: (input) => {
|
|
19
|
+
if (/^[a-z0-9-]+$/.test(input)) return true;
|
|
20
|
+
return 'Project name must be lowercase with hyphens only';
|
|
68
21
|
}
|
|
22
|
+
}
|
|
69
23
|
]);
|
|
24
|
+
name = answers.name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const projectDir = path.resolve(process.cwd(), name);
|
|
28
|
+
|
|
29
|
+
// Check if directory exists
|
|
30
|
+
if (await fs.pathExists(projectDir)) {
|
|
31
|
+
console.log(chalk.red(`\n❌ Directory "${name}" already exists.\n`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Ask for project details
|
|
36
|
+
const answers = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'template',
|
|
40
|
+
message: 'Select a template:',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'React + Vite', value: 'react' },
|
|
43
|
+
{ name: 'Vue + Vite', value: 'vue' },
|
|
44
|
+
{ name: 'Vanilla JavaScript', value: 'vanilla' }
|
|
45
|
+
],
|
|
46
|
+
default: options.template
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: 'checkbox',
|
|
50
|
+
name: 'platforms',
|
|
51
|
+
message: 'Target platforms:',
|
|
52
|
+
choices: [
|
|
53
|
+
{ name: 'Web', value: 'web', checked: true },
|
|
54
|
+
{ name: 'Windows', value: 'windows' },
|
|
55
|
+
{ name: 'macOS', value: 'macos' },
|
|
56
|
+
{ name: 'Linux', value: 'linux' }
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'desktopFramework',
|
|
62
|
+
message: 'Desktop framework:',
|
|
63
|
+
choices: [
|
|
64
|
+
{ name: 'Tauri (Recommended - smaller, faster)', value: 'tauri' },
|
|
65
|
+
{ name: 'Electron (Larger, more compatible)', value: 'electron' }
|
|
66
|
+
],
|
|
67
|
+
when: (ans) => ans.platforms.some(p => ['windows', 'macos', 'linux'].includes(p))
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const spinner = ora('Creating project...').start();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Create project directory
|
|
75
|
+
await fs.ensureDir(projectDir);
|
|
76
|
+
|
|
77
|
+
// Create select.json config
|
|
78
|
+
const selectConfig = {
|
|
79
|
+
name: name,
|
|
80
|
+
version: '1.0.0',
|
|
81
|
+
description: 'A Select app',
|
|
82
|
+
platforms: answers.platforms,
|
|
83
|
+
desktop: {
|
|
84
|
+
framework: answers.desktopFramework || 'tauri'
|
|
85
|
+
},
|
|
86
|
+
build: {
|
|
87
|
+
command: 'npm run build',
|
|
88
|
+
devCommand: 'npm run dev',
|
|
89
|
+
outDir: 'dist'
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
await fs.writeJson(path.join(projectDir, 'select.json'), selectConfig, { spaces: 2 });
|
|
94
|
+
|
|
95
|
+
// Create basic package.json
|
|
96
|
+
const packageJson = {
|
|
97
|
+
name: name,
|
|
98
|
+
version: '1.0.0',
|
|
99
|
+
private: true,
|
|
100
|
+
scripts: {
|
|
101
|
+
dev: 'vite',
|
|
102
|
+
build: 'vite build',
|
|
103
|
+
preview: 'vite preview',
|
|
104
|
+
'slct:build': 'slct build all',
|
|
105
|
+
'slct:deploy': 'slct deploy'
|
|
106
|
+
},
|
|
107
|
+
dependencies: {},
|
|
108
|
+
devDependencies: {
|
|
109
|
+
'vite': '^5.0.0'
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Add template-specific dependencies
|
|
114
|
+
if (answers.template === 'react') {
|
|
115
|
+
packageJson.dependencies['react'] = '^18.2.0';
|
|
116
|
+
packageJson.dependencies['react-dom'] = '^18.2.0';
|
|
117
|
+
packageJson.devDependencies['@vitejs/plugin-react'] = '^4.2.0';
|
|
118
|
+
} else if (answers.template === 'vue') {
|
|
119
|
+
packageJson.dependencies['vue'] = '^3.4.0';
|
|
120
|
+
packageJson.devDependencies['@vitejs/plugin-vue'] = '^4.5.0';
|
|
121
|
+
}
|
|
70
122
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
// Create project directory
|
|
75
|
-
await fs.ensureDir(projectDir);
|
|
76
|
-
|
|
77
|
-
// Create select.json config
|
|
78
|
-
const selectConfig = {
|
|
79
|
-
name: name,
|
|
80
|
-
version: '1.0.0',
|
|
81
|
-
description: 'A Select app',
|
|
82
|
-
platforms: answers.platforms,
|
|
83
|
-
desktop: {
|
|
84
|
-
framework: answers.desktopFramework || 'tauri'
|
|
85
|
-
},
|
|
86
|
-
build: {
|
|
87
|
-
command: 'npm run build',
|
|
88
|
-
devCommand: 'npm run dev',
|
|
89
|
-
outDir: 'dist'
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
await fs.writeJson(path.join(projectDir, 'select.json'), selectConfig, { spaces: 2 });
|
|
94
|
-
|
|
95
|
-
// Create basic package.json
|
|
96
|
-
const packageJson = {
|
|
97
|
-
name: name,
|
|
98
|
-
version: '1.0.0',
|
|
99
|
-
private: true,
|
|
100
|
-
scripts: {
|
|
101
|
-
dev: 'vite',
|
|
102
|
-
build: 'vite build',
|
|
103
|
-
preview: 'vite preview',
|
|
104
|
-
'slct:build': 'slct build all',
|
|
105
|
-
'slct:deploy': 'slct deploy'
|
|
106
|
-
},
|
|
107
|
-
dependencies: {},
|
|
108
|
-
devDependencies: {
|
|
109
|
-
'vite': '^5.0.0'
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
// Add template-specific dependencies
|
|
114
|
-
if (answers.template === 'react') {
|
|
115
|
-
packageJson.dependencies['react'] = '^18.2.0';
|
|
116
|
-
packageJson.dependencies['react-dom'] = '^18.2.0';
|
|
117
|
-
packageJson.devDependencies['@vitejs/plugin-react'] = '^4.2.0';
|
|
118
|
-
} else if (answers.template === 'vue') {
|
|
119
|
-
packageJson.dependencies['vue'] = '^3.4.0';
|
|
120
|
-
packageJson.devDependencies['@vitejs/plugin-vue'] = '^4.5.0';
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
|
123
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
// Create basic index.html
|
|
126
|
+
const indexHtml = `<!DOCTYPE html>
|
|
127
127
|
<html lang="en">
|
|
128
128
|
<head>
|
|
129
129
|
<meta charset="UTF-8">
|
|
@@ -136,21 +136,22 @@ async function init(name, options) {
|
|
|
136
136
|
</body>
|
|
137
137
|
</html>`;
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
await fs.writeFile(path.join(projectDir, 'index.html'), indexHtml);
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// Create src directory with basic file
|
|
142
|
+
await fs.ensureDir(path.join(projectDir, 'src'));
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
// Create GitHub Actions workflow
|
|
145
|
+
await createGithubWorkflow(projectDir, answers.desktopFramework || 'tauri', name);
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
if (answers.template === 'react') {
|
|
148
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.jsx'), `import React from 'react'
|
|
149
149
|
import ReactDOM from 'react-dom/client'
|
|
150
|
+
import './style.css'
|
|
150
151
|
|
|
151
152
|
function App() {
|
|
152
153
|
return (
|
|
153
|
-
<div
|
|
154
|
+
<div className="app">
|
|
154
155
|
<h1>Welcome to ${name}</h1>
|
|
155
156
|
<p>Built with Select CLI</p>
|
|
156
157
|
</div>
|
|
@@ -159,19 +160,88 @@ function App() {
|
|
|
159
160
|
|
|
160
161
|
ReactDOM.createRoot(document.getElementById('app')).render(<App />)
|
|
161
162
|
`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
// Create React style.css
|
|
164
|
+
await fs.writeFile(path.join(projectDir, 'src', 'style.css'), `* {
|
|
165
|
+
margin: 0;
|
|
166
|
+
padding: 0;
|
|
167
|
+
box-sizing: border-box;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
body {
|
|
171
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
172
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
173
|
+
min-height: 100vh;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.app {
|
|
180
|
+
background: white;
|
|
181
|
+
padding: 3rem 4rem;
|
|
182
|
+
border-radius: 1rem;
|
|
183
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
184
|
+
text-align: center;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
h1 {
|
|
188
|
+
color: #1a202c;
|
|
189
|
+
margin-bottom: 0.5rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
p {
|
|
193
|
+
color: #718096;
|
|
194
|
+
}
|
|
195
|
+
`);
|
|
196
|
+
} else {
|
|
197
|
+
// Vanilla JS template
|
|
198
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.js'), `import './style.css'
|
|
199
|
+
|
|
200
|
+
document.getElementById('app').innerHTML = \`
|
|
201
|
+
<div class="app">
|
|
165
202
|
<h1>Welcome to ${name}</h1>
|
|
166
203
|
<p>Built with Select CLI</p>
|
|
167
204
|
</div>
|
|
168
205
|
\`
|
|
169
206
|
`);
|
|
170
|
-
|
|
207
|
+
// Create style.css for vanilla
|
|
208
|
+
await fs.writeFile(path.join(projectDir, 'src', 'style.css'), `* {
|
|
209
|
+
margin: 0;
|
|
210
|
+
padding: 0;
|
|
211
|
+
box-sizing: border-box;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
body {
|
|
215
|
+
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
216
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
217
|
+
min-height: 100vh;
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: center;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.app {
|
|
224
|
+
background: white;
|
|
225
|
+
padding: 3rem 4rem;
|
|
226
|
+
border-radius: 1rem;
|
|
227
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
228
|
+
text-align: center;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
h1 {
|
|
232
|
+
color: #1a202c;
|
|
233
|
+
margin-bottom: 0.5rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
p {
|
|
237
|
+
color: #718096;
|
|
238
|
+
}
|
|
239
|
+
`);
|
|
240
|
+
}
|
|
171
241
|
|
|
172
|
-
|
|
242
|
+
spinner.succeed('Project created successfully!');
|
|
173
243
|
|
|
174
|
-
|
|
244
|
+
console.log(chalk.green(`
|
|
175
245
|
✅ Project "${name}" created!
|
|
176
246
|
|
|
177
247
|
Next steps:
|
|
@@ -181,33 +251,33 @@ Next steps:
|
|
|
181
251
|
|
|
182
252
|
`));
|
|
183
253
|
|
|
184
|
-
|
|
185
|
-
|
|
254
|
+
// Check for required dependencies
|
|
255
|
+
await checkDependencies(answers);
|
|
186
256
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
257
|
+
} catch (error) {
|
|
258
|
+
spinner.fail('Failed to create project');
|
|
259
|
+
console.error(chalk.red(error.message));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
192
262
|
}
|
|
193
263
|
|
|
194
264
|
async function checkDependencies(answers) {
|
|
195
|
-
|
|
265
|
+
const hasDesktop = answers.platforms?.some(p => ['windows', 'macos', 'linux'].includes(p));
|
|
196
266
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
267
|
+
if (hasDesktop && answers.desktopFramework === 'tauri') {
|
|
268
|
+
console.log(chalk.yellow('\n⚠️ Tauri Requirements:'));
|
|
269
|
+
console.log(chalk.gray(' Run "slct build desktop" for detailed setup instructions.\n'));
|
|
270
|
+
}
|
|
201
271
|
}
|
|
202
272
|
|
|
203
273
|
async function createGithubWorkflow(projectDir, framework, name) {
|
|
204
|
-
|
|
205
|
-
|
|
274
|
+
const workflowDir = path.join(projectDir, '.github', 'workflows');
|
|
275
|
+
await fs.ensureDir(workflowDir);
|
|
206
276
|
|
|
207
|
-
|
|
277
|
+
let workflowContent = '';
|
|
208
278
|
|
|
209
|
-
|
|
210
|
-
|
|
279
|
+
if (framework === 'tauri') {
|
|
280
|
+
workflowContent = `name: Build Apps
|
|
211
281
|
on:
|
|
212
282
|
push:
|
|
213
283
|
branches: [ main ]
|
|
@@ -253,9 +323,9 @@ jobs:
|
|
|
253
323
|
releaseDraft: true
|
|
254
324
|
prerelease: false
|
|
255
325
|
`;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
326
|
+
} else {
|
|
327
|
+
// Electron Workflow
|
|
328
|
+
workflowContent = `name: Build Apps
|
|
259
329
|
on:
|
|
260
330
|
push:
|
|
261
331
|
branches: [ main ]
|
|
@@ -287,9 +357,9 @@ jobs:
|
|
|
287
357
|
env:
|
|
288
358
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
289
359
|
`;
|
|
290
|
-
|
|
360
|
+
}
|
|
291
361
|
|
|
292
|
-
|
|
362
|
+
await fs.writeFile(path.join(workflowDir, 'build.yml'), workflowContent);
|
|
293
363
|
}
|
|
294
364
|
|
|
295
365
|
module.exports = init;
|