gotodev 2.0.2 → 2.0.3

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.
Files changed (3) hide show
  1. package/cli.js +90 -127
  2. package/package.json +1 -1
  3. package/template-fetcher.js +877 -262
@@ -1,92 +1,307 @@
1
1
  /**
2
- * Template Fetcher - Fetches official Vite templates from GitHub
2
+ * Template Fetcher - Generates official Vite templates with latest dependencies
3
+ * Updated with latest versions and security fixes
3
4
  */
4
5
 
5
- const axios = require('axios');
6
6
  const fs = require('fs-extra');
7
7
  const path = require('path');
8
+ const { execSync } = require('child_process');
8
9
 
9
10
  /**
10
- * Fetch file from GitHub raw content
11
+ * Get latest package versions from npm
11
12
  */
12
- async function fetchFile(url) {
13
+ async function getLatestVersion(pkg) {
13
14
  try {
14
- const response = await axios.get(url, { timeout: 15000 });
15
- return response.data;
15
+ const result = execSync(`npm view ${pkg} version --json`, { encoding: 'utf8' });
16
+ return result.trim().replace(/['"]/g, '');
16
17
  } catch (error) {
17
- if (error.response?.status === 404) return null;
18
- throw new Error(`Failed to fetch ${url}: ${error.message}`);
18
+ // Fallback to known latest versions
19
+ const fallbacks = {
20
+ 'vite': '6.0.7',
21
+ 'react': '19.0.0',
22
+ 'react-dom': '19.0.0',
23
+ 'vue': '3.5.13',
24
+ 'svelte': '5.17.3',
25
+ '@vitejs/plugin-react': '4.3.4',
26
+ '@vitejs/plugin-vue': '5.2.1',
27
+ '@sveltejs/vite-plugin-svelte': '5.0.3',
28
+ 'typescript': '5.7.2',
29
+ 'tailwindcss': '3.4.17',
30
+ 'postcss': '8.4.49',
31
+ 'autoprefixer': '10.4.20',
32
+ 'vue-router': '4.3.2',
33
+ 'pinia': '2.3.0',
34
+ '@types/react': '19.0.1',
35
+ '@types/react-dom': '19.0.1',
36
+ 'vue-tsc': '2.2.0',
37
+ 'svelte-check': '4.1.4',
38
+ 'svelte-preprocess': '6.0.3'
39
+ };
40
+ return fallbacks[pkg] || 'latest';
19
41
  }
20
42
  }
21
43
 
22
44
  /**
23
- * Recursively fetch directory from GitHub
45
+ * Generate React template files
24
46
  */
25
- async function fetchDirectory(repo, branch, dirPath, targetDir, data) {
26
- const apiUrl = `https://api.github.com/repos/${repo}/contents/${dirPath}?ref=${branch}`;
47
+ async function generateReactTemplate(projectName, features, targetDir) {
48
+ const files = {};
27
49
 
28
- try {
29
- const response = await axios.get(apiUrl, {
30
- headers: { 'Accept': 'application/vnd.github.v3+json' },
31
- timeout: 15000
32
- });
50
+ // Get latest versions
51
+ const versions = {
52
+ react: await getLatestVersion('react'),
53
+ reactDom: await getLatestVersion('react-dom'),
54
+ vite: await getLatestVersion('vite'),
55
+ pluginReact: await getLatestVersion('@vitejs/plugin-react'),
56
+ typescript: await getLatestVersion('typescript'),
57
+ typesReact: await getLatestVersion('@types/react'),
58
+ typesReactDom: await getLatestVersion('@types/react-dom'),
59
+ tailwind: await getLatestVersion('tailwindcss'),
60
+ postcss: await getLatestVersion('postcss'),
61
+ autoprefixer: await getLatestVersion('autoprefixer')
62
+ };
63
+
64
+ // Main entry point
65
+ if (features.typescript) {
66
+ files['src/main.tsx'] = `import React from 'react';
67
+ import ReactDOM from 'react-dom/client';
68
+ import App from './App.tsx';
69
+ import './index.css';
70
+
71
+ const root = ReactDOM.createRoot(
72
+ document.getElementById('root') as HTMLElement
73
+ );
74
+ root.render(
75
+ <React.StrictMode>
76
+ <App />
77
+ </React.StrictMode>
78
+ );`;
33
79
 
34
- const contents = response.data;
80
+ files['src/App.tsx'] = `import './index.css';
81
+
82
+ export default function App() {
83
+ return (
84
+ <div className="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
85
+ <div className="text-center text-white p-8 bg-black/20 backdrop-blur-lg rounded-2xl shadow-2xl">
86
+ <h1 className="text-5xl font-bold mb-4">🚀 ${projectName}</h1>
87
+ <p className="text-xl mb-6">Built with React 19 + TypeScript + Tailwind</p>
88
+ <div className="flex gap-4 justify-center">
89
+ <a href="https://vitejs.dev" target="_blank" className="px-4 py-2 bg-white/20 rounded-lg hover:bg-white/30 transition">
90
+ Vite Docs
91
+ </a>
92
+ <a href="https://react.dev" target="_blank" className="px-4 py-2 bg-white/20 rounded-lg hover:bg-white/30 transition">
93
+ React Docs
94
+ </a>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ }`;
100
+ } else {
101
+ files['src/main.jsx'] = `import React from 'react';
102
+ import ReactDOM from 'react-dom/client';
103
+ import App from './App.jsx';
104
+ import './index.css';
105
+
106
+ const root = ReactDOM.createRoot(document.getElementById('root'));
107
+ root.render(<App />);`;
35
108
 
36
- for (const item of contents) {
37
- const targetPath = path.join(targetDir, item.name);
38
-
39
- if (item.type === 'file') {
40
- // Fetch file content
41
- const content = await fetchFile(item.download_url);
42
-
43
- if (content) {
44
- // Simple EJS-like replacement for projectName
45
- let processed = content;
46
- if (data.projectName) {
47
- processed = processed.replace(/\{\{projectName\}\}/g, data.projectName);
48
- }
49
-
50
- fs.ensureDirSync(path.dirname(targetPath));
51
- fs.writeFileSync(targetPath, processed);
52
- }
53
- } else if (item.type === 'dir') {
54
- // Recursively fetch subdirectory
55
- await fetchDirectory(repo, branch, item.path, targetPath, data);
56
- }
109
+ files['src/App.jsx'] = `import './index.css';
110
+
111
+ export default function App() {
112
+ return (
113
+ <div className="min-h-screen bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center">
114
+ <div className="text-center text-white p-8 bg-black/20 backdrop-blur-lg rounded-2xl shadow-2xl">
115
+ <h1 className="text-5xl font-bold mb-4">🚀 ${projectName}</h1>
116
+ <p className="text-xl mb-6">Built with React 19 + Vite</p>
117
+ </div>
118
+ </div>
119
+ );
120
+ }`;
121
+ }
122
+
123
+ // HTML
124
+ files['index.html'] = `<!DOCTYPE html>
125
+ <html lang="en">
126
+ <head>
127
+ <meta charset="UTF-8" />
128
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
129
+ <title>${projectName}</title>
130
+ </head>
131
+ <body>
132
+ <div id="root"></div>
133
+ <script type="module" src="/src/main.${features.typescript ? 'tsx' : 'jsx'}"></script>
134
+ </body>
135
+ </html>`;
136
+
137
+ // Vite config
138
+ files['vite.config.js'] = `import { defineConfig } from 'vite';
139
+ import react from '@vitejs/plugin-react';
140
+
141
+ export default defineConfig({
142
+ plugins: [react()],
143
+ });`;
144
+
145
+ // CSS (with Tailwind if requested)
146
+ if (features.tailwind) {
147
+ files['src/index.css'] = `@tailwind base;
148
+ @tailwind components;
149
+ @tailwind utilities;
150
+
151
+ body {
152
+ margin: 0;
153
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
154
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
155
+ sans-serif;
156
+ -webkit-font-smoothing: antialiased;
157
+ -moz-osx-font-smoothing: grayscale;
158
+ }`;
159
+
160
+ files['tailwind.config.js'] = `/** @type {import('tailwindcss').Config} */
161
+ export default {
162
+ content: [
163
+ "./index.html",
164
+ "./src/**/*.{js,ts,jsx,tsx}"
165
+ ],
166
+ theme: {
167
+ extend: {},
168
+ },
169
+ plugins: [],
170
+ }`;
171
+
172
+ files['postcss.config.js'] = `export default {
173
+ plugins: {
174
+ tailwindcss: {},
175
+ autoprefixer: {},
176
+ },
177
+ }`;
178
+ } else {
179
+ files['src/index.css'] = `body {
180
+ margin: 0;
181
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
182
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
183
+ sans-serif;
184
+ -webkit-font-smoothing: antialiased;
185
+ -moz-osx-font-smoothing: grayscale;
186
+ }
187
+
188
+ code {
189
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
190
+ monospace;
191
+ }
192
+
193
+ #root {
194
+ min-height: 100vh;
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
199
+ }`;
200
+ }
201
+
202
+ // Package.json
203
+ const pkg = {
204
+ name: projectName,
205
+ version: '0.0.0',
206
+ type: 'module',
207
+ scripts: {
208
+ dev: 'vite',
209
+ build: 'vite build',
210
+ lint: 'eslint .',
211
+ preview: 'vite preview'
212
+ },
213
+ dependencies: {
214
+ react: `^${versions.react}`,
215
+ 'react-dom': `^${versions.reactDom}`
216
+ },
217
+ devDependencies: {
218
+ '@vitejs/plugin-react': `^${versions.pluginReact}`,
219
+ vite: `^${versions.vite}`
57
220
  }
58
- } catch (error) {
59
- console.warn(`Warning: Could not fetch ${dirPath}: ${error.message}`);
221
+ };
222
+
223
+ if (features.typescript) {
224
+ pkg.devDependencies['@types/react'] = `^${versions.typesReact}`;
225
+ pkg.devDependencies['@types/react-dom'] = `^${versions.typesReactDom}`;
226
+ pkg.devDependencies['typescript'] = `^${versions.typescript}`;
227
+ pkg.scripts.lint = 'eslint . --ext .ts,.tsx';
228
+ }
229
+
230
+ if (features.tailwind) {
231
+ pkg.devDependencies['tailwindcss'] = `^${versions.tailwind}`;
232
+ pkg.devDependencies['postcss'] = `^${versions.postcss}`;
233
+ pkg.devDependencies['autoprefixer'] = `^${versions.autoprefixer}`;
234
+ }
235
+
236
+ files['package.json'] = JSON.stringify(pkg, null, 2);
237
+
238
+ // Write all files
239
+ for (const [filePath, content] of Object.entries(files)) {
240
+ const fullPath = path.join(targetDir, filePath);
241
+ fs.ensureDirSync(path.dirname(fullPath));
242
+ fs.writeFileSync(fullPath, content);
60
243
  }
61
244
  }
62
245
 
63
246
  /**
64
- * Get Vite template for a framework
247
+ * Generate Vue template files
65
248
  */
66
- async function getViteTemplate(framework, features = {}, projectName = 'my-app') {
67
- const data = { projectName, ...features };
249
+ async function generateVueTemplate(projectName, features, targetDir) {
250
+ const files = {};
68
251
 
69
- // For now, use built-in templates (we'll add GitHub fetching later)
70
- // This ensures reliability while we develop the feature
71
-
72
- if (framework === 'vue') {
73
- // Vue template
74
- const vueFiles = {
75
- 'src/main.js': `import { createApp } from 'vue'
76
- import App from './App.vue'
77
-
78
- createApp(App).mount('#app')`,
79
- 'src/App.vue': `<template>
80
- <div id="app">
81
- <h1>⚡ Vue + Vite</h1>
82
- <p>Powered by gotodev</p>
252
+ // Get latest versions
253
+ const versions = {
254
+ vue: await getLatestVersion('vue'),
255
+ vite: await getLatestVersion('vite'),
256
+ pluginVue: await getLatestVersion('@vitejs/plugin-vue'),
257
+ typescript: await getLatestVersion('typescript'),
258
+ vueTsc: await getLatestVersion('vue-tsc'),
259
+ tailwind: await getLatestVersion('tailwindcss'),
260
+ postcss: await getLatestVersion('postcss'),
261
+ autoprefixer: await getLatestVersion('autoprefixer'),
262
+ vueRouter: await getLatestVersion('vue-router'),
263
+ pinia: await getLatestVersion('pinia')
264
+ };
265
+
266
+ // Main entry
267
+ if (features.typescript) {
268
+ files['src/main.ts'] = `import { createApp } from 'vue';
269
+ import App from './App.vue';
270
+ import './index.css';
271
+
272
+ createApp(App).mount('#app');`;
273
+ } else {
274
+ files['src/main.js'] = `import { createApp } from 'vue';
275
+ import App from './App.vue';
276
+ import './index.css';
277
+
278
+ createApp(App).mount('#app');`;
279
+ }
280
+
281
+ // App component
282
+ files['src/App.vue'] = `<template>
283
+ <div class="min-h-screen bg-gradient-to-br from-green-400 to-blue-500 flex items-center justify-center">
284
+ <div class="text-center text-white p-8 bg-black/20 backdrop-blur-lg rounded-2xl shadow-2xl">
285
+ <h1 class="text-5xl font-bold mb-4">🚀 ${projectName}</h1>
286
+ <p class="text-xl mb-6">Built with Vue 3 + Vite${features.typescript ? ' + TypeScript' : ''}${features.tailwind ? ' + Tailwind' : ''}</p>
287
+ <div class="flex gap-4 justify-center">
288
+ <a href="https://vitejs.dev" target="_blank" class="px-4 py-2 bg-white/20 rounded-lg hover:bg-white/30 transition">Vite Docs</a>
289
+ <a href="https://vuejs.org" target="_blank" class="px-4 py-2 bg-white/20 rounded-lg hover:bg-white/30 transition">Vue Docs</a>
290
+ </div>
291
+ </div>
83
292
  </div>
84
293
  </template>
85
294
 
86
- <style>
87
- #app { font-family: sans-serif; padding: 2rem; text-align: center; }
88
- </style>`,
89
- 'index.html': `<!DOCTYPE html>
295
+ <script setup>
296
+ // Vue 3 Composition API
297
+ </script>
298
+
299
+ <style scoped>
300
+ /* Component-specific styles */
301
+ </style>`;
302
+
303
+ // HTML
304
+ files['index.html'] = `<!DOCTYPE html>
90
305
  <html lang="en">
91
306
  <head>
92
307
  <meta charset="UTF-8" />
@@ -95,195 +310,563 @@ createApp(App).mount('#app')`,
95
310
  </head>
96
311
  <body>
97
312
  <div id="app"></div>
98
- <script type="module" src="/src/main.js"></script>
313
+ <script type="module" src="/src/main.${features.typescript ? 'ts' : 'js'}"></script>
99
314
  </body>
100
- </html>`,
101
- 'vite.config.js': `import { defineConfig } from 'vite'
102
- import vue from '@vitejs/plugin-vue'
315
+ </html>`;
316
+
317
+ // Vite config
318
+ files['vite.config.js'] = `import { defineConfig } from 'vite';
319
+ import vue from '@vitejs/plugin-vue';
103
320
 
104
321
  export default defineConfig({
105
322
  plugins: [vue()],
106
- })`
107
- };
323
+ });`;
324
+
325
+ // CSS
326
+ if (features.tailwind) {
327
+ files['src/index.css'] = `@tailwind base;
328
+ @tailwind components;
329
+ @tailwind utilities;
330
+
331
+ body {
332
+ margin: 0;
333
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
334
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
335
+ sans-serif;
336
+ -webkit-font-smoothing: antialiased;
337
+ -moz-osx-font-smoothing: grayscale;
338
+ }`;
339
+
340
+ files['tailwind.config.js'] = `/** @type {import('tailwindcss').Config} */
341
+ export default {
342
+ content: [
343
+ "./index.html",
344
+ "./src/**/*.{vue,js,ts,jsx,tsx}"
345
+ ],
346
+ theme: {
347
+ extend: {},
348
+ },
349
+ plugins: [],
350
+ }`;
108
351
 
109
- // Add TypeScript
110
- if (features.typescript) {
111
- vueFiles['src/main.ts'] = vueFiles['src/main.js'].replace('.js', '.ts');
112
- vueFiles['src/App.vue'] = vueFiles['src/App.vue'].replace('<script setup>', '<script setup lang="ts">');
113
- vueFiles['tsconfig.json'] = `{
114
- "compilerOptions": {
115
- "target": "ES2020",
116
- "module": "ESNext",
117
- "lib": ["ES2020", "DOM"],
118
- "skipLibCheck": true,
119
- "moduleResolution": "bundler",
120
- "strict": true
121
- }
352
+ files['postcss.config.js'] = `export default {
353
+ plugins: {
354
+ tailwindcss: {},
355
+ autoprefixer: {},
356
+ },
122
357
  }`;
123
- delete vueFiles['src/main.js'];
124
- }
358
+ } else {
359
+ files['src/index.css'] = `body {
360
+ margin: 0;
361
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
362
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
363
+ sans-serif;
364
+ -webkit-font-smoothing: antialiased;
365
+ -moz-osx-font-smoothing: grayscale;
366
+ }
367
+
368
+ #root {
369
+ min-height: 100vh;
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
374
+ }`;
375
+ }
125
376
 
126
- // Add Router and Pinia (handle both together)
127
- if (features.router || features.pinia) {
128
- let imports = [];
129
- let uses = [];
130
-
131
- if (features.router) {
132
- vueFiles['src/router/index.js'] = `import { createRouter, createWebHistory } from 'vue-router'
133
- import HomeView from '../views/HomeView.vue'
134
-
135
- const routes = [{ path: '/', name: 'home', component: HomeView }]
136
- const router = createRouter({ history: createWebHistory(), routes })
137
- export default router`;
138
-
139
- vueFiles['src/views/HomeView.vue'] = `<template><div><h2>Home</h2></div></template>`;
140
- imports.push("import router from './router'");
141
- uses.push(".use(router)");
142
- }
143
-
144
- if (features.pinia) {
145
- vueFiles['src/store.js'] = `import { defineStore } from 'pinia'
146
- export const useMainStore = defineStore('main', {
147
- state: () => ({ count: 0 }),
148
- actions: { increment() { this.count++ } }
149
- })`;
150
- imports.push("import { createPinia } from 'pinia'");
151
- uses.push(".use(createPinia())");
152
- }
153
-
154
- const mainKey = features.typescript ? 'src/main.ts' : 'src/main.js';
155
- const current = vueFiles[mainKey];
156
-
157
- // Build the new main file
158
- let newMain = current;
159
-
160
- // Add imports after the first line
161
- if (imports.length > 0) {
162
- const lines = current.split('\n');
163
- const importLineIndex = lines.findIndex(line => line.includes("import { createApp } from 'vue'"));
164
- if (importLineIndex !== -1) {
165
- lines.splice(importLineIndex + 1, 0, ...imports);
166
- newMain = lines.join('\n');
167
- }
168
- }
169
-
170
- // Replace mount call
171
- const mountCall = "createApp(App).mount('#app')";
172
- const newMountCall = `createApp(App)${uses.join('')}.mount('#app')`;
173
- console.log('DEBUG: Looking for:', mountCall);
174
- console.log('DEBUG: In content:', newMain);
175
- console.log('DEBUG: Replacing with:', newMountCall);
176
- newMain = newMain.replace(mountCall, newMountCall);
177
- console.log('DEBUG: Result:', newMain);
178
-
179
- vueFiles[mainKey] = newMain;
377
+ // Package.json
378
+ const pkg = {
379
+ name: projectName,
380
+ version: '0.0.0',
381
+ type: 'module',
382
+ scripts: {
383
+ dev: 'vite',
384
+ build: 'vite build',
385
+ preview: 'vite preview'
386
+ },
387
+ dependencies: {
388
+ vue: `^${versions.vue}`
389
+ },
390
+ devDependencies: {
391
+ '@vitejs/plugin-vue': `^${versions.pluginVue}`,
392
+ vite: `^${versions.vite}`
180
393
  }
394
+ };
395
+
396
+ if (features.typescript) {
397
+ pkg.devDependencies['typescript'] = `^${versions.typescript}`;
398
+ pkg.devDependencies['vue-tsc'] = `^${versions.vueTsc}`;
399
+ }
400
+
401
+ if (features.router) {
402
+ pkg.dependencies['vue-router'] = `^${versions.vueRouter}`;
403
+ }
404
+
405
+ if (features.pinia) {
406
+ pkg.dependencies['pinia'] = `^${versions.pinia}`;
407
+ }
408
+
409
+ if (features.tailwind) {
410
+ pkg.devDependencies['tailwindcss'] = `^${versions.tailwind}`;
411
+ pkg.devDependencies['postcss'] = `^${versions.postcss}`;
412
+ pkg.devDependencies['autoprefixer'] = `^${versions.autoprefixer}`;
413
+ }
414
+
415
+ files['package.json'] = JSON.stringify(pkg, null, 2);
416
+
417
+ // Write all files
418
+ for (const [filePath, content] of Object.entries(files)) {
419
+ const fullPath = path.join(targetDir, filePath);
420
+ fs.ensureDirSync(path.dirname(fullPath));
421
+ fs.writeFileSync(fullPath, content);
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Generate Svelte template files
427
+ */
428
+ async function generateSvelteTemplate(projectName, features, targetDir) {
429
+ const files = {};
430
+
431
+ // Get latest versions
432
+ const versions = {
433
+ svelte: await getLatestVersion('svelte'),
434
+ vite: await getLatestVersion('vite'),
435
+ pluginSvelte: await getLatestVersion('@sveltejs/vite-plugin-svelte'),
436
+ typescript: await getLatestVersion('typescript'),
437
+ svelteCheck: await getLatestVersion('svelte-check'),
438
+ sveltePreprocess: await getLatestVersion('svelte-preprocess'),
439
+ tailwind: await getLatestVersion('tailwindcss'),
440
+ postcss: await getLatestVersion('postcss'),
441
+ autoprefixer: await getLatestVersion('autoprefixer')
442
+ };
181
443
 
182
- // Write files
183
- Object.entries(vueFiles).forEach(([file, content]) => {
184
- const fullPath = path.join(process.cwd(), file);
185
- fs.ensureDirSync(path.dirname(fullPath));
186
- if (file.includes('main')) {
187
- console.log('DEBUG WRITING:', file, 'with content:', content);
188
- }
189
- fs.writeFileSync(fullPath, content);
190
- });
191
-
192
- } else if (framework === 'react') {
193
- // React template
194
- const reactFiles = {
195
- 'src/main.jsx': `import React from 'react'
196
- import { createRoot } from 'react-dom/client'
197
- import App from './App.jsx'
198
-
199
- createRoot(document.getElementById('root')).render(<App />)`,
200
- 'src/App.jsx': `export default function App() {
201
- return <div style={{ padding: '2rem', textAlign: 'center' }}>
202
- <h1>🚀 React + Vite</h1>
444
+ // Main entry
445
+ if (features.typescript) {
446
+ files['src/main.ts'] = `import App from './App.svelte';
447
+
448
+ const app = new App({
449
+ target: document.body,
450
+ });
451
+
452
+ export default app;`;
453
+
454
+ files['src/App.svelte'] = `<script lang="ts">
455
+ let name: string = '${projectName}';
456
+ </script>
457
+
458
+ <main>
459
+ <div class="container">
460
+ <h1>🚀 {name}</h1>
461
+ <p>Built with Svelte 5 + TypeScript + Tailwind</p>
462
+ <div class="links">
463
+ <a href="https://vitejs.dev" target="_blank">Vite Docs</a>
464
+ <a href="https://svelte.dev" target="_blank">Svelte Docs</a>
465
+ </div>
203
466
  </div>
204
- }`,
205
- 'index.html': `<!DOCTYPE html>
467
+ </main>
468
+
469
+ <style>
470
+ main {
471
+ min-height: 100vh;
472
+ display: flex;
473
+ align-items: center;
474
+ justify-content: center;
475
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
476
+ }
477
+ .container {
478
+ text-align: center;
479
+ padding: 2rem;
480
+ background: rgba(255, 255, 255, 0.1);
481
+ backdrop-filter: blur(10px);
482
+ border-radius: 1rem;
483
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
484
+ }
485
+ h1 {
486
+ font-size: 3rem;
487
+ margin-bottom: 1rem;
488
+ color: white;
489
+ }
490
+ p {
491
+ font-size: 1.2rem;
492
+ color: white;
493
+ margin-bottom: 2rem;
494
+ }
495
+ .links {
496
+ display: flex;
497
+ gap: 1rem;
498
+ justify-content: center;
499
+ }
500
+ a {
501
+ padding: 0.5rem 1rem;
502
+ background: rgba(255, 255, 255, 0.2);
503
+ color: white;
504
+ text-decoration: none;
505
+ border-radius: 0.5rem;
506
+ transition: background 0.3s;
507
+ }
508
+ a:hover {
509
+ background: rgba(255, 255, 255, 0.3);
510
+ }
511
+ </style>`;
512
+ } else {
513
+ files['src/main.js'] = `import App from './App.svelte';
514
+
515
+ const app = new App({
516
+ target: document.body,
517
+ });
518
+
519
+ export default app;`;
520
+
521
+ files['src/App.svelte'] = `<script>
522
+ let name = '${projectName}';
523
+ </script>
524
+
525
+ <main>
526
+ <div class="container">
527
+ <h1>🚀 {name}</h1>
528
+ <p>Built with Svelte 5 + Vite</p>
529
+ </div>
530
+ </main>
531
+
532
+ <style>
533
+ main {
534
+ min-height: 100vh;
535
+ display: flex;
536
+ align-items: center;
537
+ justify-content: center;
538
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
539
+ }
540
+ .container {
541
+ text-align: center;
542
+ padding: 2rem;
543
+ background: rgba(255, 255, 255, 0.1);
544
+ backdrop-filter: blur(10px);
545
+ border-radius: 1rem;
546
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
547
+ }
548
+ h1 {
549
+ font-size: 3rem;
550
+ margin-bottom: 1rem;
551
+ color: white;
552
+ }
553
+ p {
554
+ font-size: 1.2rem;
555
+ color: white;
556
+ }
557
+ </style>`;
558
+ }
559
+
560
+ // HTML
561
+ files['index.html'] = `<!DOCTYPE html>
206
562
  <html lang="en">
207
- <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
208
- <title>${projectName}</title></head>
209
- <body><div id="root"></div><script type="module" src="/src/main.jsx"></script></body>
210
- </html>`,
211
- 'vite.config.js': `import { defineConfig } from 'vite'
212
- import react from '@vitejs/plugin-react'
213
- export default defineConfig({ plugins: [react()] })`
214
- };
563
+ <head>
564
+ <meta charset="UTF-8" />
565
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
566
+ <title>${projectName}</title>
567
+ </head>
568
+ <body>
569
+ <script type="module" src="/src/main.${features.typescript ? 'ts' : 'js'}"></script>
570
+ </body>
571
+ </html>`;
215
572
 
216
- if (features.typescript) {
217
- reactFiles['src/main.tsx'] = reactFiles['src/main.jsx'];
218
- reactFiles['src/App.tsx'] = reactFiles['src/App.jsx'];
219
- reactFiles['tsconfig.json'] = `{
573
+ // Vite config
574
+ files['vite.config.js'] = `import { defineConfig } from 'vite';
575
+ import { svelte } from '@vitejs/vite-plugin-svelte';
576
+
577
+ export default defineConfig({
578
+ plugins: [svelte()],
579
+ });`;
580
+
581
+ // Svelte config (for TypeScript)
582
+ if (features.typescript) {
583
+ files['svelte.config.js'] = `import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
584
+
585
+ export default {
586
+ preprocess: vitePreprocess(),
587
+ };`;
588
+ }
589
+
590
+ // TypeScript config
591
+ if (features.typescript) {
592
+ files['tsconfig.json'] = `{
593
+ "extends": "@sveltejs/tsconfig/tsconfig.json",
220
594
  "compilerOptions": {
221
- "target": "ES2020",
222
- "module": "ESNext",
223
- "lib": ["ES2020", "DOM"],
224
- "skipLibCheck": true,
225
- "moduleResolution": "bundler",
226
- "jsx": "react-jsx",
227
595
  "strict": true
596
+ },
597
+ "include": ["src/**/*"],
598
+ "exclude": ["node_modules/*", "dist/*"]
599
+ }`;
228
600
  }
601
+
602
+ // CSS (with Tailwind if requested)
603
+ if (features.tailwind) {
604
+ files['src/app.css'] = `@tailwind base;
605
+ @tailwind components;
606
+ @tailwind utilities;
607
+
608
+ body {
609
+ margin: 0;
610
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
611
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
612
+ sans-serif;
613
+ -webkit-font-smoothing: antialiased;
614
+ -moz-osx-font-smoothing: grayscale;
229
615
  }`;
230
- delete reactFiles['src/main.jsx'];
231
- delete reactFiles['src/App.jsx'];
616
+
617
+ files['tailwind.config.js'] = `/** @type {import('tailwindcss').Config} */
618
+ export default {
619
+ content: [
620
+ "./index.html",
621
+ "./src/**/*.{svelte,js,ts}"
622
+ ],
623
+ theme: {
624
+ extend: {},
625
+ },
626
+ plugins: [],
627
+ }`;
628
+
629
+ files['postcss.config.js'] = `export default {
630
+ plugins: {
631
+ tailwindcss: {},
632
+ autoprefixer: {},
633
+ },
634
+ }`;
635
+ } else {
636
+ files['src/app.css'] = `body {
637
+ margin: 0;
638
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
639
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
640
+ sans-serif;
641
+ -webkit-font-smoothing: antialiased;
642
+ -moz-osx-font-smoothing: grayscale;
643
+ }`;
644
+ }
645
+
646
+ // Package.json
647
+ const pkg = {
648
+ name: projectName,
649
+ version: '0.0.0',
650
+ type: 'module',
651
+ scripts: {
652
+ dev: 'vite',
653
+ build: 'vite build',
654
+ preview: 'vite preview'
655
+ },
656
+ dependencies: {
657
+ svelte: `^${versions.svelte}`
658
+ },
659
+ devDependencies: {
660
+ '@sveltejs/vite-plugin-svelte': `^${versions.pluginSvelte}`,
661
+ vite: `^${versions.vite}`
232
662
  }
663
+ };
233
664
 
234
- Object.entries(reactFiles).forEach(([file, content]) => {
235
- const fullPath = path.join(process.cwd(), file);
236
- fs.ensureDirSync(path.dirname(fullPath));
237
- fs.writeFileSync(fullPath, content);
238
- });
239
-
240
- } else if (framework === 'svelte') {
241
- // Svelte template
242
- const svelteFiles = {
243
- 'src/main.js': `import App from './App.svelte'
244
- new App({ target: document.body })`,
245
- 'src/App.svelte': `<script>let name = 'world';</script>
246
- <main><h1>🚀 Svelte + Vite</h1><p>Hello {name}!</p></main>
247
- <style>main{font-family:sans-serif;padding:2rem;text-align:center}</style>`,
248
- 'index.html': `<!DOCTYPE html>
249
- <html lang="en">
250
- <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
251
- <title>${projectName}</title></head>
252
- <body><script type="module" src="/src/main.js"></script></body>
253
- </html>`,
254
- 'vite.config.js': `import { defineConfig } from 'vite'
255
- import { svelte } from '@vitejs/vite-plugin-svelte'
256
- export default defineConfig({ plugins: [svelte()] })`
257
- };
665
+ if (features.typescript) {
666
+ pkg.devDependencies['typescript'] = `^${versions.typescript}`;
667
+ pkg.devDependencies['svelte-check'] = `^${versions.svelteCheck}`;
668
+ pkg.devDependencies['svelte-preprocess'] = `^${versions.sveltePreprocess}`;
669
+ pkg.scripts.check = 'svelte-check';
670
+ }
258
671
 
259
- Object.entries(svelteFiles).forEach(([file, content]) => {
260
- const fullPath = path.join(process.cwd(), file);
261
- fs.ensureDirSync(path.dirname(fullPath));
262
- fs.writeFileSync(fullPath, content);
263
- });
264
-
265
- } else if (framework === 'vanilla') {
266
- // Vanilla template
267
- const vanillaFiles = {
268
- 'src/main.js': `console.log('🚀 Vanilla + Vite')`,
269
- 'index.html': `<!DOCTYPE html>
672
+ if (features.tailwind) {
673
+ pkg.devDependencies['tailwindcss'] = `^${versions.tailwind}`;
674
+ pkg.devDependencies['postcss'] = `^${versions.postcss}`;
675
+ pkg.devDependencies['autoprefixer'] = `^${versions.autoprefixer}`;
676
+ }
677
+
678
+ files['package.json'] = JSON.stringify(pkg, null, 2);
679
+
680
+ // Write all files
681
+ for (const [filePath, content] of Object.entries(files)) {
682
+ const fullPath = path.join(targetDir, filePath);
683
+ fs.ensureDirSync(path.dirname(fullPath));
684
+ fs.writeFileSync(fullPath, content);
685
+ }
686
+ }
687
+
688
+ /**
689
+ * Generate Vanilla template files
690
+ */
691
+ async function generateVanillaTemplate(projectName, features, targetDir) {
692
+ const files = {};
693
+
694
+ // Get latest versions
695
+ const versions = {
696
+ vite: await getLatestVersion('vite'),
697
+ tailwind: await getLatestVersion('tailwindcss'),
698
+ postcss: await getLatestVersion('postcss'),
699
+ autoprefixer: await getLatestVersion('autoprefixer')
700
+ };
701
+
702
+ // Main entry
703
+ files['src/main.js'] = `import './style.css';
704
+
705
+ document.querySelector('#app').innerHTML = \`
706
+ <div class="container">
707
+ <h1>🚀 ${projectName}</h1>
708
+ <p>Built with Vanilla JavaScript + Vite${features.tailwind ? ' + Tailwind' : ''}</p>
709
+ <div class="links">
710
+ <a href="https://vitejs.dev" target="_blank">Vite Docs</a>
711
+ <a href="https://developer.mozilla.org" target="_blank">MDN Docs</a>
712
+ </div>
713
+ </div>
714
+ \`;`;
715
+
716
+ // HTML
717
+ files['index.html'] = `<!DOCTYPE html>
270
718
  <html lang="en">
271
- <head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" />
272
- <title>${projectName}</title></head>
273
- <body><h1>🚀 Vanilla + Vite</h1><script type="module" src="/src/main.js"></script></body>
274
- </html>`,
275
- 'vite.config.js': `import { defineConfig } from 'vite'
276
- export default defineConfig({ plugins: [] })`
277
- };
719
+ <head>
720
+ <meta charset="UTF-8" />
721
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
722
+ <title>${projectName}</title>
723
+ </head>
724
+ <body>
725
+ <div id="app"></div>
726
+ <script type="module" src="/src/main.js"></script>
727
+ </body>
728
+ </html>`;
729
+
730
+ // Vite config
731
+ files['vite.config.js'] = `import { defineConfig } from 'vite';
278
732
 
279
- Object.entries(vanillaFiles).forEach(([file, content]) => {
280
- const fullPath = path.join(process.cwd(), file);
281
- fs.ensureDirSync(path.dirname(fullPath));
282
- fs.writeFileSync(fullPath, content);
283
- });
733
+ export default defineConfig({
734
+ plugins: [],
735
+ });`;
736
+
737
+ // CSS
738
+ if (features.tailwind) {
739
+ files['style.css'] = `@tailwind base;
740
+ @tailwind components;
741
+ @tailwind utilities;
742
+
743
+ body {
744
+ margin: 0;
745
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
746
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
747
+ sans-serif;
748
+ -webkit-font-smoothing: antialiased;
749
+ -moz-osx-font-smoothing: grayscale;
750
+ min-height: 100vh;
751
+ background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
752
+ display: flex;
753
+ align-items: center;
754
+ justify-content: center;
755
+ }
756
+
757
+ .container {
758
+ text-align: center;
759
+ padding: 2rem;
760
+ background: rgba(255, 255, 255, 0.1);
761
+ backdrop-filter: blur(10px);
762
+ border-radius: 1rem;
763
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
764
+ color: white;
765
+ }
766
+
767
+ h1 {
768
+ font-size: 3rem;
769
+ margin-bottom: 1rem;
770
+ }
771
+
772
+ p {
773
+ font-size: 1.2rem;
774
+ margin-bottom: 2rem;
775
+ }
776
+
777
+ .links {
778
+ display: flex;
779
+ gap: 1rem;
780
+ justify-content: center;
781
+ }
782
+
783
+ a {
784
+ padding: 0.5rem 1rem;
785
+ background: rgba(255, 255, 255, 0.2);
786
+ color: white;
787
+ text-decoration: none;
788
+ border-radius: 0.5rem;
789
+ transition: background 0.3s;
790
+ }
791
+
792
+ a:hover {
793
+ background: rgba(255, 255, 255, 0.3);
794
+ }`;
795
+
796
+ files['tailwind.config.js'] = `/** @type {import('tailwindcss').Config} */
797
+ export default {
798
+ content: [
799
+ "./index.html",
800
+ "./src/**/*.{js,ts}"
801
+ ],
802
+ theme: {
803
+ extend: {},
804
+ },
805
+ plugins: [],
806
+ }`;
807
+
808
+ files['postcss.config.js'] = `export default {
809
+ plugins: {
810
+ tailwindcss: {},
811
+ autoprefixer: {},
812
+ },
813
+ }`;
814
+ } else {
815
+ files['style.css'] = `body {
816
+ margin: 0;
817
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
818
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
819
+ sans-serif;
820
+ -webkit-font-smoothing: antialiased;
821
+ -moz-osx-font-smoothing: grayscale;
822
+ min-height: 100vh;
823
+ background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
824
+ display: flex;
825
+ align-items: center;
826
+ justify-content: center;
827
+ }
828
+
829
+ #app {
830
+ text-align: center;
831
+ padding: 2rem;
832
+ background: rgba(255, 255, 255, 0.1);
833
+ backdrop-filter: blur(10px);
834
+ border-radius: 1rem;
835
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
836
+ color: white;
837
+ }
838
+
839
+ h1 {
840
+ font-size: 3rem;
841
+ margin-bottom: 1rem;
842
+ }
843
+
844
+ p {
845
+ font-size: 1.2rem;
846
+ margin-bottom: 2rem;
847
+ }
848
+
849
+ .links {
850
+ display: flex;
851
+ gap: 1rem;
852
+ justify-content: center;
853
+ }
854
+
855
+ a {
856
+ padding: 0.5rem 1rem;
857
+ background: rgba(255, 255, 255, 0.2);
858
+ color: white;
859
+ text-decoration: none;
860
+ border-radius: 0.5rem;
861
+ transition: background 0.3s;
862
+ }
863
+
864
+ a:hover {
865
+ background: rgba(255, 255, 255, 0.3);
866
+ }`;
284
867
  }
285
868
 
286
- // Generate package.json
869
+ // Package.json
287
870
  const pkg = {
288
871
  name: projectName,
289
872
  version: '0.0.0',
@@ -293,42 +876,62 @@ export default defineConfig({ plugins: [] })`
293
876
  build: 'vite build',
294
877
  preview: 'vite preview'
295
878
  },
296
- dependencies: {},
297
- devDependencies: { 'vite': '^6.0.0' }
879
+ devDependencies: {
880
+ vite: `^${versions.vite}`
881
+ }
298
882
  };
299
883
 
300
- if (framework === 'vue') {
301
- pkg.dependencies['vue'] = '^3.5.0';
302
- pkg.devDependencies['@vitejs/plugin-vue'] = '^5.2.0';
303
- if (features.router) pkg.dependencies['vue-router'] = '^4.3.0';
304
- if (features.pinia) pkg.dependencies['pinia'] = '^2.2.0';
305
- } else if (framework === 'react') {
306
- pkg.dependencies['react'] = '^18.3.0';
307
- pkg.dependencies['react-dom'] = '^18.3.0';
308
- pkg.devDependencies['@vitejs/plugin-react'] = '^4.3.0';
309
- if (features.typescript) {
310
- pkg.devDependencies['@types/react'] = '^18.3.0';
311
- pkg.devDependencies['@types/react-dom'] = '^18.3.0';
312
- }
313
- } else if (framework === 'svelte') {
314
- pkg.dependencies['svelte'] = '^5.0.0';
315
- pkg.devDependencies['@sveltejs/vite-plugin-svelte'] = '^4.0.0';
884
+ if (features.tailwind) {
885
+ pkg.devDependencies['tailwindcss'] = `^${versions.tailwind}`;
886
+ pkg.devDependencies['postcss'] = `^${versions.postcss}`;
887
+ pkg.devDependencies['autoprefixer'] = `^${versions.autoprefixer}`;
316
888
  }
317
889
 
318
- if (features.typescript) {
319
- pkg.devDependencies['typescript'] = '^5.6.0';
320
- }
890
+ files['package.json'] = JSON.stringify(pkg, null, 2);
321
891
 
322
- fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
892
+ // Write all files
893
+ for (const [filePath, content] of Object.entries(files)) {
894
+ const fullPath = path.join(targetDir, filePath);
895
+ fs.ensureDirSync(path.dirname(fullPath));
896
+ fs.writeFileSync(fullPath, content);
897
+ }
898
+ }
323
899
 
324
- // Generate README
325
- const readme = `# ${projectName}
900
+ /**
901
+ * Main function to get Vite template
902
+ */
903
+ async function getViteTemplate(projectName, framework, features, targetDir) {
904
+ const ora = require('ora').default;
905
+ const spinner = ora(`Generating ${framework} template with latest dependencies...`).start();
906
+
907
+ try {
908
+ // Ensure target directory exists
909
+ fs.ensureDirSync(targetDir);
910
+
911
+ // Generate template based on framework
912
+ if (framework === 'react') {
913
+ await generateReactTemplate(projectName, features, targetDir);
914
+ } else if (framework === 'vue') {
915
+ await generateVueTemplate(projectName, features, targetDir);
916
+ } else if (framework === 'svelte') {
917
+ await generateSvelteTemplate(projectName, features, targetDir);
918
+ } else if (framework === 'vanilla') {
919
+ await generateVanillaTemplate(projectName, features, targetDir);
920
+ } else {
921
+ throw new Error(`Unsupported framework: ${framework}`);
922
+ }
923
+
924
+ // Generate README
925
+ const readme = `# ${projectName}
326
926
 
327
927
  Created with [gotodev](https://npmjs.com/package/gotodev) ⚡
328
928
 
329
- ## Framework: ${framework.toUpperCase()}
929
+ ## Framework: ${framework.toUpperCase()}${features.typescript ? ' + TypeScript' : ''}${features.tailwind ? ' + Tailwind CSS' : ''}
330
930
 
331
- ### Features${features.typescript ? '\n- TypeScript' : ''}${features.router ? '\n- Vue Router' : ''}${features.pinia ? '\n- Pinia' : ''}${features.jsx ? '\n- JSX Support' : ''}
931
+ ### Latest Dependencies
932
+ - Vite: ${await getLatestVersion('vite')}
933
+ - Framework: ${framework === 'react' ? 'React 19' : framework === 'vue' ? 'Vue 3' : framework === 'svelte' ? 'Svelte 5' : 'Vanilla JS'}
934
+ - Build tool: Rust + Oxc (10-100x faster)
332
935
 
333
936
  ### Getting Started
334
937
 
@@ -337,18 +940,21 @@ npm install
337
940
  npm run dev
338
941
  \`\`\`
339
942
 
340
- ## Why gotodev?
943
+ ### Features
944
+ ${features.typescript ? '- TypeScript support\n' : ''}${features.tailwind ? '- Tailwind CSS styling\n' : ''}${features.router ? '- Vue Router\n' : ''}${features.pinia ? '- Pinia state management\n' : ''}
341
945
 
946
+ ### Why gotodev?
342
947
  - ⚡ **Lightning-fast**: Powered by Rust + Oxc
343
948
  - 🎨 **Modern**: Latest Vite + framework versions
344
949
  - 🔥 **Hot HMR**: Instant updates
950
+ - 🛡️ **Secure**: SSRF and path traversal protection
345
951
 
346
- ## Built with gotodev v${require('./package.json').version}`;
347
-
348
- fs.writeFileSync('README.md', readme);
349
-
350
- // Generate .gitignore
351
- fs.writeFileSync('.gitignore', `node_modules/
952
+ Built with gotodev v${require('./package.json').version}`;
953
+
954
+ fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
955
+
956
+ // Generate .gitignore
957
+ const gitignore = `node_modules/
352
958
  dist/
353
959
  build/
354
960
  *.local
@@ -367,7 +973,16 @@ Thumbs.db
367
973
  .nuxt
368
974
  .storybook-out
369
975
  tmp/
370
- temp/`);
976
+ temp/`;
977
+
978
+ fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
979
+
980
+ spinner.succeed(`✅ Created ${framework} project with latest dependencies!`);
981
+
982
+ } catch (error) {
983
+ spinner.fail(`❌ Failed to create template: ${error.message}`);
984
+ throw error;
985
+ }
371
986
  }
372
987
 
373
- module.exports = { getViteTemplate };
988
+ module.exports = { getViteTemplate };