@iselect/select 1.0.2 → 1.0.4
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/lib/commands/build.js +35 -0
- package/lib/commands/deploy.js +100 -20
- package/lib/commands/init.js +140 -65
- package/package.json +1 -1
package/lib/commands/build.js
CHANGED
|
@@ -101,6 +101,10 @@ const DEPENDENCY_SIGNATURES = {
|
|
|
101
101
|
'gatsby': { name: 'Gatsby', build: 'npm run build', output: 'public', icon: '💜' },
|
|
102
102
|
'@angular/core': { name: 'Angular', build: 'npm run build', output: 'dist', icon: '🅰️' },
|
|
103
103
|
'react-scripts': { name: 'Create React App', build: 'npm run build', output: 'build', icon: '⚛️' },
|
|
104
|
+
'vite': { name: 'Vite', build: 'npm run build', output: 'dist', icon: '⚡' },
|
|
105
|
+
'astro': { name: 'Astro', build: 'npm run build', output: 'dist', icon: '🚀' },
|
|
106
|
+
'svelte': { name: 'Svelte', build: 'npm run build', output: 'build', icon: '🔶' },
|
|
107
|
+
'nuxt': { name: 'Nuxt', build: 'npm run build', output: '.nuxt', icon: '💚' },
|
|
104
108
|
};
|
|
105
109
|
|
|
106
110
|
// ==================== FRAMEWORK DETECTION ====================
|
|
@@ -289,6 +293,37 @@ async function buildWeb(config, options) {
|
|
|
289
293
|
const projectPath = process.cwd();
|
|
290
294
|
const outputDir = path.resolve(projectPath, config.build?.outDir || 'dist');
|
|
291
295
|
|
|
296
|
+
// Check if npm install has been run (for projects with package.json)
|
|
297
|
+
const hasPackageJson = await fs.pathExists(path.join(projectPath, 'package.json'));
|
|
298
|
+
const hasNodeModules = await fs.pathExists(path.join(projectPath, 'node_modules'));
|
|
299
|
+
|
|
300
|
+
if (hasPackageJson && !hasNodeModules) {
|
|
301
|
+
console.log(chalk.yellow('⚠️ Dependencies not installed'));
|
|
302
|
+
console.log(chalk.gray(' Run: npm install\n'));
|
|
303
|
+
|
|
304
|
+
const answers = await inquirer.prompt([{
|
|
305
|
+
type: 'confirm',
|
|
306
|
+
name: 'install',
|
|
307
|
+
message: 'Run npm install now?',
|
|
308
|
+
default: true
|
|
309
|
+
}]);
|
|
310
|
+
|
|
311
|
+
if (answers.install) {
|
|
312
|
+
const spinner = ora('Installing dependencies...').start();
|
|
313
|
+
try {
|
|
314
|
+
execSync('npm install', { cwd: projectPath, stdio: 'pipe' });
|
|
315
|
+
spinner.succeed('Dependencies installed!');
|
|
316
|
+
console.log('');
|
|
317
|
+
} catch (error) {
|
|
318
|
+
spinner.fail('Failed to install dependencies');
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
console.log(chalk.gray(' Skipping build.\n'));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
292
327
|
// Check for --static flag
|
|
293
328
|
if (options.static) {
|
|
294
329
|
console.log(chalk.gray(' Mode: Static (--static flag)'));
|
package/lib/commands/deploy.js
CHANGED
|
@@ -121,27 +121,107 @@ async function deploy(options) {
|
|
|
121
121
|
|
|
122
122
|
// Web App
|
|
123
123
|
if (config.platforms.includes('web')) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
124
|
+
const isStaticSite = !config.build?.command;
|
|
125
|
+
|
|
126
|
+
if (isStaticSite) {
|
|
127
|
+
// Static site - upload files directly (no zipping)
|
|
128
|
+
spinner.text = 'Preparing static files for upload...';
|
|
129
|
+
|
|
130
|
+
// For static sites, the source IS the dist
|
|
131
|
+
const sourceDir = process.cwd();
|
|
132
|
+
const staticFiles = ['index.html', 'style.css', 'app.js', 'script.js', 'main.js'];
|
|
133
|
+
const staticFolders = ['assets', 'css', 'js', 'images', 'fonts'];
|
|
134
|
+
|
|
135
|
+
const filesToUpload = [];
|
|
136
|
+
|
|
137
|
+
// Collect individual files
|
|
138
|
+
for (const file of staticFiles) {
|
|
139
|
+
const filePath = path.join(sourceDir, file);
|
|
140
|
+
if (await fs.pathExists(filePath)) {
|
|
141
|
+
const content = await fs.readFile(filePath);
|
|
142
|
+
const hash = require('crypto').createHash('md5').update(content).digest('hex').slice(0, 8);
|
|
143
|
+
filesToUpload.push({ path: file, content, hash });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Collect files from folders
|
|
148
|
+
for (const folder of staticFolders) {
|
|
149
|
+
const folderPath = path.join(sourceDir, folder);
|
|
150
|
+
if (await fs.pathExists(folderPath)) {
|
|
151
|
+
const files = await fs.readdir(folderPath, { recursive: true });
|
|
152
|
+
for (const file of files) {
|
|
153
|
+
const fullPath = path.join(folderPath, file);
|
|
154
|
+
const stat = await fs.stat(fullPath);
|
|
155
|
+
if (stat.isFile()) {
|
|
156
|
+
const content = await fs.readFile(fullPath);
|
|
157
|
+
const hash = require('crypto').createHash('md5').update(content).digest('hex').slice(0, 8);
|
|
158
|
+
filesToUpload.push({ path: `${folder}/${file}`, content, hash });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (filesToUpload.length === 0) {
|
|
165
|
+
spinner.warn('No static files found to deploy');
|
|
166
|
+
} else {
|
|
167
|
+
spinner.text = `Found ${filesToUpload.length} files to upload...`;
|
|
168
|
+
|
|
169
|
+
// Create a manifest of files with hashes
|
|
170
|
+
manifest.staticFiles = filesToUpload.map(f => ({ path: f.path, hash: f.hash }));
|
|
171
|
+
|
|
172
|
+
// For now, still use zip but with the static files only
|
|
173
|
+
// TODO: Switch to direct R2 upload when backend supports it
|
|
174
|
+
const zipPath = path.join(os.tmpdir(), `static-build-${Date.now()}.zip`);
|
|
175
|
+
const output = fs.createWriteStream(zipPath);
|
|
176
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
177
|
+
|
|
178
|
+
spinner.text = 'Creating deployment package...';
|
|
179
|
+
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
output.on('close', resolve);
|
|
182
|
+
archive.on('error', reject);
|
|
183
|
+
archive.pipe(output);
|
|
184
|
+
for (const file of filesToUpload) {
|
|
185
|
+
archive.append(file.content, { name: file.path });
|
|
186
|
+
}
|
|
187
|
+
archive.finalize();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
form.append('web', fs.createReadStream(zipPath));
|
|
191
|
+
manifest.platforms.push('web');
|
|
192
|
+
manifest.deployMode = 'static';
|
|
193
|
+
foundArtifacts++;
|
|
194
|
+
|
|
195
|
+
// Log file hashes for potential future deduplication
|
|
196
|
+
console.log(chalk.gray(`\n Files to deploy:`));
|
|
197
|
+
filesToUpload.forEach(f => {
|
|
198
|
+
console.log(chalk.gray(` ${f.hash} ${f.path}`));
|
|
199
|
+
});
|
|
200
|
+
}
|
|
143
201
|
} else {
|
|
144
|
-
|
|
202
|
+
// Bundled project - use dist directory
|
|
203
|
+
const buildDir = path.resolve(process.cwd(), config.build?.outDir || 'dist');
|
|
204
|
+
if (await fs.pathExists(buildDir)) {
|
|
205
|
+
const zipPath = path.join(os.tmpdir(), `web-build-${Date.now()}.zip`);
|
|
206
|
+
const output = fs.createWriteStream(zipPath);
|
|
207
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
208
|
+
|
|
209
|
+
spinner.text = 'Zipping web build...';
|
|
210
|
+
|
|
211
|
+
await new Promise((resolve, reject) => {
|
|
212
|
+
output.on('close', resolve);
|
|
213
|
+
archive.on('error', reject);
|
|
214
|
+
archive.pipe(output);
|
|
215
|
+
archive.directory(buildDir, false);
|
|
216
|
+
archive.finalize();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
form.append('web', fs.createReadStream(zipPath));
|
|
220
|
+
manifest.platforms.push('web');
|
|
221
|
+
foundArtifacts++;
|
|
222
|
+
} else {
|
|
223
|
+
spinner.warn('Web build directory not found (skipping web upload)');
|
|
224
|
+
}
|
|
145
225
|
}
|
|
146
226
|
} else if (foundArtifacts === 0) { // Only error if NO artifacts found and web not requested
|
|
147
227
|
spinner.fail('No build artifacts found!');
|
package/lib/commands/init.js
CHANGED
|
@@ -41,7 +41,8 @@ async function init(name, options) {
|
|
|
41
41
|
choices: [
|
|
42
42
|
{ name: 'React + Vite', value: 'react' },
|
|
43
43
|
{ name: 'Vue + Vite', value: 'vue' },
|
|
44
|
-
{ name: 'Vanilla
|
|
44
|
+
{ name: 'Vanilla + Vite', value: 'vanilla' },
|
|
45
|
+
{ name: 'Pure Static (no bundler)', value: 'static' }
|
|
45
46
|
],
|
|
46
47
|
default: options.template
|
|
47
48
|
},
|
|
@@ -82,86 +83,76 @@ async function init(name, options) {
|
|
|
82
83
|
platforms: answers.platforms,
|
|
83
84
|
desktop: {
|
|
84
85
|
framework: answers.desktopFramework || 'tauri'
|
|
85
|
-
}
|
|
86
|
-
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Add build config only for non-static templates
|
|
90
|
+
if (answers.template !== 'static') {
|
|
91
|
+
selectConfig.build = {
|
|
87
92
|
command: 'npm run build',
|
|
88
93
|
devCommand: 'npm run dev',
|
|
89
94
|
outDir: 'dist'
|
|
90
|
-
}
|
|
91
|
-
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
92
97
|
|
|
93
98
|
await fs.writeJson(path.join(projectDir, 'select.json'), selectConfig, { spaces: 2 });
|
|
94
99
|
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
// Only create package.json for bundled templates (not static)
|
|
101
|
+
if (answers.template !== 'static') {
|
|
102
|
+
const packageJson = {
|
|
103
|
+
name: name,
|
|
104
|
+
version: '1.0.0',
|
|
105
|
+
private: true,
|
|
106
|
+
scripts: {
|
|
107
|
+
dev: 'vite',
|
|
108
|
+
build: 'vite build',
|
|
109
|
+
preview: 'vite preview',
|
|
110
|
+
'slct:build': 'slct build all',
|
|
111
|
+
'slct:deploy': 'slct deploy'
|
|
112
|
+
},
|
|
113
|
+
dependencies: {},
|
|
114
|
+
devDependencies: {
|
|
115
|
+
'vite': '^5.0.0'
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// Add template-specific dependencies
|
|
120
|
+
if (answers.template === 'react') {
|
|
121
|
+
packageJson.dependencies['react'] = '^18.2.0';
|
|
122
|
+
packageJson.dependencies['react-dom'] = '^18.2.0';
|
|
123
|
+
packageJson.devDependencies['@vitejs/plugin-react'] = '^4.2.0';
|
|
124
|
+
} else if (answers.template === 'vue') {
|
|
125
|
+
packageJson.dependencies['vue'] = '^3.4.0';
|
|
126
|
+
packageJson.devDependencies['@vitejs/plugin-vue'] = '^4.5.0';
|
|
110
127
|
}
|
|
111
|
-
};
|
|
112
128
|
|
|
113
|
-
|
|
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';
|
|
129
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
|
121
130
|
}
|
|
122
131
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
132
|
+
// Handle template-specific file generation
|
|
133
|
+
if (answers.template === 'static') {
|
|
134
|
+
// Pure static template - no bundler, files in root
|
|
135
|
+
await fs.writeFile(path.join(projectDir, 'index.html'), `<!DOCTYPE html>
|
|
127
136
|
<html lang="en">
|
|
128
137
|
<head>
|
|
129
138
|
<meta charset="UTF-8">
|
|
130
139
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
131
140
|
<title>${name}</title>
|
|
141
|
+
<link rel="stylesheet" href="style.css">
|
|
132
142
|
</head>
|
|
133
143
|
<body>
|
|
134
|
-
<div id="app"
|
|
135
|
-
|
|
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
|
-
import './style.css'
|
|
151
|
-
|
|
152
|
-
function App() {
|
|
153
|
-
return (
|
|
154
|
-
<div className="app">
|
|
144
|
+
<div id="app">
|
|
145
|
+
<div class="container">
|
|
155
146
|
<h1>Welcome to ${name}</h1>
|
|
156
147
|
<p>Built with Select CLI</p>
|
|
148
|
+
<button id="counter-btn">Count: 0</button>
|
|
157
149
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
</div>
|
|
151
|
+
<script src="app.js"></script>
|
|
152
|
+
</body>
|
|
153
|
+
</html>`);
|
|
160
154
|
|
|
161
|
-
|
|
162
|
-
`);
|
|
163
|
-
// Create React style.css
|
|
164
|
-
await fs.writeFile(path.join(projectDir, 'src', 'style.css'), `* {
|
|
155
|
+
await fs.writeFile(path.join(projectDir, 'style.css'), `* {
|
|
165
156
|
margin: 0;
|
|
166
157
|
padding: 0;
|
|
167
158
|
box-sizing: border-box;
|
|
@@ -176,7 +167,7 @@ body {
|
|
|
176
167
|
justify-content: center;
|
|
177
168
|
}
|
|
178
169
|
|
|
179
|
-
.
|
|
170
|
+
.container {
|
|
180
171
|
background: white;
|
|
181
172
|
padding: 3rem 4rem;
|
|
182
173
|
border-radius: 1rem;
|
|
@@ -191,11 +182,75 @@ h1 {
|
|
|
191
182
|
|
|
192
183
|
p {
|
|
193
184
|
color: #718096;
|
|
185
|
+
margin-bottom: 1.5rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
button {
|
|
189
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
190
|
+
color: white;
|
|
191
|
+
border: none;
|
|
192
|
+
padding: 0.75rem 2rem;
|
|
193
|
+
font-size: 1rem;
|
|
194
|
+
border-radius: 0.5rem;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
194
197
|
}
|
|
198
|
+
|
|
199
|
+
button:hover {
|
|
200
|
+
transform: translateY(-2px);
|
|
201
|
+
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
|
202
|
+
}
|
|
203
|
+
`);
|
|
204
|
+
|
|
205
|
+
await fs.writeFile(path.join(projectDir, 'app.js'), `// Simple counter example
|
|
206
|
+
let count = 0;
|
|
207
|
+
const button = document.getElementById('counter-btn');
|
|
208
|
+
|
|
209
|
+
button.addEventListener('click', () => {
|
|
210
|
+
count++;
|
|
211
|
+
button.textContent = 'Count: ' + count;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
console.log('${name} loaded!');
|
|
195
215
|
`);
|
|
216
|
+
|
|
196
217
|
} else {
|
|
197
|
-
//
|
|
198
|
-
|
|
218
|
+
// Vite-based templates (react, vue, vanilla)
|
|
219
|
+
const indexHtml = `<!DOCTYPE html>
|
|
220
|
+
<html lang="en">
|
|
221
|
+
<head>
|
|
222
|
+
<meta charset="UTF-8">
|
|
223
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
224
|
+
<title>${name}</title>
|
|
225
|
+
</head>
|
|
226
|
+
<body>
|
|
227
|
+
<div id="app"></div>
|
|
228
|
+
<script type="module" src="/src/main.${answers.template === 'react' ? 'jsx' : 'js'}"></script>
|
|
229
|
+
</body>
|
|
230
|
+
</html>`;
|
|
231
|
+
|
|
232
|
+
await fs.writeFile(path.join(projectDir, 'index.html'), indexHtml);
|
|
233
|
+
await fs.ensureDir(path.join(projectDir, 'src'));
|
|
234
|
+
|
|
235
|
+
if (answers.template === 'react') {
|
|
236
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.jsx'), `import React from 'react'
|
|
237
|
+
import ReactDOM from 'react-dom/client'
|
|
238
|
+
import './style.css'
|
|
239
|
+
|
|
240
|
+
function App() {
|
|
241
|
+
return (
|
|
242
|
+
<div className="app">
|
|
243
|
+
<h1>Welcome to ${name}</h1>
|
|
244
|
+
<p>Built with Select CLI</p>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
ReactDOM.createRoot(document.getElementById('app')).render(<App />)
|
|
250
|
+
`);
|
|
251
|
+
} else {
|
|
252
|
+
// Vanilla JS template
|
|
253
|
+
await fs.writeFile(path.join(projectDir, 'src', 'main.js'), `import './style.css'
|
|
199
254
|
|
|
200
255
|
document.getElementById('app').innerHTML = \`
|
|
201
256
|
<div class="app">
|
|
@@ -204,7 +259,9 @@ document.getElementById('app').innerHTML = \`
|
|
|
204
259
|
</div>
|
|
205
260
|
\`
|
|
206
261
|
`);
|
|
207
|
-
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create style.css for Vite templates
|
|
208
265
|
await fs.writeFile(path.join(projectDir, 'src', 'style.css'), `* {
|
|
209
266
|
margin: 0;
|
|
210
267
|
padding: 0;
|
|
@@ -239,9 +296,26 @@ p {
|
|
|
239
296
|
`);
|
|
240
297
|
}
|
|
241
298
|
|
|
299
|
+
// Create GitHub Actions workflow (only for non-static with desktop targets)
|
|
300
|
+
if (answers.template !== 'static' && answers.platforms.some(p => ['windows', 'macos', 'linux'].includes(p))) {
|
|
301
|
+
await createGithubWorkflow(projectDir, answers.desktopFramework || 'tauri', name);
|
|
302
|
+
}
|
|
303
|
+
|
|
242
304
|
spinner.succeed('Project created successfully!');
|
|
243
305
|
|
|
244
|
-
|
|
306
|
+
// Different next steps for static vs bundled
|
|
307
|
+
if (answers.template === 'static') {
|
|
308
|
+
console.log(chalk.green(`
|
|
309
|
+
✅ Project "${name}" created!
|
|
310
|
+
|
|
311
|
+
Next steps:
|
|
312
|
+
${chalk.cyan(`cd ${name}`)}
|
|
313
|
+
${chalk.cyan('slct deploy')}
|
|
314
|
+
|
|
315
|
+
No npm install needed - it's pure HTML/CSS/JS!
|
|
316
|
+
`));
|
|
317
|
+
} else {
|
|
318
|
+
console.log(chalk.green(`
|
|
245
319
|
✅ Project "${name}" created!
|
|
246
320
|
|
|
247
321
|
Next steps:
|
|
@@ -250,6 +324,7 @@ Next steps:
|
|
|
250
324
|
${chalk.cyan('slct build')}
|
|
251
325
|
|
|
252
326
|
`));
|
|
327
|
+
}
|
|
253
328
|
|
|
254
329
|
// Check for required dependencies
|
|
255
330
|
await checkDependencies(answers);
|