devstarter-tool 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,126 +1,146 @@
1
- # devStarter CLI
1
+ # devstarter-tool
2
2
 
3
- CLI para generar proyectos con buenas prácticas y configuraciones predefinidas.
3
+ CLI to generate projects with best practices and predefined configurations.
4
4
 
5
- ## Instalacion
5
+ ## Installation
6
6
 
7
7
  ```bash
8
- npm install -g devstarter-cli
8
+ npm install -g devstarter-tool
9
9
  ```
10
10
 
11
- O ejecutar directamente con npx:
11
+ Or run directly with npx:
12
12
 
13
13
  ```bash
14
- npx devstarter-cli init my-app
14
+ npx devstarter-tool init my-app
15
15
  ```
16
16
 
17
- ## Uso
17
+ ## Usage
18
18
 
19
- ### Comando basico
19
+ ### Basic command
20
20
 
21
21
  ```bash
22
- devstarter init [nombre-proyecto]
22
+ devstarter init [project-name]
23
23
  ```
24
24
 
25
- ### Opciones
25
+ ### Options
26
26
 
27
- | Opcion | Descripcion |
27
+ | Option | Description |
28
28
  |--------|-------------|
29
- | `-y, --yes` | Usar valores por defecto sin preguntar |
30
- | `-t, --type <tipo>` | Tipo de proyecto: `frontend` o `backend` |
31
- | `--dry-run` | Previsualizar cambios sin crear archivos |
29
+ | `-y, --yes` | Use default values without prompting |
30
+ | `-t, --type <type>` | Project type: `frontend` or `backend` |
31
+ | `--template <name>` | Template to use |
32
+ | `--dry-run` | Preview changes without creating files |
32
33
 
33
- ### Ejemplos
34
+ ### Examples
34
35
 
35
36
  ```bash
36
- # Modo interactivo completo
37
+ # Full interactive mode
37
38
  devstarter init
38
39
 
39
- # Crear proyecto con nombre especifico
40
+ # Create project with specific name
40
41
  devstarter init my-app
41
42
 
42
- # Crear proyecto frontend sin preguntas
43
+ # Create frontend project without prompts
43
44
  devstarter init my-app --type frontend -y
44
45
 
45
- # Previsualizar que archivos se crearian
46
+ # Preview what files would be created
46
47
  devstarter init my-app --type frontend --dry-run
47
48
  ```
48
49
 
49
- ## Templates disponibles
50
+ ## Project Structures
51
+
52
+ ### Basic (single project)
53
+
54
+ ```
55
+ my-app/
56
+ ├── src/
57
+ │ └── main.ts (or main.tsx for React)
58
+ ├── package.json
59
+ ├── README.md
60
+ └── .git/ (if git is initialized)
61
+ ```
62
+
63
+ ### Monorepo (full-stack)
64
+
65
+ ```
66
+ my-app/
67
+ ├── apps/
68
+ │ ├── web/ <- frontend template
69
+ │ └── api/ <- backend template
70
+ ├── packages/
71
+ │ └── shared/ <- shared code
72
+ ├── package.json
73
+ ├── pnpm-workspace.yaml
74
+ ├── tsconfig.base.json
75
+ └── README.md
76
+ ```
77
+
78
+ ## Available Templates
50
79
 
51
80
  ### Frontend
52
81
 
53
- | Template | Descripcion |
82
+ | Template | Description |
54
83
  |----------|-------------|
55
- | `basic` | TypeScript minimal con estructura basica |
84
+ | `basic` | Minimal TypeScript with basic structure |
56
85
  | `react` | React 18 + Vite + TypeScript |
57
86
 
58
87
  ### Backend
59
88
 
60
- | Template | Descripcion |
89
+ | Template | Description |
61
90
  |----------|-------------|
62
91
  | `basic` | Express + TypeScript |
63
92
 
