Skip to content

Commit da1b426

Browse files
committed
feat: add CI/CD pipeline, integrate clack/prompts, and implement project workspace utilities with extended testing coverage.
1 parent 7b8ef24 commit da1b426

13 files changed

Lines changed: 640 additions & 325 deletions

File tree

.github/workflows/ci.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
name: Test Generation with ${{ matrix.pm }}
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
pm: [npm, pnpm, yarn, bun]
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js 20
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
26+
- name: Setup pnpm
27+
if: matrix.pm == 'pnpm'
28+
uses: pnpm/action-setup@v3
29+
with:
30+
version: 9
31+
32+
- name: Setup bun
33+
if: matrix.pm == 'bun'
34+
uses: oven-sh/setup-bun@v1
35+
36+
# Core checks on all matrices (since it's cheap)
37+
- name: Install CLI dependencies
38+
run: npm ci
39+
40+
- name: Run Biome Lint
41+
run: npm run lint
42+
43+
- name: Run Typecheck
44+
run: npm run typecheck
45+
46+
- name: Run Unit Tests
47+
run: npm run test:run
48+
49+
- name: Build CLI
50+
run: npm run build
51+
52+
- name: Test Project Generation (${{ matrix.pm }})
53+
run: |
54+
mkdir -p /tmp/test-gen
55+
cd /tmp/test-gen
56+
node $GITHUB_WORKSPACE/dist/index.js test-app --pm ${{ matrix.pm }} --no-git
57+
cd test-app
58+
59+
# Verificar que instaló dependencias (node_modules existe)
60+
if [ ! -d "node_modules" ]; then
61+
echo "❌ node_modules no fue creado por ${{ matrix.pm }}"
62+
exit 1
63+
fi
64+
65+
echo "✅ Generación e instalación exitosa con ${{ matrix.pm }}"

AGENT_TASKS.md

Lines changed: 28 additions & 259 deletions
Original file line numberDiff line numberDiff line change
@@ -1,269 +1,38 @@
1-
# AGENT_TASKS.md — Fase 1: Robustez Absoluta y Cobertura
1+
# Tareas de Agentes IA — Fase 2: Flexibilidad Interna y DX
22

3-
> Guía de proceso para agentes de IA trabajando en la Fase 1 del roadmap (v0.4.x - v0.5.x).
4-
>
5-
> Este documento complementa al `AGENTS.md` del template y se enfoca exclusivamente en las tareas de robustez y cobertura del CLI scaffolder.
3+
Este archivo es el **Sprint Plan** para los agentes de IA (OpenClaw, Cursor, Claude). Define las reglas de trabajo y las tareas específicas para la **Fase 2**.
64

