@iselect/select 1.0.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/README.md +138 -0
- package/bin/select.js +67 -0
- package/lib/commands/build.js +405 -0
- package/lib/commands/deploy.js +187 -0
- package/lib/commands/dev.js +55 -0
- package/lib/commands/init.js +295 -0
- package/lib/commands/select.js +64 -0
- package/lib/index.js +7 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Select CLI
|
|
2
|
+
|
|
3
|
+
Build and deploy apps for web, desktop, and mobile from a single codebase.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
_____ ________ _______________
|
|
7
|
+
/ ___// ____/ / / ____/ ____/_ /
|
|
8
|
+
\__ \/ __/ / / / __/ / / / /
|
|
9
|
+
___/ / /___/ /___/ /___/ /___ / /
|
|
10
|
+
/____/_____/_____/_____/\____/ /_/
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Option 1: From Source (Recommended for Contributors)
|
|
17
|
+
git clone https://github.com/yourusername/select-cli.git
|
|
18
|
+
cd select-cli
|
|
19
|
+
npm link
|
|
20
|
+
|
|
21
|
+
# Option 2: From GitHub (If public)
|
|
22
|
+
npm install -g github:yourusername/select-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Create a new project
|
|
29
|
+
slct init my-app
|
|
30
|
+
|
|
31
|
+
# Navigate to project
|
|
32
|
+
cd my-app
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Select target platforms
|
|
36
|
+
slct
|
|
37
|
+
|
|
38
|
+
# Build for all desktop platforms & web
|
|
39
|
+
slct build all
|
|
40
|
+
|
|
41
|
+
# Deploy to Select marketplace
|
|
42
|
+
slct deploy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### `slct init [name]`
|
|
48
|
+
|
|
49
|
+
Create a new Select project with interactive prompts.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
slct init my-app
|
|
53
|
+
slct init --template vue
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Options:**
|
|
57
|
+
- `-t, --template <template>` - Project template (react, vue, vanilla)
|
|
58
|
+
|
|
59
|
+
### `slct` (or `slct s`)
|
|
60
|
+
|
|
61
|
+
Interactively select target platforms for your project.
|
|
62
|
+
|
|
63
|
+
### `slct build [target]`
|
|
64
|
+
|
|
65
|
+
Build your app for specified platforms.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
slct build # Interactive selection
|
|
69
|
+
slct build web # Build for web only
|
|
70
|
+
slct build desktop # Build for desktop (Tauri/Electron)
|
|
71
|
+
slct build all # Build for all platforms (Web + Desktop)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Options:**
|
|
75
|
+
- `--tauri` - Use Tauri for desktop (default)
|
|
76
|
+
- `--electron` - Use Electron for desktop
|
|
77
|
+
- `--release` - Build for production release
|
|
78
|
+
|
|
79
|
+
### `slct deploy`
|
|
80
|
+
|
|
81
|
+
Deploy your app to the Select marketplace.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
slct deploy
|
|
85
|
+
slct deploy --prod # Deploy to production
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `slct dev`
|
|
89
|
+
|
|
90
|
+
Start the development server.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
slct dev
|
|
94
|
+
slct dev --port 3000
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## select.json
|
|
98
|
+
|
|
99
|
+
Configuration file generated by `slct init`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "my-app",
|
|
104
|
+
"version": "1.0.0",
|
|
105
|
+
"description": "A Select app",
|
|
106
|
+
"platforms": ["web", "windows", "linux"],
|
|
107
|
+
"desktop": {
|
|
108
|
+
"framework": "tauri"
|
|
109
|
+
},
|
|
110
|
+
"build": {
|
|
111
|
+
"command": "npm run build",
|
|
112
|
+
"devCommand": "npm run dev",
|
|
113
|
+
"outDir": "dist"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Platform Requirements
|
|
119
|
+
|
|
120
|
+
### Tauri (Desktop)
|
|
121
|
+
|
|
122
|
+
Tauri requires Rust and platform-specific build tools:
|
|
123
|
+
|
|
124
|
+
1. **Install Rust**: https://rustup.rs/
|
|
125
|
+
2. **Windows**: Install Visual Studio Build Tools
|
|
126
|
+
- MSVC v143 - VS 2022 C++ x64/x86 build tools
|
|
127
|
+
- Windows 10/11 SDK
|
|
128
|
+
- C++ CMake tools
|
|
129
|
+
3. **macOS**: Install Xcode Command Line Tools
|
|
130
|
+
4. **Linux**: Install build-essential and webkit2gtk
|
|
131
|
+
|
|
132
|
+
### Electron (Desktop)
|
|
133
|
+
|
|
134
|
+
Electron is automatically installed when selected.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
package/bin/select.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const pkg = require('../package.json');
|
|
6
|
+
|
|
7
|
+
// Import commands
|
|
8
|
+
const init = require('../lib/commands/init');
|
|
9
|
+
const select = require('../lib/commands/select');
|
|
10
|
+
const build = require('../lib/commands/build');
|
|
11
|
+
const deploy = require('../lib/commands/deploy');
|
|
12
|
+
const dev = require('../lib/commands/dev');
|
|
13
|
+
|
|
14
|
+
// ASCII Banner
|
|
15
|
+
console.log(chalk.cyan(`
|
|
16
|
+
_____ ________ _______________
|
|
17
|
+
/ ___// ____/ / / ____/ ____/_ /
|
|
18
|
+
\\__ \\/ __/ / / / __/ / / / /
|
|
19
|
+
___/ / /___/ /___/ /___/ /___ / /
|
|
20
|
+
/____/_____/_____/_____/\\____/ /_/
|
|
21
|
+
`));
|
|
22
|
+
console.log(chalk.gray(` v${pkg.version} - Build for web & desktop\n`));
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.name('slct')
|
|
26
|
+
.description('Build and deploy apps for multiple platforms')
|
|
27
|
+
.version(pkg.version);
|
|
28
|
+
|
|
29
|
+
// slct init - Initialize a new project
|
|
30
|
+
program
|
|
31
|
+
.command('init [name]')
|
|
32
|
+
.description('Initialize a new Select project')
|
|
33
|
+
.option('-t, --template <template>', 'Project template (react, vue, vanilla)', 'react')
|
|
34
|
+
.action(init);
|
|
35
|
+
|
|
36
|
+
// select - Interactive platform selection
|
|
37
|
+
program
|
|
38
|
+
.command('select')
|
|
39
|
+
.alias('s')
|
|
40
|
+
.description('Interactively select target platforms')
|
|
41
|
+
.action(select);
|
|
42
|
+
|
|
43
|
+
// slct build - Build for platforms
|
|
44
|
+
program
|
|
45
|
+
.command('build [target]')
|
|
46
|
+
.description('Build for target platform (web, desktop, all)')
|
|
47
|
+
.option('--tauri', 'Use Tauri for desktop (default)')
|
|
48
|
+
.option('--electron', 'Use Electron for desktop')
|
|
49
|
+
.option('--release', 'Build for production release')
|
|
50
|
+
.action(build);
|
|
51
|
+
|
|
52
|
+
// slct deploy - Deploy to Select marketplace
|
|
53
|
+
program
|
|
54
|
+
.command('deploy')
|
|
55
|
+
.description('Deploy your app to Select marketplace')
|
|
56
|
+
.option('--prod', 'Deploy to production')
|
|
57
|
+
.action(deploy);
|
|
58
|
+
|
|
59
|
+
// slct dev - Start development server
|
|
60
|
+
program
|
|
61
|
+
.command('dev')
|
|
62
|
+
.description('Start development server')
|
|
63
|
+
.option('-p, --port <port>', 'Port number', '5173')
|
|
64
|
+
.action(dev);
|
|
65
|
+
|
|
66
|
+
// Parse arguments
|
|
67
|
+
program.parse();
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { execSync, spawn } = require('child_process');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
|
|
8
|
+
async function build(target, options) {
|
|
9
|
+
console.log(chalk.bold('\n🔨 Select Build\n'));
|
|
10
|
+
|
|
11
|
+
// Load config
|
|
12
|
+
const configPath = path.resolve(process.cwd(), 'select.json');
|
|
13
|
+
|
|
14
|
+
if (!await fs.pathExists(configPath)) {
|
|
15
|
+
console.log(chalk.red('❌ No select.json found. Run "slct init" first.\n'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const config = await fs.readJson(configPath);
|
|
20
|
+
|
|
21
|
+
// If no target specified, ask
|
|
22
|
+
if (!target) {
|
|
23
|
+
const answers = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'target',
|
|
27
|
+
message: 'Build target:',
|
|
28
|
+
choices: [
|
|
29
|
+
{ name: 'Web', value: 'web' },
|
|
30
|
+
{ name: 'Desktop (Windows/macOS/Linux)', value: 'desktop' },
|
|
31
|
+
{ name: 'All platforms', value: 'all' }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
]);
|
|
35
|
+
target = answers.target;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Build based on target
|
|
39
|
+
try {
|
|
40
|
+
if (target === 'all') {
|
|
41
|
+
await buildWeb(config, options);
|
|
42
|
+
await buildDesktop(config, options);
|
|
43
|
+
} else if (target === 'web') {
|
|
44
|
+
await buildWeb(config, options);
|
|
45
|
+
} else if (target === 'desktop') {
|
|
46
|
+
await buildDesktop(config, options);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(chalk.red(`❌ Unknown target: ${target}`));
|
|
49
|
+
console.log(chalk.gray(' Valid targets: web, desktop, all\n'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(chalk.red(`\n❌ Build failed: ${error.message}\n`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ==================== WEB BUILD ====================
|
|
59
|
+
async function buildWeb(config, options) {
|
|
60
|
+
console.log(chalk.blue('\n📦 Building for Web...\n'));
|
|
61
|
+
|
|
62
|
+
const spinner = ora('Running build command...').start();
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execSync(config.build?.command || 'npm run build', {
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
cwd: process.cwd()
|
|
68
|
+
});
|
|
69
|
+
spinner.succeed('Web build complete!');
|
|
70
|
+
console.log(chalk.gray(` Output: ${config.build?.outDir || 'dist'}/\n`));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
spinner.fail('Web build failed');
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ==================== DESKTOP BUILD ====================
|
|
78
|
+
async function buildDesktop(config, options) {
|
|
79
|
+
const framework = options.electron ? 'electron' : (config.desktop?.framework || 'tauri');
|
|
80
|
+
|
|
81
|
+
console.log(chalk.blue(`\n🖥️ Building for Desktop (${framework})...\n`));
|
|
82
|
+
|
|
83
|
+
if (framework === 'tauri') {
|
|
84
|
+
await buildTauri(config, options);
|
|
85
|
+
} else {
|
|
86
|
+
await buildElectron(config, options);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function buildTauri(config, options) {
|
|
91
|
+
// Check if Tauri is installed
|
|
92
|
+
const tauriInstalled = await checkTauriInstalled();
|
|
93
|
+
|
|
94
|
+
if (!tauriInstalled) {
|
|
95
|
+
console.log(chalk.yellow('\n⚠️ Tauri is not installed. Setting up...\n'));
|
|
96
|
+
await setupTauri();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if Tauri is initialized in project
|
|
100
|
+
const tauriConfigPath = path.join(process.cwd(), 'src-tauri', 'tauri.conf.json');
|
|
101
|
+
if (!await fs.pathExists(tauriConfigPath)) {
|
|
102
|
+
console.log(chalk.yellow('⚠️ Tauri not initialized in project. Initializing...\n'));
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
execSync('npx tauri init', { stdio: 'inherit' });
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.log(chalk.red('\n❌ Failed to initialize Tauri.'));
|
|
108
|
+
await showTauriSetupInstructions();
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Build
|
|
114
|
+
const spinner = ora('Building with Tauri...').start();
|
|
115
|
+
try {
|
|
116
|
+
const buildCmd = options.release ? 'npx tauri build' : 'npx tauri build --debug';
|
|
117
|
+
execSync(buildCmd, { stdio: 'inherit' });
|
|
118
|
+
spinner.succeed('Tauri build complete!');
|
|
119
|
+
console.log(chalk.gray(' Output: src-tauri/target/release/\n'));
|
|
120
|
+
} catch (error) {
|
|
121
|
+
spinner.fail('Tauri build failed');
|
|
122
|
+
await showTauriSetupInstructions();
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function buildElectron(config, options) {
|
|
128
|
+
// Check if Electron builder is installed
|
|
129
|
+
const electronInstalled = await checkPackageInstalled('electron');
|
|
130
|
+
|
|
131
|
+
if (!electronInstalled) {
|
|
132
|
+
console.log(chalk.yellow('\n⚠️ Electron is not installed. Setting up...\n'));
|
|
133
|
+
|
|
134
|
+
const spinner = ora('Installing Electron dependencies...').start();
|
|
135
|
+
try {
|
|
136
|
+
execSync('npm install --save-dev electron electron-builder', { stdio: 'pipe' });
|
|
137
|
+
spinner.succeed('Electron installed!');
|
|
138
|
+
} catch (error) {
|
|
139
|
+
spinner.fail('Failed to install Electron');
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check if electron main file exists
|
|
145
|
+
const mainPath = path.join(process.cwd(), 'electron', 'main.js');
|
|
146
|
+
if (!await fs.pathExists(mainPath)) {
|
|
147
|
+
console.log(chalk.yellow('⚠️ Creating Electron configuration...\n'));
|
|
148
|
+
await createElectronConfig(config);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Build
|
|
152
|
+
const spinner = ora('Building with Electron...').start();
|
|
153
|
+
try {
|
|
154
|
+
execSync('npx electron-builder', { stdio: 'inherit' });
|
|
155
|
+
spinner.succeed('Electron build complete!');
|
|
156
|
+
console.log(chalk.gray(' Output: dist-electron/\n'));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
spinner.fail('Electron build failed');
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ==================== MOBILE BUILD ====================
|
|
164
|
+
async function buildMobile(config, options) {
|
|
165
|
+
console.log(chalk.blue('\n📱 Building for Mobile (Capacitor)...\n'));
|
|
166
|
+
|
|
167
|
+
// Check if Capacitor is installed
|
|
168
|
+
const capInstalled = await checkPackageInstalled('@capacitor/core');
|
|
169
|
+
|
|
170
|
+
if (!capInstalled) {
|
|
171
|
+
console.log(chalk.yellow('\n⚠️ Capacitor is not installed. Setting up...\n'));
|
|
172
|
+
await setupCapacitor(config);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Build web first
|
|
176
|
+
await buildWeb(config, options);
|
|
177
|
+
|
|
178
|
+
// Sync Capacitor
|
|
179
|
+
const spinner = ora('Syncing Capacitor...').start();
|
|
180
|
+
try {
|
|
181
|
+
execSync('npx cap sync', { stdio: 'pipe' });
|
|
182
|
+
spinner.succeed('Capacitor synced!');
|
|
183
|
+
} catch (error) {
|
|
184
|
+
spinner.fail('Capacitor sync failed');
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(chalk.green('\n✅ Mobile build ready!'));
|
|
189
|
+
console.log(chalk.gray(`
|
|
190
|
+
To build native apps:
|
|
191
|
+
|
|
192
|
+
Android:
|
|
193
|
+
${chalk.cyan('npx cap open android')}
|
|
194
|
+
Build in Android Studio
|
|
195
|
+
|
|
196
|
+
iOS:
|
|
197
|
+
${chalk.cyan('npx cap open ios')}
|
|
198
|
+
Build in Xcode
|
|
199
|
+
`));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ==================== SETUP FUNCTIONS ====================
|
|
203
|
+
async function checkTauriInstalled() {
|
|
204
|
+
try {
|
|
205
|
+
execSync('cargo --version', { stdio: 'pipe' });
|
|
206
|
+
return true;
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function checkPackageInstalled(packageName) {
|
|
213
|
+
try {
|
|
214
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
215
|
+
const pkg = await fs.readJson(packageJsonPath);
|
|
216
|
+
return !!(pkg.dependencies?.[packageName] || pkg.devDependencies?.[packageName]);
|
|
217
|
+
} catch {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function showTauriSetupInstructions() {
|
|
223
|
+
console.log(chalk.yellow(`
|
|
224
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
225
|
+
║ TAURI SETUP REQUIRED ║
|
|
226
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
227
|
+
|
|
228
|
+
Tauri requires Rust and platform-specific build tools.
|
|
229
|
+
|
|
230
|
+
${chalk.bold('Step 1: Install Rust')}
|
|
231
|
+
${chalk.cyan(' https://rustup.rs/')}
|
|
232
|
+
Run: ${chalk.white('curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh')}
|
|
233
|
+
Or on Windows: Download and run rustup-init.exe
|
|
234
|
+
|
|
235
|
+
${chalk.bold('Step 2: Install Visual Studio Build Tools (Windows Only)')}
|
|
236
|
+
${chalk.cyan(' https://visualstudio.microsoft.com/visual-cpp-build-tools/')}
|
|
237
|
+
|
|
238
|
+
Required components:
|
|
239
|
+
${chalk.white('• MSVC v143 - VS 2022 C++ x64/x86 build tools')}
|
|
240
|
+
${chalk.white('• Windows 10/11 SDK')}
|
|
241
|
+
${chalk.white('• C++ CMake tools for Windows')}
|
|
242
|
+
|
|
243
|
+
${chalk.bold('Step 3: Install Tauri CLI')}
|
|
244
|
+
${chalk.cyan('npm install -g @tauri-apps/cli')}
|
|
245
|
+
|
|
246
|
+
${chalk.bold('Step 4: Verify Installation')}
|
|
247
|
+
${chalk.cyan('rustc --version')}
|
|
248
|
+
${chalk.cyan('cargo --version')}
|
|
249
|
+
|
|
250
|
+
${chalk.bold('Step 5: Re-run Select Build')}
|
|
251
|
+
${chalk.cyan('slct build desktop')}
|
|
252
|
+
|
|
253
|
+
${chalk.gray('For more info: https://tauri.app/v1/guides/getting-started/prerequisites')}
|
|
254
|
+
`));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function setupTauri() {
|
|
258
|
+
await showTauriSetupInstructions();
|
|
259
|
+
|
|
260
|
+
const answers = await inquirer.prompt([
|
|
261
|
+
{
|
|
262
|
+
type: 'confirm',
|
|
263
|
+
name: 'continue',
|
|
264
|
+
message: 'Have you installed Rust and build tools?',
|
|
265
|
+
default: false
|
|
266
|
+
}
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
if (!answers.continue) {
|
|
270
|
+
console.log(chalk.yellow('\n⚠️ Please install the requirements and try again.\n'));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Install Tauri CLI
|
|
275
|
+
const spinner = ora('Installing Tauri CLI...').start();
|
|
276
|
+
try {
|
|
277
|
+
execSync('npm install --save-dev @tauri-apps/cli @tauri-apps/api', { stdio: 'pipe' });
|
|
278
|
+
spinner.succeed('Tauri CLI installed!');
|
|
279
|
+
} catch (error) {
|
|
280
|
+
spinner.fail('Failed to install Tauri CLI');
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function setupCapacitor(config) {
|
|
286
|
+
console.log(chalk.blue(`
|
|
287
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
288
|
+
║ CAPACITOR SETUP ║
|
|
289
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
290
|
+
|
|
291
|
+
Setting up Capacitor for mobile builds...
|
|
292
|
+
`));
|
|
293
|
+
|
|
294
|
+
const spinner = ora('Installing Capacitor...').start();
|
|
295
|
+
try {
|
|
296
|
+
execSync('npm install @capacitor/core @capacitor/cli', { stdio: 'pipe' });
|
|
297
|
+
spinner.succeed('Capacitor core installed!');
|
|
298
|
+
} catch (error) {
|
|
299
|
+
spinner.fail('Failed to install Capacitor');
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Initialize Capacitor
|
|
304
|
+
const spinner2 = ora('Initializing Capacitor...').start();
|
|
305
|
+
try {
|
|
306
|
+
execSync(`npx cap init "${config.name}" com.select.${config.name.replace(/-/g, '')} --web-dir dist`, {
|
|
307
|
+
stdio: 'pipe'
|
|
308
|
+
});
|
|
309
|
+
spinner2.succeed('Capacitor initialized!');
|
|
310
|
+
} catch (error) {
|
|
311
|
+
spinner2.fail('Failed to initialize Capacitor');
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Ask which platforms to add
|
|
316
|
+
const answers = await inquirer.prompt([
|
|
317
|
+
{
|
|
318
|
+
type: 'checkbox',
|
|
319
|
+
name: 'platforms',
|
|
320
|
+
message: 'Add mobile platforms:',
|
|
321
|
+
choices: [
|
|
322
|
+
{ name: 'Android', value: 'android', checked: true },
|
|
323
|
+
{ name: 'iOS', value: 'ios', checked: true }
|
|
324
|
+
]
|
|
325
|
+
}
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
for (const platform of answers.platforms) {
|
|
329
|
+
const spinner3 = ora(`Adding ${platform}...`).start();
|
|
330
|
+
try {
|
|
331
|
+
execSync(`npm install @capacitor/${platform}`, { stdio: 'pipe' });
|
|
332
|
+
execSync(`npx cap add ${platform}`, { stdio: 'pipe' });
|
|
333
|
+
spinner3.succeed(`${platform} added!`);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
spinner3.fail(`Failed to add ${platform}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.log(chalk.green('\n✅ Capacitor setup complete!\n'));
|
|
340
|
+
|
|
341
|
+
if (answers.platforms.includes('android')) {
|
|
342
|
+
console.log(chalk.gray(` Android: Install Android Studio from ${chalk.cyan('https://developer.android.com/studio')}`));
|
|
343
|
+
}
|
|
344
|
+
if (answers.platforms.includes('ios')) {
|
|
345
|
+
console.log(chalk.gray(` iOS: Requires macOS with Xcode installed`));
|
|
346
|
+
}
|
|
347
|
+
console.log('');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function createElectronConfig(config) {
|
|
351
|
+
const electronDir = path.join(process.cwd(), 'electron');
|
|
352
|
+
await fs.ensureDir(electronDir);
|
|
353
|
+
|
|
354
|
+
// Create main.js
|
|
355
|
+
const mainJs = `const { app, BrowserWindow } = require('electron');
|
|
356
|
+
const path = require('path');
|
|
357
|
+
|
|
358
|
+
function createWindow() {
|
|
359
|
+
const win = new BrowserWindow({
|
|
360
|
+
width: 1200,
|
|
361
|
+
height: 800,
|
|
362
|
+
webPreferences: {
|
|
363
|
+
nodeIntegration: false,
|
|
364
|
+
contextIsolation: true
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Load the built app
|
|
369
|
+
win.loadFile(path.join(__dirname, '../dist/index.html'));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
app.whenReady().then(createWindow);
|
|
373
|
+
|
|
374
|
+
app.on('window-all-closed', () => {
|
|
375
|
+
if (process.platform !== 'darwin') app.quit();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
app.on('activate', () => {
|
|
379
|
+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
380
|
+
});
|
|
381
|
+
`;
|
|
382
|
+
|
|
383
|
+
await fs.writeFile(path.join(electronDir, 'main.js'), mainJs);
|
|
384
|
+
|
|
385
|
+
// Update package.json
|
|
386
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
387
|
+
const pkg = await fs.readJson(pkgPath);
|
|
388
|
+
pkg.main = 'electron/main.js';
|
|
389
|
+
pkg.build = {
|
|
390
|
+
appId: `com.select.${config.name.replace(/-/g, '')}`,
|
|
391
|
+
productName: config.name,
|
|
392
|
+
directories: {
|
|
393
|
+
output: 'dist-electron'
|
|
394
|
+
},
|
|
395
|
+
files: [
|
|
396
|
+
'dist/**/*',
|
|
397
|
+
'electron/**/*'
|
|
398
|
+
]
|
|
399
|
+
};
|
|
400
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
401
|
+
|
|
402
|
+
console.log(chalk.green(' Electron configuration created!\n'));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
module.exports = build;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
const axios = require('axios');
|
|
7
|
+
const FormData = require('form-data');
|
|
8
|
+
const archiver = require('archiver');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { glob } = require('glob'); // Using glob pattern matching if available, or manual find
|
|
11
|
+
|
|
12
|
+
async function deploy(options) {
|
|
13
|
+
console.log(chalk.bold('\n🚀 Deploy to Select\n'));
|
|
14
|
+
|
|
15
|
+
// Load config
|
|
16
|
+
const configPath = path.resolve(process.cwd(), 'select.json');
|
|
17
|
+
if (!await fs.pathExists(configPath)) {
|
|
18
|
+
console.log(chalk.red('❌ No select.json found. Run "slct init" first.\n'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const config = await fs.readJson(configPath);
|
|
22
|
+
const API_URL = process.env.SELECT_API_URL || 'http://localhost:3001';
|
|
23
|
+
|
|
24
|
+
// Collect deploy information
|
|
25
|
+
const answers = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'name',
|
|
29
|
+
message: 'App name:',
|
|
30
|
+
default: config.name
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'tagline',
|
|
35
|
+
message: 'Tagline:',
|
|
36
|
+
default: config.description || 'An awesome app'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'list',
|
|
40
|
+
name: 'category',
|
|
41
|
+
message: 'Category:',
|
|
42
|
+
choices: ['Productivity', 'Finance', 'Developer Tools', 'Education', 'Entertainment', 'Utilities', 'Other'],
|
|
43
|
+
default: config.category || 'Productivity'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'password',
|
|
47
|
+
name: 'deployKey',
|
|
48
|
+
message: 'Deploy Key:',
|
|
49
|
+
mask: '*'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'confirm',
|
|
53
|
+
name: 'confirm',
|
|
54
|
+
message: `Deploy to ${API_URL}?`,
|
|
55
|
+
default: true
|
|
56
|
+
}
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
if (!answers.confirm) return;
|
|
60
|
+
|
|
61
|
+
const spinner = ora('Looking for build artifacts...').start();
|
|
62
|
+
|
|
63
|
+
// Prepare FormData
|
|
64
|
+
const form = new FormData();
|
|
65
|
+
const manifest = {
|
|
66
|
+
...config,
|
|
67
|
+
name: answers.name,
|
|
68
|
+
tagline: answers.tagline,
|
|
69
|
+
category: answers.category.toLowerCase().replace(' ', '-'),
|
|
70
|
+
platforms: []
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Find artifacts (Tauri logic)
|
|
74
|
+
const tauriTarget = path.join(process.cwd(), 'src-tauri', 'target', 'release', 'bundle');
|
|
75
|
+
let foundArtifacts = 0;
|
|
76
|
+
|
|
77
|
+
// Windows
|
|
78
|
+
if (config.platforms.includes('windows')) {
|
|
79
|
+
const winPath = path.join(tauriTarget, 'nsis');
|
|
80
|
+
if (await fs.pathExists(winPath)) {
|
|
81
|
+
const files = await fs.readdir(winPath);
|
|
82
|
+
const exe = files.find(f => f.endsWith('.exe'));
|
|
83
|
+
if (exe) {
|
|
84
|
+
form.append('windows', fs.createReadStream(path.join(winPath, exe)));
|
|
85
|
+
manifest.platforms.push('windows');
|
|
86
|
+
foundArtifacts++;
|
|
87
|
+
spinner.text = `Found Windows binary: ${exe}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// macOS
|
|
93
|
+
if (config.platforms.includes('macos')) {
|
|
94
|
+
const macPath = path.join(tauriTarget, 'dmg');
|
|
95
|
+
if (await fs.pathExists(macPath)) {
|
|
96
|
+
const files = await fs.readdir(macPath);
|
|
97
|
+
const dmg = files.find(f => f.endsWith('.dmg'));
|
|
98
|
+
if (dmg) {
|
|
99
|
+
form.append('macos', fs.createReadStream(path.join(macPath, dmg)));
|
|
100
|
+
manifest.platforms.push('macos');
|
|
101
|
+
foundArtifacts++;
|
|
102
|
+
spinner.text = `Found macOS binary: ${dmg}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Linux
|
|
108
|
+
if (config.platforms.includes('linux')) {
|
|
109
|
+
const linuxPath = path.join(tauriTarget, 'appimage');
|
|
110
|
+
if (await fs.pathExists(linuxPath)) {
|
|
111
|
+
const files = await fs.readdir(linuxPath);
|
|
112
|
+
const appImg = files.find(f => f.endsWith('.AppImage'));
|
|
113
|
+
if (appImg) {
|
|
114
|
+
form.append('linux', fs.createReadStream(path.join(linuxPath, appImg)));
|
|
115
|
+
manifest.platforms.push('linux');
|
|
116
|
+
foundArtifacts++;
|
|
117
|
+
spinner.text = `Found Linux binary: ${appImg}`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Web App
|
|
123
|
+
if (config.platforms.includes('web')) {
|
|
124
|
+
const buildDir = path.resolve(process.cwd(), config.build?.outDir || 'dist');
|
|
125
|
+
if (await fs.pathExists(buildDir)) {
|
|
126
|
+
const zipPath = path.join(os.tmpdir(), `web-build-${Date.now()}.zip`);
|
|
127
|
+
const output = fs.createWriteStream(zipPath);
|
|
128
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
129
|
+
|
|
130
|
+
spinner.text = 'Zipping web build...';
|
|
131
|
+
|
|
132
|
+
await new Promise((resolve, reject) => {
|
|
133
|
+
output.on('close', resolve);
|
|
134
|
+
archive.on('error', reject);
|
|
135
|
+
archive.pipe(output);
|
|
136
|
+
archive.directory(buildDir, false);
|
|
137
|
+
archive.finalize();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
form.append('web', fs.createReadStream(zipPath));
|
|
141
|
+
manifest.platforms.push('web');
|
|
142
|
+
foundArtifacts++;
|
|
143
|
+
} else {
|
|
144
|
+
spinner.warn('Web build directory not found (skipping web upload)');
|
|
145
|
+
}
|
|
146
|
+
} else if (foundArtifacts === 0) { // Only error if NO artifacts found and web not requested
|
|
147
|
+
spinner.fail('No build artifacts found!');
|
|
148
|
+
console.log(chalk.yellow('\nTip: Run "slct build desktop" or "slct build web" first.\n'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
form.append('manifest', JSON.stringify(manifest));
|
|
153
|
+
|
|
154
|
+
spinner.text = 'Uploading to Select...';
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const response = await axios.post(`${API_URL}/api/deploy`, form, {
|
|
158
|
+
headers: {
|
|
159
|
+
...form.getHeaders(),
|
|
160
|
+
'Authorization': `Bearer ${answers.deployKey}`
|
|
161
|
+
},
|
|
162
|
+
maxContentLength: Infinity,
|
|
163
|
+
maxBodyLength: Infinity
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
spinner.succeed('Deployment successful!');
|
|
167
|
+
console.log(chalk.green(`
|
|
168
|
+
╔════════════════════════════════════════════════════════════════╗
|
|
169
|
+
║ ✅ DEPLOYED SUCCESSFULLY ║
|
|
170
|
+
╚════════════════════════════════════════════════════════════════╝
|
|
171
|
+
|
|
172
|
+
App: ${response.data.slug}
|
|
173
|
+
Version: ${response.data.version}
|
|
174
|
+
URL: ${API_URL}/apps/${response.data.slug}
|
|
175
|
+
`));
|
|
176
|
+
|
|
177
|
+
} catch (error) {
|
|
178
|
+
spinner.fail('Deployment failed');
|
|
179
|
+
if (error.response) {
|
|
180
|
+
console.error(chalk.red(`Server Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`));
|
|
181
|
+
} else {
|
|
182
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = deploy;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
|
|
6
|
+
async function dev(options) {
|
|
7
|
+
console.log(chalk.bold('\n🔧 Starting Development Server\n'));
|
|
8
|
+
|
|
9
|
+
// Load config
|
|
10
|
+
const configPath = path.resolve(process.cwd(), 'select.json');
|
|
11
|
+
let devCommand = 'npm run dev';
|
|
12
|
+
|
|
13
|
+
if (await fs.pathExists(configPath)) {
|
|
14
|
+
const config = await fs.readJson(configPath);
|
|
15
|
+
devCommand = config.build?.devCommand || devCommand;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(chalk.gray(` Running: ${devCommand}\n`));
|
|
19
|
+
|
|
20
|
+
// Parse command
|
|
21
|
+
const parts = devCommand.split(' ');
|
|
22
|
+
const cmd = parts[0];
|
|
23
|
+
const args = parts.slice(1);
|
|
24
|
+
|
|
25
|
+
// Add port if specified
|
|
26
|
+
if (options.port && options.port !== '5173') {
|
|
27
|
+
args.push('--port', options.port);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Spawn dev server
|
|
31
|
+
const child = spawn(cmd, args, {
|
|
32
|
+
stdio: 'inherit',
|
|
33
|
+
shell: true,
|
|
34
|
+
cwd: process.cwd()
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
child.on('error', (error) => {
|
|
38
|
+
console.error(chalk.red(`\n❌ Failed to start dev server: ${error.message}\n`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
child.on('close', (code) => {
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
console.log(chalk.yellow(`\n⚠️ Dev server exited with code ${code}\n`));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Handle Ctrl+C
|
|
49
|
+
process.on('SIGINT', () => {
|
|
50
|
+
child.kill('SIGINT');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = dev;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const ora = require('ora');
|
|
6
|
+
|
|
7
|
+
async function init(name, options) {
|
|
8
|
+
console.log(chalk.bold('\n📦 Initialize Select Project\n'));
|
|
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);
|
|
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
|
+
}
|
|
122
|
+
|
|
123
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
|
124
|
+
|
|
125
|
+
// Create basic index.html
|
|
126
|
+
const indexHtml = `<!DOCTYPE html>
|
|
127
|
+
<html lang="en">
|
|
128
|
+
<head>
|
|
129
|
+
<meta charset="UTF-8">
|
|
130
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
131
|
+
<title>${name}</title>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div id="app"></div>
|
|
135
|
+
<script type="module" src="/src/main.${answers.template === 'vanilla' ? 'js' : answers.template === 'react' ? 'jsx' : 'js'}"></script>
|
|
136
|
+
</body>
|
|
137
|
+
</html>`;
|
|
138
|
+
|
|
139
|
+
await fs.writeFile(path.join(projectDir, 'index.html'), indexHtml);
|
|
140
|
+
|
|
141
|
+
// Create src directory with basic file
|
|
142
|
+
await fs.ensureDir(path.join(projectDir, 'src'));
|
|
143
|
+
|
|
144
|
+
// Create GitHub Actions workflow
|
|
145
|
+
await createGithubWorkflow(projectDir, answers.desktopFramework || 'tauri', name);
|
|
146
|
+
|
|
147
|
+
if (answers.template === 'react') {
|
|
148
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.jsx'), `import React from 'react'
|
|
149
|
+
import ReactDOM from 'react-dom/client'
|
|
150
|
+
|
|
151
|
+
function App() {
|
|
152
|
+
return (
|
|
153
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
154
|
+
<h1>Welcome to ${name}</h1>
|
|
155
|
+
<p>Built with Select CLI</p>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
ReactDOM.createRoot(document.getElementById('app')).render(<App />)
|
|
161
|
+
`);
|
|
162
|
+
} else {
|
|
163
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.js'), `document.getElementById('app').innerHTML = \`
|
|
164
|
+
<div style="padding: 2rem; font-family: system-ui;">
|
|
165
|
+
<h1>Welcome to ${name}</h1>
|
|
166
|
+
<p>Built with Select CLI</p>
|
|
167
|
+
</div>
|
|
168
|
+
\`
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
spinner.succeed('Project created successfully!');
|
|
173
|
+
|
|
174
|
+
console.log(chalk.green(`
|
|
175
|
+
✅ Project "${name}" created!
|
|
176
|
+
|
|
177
|
+
Next steps:
|
|
178
|
+
${chalk.cyan(`cd ${name}`)}
|
|
179
|
+
${chalk.cyan('npm install')}
|
|
180
|
+
${chalk.cyan('slct build')}
|
|
181
|
+
|
|
182
|
+
`));
|
|
183
|
+
|
|
184
|
+
// Check for required dependencies
|
|
185
|
+
await checkDependencies(answers);
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
spinner.fail('Failed to create project');
|
|
189
|
+
console.error(chalk.red(error.message));
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function checkDependencies(answers) {
|
|
195
|
+
const hasDesktop = answers.platforms?.some(p => ['windows', 'macos', 'linux'].includes(p));
|
|
196
|
+
|
|
197
|
+
if (hasDesktop && answers.desktopFramework === 'tauri') {
|
|
198
|
+
console.log(chalk.yellow('\n⚠️ Tauri Requirements:'));
|
|
199
|
+
console.log(chalk.gray(' Run "slct build desktop" for detailed setup instructions.\n'));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function createGithubWorkflow(projectDir, framework, name) {
|
|
204
|
+
const workflowDir = path.join(projectDir, '.github', 'workflows');
|
|
205
|
+
await fs.ensureDir(workflowDir);
|
|
206
|
+
|
|
207
|
+
let workflowContent = '';
|
|
208
|
+
|
|
209
|
+
if (framework === 'tauri') {
|
|
210
|
+
workflowContent = `name: Build Apps
|
|
211
|
+
on:
|
|
212
|
+
push:
|
|
213
|
+
branches: [ main ]
|
|
214
|
+
pull_request:
|
|
215
|
+
branches: [ main ]
|
|
216
|
+
|
|
217
|
+
jobs:
|
|
218
|
+
build-tauri:
|
|
219
|
+
strategy:
|
|
220
|
+
fail-fast: false
|
|
221
|
+
matrix:
|
|
222
|
+
platform: [macos-latest, ubuntu-22.04, windows-latest]
|
|
223
|
+
|
|
224
|
+
runs-on: \${{ matrix.platform }}
|
|
225
|
+
steps:
|
|
226
|
+
- uses: actions/checkout@v4
|
|
227
|
+
|
|
228
|
+
- name: setup node
|
|
229
|
+
uses: actions/setup-node@v4
|
|
230
|
+
with:
|
|
231
|
+
node-version: lts/*
|
|
232
|
+
|
|
233
|
+
- name: install Rust stable
|
|
234
|
+
uses: dtolnay/rust-toolchain@stable
|
|
235
|
+
|
|
236
|
+
- name: install dependencies (ubuntu only)
|
|
237
|
+
if: matrix.platform == 'ubuntu-22.04'
|
|
238
|
+
run: |
|
|
239
|
+
sudo apt-get update
|
|
240
|
+
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
|
241
|
+
|
|
242
|
+
- name: install frontend dependencies
|
|
243
|
+
run: npm install
|
|
244
|
+
|
|
245
|
+
- name: build tauri
|
|
246
|
+
uses: tauri-apps/tauri-action@v0
|
|
247
|
+
env:
|
|
248
|
+
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
249
|
+
with:
|
|
250
|
+
tagName: app-v__VERSION__ # the action automatically replaces \`__VERSION__\` with the app version
|
|
251
|
+
releaseName: 'App v__VERSION__'
|
|
252
|
+
releaseBody: 'See the assets to download this version and install.'
|
|
253
|
+
releaseDraft: true
|
|
254
|
+
prerelease: false
|
|
255
|
+
`;
|
|
256
|
+
} else {
|
|
257
|
+
// Electron Workflow
|
|
258
|
+
workflowContent = `name: Build Apps
|
|
259
|
+
on:
|
|
260
|
+
push:
|
|
261
|
+
branches: [ main ]
|
|
262
|
+
pull_request:
|
|
263
|
+
branches: [ main ]
|
|
264
|
+
|
|
265
|
+
jobs:
|
|
266
|
+
build-electron:
|
|
267
|
+
runs-on: \${{ matrix.os }}
|
|
268
|
+
|
|
269
|
+
strategy:
|
|
270
|
+
matrix:
|
|
271
|
+
os: [macos-latest, ubuntu-latest, windows-latest]
|
|
272
|
+
|
|
273
|
+
steps:
|
|
274
|
+
- name: Check out Git repository
|
|
275
|
+
uses: actions/checkout@v4
|
|
276
|
+
|
|
277
|
+
- name: Install Node.js
|
|
278
|
+
uses: actions/setup-node@v4
|
|
279
|
+
with:
|
|
280
|
+
node-version: lts/*
|
|
281
|
+
|
|
282
|
+
- name: Install Dependencies
|
|
283
|
+
run: npm install
|
|
284
|
+
|
|
285
|
+
- name: Build Electron App
|
|
286
|
+
run: npx electron-builder --publish always
|
|
287
|
+
env:
|
|
288
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
289
|
+
`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await fs.writeFile(path.join(workflowDir, 'build.yml'), workflowContent);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = init;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
|
|
6
|
+
async function select() {
|
|
7
|
+
console.log(chalk.bold('\n🎯 Select Target Platforms\n'));
|
|
8
|
+
|
|
9
|
+
// Check if select.json exists
|
|
10
|
+
const configPath = path.resolve(process.cwd(), 'select.json');
|
|
11
|
+
|
|
12
|
+
if (!await fs.pathExists(configPath)) {
|
|
13
|
+
console.log(chalk.red('❌ No select.json found. Run "slct init" first.\n'));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const config = await fs.readJson(configPath);
|
|
18
|
+
|
|
19
|
+
const answers = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'checkbox',
|
|
22
|
+
name: 'platforms',
|
|
23
|
+
message: 'Select platforms to build for:',
|
|
24
|
+
choices: [
|
|
25
|
+
{ name: 'Web', value: 'web', checked: config.platforms?.includes('web') },
|
|
26
|
+
{ name: 'Windows', value: 'windows', checked: config.platforms?.includes('windows') },
|
|
27
|
+
{ name: 'macOS', value: 'macos', checked: config.platforms?.includes('macos') },
|
|
28
|
+
{ name: 'Linux', value: 'linux', checked: config.platforms?.includes('linux') }
|
|
29
|
+
],
|
|
30
|
+
validate: (input) => input.length > 0 || 'Select at least one platform'
|
|
31
|
+
}
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
const hasDesktop = answers.platforms.some(p => ['windows', 'macos', 'linux'].includes(p));
|
|
35
|
+
|
|
36
|
+
if (hasDesktop) {
|
|
37
|
+
const desktopAnswer = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: 'list',
|
|
40
|
+
name: 'desktopFramework',
|
|
41
|
+
message: 'Desktop framework:',
|
|
42
|
+
choices: [
|
|
43
|
+
{ name: 'Tauri (Recommended - smaller, faster)', value: 'tauri' },
|
|
44
|
+
{ name: 'Electron (Larger, more compatible)', value: 'electron' }
|
|
45
|
+
],
|
|
46
|
+
default: config.desktop?.framework || 'tauri'
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
config.desktop = { framework: desktopAnswer.desktopFramework };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Update config
|
|
53
|
+
config.platforms = answers.platforms;
|
|
54
|
+
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
55
|
+
|
|
56
|
+
console.log(chalk.green('\n✅ Configuration saved!'));
|
|
57
|
+
console.log(chalk.gray(` Platforms: ${answers.platforms.join(', ')}`));
|
|
58
|
+
if (hasDesktop) {
|
|
59
|
+
console.log(chalk.gray(` Desktop: ${config.desktop.framework}`));
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.cyan('\n Run "slct build" to build your app.\n'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = select;
|
package/lib/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iselect/select",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Build and deploy apps for web, desktop, and mobile from a single codebase",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"slct": "bin/select.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Tests coming soon\""
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"select",
|
|
14
|
+
"cli",
|
|
15
|
+
"tauri",
|
|
16
|
+
"electron",
|
|
17
|
+
"capacitor",
|
|
18
|
+
"cross-platform",
|
|
19
|
+
"build",
|
|
20
|
+
"deploy"
|
|
21
|
+
],
|
|
22
|
+
"author": "Select",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"archiver": "^7.0.1",
|
|
26
|
+
"axios": "^1.13.2",
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"commander": "^11.1.0",
|
|
29
|
+
"form-data": "^4.0.5",
|
|
30
|
+
"fs-extra": "^11.2.0",
|
|
31
|
+
"inquirer": "^8.2.6",
|
|
32
|
+
"ora": "^5.4.1",
|
|
33
|
+
"semver": "^7.5.4"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=16.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|