64
- ## Estructura de proyecto generado
65
-
66
- ```
67
- my-app/
68
- ├── src/
69
- │ └── main.ts (o main.tsx para React)
70
- ├── package.json
71
- ├── README.md
72
- └── .git/ (si se inicializa git)
73
- ```
74
-
75
- ## Caracteristicas
93
+ ## Features
76
94
 
77
- - Deteccion automatica del package manager (npm, pnpm, yarn)
78
- - Seleccion interactiva de templates
79
- - Inicializacion de repositorio Git opcional
80
- - Modo dry-run para previsualizar cambios
81
- - Normalizacion automatica de nombres de proyecto (kebab-case)
82
- - Output con colores para mejor legibilidad
95
+ - Project structure selection (basic or monorepo)
96
+ - Automatic package manager detection (npm, pnpm, yarn)
97
+ - Interactive template selection
98
+ - Optional Git repository initialization
99
+ - Dry-run mode to preview changes
100
+ - Automatic project name normalization (kebab-case)
101
+ - Colored output for better readability
83
102
 
84
- ## Desarrollo
103
+ ## Development
85
104
 
86
- ### Requisitos
105
+ ### Requirements
87
106
 
88
107
  - Node.js 18+
89
- - npm, pnpm o yarn
108
+ - npm, pnpm or yarn
90
109
 
91
110
  ### Setup
92
111
 
93
112
  ```bash
94
- # Clonar repositorio
95
- git clone https://github.com/abraham-diaz/devStarter-cli.git
96
- cd devStarter-cli
113
+ # Clone repository
114
+ git clone https://github.com/abraham-diaz/devstarter-cli.git
115
+ cd devstarter-cli
97
116
 
98
- # Instalar dependencias
117
+ # Install dependencies
99
118
  npm install
100
119
 
101
- # Compilar
120
+ # Build
102
121
  npm run build
103
122
 
104
- # Ejecutar localmente
123
+ # Run locally
105
124
  node dist/cli.js init test-app --dry-run
106
125
  ```
107
126
 
108
- ### Scripts disponibles
127
+ ### Available Scripts
109
128
 
110
- | Script | Descripcion |
129
+ | Script | Description |
111
130
  |--------|-------------|
112
- | `npm run build` | Compila TypeScript y copia templates |
113
- | `npm run dev` | Modo watch para desarrollo |
114
- | `npm run lint` | Ejecuta ESLint |
115
- | `npm run format` | Formatea codigo con Prettier |
131
+ | `npm run build` | Compile TypeScript and copy templates |
132
+ | `npm run dev` | Watch mode for development |
133
+ | `npm run test` | Run tests |
134
+ | `npm run lint` | Run ESLint |
135
+ | `npm run format` | Format code with Prettier |
116
136
 
117
- ### Agregar nuevos templates
137
+ ### Adding New Templates
118
138
 
119
- 1. Crear carpeta en `src/templates/<tipo>/<nombre-template>/`
120
- 2. Agregar archivos del template (usar `.tpl` para archivos con placeholders)
121
- 3. Placeholders disponibles: `{{projectName}}`
122
- 4. Ejecutar `npm run build`
139
+ 1. Create folder in `src/templates/<type>/<template-name>/`
140
+ 2. Add template files (use `.tpl` extension for files with placeholders)
141
+ 3. Available placeholders: `{{projectName}}`
142
+ 4. Run `npm run build`
123
143
 
124
- ## Licencia
144
+ ## License
125
145
 
126
146
  MIT
package/dist/cli.js CHANGED
@@ -13,5 +13,7 @@ program
13
13
  .option('-t, --type <type>', 'Project type (frontend | backend)')
14
14
  .option('--template <name>', 'Template variant (e.g. basic, react)')
15
15
  .option('--dry-run', 'Show what would be generated without creating files')
16
+ .option('--no-git', 'Skip git repository initialization')
17
+ .option('--no-vitest', 'Skip Vitest testing framework')
16
18
  .action(initCommand);