7-
---
8-
9-
## 🎯 Objetivo de la Fase 1
10-
11-
**El CLI nunca debe fallar de manera inesperada ("crash") bajo ninguna condición local.**
12-
13-
Esto implica:
14-
1. Testing exhaustivo de todos los flujos del scaffolder
15-
2. Soporte confiable para `npm`, `pnpm`, `yarn` y `bun`
16-
3. Manejo amigable de edge cases del file system
17-
4. Inicialización Git robusta con fallbacks limpios
18-
19-
---
20-
21-
## 📋 Workflow Obligatorio por Tarea
22-
23-
Antes de implementar CUALQUIER tarea de esta fase, el agente DEBE seguir este ciclo:
24-
25-
1. **Leer contexto**: `docs/decisions.md`, `CHANGELOG.md`, `ROADMAP.md`
26-
2. **Verificar estado actual**: Correr `npm run test:run` para confirmar que todo pasa antes de tocar código
27-
3. **Implementar** siguiendo las convenciones del `AGENTS.md` del template
28-
4. **Testear**: Escribir tests ANTES o JUNTO con el código (no después)
29-
5. **Lint**: Correr `npm run lint:fix` tras cada modificación
30-
6. **Typecheck**: Correr `npm run typecheck` para verificar tipos
31-
7. **Documentar**: Actualizar `docs/decisions.md` (ADR) si se tomó una decisión técnica
32-
8. **Changelog**: Registrar cambios en `CHANGELOG.md` bajo `[Unreleased]`
33-
34-
---
35-
36-
## 📐 Decisiones de Diseño Aprobadas
37-
38-
| # | Decisión | Resolución |
39-
|---|----------|------------|
40-
| D002 | Exportar funciones internas (`validateProjectName`, `execInDir`) | ✅ Aprobado — para testabilidad directa |
41-
| D003 | Fallback Git sin `user.name`/`user.email` | ✅ Opción A — usar valores genéricos (`create-stack-next` / `noreply@create-stack-next`) con warning |
42-
| D004 | Directorio destino existente vacío | ✅ Aceptar y continuar generación. Solo rechazar si NO está vacío |
43-
| D005 | Tests multi-PM en CI | ⏸️ Pospuesto a fase posterior. Tests locales usan `skipIf` si el PM no está disponible |
44-
45-
---
46-
47-
## 🛠️ Épica 1: Testing Exhaustivo del Scaffolder
48-
49-
### Archivos involucrados
50-
- `src/test/cli.test.ts`
51-
- `src/test/copy-template.test.ts`
52-
- `src/test/integration.test.ts`
53-
54-
### Tareas
55-
56-
#### 1.1 Ampliar `cli.test.ts`
57-
58-
**Estado actual:** 13 tests que validan parsing de args y regex de nombres (sin usar `validateProjectName` directamente).
59-
60-
**Tests nuevos requeridos:**
61-
- [ ] Importar y testear `validateProjectName` directamente
62-
- [ ] Nombre vacío (`""`) → `{ ok: false }`
63-
- [ ] Nombre con caracteres especiales (`@scope/name`, `my-app!`, `foo/bar`) → `{ ok: false }`
64-
- [ ] Directorio existente NO vacío → `{ ok: false, reason: "...ya existe" }`
65-
- [ ] Directorio existente VACÍO → `{ ok: true }`
66-
- [ ] Nombre que empieza con `-``{ ok: false }`
67-
- [ ] `parseArgs` con múltiples flags combinadas (`--no-git --no-install --pm pnpm`)
68-
69-
**Criterio de aceptación:** Cobertura completa de `validateProjectName` y `parseArgs`.
70-
71-
#### 1.2 Ampliar `copy-template.test.ts`
72-
73-
**Estado actual:** 2 tests (nombre en package.json + PM en README).
74-
75-
**Tests nuevos requeridos:**
76-
- [ ] Verificar que todos los archivos del template se copian (comparar `listFiles(templateDir)` vs `listFiles(targetDir)`)
77-
- [ ] Verificar que dotfiles se copian (`.gitignore`, `.openclaw/`, `.agents/`, `.github/`, `.vscode/`)
78-
- [ ] Verificar que archivos sin placeholders no se modifican (comparar contenido byte a byte)
79-
- [ ] Verificar que paths anidados profundos existen (`docs/`, `src/app/`, `tests/e2e/`)
80-
81-
**Criterio de aceptación:** La copia del template es verificada exhaustivamente.
82-
83-
#### 1.3 Ampliar `integration.test.ts`
84-
85-
**Estado actual:** 1 test que genera un proyecto con `npm` y corre lint/typecheck/test/build.
86-
87-
**Tests nuevos requeridos (condicionales):**
88-
- [ ] Generación con `pnpm` (skip si no está instalado)
89-
- [ ] Generación con `yarn` (skip si no está instalado)
90-
- [ ] Generación con `bun` (skip si no está instalado)
91-
92-
> **Nota:** Estos tests multi-PM se posponen a una fase posterior según decisión del usuario. En esta fase solo se deja la estructura preparada con `it.skip`.
93-
94-
**Criterio de aceptación:** Test de `npm` robusto + placeholders para otros PMs.
95-
96-
---
97-
98-
## 🛠️ Épica 2: Garantía Multi-Package Manager
5+
## 🛑 Workflow Obligatorio por Tarea
996

