@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.
@@ -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)'));
@@ -121,27 +121,107 @@ async function deploy(options) {
121
121
 
122
122
  // Web App
123
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++;
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
- spinner.warn('Web build directory not found (skipping web upload)');
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!');
@@ -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 JavaScript', value: '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
- build: {
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
- // 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'
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
- // 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';
129
+ await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
121
130
  }
122
131
 
123
- await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
124
-
125
- // Create basic index.html
126
- const indexHtml = `<!DOCTYPE html>
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"></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
- 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
- ReactDOM.createRoot(document.getElementById('app')).render(<App />)
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
- .app {
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
- // Vanilla JS template
198
- await fs.writeFile(path.join(projectDir, 'src', 'main.js'), `import './style.css'
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
- // Create style.css for vanilla
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
- console.log(chalk.green(`
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iselect/select",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Build and deploy apps for web, desktop, and mobile from a single codebase",
5
5
  "main": "lib/index.js",
6
6
  "bin": {