17
19
  program.parse(process.argv);
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_INIT_OPTIONS } from '../../types/project.js';
2
- import { askProjectName, askProjectStructure, askInitQuestions, askTemplate, askInitGit, } from '../../prompts/initPrompts.js';
2
+ import { askProjectName, askProjectStructure, askInitQuestions, askTemplate, askInitGit, askIncludeVitest, } from '../../prompts/initPrompts.js';
3
3
  import { normalizeProjectName } from '../../utils/normalize.js';
4
4
  import { detectPackageManager } from '../../utils/detectPackageManager.js';
5
5
  import { listTemplates } from '../../utils/listTemplate.js';
@@ -39,20 +39,27 @@ async function collectStructure(useDefaults) {
39
39
  }
40
40
  async function collectBasicContext(projectName, options, useDefaults) {
41
41
  const typeFromFlag = resolveProjectType(options.type);
42
- // Obtener tipo de proyecto e initGit
42
+ const gitFlagProvided = options.git !== undefined;
43
+ const vitestFlagProvided = options.vitest !== undefined;
44
+ // Obtener tipo de proyecto, initGit e includeVitest
43
45
  let projectType;
44
46
  let initGit;
47
+ let includeVitest;
45
48
  if (useDefaults) {
46
49
  projectType = typeFromFlag ?? DEFAULT_INIT_OPTIONS.projectType;
47
- initGit = DEFAULT_INIT_OPTIONS.initGit;
50
+ initGit = gitFlagProvided ? options.git : DEFAULT_INIT_OPTIONS.initGit;
51
+ includeVitest = vitestFlagProvided ? options.vitest : DEFAULT_INIT_OPTIONS.includeVitest;
48
52
  }
49
53
  else {
50
54
  const answers = await askInitQuestions({
51
55
  skipProjectName: true,
52
56
  skipProjectType: Boolean(typeFromFlag),
57
+ skipInitGit: gitFlagProvided,
58
+ skipVitest: vitestFlagProvided,
53
59
  });
54
60
  projectType = typeFromFlag ?? answers.projectType;
55
- initGit = answers.initGit;
61
+ initGit = gitFlagProvided ? options.git : answers.initGit;
62
+ includeVitest = vitestFlagProvided ? options.vitest : answers.includeVitest;
56
63
  }
57
64
  // Obtener template
58
65
  const templates = listTemplates(projectType);
@@ -64,6 +71,7 @@ async function collectBasicContext(projectName, options, useDefaults) {
64
71
  projectType,
65
72
  template,
66
73
  initGit,
74
+ includeVitest,
67
75
  packageManager: detectPackageManager(),
68
76
  isDryRun: Boolean(options.dryRun),
69
77
  };
@@ -72,13 +80,17 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
72
80
  // Templates para web (frontend) y api (backend)
73
81
  const frontendTemplates = listTemplates('frontend');
74
82
  const backendTemplates = listTemplates('backend');
83
+ const gitFlagProvided = options.git !== undefined;
84
+ const vitestFlagProvided = options.vitest !== undefined;
75
85
  let webTemplate;
76
86
  let apiTemplate;
77
87
  let initGit;
88
+ let includeVitest;
78
89
  if (useDefaults) {
79
90
  webTemplate = frontendTemplates[0] ?? 'basic';
80
91
  apiTemplate = backendTemplates[0] ?? 'basic';
81
- initGit = DEFAULT_INIT_OPTIONS.initGit;
92
+ initGit = gitFlagProvided ? options.git : DEFAULT_INIT_OPTIONS.initGit;
93
+ includeVitest = vitestFlagProvided ? options.vitest : DEFAULT_INIT_OPTIONS.includeVitest;
82
94
  }
83
95
  else {
84
96
  const webAnswer = await askTemplate({
@@ -91,8 +103,20 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
91
103
  message: 'Template for apps/api (backend):',
92
104
  });
93
105
  apiTemplate = apiAnswer.template;
94
- const gitAnswer = await askInitGit();
95
- initGit = gitAnswer.initGit;
106
+ if (gitFlagProvided) {
107
+ initGit = options.git;
108
+ }
109
+ else {
110
+ const gitAnswer = await askInitGit();
111
+ initGit = gitAnswer.initGit;
112
+ }
113
+ if (vitestFlagProvided) {
114
+ includeVitest = options.vitest;
115
+ }
116
+ else {
117
+ const vitestAnswer = await askIncludeVitest();
118
+ includeVitest = vitestAnswer.includeVitest;
119
+ }
96
120
  }
97
121
  return {
98
122
  structure: 'monorepo',
@@ -100,6 +124,7 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
100
124
  webTemplate,
101
125
  apiTemplate,
102
126
  initGit,
127
+ includeVitest,
103
128
  packageManager: 'pnpm', // Monorepo usa pnpm por defecto
104
129
  isDryRun: Boolean(options.dryRun),
105
130
  };
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { getTemplatePath } from '../utils/getTemplatePath.js';
4
4
  import { copyTemplate } from '../utils/copyTemplate.js';
5
5
  import { initGitRepo } from '../utils/git.js';
6
- export async function createMonorepo({ projectName, webTemplate, apiTemplate, initGit, }) {
6
+ import { injectVitest } from '../utils/injectVitest.js';
7
+ export async function createMonorepo({ projectName, webTemplate, apiTemplate, initGit, includeVitest, }) {
7
8
  // 1. Resolver ruta absoluta del proyecto
8
9
  const projectRoot = path.resolve(process.cwd(), projectName);
9
10
  // 2. Evitar sobrescribir carpetas existentes
@@ -17,18 +18,26 @@ export async function createMonorepo({ projectName, webTemplate, apiTemplate, in
17
18
  // 4. Crear archivos de configuración del monorepo
18
19
  await createMonorepoConfig(projectRoot, projectName);
19
20
  // 5. Copiar template de frontend a apps/web
21
+ const webPath = path.join(projectRoot, 'apps', 'web');
20
22
  const webTemplatePath = getTemplatePath('frontend', webTemplate);
21
23
  if (await fs.pathExists(webTemplatePath)) {
22
- await copyTemplate(webTemplatePath, path.join(projectRoot, 'apps', 'web'), {
24
+ await copyTemplate(webTemplatePath, webPath, {
23
25
  projectName: `${projectName}-web`,
24
26
  });
27
+ if (includeVitest) {
28
+ await injectVitest(webPath, 'frontend', webTemplate);
29
+ }
25
30
  }
26
31
  // 6. Copiar template de backend a apps/api
32
+ const apiPath = path.join(projectRoot, 'apps', 'api');
27
33
  const apiTemplatePath = getTemplatePath('backend', apiTemplate);
28
34
  if (await fs.pathExists(apiTemplatePath)) {
29
- await copyTemplate(apiTemplatePath, path.join(projectRoot, 'apps', 'api'), {
35
+ await copyTemplate(apiTemplatePath, apiPath, {
30
36
  projectName: `${projectName}-api`,
31
37
  });
38
+ if (includeVitest) {
39
+ await injectVitest(apiPath, 'backend', apiTemplate);
40
+ }
32
41
  }
33
42
  // 7. Crear package shared básico
34
43
  await createSharedPackage(projectRoot, projectName);
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { getTemplatePath } from '../utils/getTemplatePath.js';
4
4
  import { copyTemplate } from '../utils/copyTemplate.js';
5
5
  import { initGitRepo } from '../utils/git.js';
6
- export async function createProject({ projectName, projectType, template, initGit, }) {
6
+ import { injectVitest } from '../utils/injectVitest.js';
7
+ export async function createProject({ projectName, projectType, template, initGit, includeVitest, }) {
7
8
  // 1. Resolver ruta absoluta del proyecto
8
9
  const projectRoot = path.resolve(process.cwd(), projectName);
9
10
  // 2. Evitar sobrescribir carpetas existentes
@@ -21,7 +22,11 @@ export async function createProject({ projectName, projectType, template, initGi
21
22
  await copyTemplate(templatePath, projectRoot, {
22
23
  projectName,
23
24
  });
24
- // 6. Inicializar Git (si aplica)
25
+ // 6. Inyectar Vitest (si aplica)
26
+ if (includeVitest) {
27
+ await injectVitest(projectRoot, projectType, template);
28
+ }
29
+ // 7. Inicializar Git (si aplica)
25
30
  if (initGit) {
26
31
  initGitRepo(projectRoot);
27
32
  }
@@ -48,12 +48,22 @@ export async function askInitQuestions(options = {}) {
48
48
  ],
49
49
  });
50
50
  }
51
- questions.push({
52
- type: 'confirm',
53
- name: 'initGit',
54
- message: 'Initialize a git repository?',
55
- initial: true,
56
- });
51
+ if (!options.skipInitGit) {
52
+ questions.push({
53
+ type: 'confirm',
54
+ name: 'initGit',
55
+ message: 'Initialize a git repository?',
56
+ initial: true,
57
+ });
58
+ }
59
+ if (!options.skipVitest) {
60
+ questions.push({
61
+ type: 'confirm',
62
+ name: 'includeVitest',
63
+ message: 'Include Vitest for testing?',
64
+ initial: true,
65
+ });
66
+ }
57
67
  return prompts(questions, { onCancel });
58
68
  }
59
69
  export async function askTemplate(options) {
@@ -78,3 +88,11 @@ export async function askInitGit() {
78
88
  initial: true,
79
89
  }, { onCancel });
80
90
  }
91
+ export async function askIncludeVitest() {
92
+ return prompts({
93
+ type: 'confirm',
94
+ name: 'includeVitest',
95
+ message: 'Include Vitest for testing?',
96
+ initial: true,
97
+ }, { onCancel });
98
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{projectName}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
3
  "private": true,
4
+ "type": "module",
4
5
  "scripts": {
5
- "dev": "echo \"TODO\""
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "devDependencies": {
11
+ "typescript": "^5.5.4",
12
+ "vite": "^5.4.0"
6
13
  }
7
14
  }
@@ -1 +1,6 @@
1
- console.log('Hello from {{projectName}}');
1
+ const app = document.querySelector<HTMLDivElement>('#app')!;
2
+
3
+ app.innerHTML = `
4
+ <h1>{{projectName}}</h1>
5
+ <p>Edit <code>src/main.ts</code> and save to see HMR in action.</p>
6
+ `;
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "Bundler",
9
+ "strict": true
10
+ },
11
+ "include": ["src"]
12
+ }
@@ -2,4 +2,5 @@ export const DEFAULT_INIT_OPTIONS = {
2
2
  projectStructure: 'basic',
3
3
  projectType: 'frontend',
4
4
  initGit: true,
5
+ includeVitest: true,
5
6
  };
@@ -0,0 +1,68 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
3
+ const VITEST_CONFIG = `import { defineConfig } from 'vitest/config';
4
+
5
+ export default defineConfig({
6
+ test: {
7
+ globals: true,
8
+ },
9
+ });
10
+ `;
11
+ const VITEST_CONFIG_REACT = `import { defineConfig } from 'vitest/config';
12
+ import react from '@vitejs/plugin-react';
13
+
14
+ export default defineConfig({
15
+ plugins: [react()],
16
+ test: {
17
+ globals: true,
18
+ environment: 'jsdom',
19
+ },
20
+ });
21
+ `;
22
+ function getExampleTest(projectType, template) {
23
+ if (projectType === 'frontend' && template === 'react') {
24
+ return `import { describe, it, expect } from 'vitest';
25
+
26
+ describe('Example test', () => {
27
+ it('should pass', () => {
28
+ expect(1 + 1).toBe(2);
29
+ });
30
+ });
31
+ `;
32
+ }
33
+ return `import { describe, it, expect } from 'vitest';
34
+
35
+ describe('Example test', () => {
36
+ it('should pass', () => {
37
+ expect(1 + 1).toBe(2);
38
+ });
39
+ });
40
+ `;
41
+ }
42
+ export async function injectVitest(projectRoot, projectType, template) {
43
+ // 1. Modificar package.json
44
+ const packageJsonPath = path.join(projectRoot, 'package.json');
45
+ const packageJson = await fs.readJson(packageJsonPath);
46
+ // Agregar script test
47
+ packageJson.scripts = packageJson.scripts || {};
48
+ packageJson.scripts.test = 'vitest';
49
+ // Agregar devDependencies
50
+ packageJson.devDependencies = packageJson.devDependencies || {};
51
+ packageJson.devDependencies.vitest = '^3.1.4';
52
+ // Para React, agregar jsdom
53
+ if (projectType === 'frontend' && template === 'react') {
54
+ packageJson.devDependencies.jsdom = '^26.1.0';
55
+ }
56
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
57
+ // 2. Crear vitest.config.ts
58
+ const vitestConfigPath = path.join(projectRoot, 'vitest.config.ts');
59
+ const configContent = projectType === 'frontend' && template === 'react'
60
+ ? VITEST_CONFIG_REACT
61
+ : VITEST_CONFIG;
62
+ await fs.writeFile(vitestConfigPath, configContent);
63
+ // 3. Crear test de ejemplo
64
+ const testDir = path.join(projectRoot, 'src', '__tests__');
65
+ await fs.ensureDir(testDir);
66
+ const testFilePath = path.join(testDir, 'example.test.ts');
67
+ await fs.writeFile(testFilePath, getExampleTest(projectType, template));
68
+ }
@@ -12,7 +12,8 @@ export function printDryRun(context) {
12
12
  else {
13
13
  printBasicPlan(context);
14
14
  }
15
- console.log(`${styles.info('- Git:')} ${context.initGit ? 'would initialize' : 'skipped'}\n`);
15
+ console.log(`${styles.info('- Git:')} ${context.initGit ? 'would initialize' : 'skipped'}`);
16
+ console.log(`${styles.info('- Vitest:')} ${context.includeVitest ? 'would add' : 'skipped'}\n`);
16
17
  console.log(styles.title('Next steps'));
17
18
  console.log(` ${styles.highlight(`cd ${context.projectName}`)}`);
18
19
  console.log(` ${styles.highlight(`${context.packageManager} install`)}`);
@@ -11,7 +11,8 @@ export function printSummary(context) {
11
11
  console.log(`${styles.info('- Template:')} ${context.projectType}/${context.template}`);
12
12
  }
13
13
  console.log(`${styles.info('- Directory:')} ./${context.projectName}`);
14
- console.log(`${styles.info('- Git:')} ${context.initGit ? styles.success('initialized') : styles.muted('not initialized')}\n`);
14
+ console.log(`${styles.info('- Git:')} ${context.initGit ? styles.success('initialized') : styles.muted('not initialized')}`);
15
+ console.log(`${styles.info('- Vitest:')} ${context.includeVitest ? styles.success('added') : styles.muted('not included')}\n`);
15
16
  console.log(styles.title('Next steps'));
16
17
  console.log(` ${styles.highlight(`cd ${context.projectName}`)}`);
17
18
  console.log(` ${styles.highlight(`${context.packageManager} install`)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devstarter-tool",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "CLI to generate projects with best practices (basic or monorepo)",
6
6
  "author": "abraham-diaz",