100-
> ⏸️ **Pospuesto a fase posterior** según decisión del usuario.
101-
> Solo se valida que el flag `--pm` parsea correctamente y que `runInstall` construye el comando correcto.
102-
103-
### Tareas mínimas para esta fase
104-
- [ ] Tests unitarios de `parseArgs` ya cubren validación de `--pm`
105-
- [ ] Agregar test que verifique que el output de "Próximos pasos" muestra el PM correcto
106-
107-
---
108-
109-
## 🛠️ Épica 3: Edge Cases de File System
110-
111-
### Archivos involucrados
112-
- `src/cli.ts` (modificar `validateProjectName`)
113-
114-
### Tareas
115-
116-
#### 3.1 Directorio existente vacío → permitir
117-
118-
**Cambio en `validateProjectName`:**
119-
```typescript
120-
// Antes: rechaza si existe
121-
if (existsSync(name)) {
122-
return { ok: false, reason: `El directorio "${name}" ya existe` };
123-
}
124-
125-
// Después: rechaza solo si existe Y no está vacío
126-
if (existsSync(name)) {
127-
const entries = readdirSync(name);
128-
// Permitir .DS_Store y similares
129-
const meaningful = entries.filter(e => !e.startsWith('.DS_'));
130-
if (meaningful.length > 0) {
131-
return { ok: false, reason: `El directorio "${name}" ya existe y no está vacío` };
132-
}
133-
}
134-
```
135-
136-
#### 3.2 Validación de permisos de escritura
137-
138-
**Nuevo chequeo antes de crear el directorio:**
139-
```typescript
140-
// Verificar que el directorio padre tiene permisos de escritura
141-
try {
142-
accessSync(dirname(resolve(name)), constants.W_OK);
143-
} catch {
144-
return { ok: false, reason: `Sin permisos de escritura en "${dirname(resolve(name))}"` };
145-
}
146-
```
147-
148-
#### 3.3 Tests para edge cases de FS
149-
150-
- [ ] Directorio existente no vacío → error con mensaje claro
151-
- [ ] Directorio existente vacío → OK
152-
- [ ] Directorio existente con solo `.DS_Store` → OK
153-
154-
---
155-
156-
## 🛠️ Épica 4: Robustez en Inicialización Git
157-
158-
### Archivos involucrados
159-
- `src/cli.ts` (modificar `runGitInit`)
160-
- `src/test/git.test.ts` (nuevo)
161-
162-
### Tareas
163-
164-
#### 4.1 Detectar si Git está instalado
165-
166-
```typescript
167-
async function isGitAvailable(): Promise<boolean> {
168-
try {
169-
await execInDir("git", ["--version"], process.cwd());
170-
return true;
171-
} catch {
172-
return false;
173-
}
174-
}
175-
```
176-
177-
#### 4.2 Detectar y manejar `user.name` / `user.email`
178-
179-
```typescript
180-
async function getGitConfig(key: string): Promise<string | null> {
181-
try {
182-
// Capturar stdout en vez de heredar
183-
const value = await execCapture("git", ["config", "--global", key]);
184-
return value.trim() || null;
185-
} catch {
186-
return null;
187-
}
188-
}
189-
```
190-
191-
#### 4.3 Fallback con valores genéricos
192-
193-
```typescript
194-
async function runGitInit(projectDir: string): Promise<void> {
195-
// 1. Verificar que git está disponible
196-
if (!(await isGitAvailable())) {
197-
logStep("⚠️", "Git no encontrado. Saltando inicialización git.");
198-
logStep("💡", `Instalá Git y corré ${pc.cyan("git init")} manualmente.`);
199-
return;
200-
}
201-
202-
await execInDir("git", ["init", "-b", "main"], projectDir);
203-
await execInDir("git", ["add", "."], projectDir);
204-
205-
// 2. Verificar user.name/user.email
206-
const userName = await getGitConfig("user.name");
207-
const userEmail = await getGitConfig("user.email");
208-
209-
if (!userName || !userEmail) {
210-
logStep("⚠️", "Git user.name/user.email no configurados. Usando valores temporales.");
211-
await execInDir(
212-
"git",
213-
[
214-
"-c", "user.name=create-stack-next",
215-
"-c", "user.email=noreply@create-stack-next",
216-
"commit", "-m", "chore: initial commit from create-stack-next",
217-
],
218-
projectDir,
219-
);
220-
logStep("💡", `Configurá tu Git: ${pc.cyan("git config --global user.name \"Tu Nombre\"")}`);
221-
} else {
222-
await execInDir(
223-
"git",
224-
["commit", "-m", "chore: initial commit from create-stack-next"],
225-
projectDir,
226-
);
227-
}
228-
}
229-
```
230-
231-
#### 4.4 Tests de Git (`src/test/git.test.ts`)
232-
233-
- [ ] Mock de `execInDir` para simular git no disponible → verifica warning
234-
- [ ] Mock para simular `user.name`/`user.email` no configurados → verifica fallback
235-
- [ ] Test de `--no-git` → verifica que `runGitInit` no se ejecuta
236-
- [ ] Test de flujo exitoso completo
7+
Antes de escribir una sola línea de código, el agente DEBE:
8+
1. Leer el `ROADMAP.md` y este archivo para contexto.
9+
2. Si la tarea implica una decisión arquitectónica nueva o cambio de diseño, documentarla como ADR en `docs/decisions.md`.
10+
3. Planificar la implementación paso a paso (en la memoria o artifacts temporales).
11+
4. Ejecutar cambios atómicos.
12+
5. Correr linting (`npm run lint`), typecheck (`npm run typecheck`) y tests (`npm run test:run`).
13+
6. Actualizar `CHANGELOG.md` en la sección `[Unreleased]`.
23714

23815
---
23916

240-
## 📚 Documentación Requerida
241-
242-
Al completar CADA épica, actualizar:
17+
## 🎯 Épicas Activas (Fase 2)
24318

