fwinit 1.3.0 → 1.3.2

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.
Files changed (3) hide show
  1. package/README.md +84 -22
  2. package/dist/index.js +64 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # fwinit
2
2
 
3
- CLI para crear proyectos desde templates. Instalás una vez y scaffoldás proyectos con un comando.
3
+ CLI para crear proyectos desde templates. Instalás una vez y scaffoldás proyectos con un comando: un solo template, o un proyecto full stack completo (backend + frontend + base de datos).
4
4
 
5
5
  ## Instalación
6
6
 
@@ -17,51 +17,112 @@ bun add -g fwinit
17
17
 
18
18
  ## Uso
19
19
 
20
- ### Modo directo
21
-
22
20
  ```bash
21
+ # Menú interactivo: elegís entre full stack, solo backend o solo frontend
22
+ fwinit
23
+
24
+ # Crear un solo template (backend o frontend)
23
25
  fwinit fastify mi-api
24
26
  fwinit aspnet mi-app
25
27
  fwinit react-native mi-mobile
28
+
29
+ # Crear un proyecto completo: backend + frontend (+ db incluida)
30
+ fwinit fullstack mi-proyecto
31
+
32
+ # Elegir los templates sin preguntas (modo scripting/CI)
33
+ fwinit fullstack mi-proyecto -b fastify -f react-native -p pnpm
34
+
35
+ # Ver templates disponibles (agrupados por capa)
36
+ fwinit list
37
+
38
+ # Ayuda
39
+ fwinit --help
26
40
  ```
27
41
 
28
42
  ### Modo interactivo
29
43
 
30
- ```bash
31
- fwinit
44
+ `fwinit` sin argumentos abre el menú principal:
45
+
46
+ ```
47
+ ? ¿Qué querés crear?
48
+ > Full stack (backend + frontend)
49
+ Solo backend (API)
50
+ Solo frontend (app)
32
51
  ```
33
52
 
34
- Te pregunta qué template usar y el nombre del proyecto.
53
+ - **Full stack**: te pregunta primero el backend y después el frontend, y arma el monorepo completo.
54
+ - **Solo backend / Solo frontend**: te pregunta el template de la capa, el nombre, el layout y el package manager.
35
55
 
36
- ### Ver templates disponibles
56
+ ### Proyecto full stack
37
57
 
38
58
  ```bash
39
- fwinit list
59
+ fwinit fullstack mi-proyecto
40
60
  ```
41
61
 
42
- ### Help
62
+ Pregunta qué backend querés (ASP.NET, Express, Fastify, Node.js Vanilla) y qué frontend (React Native). Genera:
63
+
64
+ ```
65
+ mi-proyecto/
66
+ ├── backend/ # API (template elegido — trae su DB: Prisma o EF)
67
+ ├── frontend/ # App (template frontend elegido)
68
+ ├── .gitignore # .atl/ · odd · .opencode/
69
+ ├── .opencode/ # Skills de opencode (create-specs incluida)
70
+ ├── README.md # Cómo correr backend y frontend
71
+ └── specs/ # La creás con /create-specs
72
+ ```
73
+
74
+ La base de datos no se configura aparte: viene incluida en el template backend (Prisma en los templates JS/TS, EF Core en ASP.NET).
75
+
76
+ ### Layout de capa (monorepo liviano)
77
+
78
+ Al crear un solo template, el CLI pregunta si querés empaquetar el código en una carpeta de capa (default: sí):
43
79
 
44
80
  ```bash
