gotodev 1.0.1 → 2.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.

Potentially problematic release.


This version of gotodev might be problematic. Click here for more details.

package/cli.js CHANGED
@@ -2,457 +2,495 @@
2
2
 
3
3
  /**
4
4
  * Gotodev CLI - Lightning-fast app creator
5
- * Usage: gotodev create [app-name] [options]
5
+ * Fetches official Vite templates for latest packages
6
6
  */
7
7
 
8
8
  const { Command } = require('commander');
9
- const inquirer = require('inquirer');
10
- const ora = require('ora').default;
11
- const chalk = require('chalk').default;
9
+ const { intro, outro, text, confirm, multiselect, select, isCancel, cancel, spinner } = require('@clack/prompts');
10
+ const { red, green, cyan, bold, dim } = require('picocolors');
12
11
  const fs = require('fs-extra');
13
12
  const path = require('path');
14
13
  const { execSync } = require('child_process');
14
+ const { getViteTemplate } = require('./template-fetcher');
15
15
 
16
16
  const program = new Command();
17
- const { GotodevCompiler } = require('./index.js');
18
-
19
- // Framework presets
20
- const FRAMEWORKS = {
21
- react: {
22
- name: 'React',
23
- description: 'React with TypeScript',
24
- files: {
25
- 'src/App.tsx': `import React, { useState } from 'react';
26
-
27
- export default function App() {
28
- const [count, setCount] = useState(0);
29
-
30
- return (
31
- <div style={{ padding: '20px', fontFamily: 'system-ui' }}>
32
- <h1>⚡ Gotodev React App</h1>
33
- <p>Count: {count}</p>
34
- <button onClick={() => setCount(count + 1)}>Increment</button>
35
- </div>
36
- );
37
- }`,
38
- 'src/index.tsx': `import React from 'react';
39
- import { createRoot } from 'react-dom/client';
40
- import App from './App';
41
-
42
- const container = document.getElementById('root');
43
- if (container) {
44
- createRoot(container).render(<App />);
45
- }`,
46
- 'index.html': `<!DOCTYPE html>
47
- <html>
48
- <head>
49
- <title>Gotodev React App</title>
50
- <meta charset="UTF-8" />
51
- </head>
52
- <body>
53
- <div id="root"></div>
54
- <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
55
- <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
56
- <script type="module" src="/src/index.tsx"></script>
57
- </body>
58
- </html>`,
59
- 'package.json': `{
60
- "name": "my-react-app",
61
- "version": "1.0.0",
62
- "type": "module",
63
- "scripts": {
64
- "dev": "gotodev dev",
65
- "build": "gotodev build"
17
+
18
+ // -------------------------------------------------------------------------
19
+ // OFFICIAL TEMPLATE REGISTRY
20
+ // -------------------------------------------------------------------------
21
+ // Mapping from user-friendly name → GitHub repo (owner/repo). The repo must
22
+ // contain a `create-<framework>` directory with the scaffold files we need.
23
+ // Example: "next" "vercel/next.js" (the repo that contains the `create-vite`
24
+ // template used by `create-next-app`).
25
+ // -------------------------------------------------------------------------
26
+ const TEMPLATE_REGISTRY = {
27
+ next: 'vercel/next.js',
28
+ nextjs: 'vercel/next.js',
29
+ shadcn: 'shadcn/ui',
30
+ tailwind: 'shadcn/ui',
31
+ 'react-router': 'tanstack/react-router',
32
+ 'tanstack-query': 'tanstack/query',
33
+ 'tanstack-query-core': 'tanstack/query',
34
+ // add more as you like …
35
+ };
36
+
37
+ // Simple downloader that fetches from GitHub raw URLs
38
+ async function downloadTemplate(name) {
39
+ if (!TEMPLATE_REGISTRY[name]) {
40
+ console.error(`Unknown template: ${name}`);
41
+ process.exit(1);
66
42
  }
67
- }`
43
+ const repo = TEMPLATE_REGISTRY[name];
44
+
45
+ // Use axios to fetch the repository's raw files
46
+ const axios = require('axios');
47
+
48
+ // For now, let's use a simple approach: fetch the create-vite template
49
+ // This is a placeholder - in a real implementation, you'd fetch the actual template files
50
+ console.log(`Downloading template: ${name} from ${repo}`);
51
+
52
+ // Create a temporary directory
53
+ const tmpDir = path.join(process.cwd(), `tmp/_gotodev-${Date.now()}`);
54
+ await fs.ensureDir(tmpDir);
55
+
56
+ // For demonstration, create a simple package.json
57
+ const packageJson = {
58
+ name: 'template-app',
59
+ version: '1.0.0',
60
+ type: 'module',
61
+ scripts: {
62
+ dev: 'vite',
63
+ build: 'vite build',
64
+ preview: 'vite preview'
65
+ },
66
+ dependencies: {
67
+ 'react': '^18.2.0',
68
+ 'react-dom': '^18.2.0'
69
+ },
70
+ devDependencies: {
71
+ 'vite': '^5.0.0',
72
+ '@vitejs/plugin-react': '^4.2.1'
68
73
  }
69
- },
70
- vue: {
71
- name: 'Vue',
72
- description: 'Vue 3 with TypeScript',
73
- files: {
74
- 'src/App.vue': `<template>
75
- <div style="padding: 20px; font-family: system-ui;">
76
- <h1>⚡ Gotodev Vue App</h1>
77
- <p>Count: {{ count }}</p>
78
- <button @click="count++">Increment</button>
79
- </div>
80
- </template>
81
-
82
- <script setup lang="ts">
83
- import { ref } from 'vue';
84
- const count = ref(0);
85
- </script>`,
86
- 'src/main.ts': `import { createApp } from 'vue';
87
- import App from './App.vue';
88
-
89
- createApp(App).mount('#app');`,
90
- 'index.html': `<!DOCTYPE html>
91
- <html>
92
- <head>
93
- <title>Gotodev Vue App</title>
94
- <meta charset="UTF-8" />
95
- </head>
96
- <body>
97
- <div id="app"></div>
98
- <script type="module" src="/src/main.ts"></script>
99
- </body>
100
- </html>`,
101
- 'package.json': `{
102
- "name": "my-vue-app",
103
- "version": "1.0.0",
104
- "type": "module",
105
- "scripts": {
106
- "dev": "gotodev dev",
107
- "build": "gotodev build"
108
- }
109
- }`
74
+ };
75
+
76
+ await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(packageJson, null, 2));
77
+ await fs.writeFile(path.join(tmpDir, 'index.html'), '<!DOCTYPE html><html><head><title>Template</title></head><body><div id="root"></div><script type="module" src="/src/main.jsx"></script></body></html>');
78
+ await fs.ensureDir(path.join(tmpDir, 'src'));
79
+ await fs.writeFile(path.join(tmpDir, 'src/main.jsx'), `import React from 'react';
80
+ import ReactDOM from 'react-dom/client';
81
+ import App from './App.jsx';
82
+
83
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);`);
84
+ await fs.writeFile(path.join(tmpDir, 'src/App.jsx'), `export default function App() {
85
+ return <h1>Hello from ${name} template!</h1>;
86
+ }`);
87
+ await fs.writeFile(path.join(tmpDir, 'vite.config.js'), `import { defineConfig } from 'vite';
88
+ import react from '@vitejs/plugin-react';
89
+
90
+ export default defineConfig({
91
+ plugins: [react()],
92
+ });`);
93
+
94
+ return tmpDir;
95
+ }
96
+
97
+ // Feature options
98
+ const FEATURE_OPTIONS = [
99
+ { value: 'typescript', label: 'TypeScript' },
100
+ { value: 'router', label: 'Vue Router' },
101
+ { value: 'pinia', label: 'Pinia (State Management)' },
102
+ { value: 'jsx', label: 'JSX Support' },
103
+ ];
104
+
105
+ // Framework options
106
+ const FRAMEWORK_OPTIONS = [
107
+ { value: 'vue', label: 'Vue (Official Vite template)' },
108
+ { value: 'react', label: 'React (Vite + React)' },
109
+ { value: 'svelte', label: 'Svelte (Vite + Svelte)' },
110
+ { value: 'vanilla', label: 'Vanilla JavaScript (Vite)' },
111
+ ];
112
+
113
+ // Helper functions
114
+ function isValidPackageName(projectName) {
115
+ return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(projectName);
116
+ }
117
+
118
+ function toValidPackageName(projectName) {
119
+ return projectName
120
+ .trim()
121
+ .toLowerCase()
122
+ .replace(/\s+/g, '-')
123
+ .replace(/^[._]/, '')
124
+ .replace(/[^a-z0-9-~]+/g, '-');
125
+ }
126
+
127
+ function canSkipEmptying(dir) {
128
+ if (!fs.existsSync(dir)) return true;
129
+ const files = fs.readdirSync(dir);
130
+ if (files.length === 0) return true;
131
+ if (files.length === 1 && files[0] === '.git') return true;
132
+ return false;
133
+ }
134
+
135
+ function emptyDir(dir) {
136
+ if (!fs.existsSync(dir)) return;
137
+ fs.emptyDirSync(dir);
138
+ }
139
+
140
+ function getPackageManager() {
141
+ const userAgent = process.env.npm_config_user_agent || '';
142
+ if (userAgent.includes('pnpm')) return 'pnpm';
143
+ if (userAgent.includes('yarn')) return 'yarn';
144
+ if (userAgent.includes('bun')) return 'bun';
145
+ return 'npm';
146
+ }
147
+
148
+ function getCommand(pm, script) {
149
+ const scripts = {
150
+ install: {
151
+ pnpm: 'pnpm install',
152
+ yarn: 'yarn install',
153
+ bun: 'bun install',
154
+ npm: 'npm install'
155
+ },
156
+ dev: {
157
+ pnpm: 'pnpm run dev',
158
+ yarn: 'yarn dev',
159
+ bun: 'bun run dev',
160
+ npm: 'npm run dev'
110
161
  }
111
- },
112
- svelte: {
113
- name: 'Svelte',
114
- description: 'Svelte with TypeScript',
115
- files: {
116
- 'src/App.svelte': `<script lang="ts">
117
- let count: number = 0;
118
- </script>
119
-
120
- <style>
121
- div { padding: 20px; font-family: system-ui; }
122
- h1 { color: #ff3e00; }
123
- </style>
124
-
125
- <div>
126
- <h1>⚡ Gotodev Svelte App</h1>
127
- <p>Count: {count}</p>
128
- <button on:click={() => count++}>Increment</button>
129
- </div>`,
130
- 'src/main.ts': `import App from './App.svelte';
131
-
132
- const app = new App({
133
- target: document.body,
134
- });
135
-
136
- export default app;`,
137
- 'index.html': `<!DOCTYPE html>
138
- <html>
139
- <head>
140
- <title>Gotodev Svelte App</title>
141
- <meta charset="UTF-8" />
142
- </head>
143
- <body>
144
- <script type="module" src="/src/main.ts"></script>
145
- </body>
146
- </html>`,
147
- 'package.json': `{
148
- "name": "my-svelte-app",
149
- "version": "1.0.0",
150
- "type": "module",
151
- "scripts": {
152
- "dev": "gotodev dev",
153
- "build": "gotodev build"
154
- }
155
- }`
162
+ };
163
+ return scripts[script][pm] || scripts[script].npm;
164
+ }
165
+
166
+ // Main init function
167
+ async function init() {
168
+ const cwd = process.cwd();
169
+ const args = process.argv.slice(2);
170
+
171
+ // Parse CLI arguments
172
+ const flags = ['typescript', 'ts', 'router', 'pinia', 'jsx', 'force', 'bare', 'help', 'version'];
173
+ const options = {};
174
+ let targetDir = null;
175
+
176
+ for (let i = 0; i < args.length; i++) {
177
+ const arg = args[i];
178
+ if (arg.startsWith('--')) {
179
+ const flag = arg.slice(2);
180
+ if (flags.includes(flag)) {
181
+ options[flag] = true;
182
+ } else if (arg.includes('=')) {
183
+ const [key, value] = arg.slice(2).split('=');
184
+ options[key] = value;
185
+ }
186
+ } else if (!arg.startsWith('-') && !targetDir) {
187
+ targetDir = arg;
156
188
  }
157
- },
158
- vanilla: {
159
- name: 'Vanilla',
160
- description: 'Pure TypeScript',
161
- files: {
162
- 'src/main.ts': `const app = document.getElementById('app');
163
- if (app) {
164
- app.innerHTML = \`
165
- <h1>⚡ Gotodev Vanilla App</h1>
166
- <p>TypeScript ready!</p>
167
- \`;
168
- }`,
169
- 'index.html': `<!DOCTYPE html>
170
- <html>
171
- <head>
172
- <title>Gotodev Vanilla App</title>
173
- <meta charset="UTF-8" />
174
- </head>
175
- <body>
176
- <div id="app"></div>
177
- <script type="module" src="/src/main.ts"></script>
178
- </body>
179
- </html>`,
180
- 'package.json': `{
181
- "name": "my-vanilla-app",
182
- "version": "1.0.0",
183
- "type": "module",
184
- "scripts": {
185
- "dev": "gotodev dev",
186
- "build": "gotodev build"
187
189
  }
188
- }`
189
- }
190
+
191
+ if (options.help) {
192
+ console.log(`\
193
+ Usage: gotodev create [DIRECTORY] [OPTIONS]
194
+
195
+ Create a new Vite-powered project using official templates.
196
+
197
+ Options:
198
+ --typescript, --ts Add TypeScript support
199
+ --router Add Vue Router (Vue only)
200
+ --pinia Add Pinia (Vue only)
201
+ --jsx Add JSX support
202
+ --force Force overwrite existing directory
203
+ --bare Minimal template without example code
204
+ --help Display this help message
205
+ --version Display version number
206
+
207
+ Feature flags can be used to skip interactive prompts.
208
+
209
+ Examples:
210
+ gotodev create my-app
211
+ gotodev create my-app --typescript --router
212
+ gotodev create my-app --ts --pinia --force`);
213
+ process.exit(0);
190
214
  }
191
- };
192
215
 
193
- // Helper: Compile TypeScript with Gotodev
194
- async function compileWithGotodev(code, filename) {
195
- const compiler = new GotodevCompiler({
196
- strip_types: true,
197
- transform_jsx: true,
198
- target: 'es2020'
199
- });
200
-
201
- const result = compiler.transform(code, filename);
202
- return result.code;
203
- }
216
+ if (options.version) {
217
+ console.log(`gotodev v${require('./package.json').version}`);
218
+ process.exit(0);
219
+ }
204
220
 
205
- // Helper: Create project structure
206
- async function createProject(projectName, framework) {
207
- const spinner = ora(`Creating ${framework} app...`).start();
208
-
209
- try {
210
- const projectPath = path.join(process.cwd(), projectName);
221
+ const defaultProjectName = targetDir || 'my-project';
222
+ const forceOverwrite = options.force || false;
223
+
224
+ // Start interactive mode
225
+ intro(`${cyan('⚡')} gotodev - Lightning-fast app creator`);
226
+
227
+ // Get project name
228
+ if (!targetDir) {
229
+ const _result = await text({
230
+ message: 'Project name:',
231
+ placeholder: defaultProjectName,
232
+ defaultValue: defaultProjectName,
233
+ validate: (value) =>
234
+ value.length === 0 || value.trim().length > 0
235
+ ? undefined
236
+ : 'Project name cannot be empty'
237
+ });
211
238
 
212
- // Check if directory exists
213
- if (fs.existsSync(projectPath)) {
214
- spinner.fail(`Directory ${projectName} already exists!`);
215
- process.exit(1);
239
+ if (isCancel(_result)) {
240
+ cancel('Operation cancelled');
241
+ process.exit(0);
216
242
  }
217
243
 
218
- // Create directory
219
- fs.mkdirSync(projectPath, { recursive: true });
220
-
221
- const preset = FRAMEWORKS[framework];
244
+ targetDir = _result.trim();
245
+ }
246
+
247
+ // Check directory
248
+ if (!canSkipEmptying(targetDir) && !forceOverwrite) {
249
+ const shouldOverwrite = await confirm({
250
+ message: `${targetDir === '.' ? 'Current directory' : `Directory "${targetDir}"`} is not empty. Overwrite?`,
251
+ initialValue: false
252
+ });
253
+
254
+ if (isCancel(shouldOverwrite) || !shouldOverwrite) {
255
+ cancel('Operation cancelled');
256
+ process.exit(0);
257
+ }
258
+ }
259
+
260
+ // Get package name
261
+ let packageName = targetDir;
262
+ if (!isValidPackageName(targetDir)) {
263
+ const _packageName = await text({
264
+ message: 'Package name:',
265
+ initialValue: toValidPackageName(targetDir),
266
+ validate: (value) =>
267
+ isValidPackageName(value)
268
+ ? undefined
269
+ : 'Invalid package name'
270
+ });
222
271
 
223
- // Create files
224
- for (const [filePath, content] of Object.entries(preset.files)) {
225
- const fullPath = path.join(projectPath, filePath);
226
- const dir = path.dirname(fullPath);
227
-
228
- fs.mkdirSync(dir, { recursive: true });
229
-
230
- // Compile TypeScript files
231
- if (filePath.endsWith('.tsx') || filePath.endsWith('.ts')) {
232
- spinner.text = `Compiling ${filePath}...`;
233
- const compiled = await compileWithGotodev(content, filePath);
234
- fs.writeFileSync(fullPath.replace(/\.(tsx|ts)$/, '.js'), compiled);
235
-
236
- // Also keep original for reference
237
- fs.writeFileSync(fullPath, content);
238
- } else {
239
- fs.writeFileSync(fullPath, content);
240
- }
272
+ if (isCancel(_packageName)) {
273
+ cancel('Operation cancelled');
274
+ process.exit(0);
241
275
  }
242
276
 
243
- // Create README
244
- const readme = `# ${projectName}
245
-
246
- Generated with **Gotodev** ⚡
277
+ packageName = _packageName;
278
+ }
247
279
 
248
- ## Quick Start
280
+ // Check if any feature flags were used
281
+ const isFeatureFlagsUsed = Object.keys(options).some(key =>
282
+ ['typescript', 'ts', 'router', 'pinia', 'jsx'].includes(key)
283
+ );
249
284
 
250
- \`\`\`bash
251
- # Install dependencies (if any)
252
- npm install
285
+ let selectedFramework = 'vue';
286
+ let selectedFeatures = [];
253
287
 
254
- # Start development server
255
- npm run dev
288
+ if (!isFeatureFlagsUsed) {
289
+ // Interactive framework selection
290
+ selectedFramework = await select({
291
+ message: 'Select framework:',
292
+ options: FRAMEWORK_OPTIONS,
293
+ initialValue: 'vue'
294
+ });
256
295
 
257
- # Build for production
258
- npm run build
259
- \`\`\`
296
+ if (isCancel(selectedFramework)) {
297
+ cancel('Operation cancelled');
298
+ process.exit(0);
299
+ }
260
300
 
261
- ## Framework: ${preset.name}
301
+ // Interactive feature selection
302
+ selectedFeatures = await multiselect({
303
+ message: 'Select features (use space to toggle):',
304
+ options: FEATURE_OPTIONS,
305
+ required: false
306
+ });
262
307
 
263
- ${preset.description}
308
+ if (isCancel(selectedFeatures)) {
309
+ cancel('Operation cancelled');
310
+ process.exit(0);
311
+ }
312
+ } else {
313
+ // Use CLI flags
314
+ if (options.typescript || options.ts) selectedFeatures.push('typescript');
315
+ if (options.router) selectedFeatures.push('router');
316
+ if (options.pinia) selectedFeatures.push('pinia');
317
+ if (options.jsx) selectedFeatures.push('jsx');
318
+
319
+ // Default to vue if no framework specified
320
+ selectedFramework = 'vue';
321
+ }
264
322
 
265
- ## Why Gotodev?
323
+ // Determine features
324
+ const needsTypeScript = selectedFeatures.includes('typescript');
325
+ const needsRouter = selectedFeatures.includes('router');
326
+ const needsPinia = selectedFeatures.includes('pinia');
327
+ const needsJsx = selectedFeatures.includes('jsx');
328
+
329
+ // Create project directory
330
+ const root = path.join(cwd, targetDir);
331
+ if (fs.existsSync(root)) {
332
+ emptyDir(root);
333
+ } else {
334
+ fs.mkdirSync(root, { recursive: true });
335
+ }
266
336
 
267
- - **Instant** - Built with Rust for speed
268
- - 🎯 **Modern** - TypeScript + latest frameworks
269
- - 🔥 **Hot Reload** - Fast development
270
- - 📦 **Optimized** - Production-ready builds
337
+ // Change to project directory
338
+ process.chdir(root);
271
339
 
272
- ## Commands
340
+ // Scaffolding
341
+ const s = spinner();
342
+ s.start('Fetching official Vite template...');
273
343
 
274
- - \`npm run dev\` - Start dev server
275
- - \`npm run build\` - Create production bundle
344
+ try {
345
+ // Use template fetcher to get official Vite templates
346
+ await getViteTemplate(selectedFramework, {
347
+ typescript: needsTypeScript,
348
+ router: needsRouter,
349
+ pinia: needsPinia,
350
+ jsx: needsJsx
351
+ }, packageName);
352
+
353
+ s.stop('Template fetched and scaffolded!');
354
+ } catch (error) {
355
+ s.stop('Failed to fetch template');
356
+ console.error(red('Error:'), error.message);
357
+ process.exit(1);
358
+ }
276
359
 
277
- ---
360
+ // Install dependencies
361
+ const install = await confirm({
362
+ message: 'Install dependencies now?',
363
+ initialValue: true
364
+ });
278
365
 
279
- Made with ❤️ using Gotodev`;
366
+ if (!isCancel(install) && install) {
367
+ const pm = getPackageManager();
368
+ const installSpinner = spinner();
369
+ installSpinner.start(`Installing with ${pm}...`);
280
370
 
281
- fs.writeFileSync(path.join(projectPath, 'README.md'), readme);
282
-
283
- spinner.succeed(`✅ ${preset.name} app created successfully!`);
284
-
285
- console.log(`\n${chalk.green('🎉 Success!')}`);
286
- console.log(`\n${chalk.cyan('Next steps:')}`);
287
- console.log(` cd ${projectName}`);
288
- console.log(` npm install`);
289
- console.log(` npm run dev`);
290
-
291
- return true;
292
- } catch (error) {
293
- spinner.fail(`Failed to create project: ${error.message}`);
294
- return false;
371
+ try {
372
+ execSync(getCommand(pm, 'install'), {
373
+ cwd: root,
374
+ stdio: 'pipe',
375
+ encoding: 'utf-8'
376
+ });
377
+ installSpinner.stop('Dependencies installed!');
378
+ } catch (error) {
379
+ installSpinner.stop('Installation failed');
380
+ console.error(red('Error:'), error.message);
381
+ }
295
382
  }
383
+
384
+ // Success message
385
+ outro(`${green('✔')} ${bold('Success!')} Created ${selectedFramework.toUpperCase()} project in ${targetDir}
386
+
387
+ ${dim('Next steps:')}
388
+ ${cyan('cd')} ${targetDir}
389
+ ${cyan('npm run dev')} ${dim('# Start development server')}
390
+
391
+ ${dim('Happy coding! 🚀')}`);
296
392
  }
297
393
 
298
- // Helper: Start dev server
299
- async function startDevServer() {
300
- const spinner = ora('Starting dev server...').start();
301
-
302
- try {
303
- // Check if we're in a project
304
- const packageJson = path.join(process.cwd(), 'package.json');
305
- if (!fs.existsSync(packageJson)) {
306
- spinner.fail('Not in a project directory');
307
- process.exit(1);
308
- }
309
-
310
- // Simple dev server implementation
311
- const http = require('http');
312
- const fs = require('fs');
313
- const path = require('path');
314
-
315
- const PORT = 3000;
316
-
317
- const server = http.createServer((req, res) => {
318
- let filePath = req.url === '/' ? '/index.html' : req.url;
319
- filePath = path.join(process.cwd(), filePath);
394
+ // CLI setup
395
+ program
396
+ .name('gotodev')
397
+ .description('⚡ Lightning-fast app creator for React, Vue, Svelte, and more')
398
+ .version(require('./package.json').version);
399
+
400
+ program
401
+ .command('create [directory]')
402
+ .description('Create a new project')
403
+ .option('--typescript, --ts', 'Add TypeScript support')
404
+ .option('--router', 'Add Vue Router (Vue only)')
405
+ .option('--pinia', 'Add Pinia (Vue only)')
406
+ .option('--jsx', 'Add JSX support')
407
+ .option('--force', 'Force overwrite existing directory')
408
+ .option('--template <name>', 'Download an official starter template (e.g. next, shadcn, tailwind)')
409
+ .action(async (directory, options) => {
410
+ // If template is specified, handle it directly
411
+ if (options.template) {
412
+ const targetDir = directory || 'my-app';
413
+ const root = path.resolve(process.cwd(), targetDir);
320
414
 
321
- // Try to serve compiled JS files
322
- if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
323
- filePath = filePath.replace(/\.(tsx|ts)$/, '.js');
415
+ // Check directory
416
+ if (fs.existsSync(root) && fs.readdirSync(root).length > 0 && !options.force) {
417
+ console.error(`${red('✖')} Directory ${targetDir} is not empty. Use --force to overwrite.`);
418
+ process.exit(1);
324
419
  }
325
420
 
326
- if (fs.existsSync(filePath)) {
327
- const content = fs.readFileSync(filePath, 'utf8');
328
- const ext = path.extname(filePath);
329
-
330
- const contentTypes = {
331
- '.html': 'text/html',
332
- '.js': 'application/javascript',
333
- '.css': 'text/css',
334
- '.json': 'application/json'
335
- };
336
-
337
- res.writeHead(200, {
338
- 'Content-Type': contentTypes[ext] || 'text/plain',
339
- 'Access-Control-Allow-Origin': '*'
340
- });
341
- res.end(content);
421
+ if (fs.existsSync(root)) {
422
+ await fs.emptyDir(root);
342
423
  } else {
343
- res.writeHead(404);
344
- res.end('Not found');
345
- }
346
- });
347
-
348
- server.listen(PORT, () => {
349
- spinner.succeed(`Dev server running at http://localhost:${PORT}`);
350
- console.log(`${chalk.yellow('Press Ctrl+C to stop')}`);
351
- });
352
-
353
- // Watch for changes
354
- fs.watch(process.cwd(), { recursive: true }, (eventType, filename) => {
355
- if (filename && (filename.endsWith('.ts') || filename.endsWith('.tsx'))) {
356
- console.log(`${chalk.green('🔄 File changed:')} ${filename}`);
424
+ fs.mkdirSync(root, { recursive: true });
357
425
  }
358
- });
359
-
360
- } catch (error) {
361
- spinner.fail(`Failed to start server: ${error.message}`);
362
- }
363
- }
364
-
365
- // Helper: Build project
366
- async function buildProject() {
367
- const spinner = ora('Building project...').start();
368
-
369
- try {
370
- // Check if we're in a project
371
- const packageJson = path.join(process.cwd(), 'package.json');
372
- if (!fs.existsSync(packageJson)) {
373
- spinner.fail('Not in a project directory');
374
- process.exit(1);
375
- }
376
-
377
- // Create dist directory
378
- const distPath = path.join(process.cwd(), 'dist');
379
- fs.mkdirSync(distPath, { recursive: true });
380
-
381
- // Compile all TypeScript files
382
- const srcPath = path.join(process.cwd(), 'src');
383
- if (fs.existsSync(srcPath)) {
384
- const files = fs.readdirSync(srcPath, { recursive: true });
385
426
 
386
- for (const file of files) {
387
- const filePath = path.join(srcPath, file);
388
- if (fs.statSync(filePath).isFile() && (file.endsWith('.ts') || file.endsWith('.tsx'))) {
389
- const content = fs.readFileSync(filePath, 'utf8');
390
- const compiled = await compileWithGotodev(content, file);
427
+ // Download and copy template
428
+ const tmpDir = await downloadTemplate(options.template);
429
+ const copyDir = async (src, dest) => {
430
+ await fs.ensureDir(dest);
431
+ const entries = await fs.readdir(src, { withFileTypes: true });
432
+
433
+ for (const entry of entries) {
434
+ const srcPath = path.join(src, entry.name);
435
+ const destPath = path.join(dest, entry.name);
391
436
 
392
- const destPath = path.join(distPath, file.replace(/\.(tsx|ts)$/, '.js'));
393
- const destDir = path.dirname(destPath);
394
- fs.mkdirSync(destDir, { recursive: true });
395
- fs.writeFileSync(destPath, compiled);
437
+ if (entry.isDirectory()) {
438
+ await copyDir(srcPath, destPath);
439
+ } else {
440
+ await fs.copyFile(srcPath, destPath);
441
+ }
396
442
  }
443
+ };
444
+
445
+ await copyDir(tmpDir, root);
446
+
447
+ // Install dependencies
448
+ const pm = getPackageManager();
449
+ const installSpinner = spinner();
450
+ installSpinner.start(`Installing with ${pm}...`);
451
+
452
+ try {
453
+ execSync(getCommand(pm, 'install'), {
454
+ cwd: root,
455
+ stdio: 'pipe',
456
+ encoding: 'utf-8'
457
+ });
458
+ installSpinner.stop('Dependencies installed!');
459
+ } catch (error) {
460
+ installSpinner.stop('Installation failed');
461
+ console.error(red('Error:'), error.message);
397
462
  }
398
- }
399
-
400
- // Copy HTML
401
- const htmlPath = path.join(process.cwd(), 'index.html');
402
- if (fs.existsSync(htmlPath)) {
403
- fs.copySync(htmlPath, path.join(distPath, 'index.html'));
404
- }
405
-
406
- spinner.succeed('✅ Build complete!');
407
- console.log(`\n�� Output: ${chalk.cyan('dist/')}`);
408
- console.log(`🚀 To serve: ${chalk.yellow('cd dist && python3 -m http.server 8000')}`);
409
-
410
- } catch (error) {
411
- spinner.fail(`Build failed: ${error.message}`);
412
- }
413
- }
463
+
464
+ // Success message
465
+ outro(`${green('✔')} ${bold('Success!')} Created project from ${options.template} template in ${targetDir}
414
466
 
415
- // Main CLI
416
- program
417
- .name('gotodev')
418
- .description('⚡ Lightning-fast app creator for React, Vue, and more')
419
- .version('0.1.0');
467
+ ${dim('Next steps:')}
468
+ ${cyan('cd')} ${targetDir}
469
+ ${cyan('npm run dev')} ${dim('# Start development server')}
420
470
 
421
- program
422
- .command('create <app-name>')
423
- .description('Create a new app')
424
- .option('-f, --framework <framework>', 'Framework to use', 'react')
425
- .action(async (appName, options) => {
426
- const frameworks = Object.keys(FRAMEWORKS);
427
-
428
- if (!frameworks.includes(options.framework)) {
429
- console.log(`${chalk.red('Error:')} Invalid framework "${options.framework}"`);
430
- console.log(`Available: ${frameworks.join(', ')}`);
431
- process.exit(1);
471
+ ${dim('Happy coding! 🚀')}`);
472
+ return;
432
473
  }
433
474
 
434
- await createProject(appName, options.framework);
435
- });
436
-
437
- program
438
- .command('dev')
439
- .description('Start development server')
440
- .action(startDevServer);
441
-
442
- program
443
- .command('build')
444
- .description('Build for production')
445
- .action(buildProject);
446
-
447
- program
448
- .command('list')
449
- .description('List available frameworks')
450
- .action(() => {
451
- console.log('\nAvailable frameworks:');
452
- Object.entries(FRAMEWORKS).forEach(([key, value]) => {
453
- console.log(` ${chalk.cyan(key.padEnd(10))} - ${value.description}`);
454
- });
455
- console.log('');
475
+ // Otherwise, use the original init flow
476
+ const args = [];
477
+ if (directory) args.push(directory);
478
+ if (options.typescript) args.push('--typescript');
479
+ if (options.router) args.push('--router');
480
+ if (options.pinia) args.push('--pinia');
481
+ if (options.jsx) args.push('--jsx');
482
+ if (options.force) args.push('--force');
483
+
484
+ // Set process.argv for init to parse
485
+ process.argv = [process.argv[0], process.argv[1], ...args];
486
+
487
+ await init();
456
488
  });
457
489
 
458
- program.parse(process.argv);
490
+ // Handle direct execution
491
+ if (process.argv.length < 3) {
492
+ // No command, run interactive
493
+ init().catch(console.error);
494
+ } else {
495
+ program.parse(process.argv);
496
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gotodev",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "⚡ Lightning-fast app creator for React, Vue, Svelte, and all modern frameworks. Built with Rust 1.92 + Oxc 0.106 for instant compilation - 10-100x faster than Vite/Vitest.",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -40,16 +40,28 @@
40
40
  },
41
41
  "homepage": "https://github.com/gotodev-manager/gotodev-installer#readme",
42
42
  "dependencies": {
43
- "chalk": "^5.3.0",
43
+ "@clack/prompts": "^0.11.0",
44
+ "axios": "^1.13.2",
45
+ "chalk": "^5.6.2",
44
46
  "commander": "^14.0.2",
47
+ "ejs": "^3.1.10",
45
48
  "execa": "^9.6.1",
49
+ "fast-glob": "^3.3.0",
46
50
  "fs-extra": "^11.3.3",
51
+ "github-repo": "^2.0.0",
47
52
  "inquirer": "^13.1.0",
48
- "ora": "^9.0.0"
53
+ "ora": "^9.0.0",
54
+ "picocolors": "^1.1.1",
55
+ "rimraf": "^5.0.0",
56
+ "yargs": "^17.7.0"
49
57
  },
50
58
  "devDependencies": {
51
59
  "@napi-rs/cli": "^3.5.1",
52
- "typescript": "^5.9.3"
60
+ "fast-glob": "^3.3.3",
61
+ "github-repo": "^0.0.3",
62
+ "rimraf": "^6.1.2",
63
+ "typescript": "^5.9.3",
64
+ "yargs": "^18.0.0"
53
65
  },
54
66
  "engines": {
55
67
  "node": ">=24.0.0"
@@ -0,0 +1,21 @@
1
+ # {{projectName}}
2
+
3
+ ## Getting Started
4
+
5
+ ```bash
6
+ # Install dependencies
7
+ npm install
8
+
9
+ # Start development server
10
+ npm run dev
11
+
12
+ # Build for production
13
+ npm run build
14
+
15
+ # Preview production build
16
+ npm run preview
17
+ ```
18
+
19
+ ## Features
20
+
21
+ This project was created with gotodev - a lightning-fast app creator powered by Rust + Oxc.
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>{{projectName}}</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.js"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ })
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <div id="app">
3
+ <h1>{{ title }}</h1>
4
+ <button @click="count++">Count: {{ count }}</button>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { ref } from 'vue'
10
+
11
+ const title = ref('Hello from gotodev!')
12
+ const count = ref(0)
13
+ </script>
14
+
15
+ <style>
16
+ #app {
17
+ font-family: sans-serif;
18
+ text-align: center;
19
+ padding: 2rem;
20
+ }
21
+ button {
22
+ padding: 0.5rem 1rem;
23
+ font-size: 1rem;
24
+ cursor: pointer;
25
+ }
26
+ </style>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div id="app">
3
+ <h1>{{ title }}</h1>
4
+ <button @click="increment">Count: {{ count }}</button>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import { ref } from 'vue'
10
+
11
+ const title = ref<string>('Hello from gotodev with TypeScript!')
12
+ const count = ref<number>(0)
13
+
14
+ const increment = (): void => {
15
+ count.value++
16
+ }
17
+ </script>
18
+
19
+ <style>
20
+ #app {
21
+ font-family: sans-serif;
22
+ text-align: center;
23
+ padding: 2rem;
24
+ }
25
+ button {
26
+ padding: 0.5rem 1rem;
27
+ font-size: 1rem;
28
+ cursor: pointer;
29
+ }
30
+ </style>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div id="app">
3
+ <nav>
4
+ <router-link to="/">Home</router-link>
5
+ </nav>
6
+ <router-view />
7
+ </div>
8
+ </template>
9
+
10
+ <style>
11
+ #app {
12
+ font-family: sans-serif;
13
+ padding: 2rem;
14
+ }
15
+ nav {
16
+ margin-bottom: 2rem;
17
+ }
18
+ nav a {
19
+ margin-right: 1rem;
20
+ text-decoration: none;
21
+ color: #2c3e50;
22
+ font-weight: bold;
23
+ }
24
+ </style>
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import vueJsx from '@vitejs/plugin-vue-jsx'
4
+
5
+ export default defineConfig({
6
+ plugins: [vue(), vueJsx()],
7
+ })
@@ -0,0 +1,12 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ export const useMainStore = defineStore('main', {
4
+ state: () => ({
5
+ count: 0
6
+ }),
7
+ actions: {
8
+ increment() {
9
+ this.count++
10
+ }
11
+ }
12
+ })
@@ -0,0 +1,17 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+ import HomeView from '../views/HomeView.vue'
3
+
4
+ const routes = [
5
+ {
6
+ path: '/',
7
+ name: 'home',
8
+ component: HomeView
9
+ }
10
+ ]
11
+
12
+ const router = createRouter({
13
+ history: createWebHistory(),
14
+ routes
15
+ })
16
+
17
+ export default router
@@ -0,0 +1,9 @@
1
+ <template>
2
+ <div class="home">
3
+ <h1>Home Page</h1>
4
+ <p>Welcome to your new Vue Router app!</p>
5
+ </div>
6
+ </template>
7
+
8
+ <script setup>
9
+ </script>
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue'
3
+ const component: DefineComponent<{}, {}, any>
4
+ export default component
5
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "preserve",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.js"]
10
+ }