devstarter-tool 0.1.1 → 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/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.1",
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",