45
- fwinit --help
81
+ # backend/ + .opencode/ + specs/ al mismo nivel
82
+ fwinit express mi-api
83
+ # raíz limpia (todo el código en mi-api/)
84
+ fwinit express mi-api --no-backend
85
+ # forzar el layout de capa sin preguntas
86
+ fwinit express mi-api --backend
46
87
  ```
47
88
 
89
+ Igual para frontend: `fwinit react-native mi-app` pregunta por `frontend/`.
90
+
91
+ ## Flags
92
+
93
+ | Flag | Descripción |
94
+ |------|-------------|
95
+ | `-p, --pm <manager>` | Package manager: `npm`, `pnpm` o `bun` (saltea la pregunta) |
96
+ | `--backend` | Empaquetar el código en `backend/` (solo modo single, saltea la pregunta) |
97
+ | `--no-backend` | Dejar el código en la raíz del proyecto (saltea la pregunta) |
98
+ | `-b, --backend-template <tpl>` | Backend a usar en fullstack (saltea la pregunta) |
99
+ | `-f, --frontend-template <tpl>` | Frontend a usar en fullstack (saltea la pregunta) |
100
+
48
101
  ## Templates disponibles
49
102
 
50
- | Template | Descripción | Runtime |
51
- |----------|-------------|---------|
52
- | `aspnet` | API REST con ASP.NET Core, Clean Architecture y C# | dotnet |
53
- | `express` | API REST con Express, Prisma, TypeScript y Bun | bun |
54
- | `fastify` | API REST con Fastify, Prisma, TypeScript y Bun | bun |
55
- | `nodejs` | API con Node.js puro, Prisma, TypeScript y tsx | node |
56
- | `react-native` | App móvil con React Native, Expo y TypeScript | node |
103
+ | Template | Capa | Descripción | Runtime |
104
+ |----------|------|-------------|---------|
105
+ | `aspnet` | backend | API REST con ASP.NET Core, Clean Architecture y C# | dotnet |
106
+ | `express` | backend | API REST con Express, Prisma, TypeScript y Bun | bun |
107
+ | `fastify` | backend | API REST con Fastify, Prisma, TypeScript y Bun | bun |
108
+ | `nodejs` | backend | API con Node.js puro, Prisma, TypeScript y tsx | node |
109
+ | `react-native` | frontend | App móvil con React Native, Expo y TypeScript | node |
110
+
111
+ La capa es metadata de cada template (`layer` en `templates/<TEMPLATE>/template.json`): para sumar un frontend nuevo (Astro, Next.js...), alcanza con agregar la carpeta del template y su `layer`. Los templates se clasifican por capa en la metadata, no por estructura de carpetas.
112
+
113
+ El runtime `bun` de un template se porta automáticamente a `node` (npm/pnpm) si elegís npm o pnpm: scripts con `tsx`, tests con `vitest` y sin `bun-types`.
57
114
 
58
115
  ## Qué hace
59
116
 
60
- 1. Descarga el ZIP del repositorio de templates desde GitHub
61
- 2. Extrae el template seleccionado
62
- 3. Lo copia al directorio del proyecto
63
- 4. Limpia los lock files (cada proyecto instala los suyos)
64
- 5. Te muestra los próximos pasos (install y dev)
117
+ 1. Descarga el ZIP del repositorio de templates desde GitHub (una sola vez, también en fullstack)
118
+ 2. Extrae y copia el template (o los dos, `backend/` + `frontend/`) al proyecto
119
+ 3. Sustituye el nombre del proyecto (package.json, app.json de Expo, proyectos ASP.NET con "Example")
120
+ 4. Si corresponde, porta el template de runtime bun a node (npm/pnpm)
121
+ 5. Instala `.opencode/` con la skill `create-specs` y el comando `/create-specs`
122
+ 6. Limpia los lock files (cada proyecto instala los suyos)
123
+ 7. Te muestra los próximos pasos (install y dev de cada carpeta)
124
+
125
+ En modo fullstack, además crea `.gitignore` y `README.md` en la raíz del monorepo.
65
126
 
66
127
  ## Desarrollo
67
128
 
@@ -93,6 +154,7 @@ bun run typecheck
93
154
  ```bash
94
155
  node dist/index.js list
95
156
  node dist/index.js fastify mi-api