244-
1. **`docs/decisions.md`** — Nuevo ADR para cada decisión técnica tomada
245-
2. **`CHANGELOG.md`** — Bajo `[Unreleased]`:
246-
- `Added`: tests nuevos, `AGENT_TASKS.md`
247-
- `Changed`: validaciones mejoradas, Git robusto
248-
- `Fixed`: edge cases de FS
249-
3. **`ROADMAP.md`** — Marcar tareas completadas con `[x]`
19+
### Épica 1: Soporte Oficial para Workspaces (Monorepos)
20+
El CLI debe funcionar de forma fluida si se lanza dentro de un monorepo (ej: carpeta `apps/`).
21+
- [ ] Implementar función para detectar si estamos en un workspace (buscar `pnpm-workspace.yaml`, `turbo.json`, `lerna.json` o campo `workspaces` en `package.json` hacia arriba).
22+
- [ ] Si es un workspace: saltar automáticamente el `git init` (o preguntar) para evitar repos anidados.
23+
- [ ] Escribir tests unitarios para la detección de workspaces.
25024

251-
---
252-
253-
## ✅ Criterios de "Done" para la Fase 1
25+
### Épica 2: Pulido Visual Extremo del CLI
26+
La terminal debe sentirse "premium" (spinners, colores consistentes).
27+
- [ ] Instalar `@clack/prompts` como dependencia.
28+
- [ ] Refactorizar el flujo actual de logs para usar los componentes de Clack (intro, spinners para instalación de npm y git, outro).
29+
- [ ] Estandarizar el uso de `picocolors` para outputs intermedios o custom.
25430

255-
- [ ] `npm run test:run` pasa al 100%
256-
- [ ] `npm run typecheck` sin errores
257-
- [ ] `npm run lint` sin errores
258-
- [ ] `validateProjectName` cubre: nombre vacío, caracteres inválidos, directorio existente vacío/no vacío, permisos
259-
- [ ] `runGitInit` no crashea bajo ninguna condición (git no instalado, user no configurado)
260-
- [ ] Todos los archivos del template se copian correctamente (verificado por test)
261-
- [ ] ADRs documentados (D002, D003, D004)
262-
- [ ] CHANGELOG actualizado
263-
- [ ] ROADMAP Fase 1 checklist marcada como completada
264-
265-
---
31+
### Épica 3: Template API (`--template api`)
32+
Permitir generar un backend puro sin UI React.
33+
- [ ] Agregar el flag `--template` (con valores `app` | `api`, por defecto `app`) en `parse-args.ts`.
34+
- [ ] Crear la carpeta `template-api/` con la base (ej: un Next.js solo con Route Handlers o un setup agnóstico).
35+
- [ ] Ajustar la lógica de copia en `cli.ts` para usar la carpeta de template correspondiente.
26636

267-
**Última actualización:** 2026-06-30
268-
**Fase:** 1 de 3 (Robustez Absoluta y Cobertura)
269-
**Versión target:** v0.4.0 - v0.5.0
37+
### Épica 4: Garantía Multi-PM en CI (De Fase 1)
38+
- [ ] Configurar el GitHub Action (`.github/workflows/ci.yml`) para que pruebe la generación de proyecto con `npm`, `pnpm`, `yarn` y `bun`.

README.md

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,11 @@ El proyecto incluye:
6767

6868
## Roadmap
6969

70-
### M0 — Definición ✅
71-
### M1 — MVP ✅
72-
### M2 — AI-native ✅
73-
### M3 — Adopción (Lite) ⏳ En progreso
70+
El proyecto se encuentra en desarrollo activo hacia la versión estable (v1.0.0). Podés ver el detalle completo y estado actual en [`ROADMAP.md`](ROADMAP.md).
7471

75-
- [ ] README pulido
76-
- [ ] Video demo (2-4 min)
77-
- [ ] Release note
78-
- [ ] Presencia básica
79-
80-
### M4 — Estabilización y v1.0.0 🎯 Próximo
81-
82-
- Documentación de decisiones (ADRs) persistente.
83-
- Testing exhaustivo del Scaffolder y manejo de Edge Cases.
84-
- Refinamiento de la Developer Experience (DX).
72+
- **Fase 1: Robustez Absoluta** ✅ (Completada en v0.5.0)
73+
- **Fase 2: Flexibilidad Interna y DX** ⏳ En progreso
74+
- **Fase 3: Adopción y Documentación** 🎯 Próximo
8575

8676
Ver [`FUTURE.md`](FUTURE.md) para el registro de ideas futuras.
8777

0 commit comments

Comments
 (0)