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 +2 -0
- package/dist/commands/init/collector.js +32 -7
- package/dist/generators/createMonorepo.js +12 -3
- package/dist/generators/createProject.js +7 -2
- package/dist/prompts/initPrompts.js +24 -6
- package/dist/templates/frontend/basic/index.html +12 -0
- package/dist/templates/frontend/basic/package.json.tpl +8 -1
- package/dist/templates/frontend/basic/src/main.ts +6 -1
- package/dist/templates/frontend/basic/tsconfig.json +12 -0
- package/dist/types/project.js +1 -0
- package/dist/utils/injectVitest.js +68 -0
- package/dist/utils/printDryRun.js +2 -1
- package/dist/utils/printSummary.js +2 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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": "
|
|
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
|
}
|
package/dist/types/project.js
CHANGED
|
@@ -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'}
|
|
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')}
|
|
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`)}`);
|