157
+ node dist/index.js fullstack mi-proyecto -b fastify -f react-native -p pnpm
96
158
  ```
97
159
 
98
160
  ### Link global para desarrollo
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import ora from "ora";
6
6
  import chalk from "chalk";
7
7
  import { readFileSync } from "fs";
8
8
  import { mkdir as mkdir2, cp as cp2, access, readFile as readFile2 } from "fs/promises";
9
- import { join as join2 } from "path";
9
+ import { join as join2, basename } from "path";
10
10
 
11
11
  // src/templates.ts
12
12
  var TEMPLATES = [
@@ -243,6 +243,17 @@ async function selectTemplateFromLayer(layer) {
243
243
  ]);
244
244
  return template;
245
245
  }
246
+ async function askGitInit() {
247
+ const { initGit } = await inquirer.prompt([
248
+ {
249
+ type: "confirm",
250
+ name: "initGit",
251
+ message: "\xBFInicializar un repositorio git?",
252
+ default: true
253
+ }
254
+ ]);
255
+ return initGit;
256
+ }
246
257
  async function selectPackageManager() {
247
258
  const { pm } = await inquirer.prompt([
248
259
  {
@@ -431,10 +442,9 @@ async function adaptToNodeRuntime(projectPath) {
431
442
  } catch {
432
443
  }
433
444
  }
434
- async function writeRootFiles(info) {
445
+ async function writeRootGitignore(projectPath) {
435
446
  const { writeFile: writeFile2, access: access2 } = await import("fs/promises");
436
447
  const { join: join3 } = await import("path");
437
- const projectPath = resolve(process.cwd(), info.projectName);
438
448
  const gitignorePath = join3(projectPath, ".gitignore");
439
449
  try {
440
450
  await access2(gitignorePath);
@@ -444,6 +454,28 @@ async function writeRootFiles(info) {
444
454
  "# AI dev state\n.atl/\nodd\n.opencode/\n"
445
455
  );
446
456
  }
457
+ }
458
+ async function gitInit(projectPath) {
459
+ const { execFile } = await import("child_process");
460
+ try {
461
+ await new Promise((resolve2, reject) => {
462
+ execFile(
463
+ "git",
464
+ ["init"],
465
+ { cwd: projectPath },
466
+ (err) => err ? reject(err) : resolve2()
467
+ );
468
+ });
469
+ return true;
470
+ } catch {
471
+ return false;
472
+ }
473
+ }
474
+ async function writeRootFiles(info) {
475
+ const { writeFile: writeFile2 } = await import("fs/promises");
476
+ const { join: join3 } = await import("path");
477
+ const projectPath = resolve(process.cwd(), info.projectName);
478
+ await writeRootGitignore(projectPath);
447
479
  const installB = info.backend.postInit?.install ? resolveCommand(info.backend.postInit.install, info.pm) : `${info.pm} install`;
448
480
  const devB = info.backend.postInit?.dev ? resolveCommand(info.backend.postInit.dev, info.pm) : `${info.pm} run dev`;
449
481
  const installF = info.frontend.postInit?.install ? resolveCommand(info.frontend.postInit.install, info.pm) : `${info.pm} install`;
@@ -526,9 +558,9 @@ async function readTemplatePostInit(dir) {
526
558
  return void 0;
527
559
  }
528
560
  }
529
- async function createFullStackProject(projectName, backendTpl, frontendTpl, pm) {
561
+ async function createFullStackProject(projectPath, backendTpl, frontendTpl, pm) {
530
562
  const { tempDir, repoRoot } = await downloadAndExtractRepo();
531
- const projectPath = getProjectPath(projectName);
563
+ const projectName = basename(projectPath);
532
564
  const backendPath = join2(projectPath, "backend");
533
565
  const frontendPath = join2(projectPath, "frontend");
534
566
  await copyTemplate(
@@ -595,10 +627,10 @@ program.command("list").description("Mostrar templates disponibles").action(() =
595
627
  console.log();
596
628
  }
597
629
  });
598
- program.argument("[template]", "Template a usar (o 'fullstack' para backend + frontend)").argument("[project-name]", "Nombre del proyecto").option("-p, --pm <package-manager>", "Package manager a usar: npm, pnpm o bun").option("--backend", "Empaquetar el c\xF3digo en backend/ (saltea la pregunta)").option("--no-backend", "Dejar el c\xF3digo en la ra\xEDz del proyecto (saltea la pregunta)").option("-b, --backend-template <tpl>", "Backend a usar en fullstack (saltea la pregunta)").option("-f, --frontend-template <tpl>", "Frontend a usar en fullstack (saltea la pregunta)").action(
630
+ program.argument("[template]", "Template a usar (o 'fullstack' para backend + frontend)").argument("[project-name]", "Nombre del proyecto").option("-p, --pm <package-manager>", "Package manager a usar: npm, pnpm o bun").option("--backend", "Empaquetar el c\xF3digo en backend/ (saltea la pregunta)").option("--no-backend", "Dejar el c\xF3digo en la ra\xEDz del proyecto (saltea la pregunta)").option("-b, --backend-template <tpl>", "Backend a usar en fullstack (saltea la pregunta)").option("-f, --frontend-template <tpl>", "Frontend a usar en fullstack (saltea la pregunta)").option("--git", "Inicializar un repositorio git (saltea la pregunta)").option("--no-git", "No inicializar git (saltea la pregunta)").action(
599
631
  async (templateArg, projectNameArg) => {
600
632
  try {
601
- const { pm: pmArg, backend, backendTemplate, frontendTemplate } = program.opts();
633
+ const { pm: pmArg, backend, backendTemplate, frontendTemplate, git } = program.opts();
602
634
  if (pmArg && !PACKAGE_MANAGERS.includes(pmArg)) {
603
635
  console.error(
604
636
  chalk.red(
@@ -688,6 +720,7 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
688
720
  if (!pm && needsPm) {
689
721
  pm = await selectPackageManager();
690
722
  }
723
+ const wantsGit = git === void 0 ? await askGitInit() : git;
691
724
  if (projectExists(projectName)) {
692
725
  console.error(
693
726
  chalk.red(
@@ -697,21 +730,29 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
697
730
  );
698
731
  process.exit(1);
699
732
  }
733
+ const projectPath = getProjectPath(projectName);
700
734
  const spinner = ora(
701
735
  isFullStack ? "Descargando templates..." : `Descargando template ${template.name}...`
702
736
  ).start();
703
737
  if (isFullStack) {
704
738
  spinner.text = "Creando proyecto...";
705
- await createFullStackProject(projectName, backendTpl, frontendTpl, pm ?? "npm");
739
+ await createFullStackProject(
740
+ projectPath,
741
+ backendTpl,
742
+ frontendTpl,
743
+ pm ?? "npm"
744
+ );
706
745
  spinner.succeed("Template descargado");
707
746
  } else {
708
747
  const { tempDir, templatePath, repoRoot } = await downloadAndExtract(template.folder);
709
- const projectPath = getProjectPath(projectName);
710
748
  const codePath = useLayerLayout ? join2(projectPath, layer) : projectPath;
711
749
  spinner.text = "Creando proyecto...";
712
750
  await copyTemplate(templatePath, codePath);
713
751
  await substituteTemplate(codePath, projectName, template.folder);
714
752
  await installOpenCodeFiles(repoRoot, projectPath);
753
+ if (useLayerLayout) {
754
+ await writeRootGitignore(projectPath);
755
+ }
715
756
  if (template.runtime === "bun" && pm && pm !== "bun") {
716
757
  await adaptToNodeRuntime(codePath);
717
758
  }
@@ -733,6 +774,20 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
733
774
  );
734
775
  }
735
776
  }
777
+ if (wantsGit) {
778
+ const ok = await gitInit(projectPath);
779
+ if (ok) {
780
+ console.log(
781
+ chalk.dim(" Git: repositorio inicializado\n")
782
+ );
783
+ } else {
784
+ console.log(
785
+ chalk.yellow(
786
+ " \u26A0 No se pudo inicializar git (\xBFest\xE1 instalado?)\n"
787
+ )
788
+ );
789
+ }
790
+ }
736
791
  } catch (error) {
737
792
  console.error(
738
793
  chalk.red(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fwinit",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "CLI para crear proyectos desde templates",
5
5
  "type": "module",
6
6
  "bin": {