devstarter-tool 0.2.0 → 0.2.1
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 +30 -1
- package/dist/cli.js +0 -1
- package/dist/commands/init/collector.js +2 -19
- package/dist/generators/createMonorepo.js +3 -12
- package/dist/generators/createProject.js +2 -7
- package/dist/prompts/initPrompts.js +0 -16
- package/dist/types/project.js +0 -1
- package/dist/utils/printDryRun.js +1 -2
- package/dist/utils/printSummary.js +1 -2
- package/package.json +1 -1
- package/dist/utils/injectVitest.js +0 -68
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ devstarter init [project-name]
|
|
|
30
30
|
| `-t, --type <type>` | Project type: `frontend` or `backend` |
|
|
31
31
|
| `--template <name>` | Template to use |
|
|
32
32
|
| `--dry-run` | Preview changes without creating files |
|
|
33
|
+
| `--no-git` | Skip git repository initialization |
|
|
34
|
+
| `--no-vitest` | Skip Vitest testing framework setup |
|
|
33
35
|
|
|
34
36
|
### Examples
|
|
35
37
|
|
|
@@ -45,6 +47,12 @@ devstarter init my-app --type frontend -y
|
|
|
45
47
|
|
|
46
48
|
# Preview what files would be created
|
|
47
49
|
devstarter init my-app --type frontend --dry-run
|
|
50
|
+
|
|
51
|
+
# Create project without git initialization
|
|
52
|
+
devstarter init my-app --no-git
|
|
53
|
+
|
|
54
|
+
# Create project without Vitest setup
|
|
55
|
+
devstarter init my-app --no-vitest
|
|
48
56
|
```
|
|
49
57
|
|
|
50
58
|
## Project Structures
|
|
@@ -54,7 +62,10 @@ devstarter init my-app --type frontend --dry-run
|
|
|
54
62
|
```
|
|
55
63
|
my-app/
|
|
56
64
|
├── src/
|
|
57
|
-
│
|
|
65
|
+
│ ├── main.ts (or main.tsx for React)
|
|
66
|
+
│ └── __tests__/
|
|
67
|
+
│ └── example.test.ts
|
|
68
|
+
├── vitest.config.ts
|
|
58
69
|
├── package.json
|
|
59
70
|
├── README.md
|
|
60
71
|
└── .git/ (if git is initialized)
|
|
@@ -96,10 +107,28 @@ my-app/
|
|
|
96
107
|
- Automatic package manager detection (npm, pnpm, yarn)
|
|
97
108
|
- Interactive template selection
|
|
98
109
|
- Optional Git repository initialization
|
|
110
|
+
- Optional Vitest testing framework setup
|
|
99
111
|
- Dry-run mode to preview changes
|
|
100
112
|
- Automatic project name normalization (kebab-case)
|
|
101
113
|
- Colored output for better readability
|
|
102
114
|
|
|
115
|
+
## Testing Setup (Vitest)
|
|
116
|
+
|
|
117
|
+
By default, projects are created with Vitest configured. This includes:
|
|
118
|
+
|
|
119
|
+
- `vitest` as a dev dependency
|
|
120
|
+
- `vitest.config.ts` configuration file
|
|
121
|
+
- Example test file in `src/__tests__/example.test.ts`
|
|
122
|
+
- `test` script in package.json
|
|
123
|
+
|
|
124
|
+
For React projects, it also adds `jsdom` for DOM testing.
|
|
125
|
+
|
|
126
|
+
To skip Vitest setup, use the `--no-vitest` flag:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
devstarter init my-app --no-vitest
|
|
130
|
+
```
|
|
131
|
+
|
|
103
132
|
## Development
|
|
104
133
|
|
|
105
134
|
### Requirements
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,5 @@ program
|
|
|
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
16
|
.option('--no-git', 'Skip git repository initialization')
|
|
17
|
-
.option('--no-vitest', 'Skip Vitest testing framework')
|
|
18
17
|
.action(initCommand);
|
|
19
18
|
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,
|
|
2
|
+
import { askProjectName, askProjectStructure, askInitQuestions, askTemplate, askInitGit, } 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';
|
|
@@ -40,26 +40,21 @@ async function collectStructure(useDefaults) {
|
|
|
40
40
|
async function collectBasicContext(projectName, options, useDefaults) {
|
|
41
41
|
const typeFromFlag = resolveProjectType(options.type);
|
|
42
42
|
const gitFlagProvided = options.git !== undefined;
|
|
43
|
-
|
|
44
|
-
// Obtener tipo de proyecto, initGit e includeVitest
|
|
43
|
+
// Obtener tipo de proyecto e initGit
|
|
45
44
|
let projectType;
|
|
46
45
|
let initGit;
|
|
47
|
-
let includeVitest;
|
|
48
46
|
if (useDefaults) {
|
|
49
47
|
projectType = typeFromFlag ?? DEFAULT_INIT_OPTIONS.projectType;
|
|
50
48
|
initGit = gitFlagProvided ? options.git : DEFAULT_INIT_OPTIONS.initGit;
|
|
51
|
-
includeVitest = vitestFlagProvided ? options.vitest : DEFAULT_INIT_OPTIONS.includeVitest;
|
|
52
49
|
}
|
|
53
50
|
else {
|
|
54
51
|
const answers = await askInitQuestions({
|
|
55
52
|
skipProjectName: true,
|
|
56
53
|
skipProjectType: Boolean(typeFromFlag),
|
|
57
54
|
skipInitGit: gitFlagProvided,
|
|
58
|
-
skipVitest: vitestFlagProvided,
|
|
59
55
|
});
|
|
60
56
|
projectType = typeFromFlag ?? answers.projectType;
|
|
61
57
|
initGit = gitFlagProvided ? options.git : answers.initGit;
|
|
62
|
-
includeVitest = vitestFlagProvided ? options.vitest : answers.includeVitest;
|
|
63
58
|
}
|
|
64
59
|
// Obtener template
|
|
65
60
|
const templates = listTemplates(projectType);
|
|
@@ -71,7 +66,6 @@ async function collectBasicContext(projectName, options, useDefaults) {
|
|
|
71
66
|
projectType,
|
|
72
67
|
template,
|
|
73
68
|
initGit,
|
|
74
|
-
includeVitest,
|
|
75
69
|
packageManager: detectPackageManager(),
|
|
76
70
|
isDryRun: Boolean(options.dryRun),
|
|
77
71
|
};
|
|
@@ -81,16 +75,13 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
|
|
|
81
75
|
const frontendTemplates = listTemplates('frontend');
|
|
82
76
|
const backendTemplates = listTemplates('backend');
|
|
83
77
|
const gitFlagProvided = options.git !== undefined;
|
|
84
|
-
const vitestFlagProvided = options.vitest !== undefined;
|
|
85
78
|
let webTemplate;
|
|
86
79
|
let apiTemplate;
|
|
87
80
|
let initGit;
|
|
88
|
-
let includeVitest;
|
|
89
81
|
if (useDefaults) {
|
|
90
82
|
webTemplate = frontendTemplates[0] ?? 'basic';
|
|
91
83
|
apiTemplate = backendTemplates[0] ?? 'basic';
|
|
92
84
|
initGit = gitFlagProvided ? options.git : DEFAULT_INIT_OPTIONS.initGit;
|
|
93
|
-
includeVitest = vitestFlagProvided ? options.vitest : DEFAULT_INIT_OPTIONS.includeVitest;
|
|
94
85
|
}
|
|
95
86
|
else {
|
|
96
87
|
const webAnswer = await askTemplate({
|
|
@@ -110,13 +101,6 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
|
|
|
110
101
|
const gitAnswer = await askInitGit();
|
|
111
102
|
initGit = gitAnswer.initGit;
|
|
112
103
|
}
|
|
113
|
-
if (vitestFlagProvided) {
|
|
114
|
-
includeVitest = options.vitest;
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
const vitestAnswer = await askIncludeVitest();
|
|
118
|
-
includeVitest = vitestAnswer.includeVitest;
|
|
119
|
-
}
|
|
120
104
|
}
|
|
121
105
|
return {
|
|
122
106
|
structure: 'monorepo',
|
|
@@ -124,7 +108,6 @@ async function collectMonorepoContext(projectName, useDefaults, options) {
|
|
|
124
108
|
webTemplate,
|
|
125
109
|
apiTemplate,
|
|
126
110
|
initGit,
|
|
127
|
-
includeVitest,
|
|
128
111
|
packageManager: 'pnpm', // Monorepo usa pnpm por defecto
|
|
129
112
|
isDryRun: Boolean(options.dryRun),
|
|
130
113
|
};
|
|
@@ -3,8 +3,7 @@ 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
|
-
|
|
7
|
-
export async function createMonorepo({ projectName, webTemplate, apiTemplate, initGit, includeVitest, }) {
|
|
6
|
+
export async function createMonorepo({ projectName, webTemplate, apiTemplate, initGit, }) {
|
|
8
7
|
// 1. Resolver ruta absoluta del proyecto
|
|
9
8
|
const projectRoot = path.resolve(process.cwd(), projectName);
|
|
10
9
|
// 2. Evitar sobrescribir carpetas existentes
|
|
@@ -18,26 +17,18 @@ export async function createMonorepo({ projectName, webTemplate, apiTemplate, in
|
|
|
18
17
|
// 4. Crear archivos de configuración del monorepo
|
|
19
18
|
await createMonorepoConfig(projectRoot, projectName);
|
|
20
19
|
// 5. Copiar template de frontend a apps/web
|
|
21
|
-
const webPath = path.join(projectRoot, 'apps', 'web');
|
|
22
20
|
const webTemplatePath = getTemplatePath('frontend', webTemplate);
|
|
23
21
|
if (await fs.pathExists(webTemplatePath)) {
|
|
24
|
-
await copyTemplate(webTemplatePath,
|
|
22
|
+
await copyTemplate(webTemplatePath, path.join(projectRoot, 'apps', 'web'), {
|
|
25
23
|
projectName: `${projectName}-web`,
|
|
26
24
|
});
|
|
27
|
-
if (includeVitest) {
|
|
28
|
-
await injectVitest(webPath, 'frontend', webTemplate);
|
|
29
|
-
}
|
|
30
25
|
}
|
|
31
26
|
// 6. Copiar template de backend a apps/api
|
|
32
|
-
const apiPath = path.join(projectRoot, 'apps', 'api');
|
|
33
27
|
const apiTemplatePath = getTemplatePath('backend', apiTemplate);
|
|
34
28
|
if (await fs.pathExists(apiTemplatePath)) {
|
|
35
|
-
await copyTemplate(apiTemplatePath,
|
|
29
|
+
await copyTemplate(apiTemplatePath, path.join(projectRoot, 'apps', 'api'), {
|
|
36
30
|
projectName: `${projectName}-api`,
|
|
37
31
|
});
|
|
38
|
-
if (includeVitest) {
|
|
39
|
-
await injectVitest(apiPath, 'backend', apiTemplate);
|
|
40
|
-
}
|
|
41
32
|
}
|
|
42
33
|
// 7. Crear package shared básico
|
|
43
34
|
await createSharedPackage(projectRoot, projectName);
|
|
@@ -3,8 +3,7 @@ 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
|
-
|
|
7
|
-
export async function createProject({ projectName, projectType, template, initGit, includeVitest, }) {
|
|
6
|
+
export async function createProject({ projectName, projectType, template, initGit, }) {
|
|
8
7
|
// 1. Resolver ruta absoluta del proyecto
|
|
9
8
|
const projectRoot = path.resolve(process.cwd(), projectName);
|
|
10
9
|
// 2. Evitar sobrescribir carpetas existentes
|
|
@@ -22,11 +21,7 @@ export async function createProject({ projectName, projectType, template, initGi
|
|
|
22
21
|
await copyTemplate(templatePath, projectRoot, {
|
|
23
22
|
projectName,
|
|
24
23
|
});
|
|
25
|
-
// 6.
|
|
26
|
-
if (includeVitest) {
|
|
27
|
-
await injectVitest(projectRoot, projectType, template);
|
|
28
|
-
}
|
|
29
|
-
// 7. Inicializar Git (si aplica)
|
|
24
|
+
// 6. Inicializar Git (si aplica)
|
|
30
25
|
if (initGit) {
|
|
31
26
|
initGitRepo(projectRoot);
|
|
32
27
|
}
|
|
@@ -56,14 +56,6 @@ export async function askInitQuestions(options = {}) {
|
|
|
56
56
|
initial: true,
|
|
57
57
|
});
|
|
58
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
|
-
}
|
|
67
59
|
return prompts(questions, { onCancel });
|
|
68
60
|
}
|
|
69
61
|
export async function askTemplate(options) {
|
|
@@ -88,11 +80,3 @@ export async function askInitGit() {
|
|
|
88
80
|
initial: true,
|
|
89
81
|
}, { onCancel });
|
|
90
82
|
}
|
|
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
|
-
}
|
package/dist/types/project.js
CHANGED
|
@@ -12,8 +12,7 @@ export function printDryRun(context) {
|
|
|
12
12
|
else {
|
|
13
13
|
printBasicPlan(context);
|
|
14
14
|
}
|
|
15
|
-
console.log(`${styles.info('- Git:')} ${context.initGit ? 'would initialize' : 'skipped'}`);
|
|
16
|
-
console.log(`${styles.info('- Vitest:')} ${context.includeVitest ? 'would add' : 'skipped'}\n`);
|
|
15
|
+
console.log(`${styles.info('- Git:')} ${context.initGit ? 'would initialize' : 'skipped'}\n`);
|
|
17
16
|
console.log(styles.title('Next steps'));
|
|
18
17
|
console.log(` ${styles.highlight(`cd ${context.projectName}`)}`);
|
|
19
18
|
console.log(` ${styles.highlight(`${context.packageManager} install`)}`);
|
|
@@ -11,8 +11,7 @@ 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')}`);
|
|
15
|
-
console.log(`${styles.info('- Vitest:')} ${context.includeVitest ? styles.success('added') : styles.muted('not included')}\n`);
|
|
14
|
+
console.log(`${styles.info('- Git:')} ${context.initGit ? styles.success('initialized') : styles.muted('not initialized')}\n`);
|
|
16
15
|
console.log(styles.title('Next steps'));
|
|
17
16
|
console.log(` ${styles.highlight(`cd ${context.projectName}`)}`);
|
|
18
17
|
console.log(` ${styles.highlight(`${context.packageManager} install`)}`);
|
package/package.json
CHANGED
|
@@ -1,68 +0,0 @@
|
|
|
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
|
-
}
|