From f1a69d4b9613719461349e968099a1bf80888d49 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:02:57 -0300 Subject: [PATCH 1/7] =?UTF-8?q?v0.8.0:=20reidentificaci=C3=B3n=20a=20@gonz?= =?UTF-8?q?oblasco/create-stack=20con=20stacks=20modulares?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renombre: create-stack-next → @gonzoblasco/create-stack - Nuevo CLI: npx @gonzoblasco/create-stack [nombre] - Arquitectura modular: cada stack en src/stacks// - Stacks: next (full app) y api (API-only) - Compatibilidad hacia atrás con formato legacy - ADRs, OpenSpec specs, BRIEF y STATUS documentados - 67 tests pasando --- .knowledge/BRIEF.md | 26 +++ .knowledge/STATUS.md | 28 +++ ...001-use-openspec-for-product-definition.md | 10 + ...-to-create-stack-with-positional-stacks.md | 16 ++ .../adr/0003-modular-stack-architecture.md | 14 ++ .knowledge/specs/product.md | 119 ++++++++++ CHANGELOG.md | 214 +++--------------- FUTURE.md | 21 +- HANDOFF.md | 123 ++++------ README.md | 101 ++++++--- ROADMAP.md | 22 +- package-lock.json | 36 +-- package.json | 18 +- src/cli.ts | 102 +++++---- src/parse-args.ts | 45 +++- src/stacks/api/index.ts | 16 ++ .../api/template}/.vscode/extensions.json | 0 .../api/template}/.vscode/settings.json | 0 .../stacks/api/template}/AGENTS.md | 0 .../stacks/api/template}/AGENT_TASKS.md | 0 .../stacks/api/template}/CHANGELOG.md | 0 .../stacks/api/template}/HANDOFF.md | 0 .../stacks/api/template}/README.md | 0 .../stacks/api/template}/ROADMAP.md | 0 .../stacks/api/template}/biome.json | 0 .../stacks/api/template}/docs/architecture.md | 0 .../stacks/api/template}/docs/contributing.md | 0 .../stacks/api/template}/docs/decisions.md | 0 .../stacks/api/template}/docs/workflow_ai.md | 0 .../stacks/api/template}/drizzle.config.ts | 0 .../stacks/api/template}/gitignore | 0 .../openspec/changes/archive/.gitkeep | 0 .../stacks/api/template}/openspec/config.yaml | 0 .../api/template}/openspec/specs/.gitkeep | 0 .../stacks/api/template}/package.json | 0 .../api/template}/src/app/api/health/route.ts | 0 .../api/template}/src/app/api/users/route.ts | 0 .../api/template}/src/lib/api-response.ts | 0 .../stacks/api/template}/src/lib/db.ts | 0 .../stacks/api/template}/src/lib/errors.ts | 0 .../stacks/api/template}/src/lib/schema.ts | 0 .../stacks/api/template}/src/middleware.ts | 0 .../api/template}/src/test/health.test.ts | 0 .../stacks/api/template}/tsconfig.json | 0 .../stacks/api/template}/vitest.config.ts | 0 src/stacks/index.ts | 34 +++ src/stacks/next/index.ts | 30 +++ .../stacks/next/template}/.agents/config.json | 0 .../next/template}/.agents/prompts/bugfix.md | 0 .../next/template}/.agents/prompts/feature.md | 0 .../template}/.agents/prompts/refactor.md | 0 .../next/template}/.agents/prompts/tests.md | 0 .../next/template}/.github/workflows/ci.yml | 0 .../.github/workflows/playwright.yml | 0 .../stacks/next/template}/.openclaw/README.md | 0 .../next/template}/.openclaw/config.json | 0 .../template}/.openclaw/prompts/bugfix.md | 0 .../template}/.openclaw/prompts/feature.md | 0 .../template}/.openclaw/prompts/refactor.md | 0 .../next/template}/.openclaw/prompts/tests.md | 0 .../next/template}/.vscode/extensions.json | 0 .../next/template}/.vscode/settings.json | 0 .../stacks/next/template}/AGENTS.md | 0 .../stacks/next/template}/AGENT_TASKS.md | 0 .../stacks/next/template}/CHANGELOG.md | 0 .../stacks/next/template}/HANDOFF.md | 0 .../stacks/next/template}/LICENSE | 0 .../stacks/next/template}/README.md | 0 .../stacks/next/template}/ROADMAP.md | 0 .../stacks/next/template}/biome.json | 0 .../next/template}/docs/architecture.md | 0 .../next/template}/docs/contributing.md | 0 .../stacks/next/template}/docs/decisions.md | 0 .../stacks/next/template}/docs/workflow_ai.md | 0 .../stacks/next/template}/gitignore | 0 .../stacks/next/template}/next.config.ts | 0 .../openspec/changes/archive/.gitkeep | 0 .../next/template}/openspec/config.yaml | 0 .../next/template}/openspec/specs/.gitkeep | 0 .../stacks/next/template}/package.json | 0 .../next/template}/playwright.config.ts | 0 .../stacks/next/template}/public/.gitkeep | 0 .../template}/src/app/api/health/route.ts | 0 .../stacks/next/template}/src/app/globals.css | 0 .../stacks/next/template}/src/app/layout.tsx | 0 .../stacks/next/template}/src/app/page.tsx | 0 .../next/template}/src/components/ui/.gitkeep | 0 .../stacks/next/template}/src/global.d.ts | 0 .../stacks/next/template}/src/lib/env.ts | 0 .../stacks/next/template}/src/lib/utils.ts | 0 .../next/template}/src/test/example.test.ts | 0 .../stacks/next/template}/src/test/setup.ts | 0 .../next/template}/tests/e2e/home.spec.ts | 0 .../stacks/next/template}/tsconfig.json | 0 .../stacks/next/template}/vitest.config.ts | 0 src/stacks/types.ts | 14 ++ src/test/cli.test.ts | 64 +++--- src/test/copy-template.test.ts | 58 +++-- src/test/git.test.ts | 8 +- src/test/integration.test.ts | 12 +- src/test/openspec.test.ts | 19 +- tsconfig.json | 2 +- vitest.config.ts | 5 + 103 files changed, 686 insertions(+), 471 deletions(-) create mode 100644 .knowledge/BRIEF.md create mode 100644 .knowledge/STATUS.md create mode 100644 .knowledge/adr/0001-use-openspec-for-product-definition.md create mode 100644 .knowledge/adr/0002-rename-to-create-stack-with-positional-stacks.md create mode 100644 .knowledge/adr/0003-modular-stack-architecture.md create mode 100644 .knowledge/specs/product.md create mode 100644 src/stacks/api/index.ts rename {template-api => src/stacks/api/template}/.vscode/extensions.json (100%) rename {template-api => src/stacks/api/template}/.vscode/settings.json (100%) rename {template-api => src/stacks/api/template}/AGENTS.md (100%) rename {template-api => src/stacks/api/template}/AGENT_TASKS.md (100%) rename {template-api => src/stacks/api/template}/CHANGELOG.md (100%) rename {template-api => src/stacks/api/template}/HANDOFF.md (100%) rename {template-api => src/stacks/api/template}/README.md (100%) rename {template-api => src/stacks/api/template}/ROADMAP.md (100%) rename {template-api => src/stacks/api/template}/biome.json (100%) rename {template-api => src/stacks/api/template}/docs/architecture.md (100%) rename {template-api => src/stacks/api/template}/docs/contributing.md (100%) rename {template-api => src/stacks/api/template}/docs/decisions.md (100%) rename {template-api => src/stacks/api/template}/docs/workflow_ai.md (100%) rename {template-api => src/stacks/api/template}/drizzle.config.ts (100%) rename {template-api => src/stacks/api/template}/gitignore (100%) rename {template-api => src/stacks/api/template}/openspec/changes/archive/.gitkeep (100%) rename {template-api => src/stacks/api/template}/openspec/config.yaml (100%) rename {template-api => src/stacks/api/template}/openspec/specs/.gitkeep (100%) rename {template-api => src/stacks/api/template}/package.json (100%) rename {template-api => src/stacks/api/template}/src/app/api/health/route.ts (100%) rename {template-api => src/stacks/api/template}/src/app/api/users/route.ts (100%) rename {template-api => src/stacks/api/template}/src/lib/api-response.ts (100%) rename {template-api => src/stacks/api/template}/src/lib/db.ts (100%) rename {template-api => src/stacks/api/template}/src/lib/errors.ts (100%) rename {template-api => src/stacks/api/template}/src/lib/schema.ts (100%) rename {template-api => src/stacks/api/template}/src/middleware.ts (100%) rename {template-api => src/stacks/api/template}/src/test/health.test.ts (100%) rename {template-api => src/stacks/api/template}/tsconfig.json (100%) rename {template-api => src/stacks/api/template}/vitest.config.ts (100%) create mode 100644 src/stacks/index.ts create mode 100644 src/stacks/next/index.ts rename {template => src/stacks/next/template}/.agents/config.json (100%) rename {template => src/stacks/next/template}/.agents/prompts/bugfix.md (100%) rename {template => src/stacks/next/template}/.agents/prompts/feature.md (100%) rename {template => src/stacks/next/template}/.agents/prompts/refactor.md (100%) rename {template => src/stacks/next/template}/.agents/prompts/tests.md (100%) rename {template => src/stacks/next/template}/.github/workflows/ci.yml (100%) rename {template => src/stacks/next/template}/.github/workflows/playwright.yml (100%) rename {template => src/stacks/next/template}/.openclaw/README.md (100%) rename {template => src/stacks/next/template}/.openclaw/config.json (100%) rename {template => src/stacks/next/template}/.openclaw/prompts/bugfix.md (100%) rename {template => src/stacks/next/template}/.openclaw/prompts/feature.md (100%) rename {template => src/stacks/next/template}/.openclaw/prompts/refactor.md (100%) rename {template => src/stacks/next/template}/.openclaw/prompts/tests.md (100%) rename {template => src/stacks/next/template}/.vscode/extensions.json (100%) rename {template => src/stacks/next/template}/.vscode/settings.json (100%) rename {template => src/stacks/next/template}/AGENTS.md (100%) rename {template => src/stacks/next/template}/AGENT_TASKS.md (100%) rename {template => src/stacks/next/template}/CHANGELOG.md (100%) rename {template => src/stacks/next/template}/HANDOFF.md (100%) rename {template => src/stacks/next/template}/LICENSE (100%) rename {template => src/stacks/next/template}/README.md (100%) rename {template => src/stacks/next/template}/ROADMAP.md (100%) rename {template => src/stacks/next/template}/biome.json (100%) rename {template => src/stacks/next/template}/docs/architecture.md (100%) rename {template => src/stacks/next/template}/docs/contributing.md (100%) rename {template => src/stacks/next/template}/docs/decisions.md (100%) rename {template => src/stacks/next/template}/docs/workflow_ai.md (100%) rename {template => src/stacks/next/template}/gitignore (100%) rename {template => src/stacks/next/template}/next.config.ts (100%) rename {template => src/stacks/next/template}/openspec/changes/archive/.gitkeep (100%) rename {template => src/stacks/next/template}/openspec/config.yaml (100%) rename {template => src/stacks/next/template}/openspec/specs/.gitkeep (100%) rename {template => src/stacks/next/template}/package.json (100%) rename {template => src/stacks/next/template}/playwright.config.ts (100%) rename {template => src/stacks/next/template}/public/.gitkeep (100%) rename {template => src/stacks/next/template}/src/app/api/health/route.ts (100%) rename {template => src/stacks/next/template}/src/app/globals.css (100%) rename {template => src/stacks/next/template}/src/app/layout.tsx (100%) rename {template => src/stacks/next/template}/src/app/page.tsx (100%) rename {template => src/stacks/next/template}/src/components/ui/.gitkeep (100%) rename {template => src/stacks/next/template}/src/global.d.ts (100%) rename {template => src/stacks/next/template}/src/lib/env.ts (100%) rename {template => src/stacks/next/template}/src/lib/utils.ts (100%) rename {template => src/stacks/next/template}/src/test/example.test.ts (100%) rename {template => src/stacks/next/template}/src/test/setup.ts (100%) rename {template => src/stacks/next/template}/tests/e2e/home.spec.ts (100%) rename {template => src/stacks/next/template}/tsconfig.json (100%) rename {template => src/stacks/next/template}/vitest.config.ts (100%) create mode 100644 src/stacks/types.ts diff --git a/.knowledge/BRIEF.md b/.knowledge/BRIEF.md new file mode 100644 index 0000000..e046bd7 --- /dev/null +++ b/.knowledge/BRIEF.md @@ -0,0 +1,26 @@ +# BRIEF: @gonzoblasco/create-stack + +## Qué es + +Scaffolder opinado multi-stack. Un solo comando y tenés un proyecto moderno, testeado, lintado y listo para que un AI agent lo habite. + +## Por qué existe + +Gonzo arranca proyectos constantemente y se cansó de repetir las mismas 40 decisiones. `create-stack-next` demostró el concepto pero quedó atado a Next.js. Esta es la evolución: un CLI que permite elegir el stack como argumento posicional, con arquitectura modular para agregar stacks sin tocar el core. + +## Stack inicial + +- **next:** Next.js 15 + React 19 + TypeScript estricto + Biome + Vitest + Playwright + Zod + OpenSpec + AI agent config +- **api:** Next.js 15 App Router (API-only) + Drizzle ORM + SQLite + middleware Bearer + tests + +## Stack futuros (en consideración) + +- tanstack, astro, cli + +## Diferenciador + +No es solo un scaffolder de código. Es un scaffolder de **entorno de desarrollo**: tests, lint, CI, AI agent config, OpenSpec, todo desde el día 1. El proyecto generado está listo para que un agente IA lo habite productivamente. + +## Público + +Gonzo (uso primario), y cualquier dev que quiera arrancar proyectos rápido con buena base técnica y AI-ready. diff --git a/.knowledge/STATUS.md b/.knowledge/STATUS.md new file mode 100644 index 0000000..d62f536 --- /dev/null +++ b/.knowledge/STATUS.md @@ -0,0 +1,28 @@ +# STATUS: @gonzoblasco/create-stack + +**Fecha:** 2026-07-20 +**Fase:** Reidentificación (v2-rebrand) +**Última versión publicada:** 0.7.2 (como create-stack-next) + +## Estado actual + +En plena reidentificación de `create-stack-next` → `@gonzoblasco/create-stack`. Se está refactorizando la arquitectura a stacks modulares, renombrando el paquete, y documentando con ADRs + OpenSpec specs. + +## Lo que está pasando ahora + +- [x] ADRs de decisiones arquitectónicas (3/3) +- [x] OpenSpec specs del producto (7 specs) +- [x] BRIEF definido +- [ ] Refactor de `src/` a arquitectura modular +- [ ] Migración de templates a `src/stacks/` +- [ ] Renombre de package.json y bin +- [ ] Tests actualizados +- [ ] Documentación actualizada + +## Próximos pasos inmediatos + +1. Refactorizar `src/` (parse-args, cli, index) +2. Mover templates a `src/stacks/next/template/` y `src/stacks/api/template/` +3. Renombrar package.json +4. Actualizar tests +5. Verificar build + lint + typecheck + tests diff --git a/.knowledge/adr/0001-use-openspec-for-product-definition.md b/.knowledge/adr/0001-use-openspec-for-product-definition.md new file mode 100644 index 0000000..e843006 --- /dev/null +++ b/.knowledge/adr/0001-use-openspec-for-product-definition.md @@ -0,0 +1,10 @@ +# ADR 0001: Usar OpenSpec para definición de producto + +- **Fecha:** 2026-07-20 +- **Contexto:** Reidentificación de `create-stack-next` → `@gonzoblasco/create-stack`. Necesitamos documentar decisiones de producto y especificaciones de manera estructurada y ejecutable por IA. +- **Decisión:** Usar OpenSpec como formato de especificación. Las specs viven en `.knowledge/specs/` y se sincronizan con el workspace git (no con el repo del proyecto). Los ADRs documentan decisiones arquitectónicas. +- **Consecuencias:** + - Las features se definen como OpenSpec specs antes de implementar + - Los ADRs capturan el "por qué" de cada decisión + - El workflow es: spec → implementación → spec actualizada +- **Estado:** Aceptado diff --git a/.knowledge/adr/0002-rename-to-create-stack-with-positional-stacks.md b/.knowledge/adr/0002-rename-to-create-stack-with-positional-stacks.md new file mode 100644 index 0000000..76c061f --- /dev/null +++ b/.knowledge/adr/0002-rename-to-create-stack-with-positional-stacks.md @@ -0,0 +1,16 @@ +# ADR 0002: Renombrar a `@gonzoblasco/create-stack` con stacks posicionales + +- **Fecha:** 2026-07-20 +- **Contexto:** `create-stack-next` está atado semánticamente a Next.js, pero ya tiene `--template api` y se planean más stacks (TanStack, Astro). El nombre actual es engañoso y no escala. +- **Decisión:** + 1. **Nombre npm:** `@gonzoblasco/create-stack` (scoped package público) + 2. **Nombre repo:** `create-stack` (se renombrará en GitHub al estabilizar) + 3. **CLI:** `npx @gonzoblasco/create-stack [nombre]` donde `` es el primer argumento posicional + 4. **Stacks iniciales:** `next` (full app), `api` (Next.js API-only) + 5. **Stacks futuros:** `tanstack`, `astro`, `cli` +- **Consecuencias:** + - Breaking change en la interfaz CLI (de `npx create-stack-next nombre` a `npx @gonzoblasco/create-stack next nombre`) + - El comando es más verboso pero más explícito y extensible + - El flag `--template` se depreca; el stack se pasa como argumento + - Se mantiene compatibilidad hacia atrás con `--template` por un tiempo +- **Estado:** Aceptado diff --git a/.knowledge/adr/0003-modular-stack-architecture.md b/.knowledge/adr/0003-modular-stack-architecture.md new file mode 100644 index 0000000..9fbd32f --- /dev/null +++ b/.knowledge/adr/0003-modular-stack-architecture.md @@ -0,0 +1,14 @@ +# ADR 0003: Arquitectura modular de stacks + +- **Fecha:** 2026-07-20 +- **Contexto:** Necesitamos que agregar un nuevo stack sea trivial: crear una carpeta con template + config, sin tocar el core del CLI. +- **Decisión:** Cada stack es un módulo independiente en `src/stacks//` con: + - `index.ts` — configuración del stack (nombre, descripción, template dir, package.json base, prompts) + - `template/` — archivos del template (copiados al proyecto destino) + - El core del CLI descubre stacks por convención de directorios +- **Consecuencias:** + - Agregar un stack = crear `src/stacks//` con su `index.ts` y `template/` + - El core no necesita saber qué stacks existen + - Los tests de cada stack pueden ser independientes + - Los templates actuales (`template/` y `template-api/`) se migran a `src/stacks/next/template/` y `src/stacks/api/template/` +- **Estado:** Aceptado diff --git a/.knowledge/specs/product.md b/.knowledge/specs/product.md new file mode 100644 index 0000000..b3d3312 --- /dev/null +++ b/.knowledge/specs/product.md @@ -0,0 +1,119 @@ +# OpenSpec: @gonzoblasco/create-stack + +## Summary + +Scaffolder opinado multi-stack. Un solo comando y tenés un proyecto moderno, testeado, lintado y listo para que un AI agent lo habite. El stack se elige como primer argumento posicional. + +## Specs + +### spec-001: CLI entrypoint + +- **ID:** spec-001 +- **Title:** CLI entrypoint con argumento posicional de stack +- **Status:** proposed +- **Priority:** P0 + +**Description:** +El CLI se ejecuta con `npx @gonzoblasco/create-stack [nombre]`. El stack es obligatorio. El nombre del proyecto es opcional (si no se pasa, prompt interactivo). + +**Acceptance Criteria:** +- [ ] `npx @gonzoblasco/create-stack next my-app` genera un proyecto Next.js full en `./my-app` +- [ ] `npx @gonzoblasco/create-stack api my-app` genera un proyecto Next.js API-only en `./my-app` +- [ ] `npx @gonzoblasco/create-stack` sin argumentos muestra help con stacks disponibles +- [ ] `npx @gonzoblasco/create-stack --help` muestra help +- [ ] `npx @gonzoblasco/create-stack inexistente` muestra error con stacks válidos +- [ ] El flag `--template` legacy sigue funcionando (compatibilidad) + +### spec-002: Stacks modulares + +- **ID:** spec-002 +- **Title:** Stacks como módulos independientes +- **Status:** proposed +- **Priority:** P0 + +**Description:** +Cada stack vive en `src/stacks//` con su propia configuración y template. El core descubre stacks automáticamente. + +**Acceptance Criteria:** +- [ ] `src/stacks/next/index.ts` exporta config del stack Next.js full +- [ ] `src/stacks/api/index.ts` exporta config del stack Next.js API-only +- [ ] El core itera `src/stacks/` para listar stacks disponibles +- [ ] Agregar un stack nuevo no requiere modificar el core + +### spec-003: Template copiado con personalización + +- **ID:** spec-003 +- **Title:** Template copiado con personalización por stack +- **Status:** proposed +- **Priority:** P0 + +**Description:** +Cada stack tiene su carpeta `template/` que se copia al directorio destino. Durante la copia se personaliza el `package.json` (name, version) y se ejecutan hooks post-copia. + +**Acceptance Criteria:** +- [ ] `src/stacks/next/template/` contiene el template Next.js full +- [ ] `src/stacks/api/template/` contiene el template Next.js API-only +- [ ] El `package.json` del proyecto destino tiene el nombre correcto +- [ ] Los hooks post-copia se ejecutan (OpenSpec init, etc.) + +### spec-004: OpenSpec integrado + +- **ID:** spec-004 +- **Title:** OpenSpec init post-scaffold +- **Status:** proposed +- **Priority:** P1 + +**Description:** +Después de copiar el template e instalar deps, se ejecuta `openspec init` con las herramientas IA seleccionadas por el usuario. + +**Acceptance Criteria:** +- [ ] Select interactivo de herramientas IA (Claude Code, Cursor, etc.) +- [ ] `openspec init` se ejecuta con las herramientas seleccionadas +- [ ] Flag `--no-openspec` para saltear +- [ ] Fallback si `openspec init` falla (estructura base ya copiada del template) + +### spec-005: Git init inteligente + +- **ID:** spec-005 +- **Title:** Git init con detección de workspace +- **Status:** proposed +- **Priority:** P1 + +**Description:** +Se inicializa git automáticamente a menos que se detecte un workspace (Turborepo, pnpm workspace) o se pase `--no-git`. + +**Acceptance Criteria:** +- [ ] `git init -b main` + commit inicial +- [ ] Detección de workspace salta git init +- [ ] `--no-git` salta git init +- [ ] Si falta user.name/user.email, usa valores temporales y advierte + +### spec-006: Package manager selection + +- **ID:** spec-006 +- **Title:** Selección de package manager +- **Status:** proposed +- **Priority:** P1 + +**Description:** +El usuario puede elegir el package manager con `--pm`. Default: npm. + +**Acceptance Criteria:** +- [ ] `--pm npm|pnpm|yarn|bun` funciona +- [ ] Default: npm +- [ ] `npm install` se ejecuta post-copia + +### spec-007: Help y discoverability + +- **ID:** spec-007 +- **Title:** Help con stacks disponibles +- **Status:** proposed +- **Priority:** P1 + +**Description:** +El comando `--help` muestra todos los stacks disponibles con su descripción, y ejemplos de uso. + +**Acceptance Criteria:** +- [ ] `--help` lista stacks con nombre y descripción +- [ ] `--help` muestra ejemplos de uso +- [ ] Los stacks se listan dinámicamente (no hardcodeados) diff --git a/CHANGELOG.md b/CHANGELOG.md index b52f8b6..17d34b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,200 +1,52 @@ # Changelog -Todas las versiones notables de este proyecto se documentan en este archivo. +## [Unreleased] — v0.8.0 (Reidentificación) -El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +### 🚀 Cambios mayores ---- +- **Renombre:** `create-stack-next` → `@gonzoblasco/create-stack` +- **Nuevo CLI:** `npx @gonzoblasco/create-stack [nombre]` +- **Arquitectura modular:** cada stack es un módulo independiente en `src/stacks//` +- **Compatibilidad hacia atrás:** `npx create-stack-next my-app` y `--template api` siguen funcionando -## [Unreleased] +### ✨ Nuevas features -### Changed +- Stack posicional como primer argumento (`next`, `api`) +- Help dinámico que lista stacks disponibles +- Stacks `next` (full app) y `api` (API-only) como módulos -- **Templates actualizados para compatibilidad con Biome 2.5+ y TypeScript 5.x/7.x:** - - Schema de Biome actualizado de 1.9.0 a 2.5.4 - - `organizeImports.enabled` → `assist.actions.source.organizeImports.level: "on"` - - `linter.rules.recommended: true` → `linter.rules.preset: "recommended"` - - `files.ignore` → `files.includes` (formato Biome 2.5+) - - Agregado `css.parser.tailwindDirectives: true` para Tailwind v4 - - Bump de `@biomejs/biome` de `^1.9.0` a `^2.5.0` - - Bump de `vitest` de `^2.1.0` a `^4.0.0` - - Agregado `src/global.d.ts` con declaraciones de módulos para CSS/imágenes - - `@types/react` y `@types/react-dom` actualizados a `^19.0.0` en template-api -- **Proyecto CSN actualizado a Biome 2.5+ y TypeScript 7+:** - - Schema de Biome actualizado - - `@biomejs/biome` bump a `^2.5.0`, `typescript` a `^7.0.0`, `vitest` a `^4.0.0` - - Agregado `"types": ["node"]` en tsconfig para compatibilidad con TS 7 +### 🔧 Internals -### Fixed - -- Error de schema en biome.json de templates (schema URL desactualizada) -- Error de `files.ignore` deprecado en Biome 2.5+ -- Error de `organizeImports.enabled` → `level: "on"` en Biome 2.5.4 -- Error de módulo CSS no encontrado en TypeScript (agregado `global.d.ts`) -- Error de `baseUrl` deprecado en TS 6+ (agregado `ignoreDeprecations: "6.0"` en templates) - ---- - -## [0.7.1] - 2026-07-14 - -### Added - -- Opción "Todas las herramientas (30+)" en el select de IA: si el usuario usa herramientas menos comunes (Antigravity, OpenClaw, Gemini, etc.) puede seleccionar "Todas" y se pasa `--tools all` a `openspec init`. - -### Changed - -- Removido "Primera utilidad publicada. M4 Lite en progreso." del README. -- ROADMAP reescrito: lista de logros actuales en vez de fases rígidas. - -## [0.7.0] - 2026-07-14 - -### Added - -- **Spec-Driven Development integrado con OpenSpec**: todos los proyectos generados ahora incluyen [OpenSpec](https://github.com/Fission-AI/OpenSpec) pre-configurado. El scaffolder pregunta qué herramientas de IA usás (Claude Code, Cursor, Windsurf, GitHub Copilot, Cline, Codex) e instala los skills y slash commands correspondientes. - - Flujo `/opsx:propose` → `/opsx:apply` → `/opsx:archive` listo para usar desde el día 1. - - Estructura `openspec/` pre-armada en el template como fallback si `openspec init` falla (sin internet, npm caído, etc.). - - `@fission-ai/openspec` agregado como `devDependency` en los proyectos generados. - - AGENTS.md actualizado con sección SDD: specs como fuente de verdad, delta specs, flujo explore→propose→apply→archive. - - README de los proyectos generados incluye sección "Desarrollo por Especificaciones". - - Nuevo flag `--no-openspec` para saltear la inicialización de OpenSpec. - - Próximos pasos en el CLI ahora incluye `/opsx:propose "tu primera feature"`. - -### Changed +- `src/stacks/` con registry, types y módulos por stack +- Templates movidos a `src/stacks/*/template/` +- `parse-args.ts` refactorizado para nueva API +- `cli.ts` refactorizado para usar `getStack()` y `listStacks()` +- ADRs documentados en `.knowledge/adr/` +- OpenSpec specs en `.knowledge/specs/` -- OpenSpec init se ejecuta **antes** de `git init` para que los skills y comandos queden en el commit inicial. -- README principal del repo actualizado con tabla comparativa (Spec-Driven Development: Ninguno vs OpenSpec integrado). +### 📚 Documentación -## [0.6.2] - 2026-07-14 +- README actualizado con nueva API y arquitectura +- ROADMAP actualizado +- BRIEF, STATUS, ADRs y specs agregados -### Fixed - -- `copy-template.ts`: agregado `dotfiles: true` al `cp()` para que el scaffolding copie archivos ocultos del template (`.env.example`, `.gitignore`, etc.). Sin este flag, los dotfiles del template se ignoraban silenciosamente al generar un proyecto nuevo. - -## [0.6.1] - 2026-07-01 - -### Added - -- Publicación en npm con soporte oficial del paquete para el template API (`files`: `dist`, `template`, `template-api`). -- Documentación del flag `--template api` en README. +## [0.7.2] — 2026-07-15 ### Fixed +- Templates actualizados para Biome 2.5.4, Vitest 4, TS 5.x compat +- Agregado `src/global.d.ts` con declaraciones de módulos +- `@types/react` y `@types/react-dom` actualizados a ^19.0.0 en template-api -- Fix del infame error `ERESOLVE` de `npm install` al fijar la dependencia de React de forma armónica para Next.js 15 en los proyectos generados. - -## [0.6.0] - 2026-07-01 +## [0.7.1] — 2026-07-14 ### Added +- Opción "Todas las herramientas (30+)" en el select de IA +- Limpieza de docs -- **Template API (`--template api`)**: arquitectura robusta de backend puro usando Next.js App Router. - - **Base de Datos**: integración de Drizzle ORM + `@libsql/client` (SQLite) con tabla de usuarios y `drizzle.config.ts`. - - **Manejo de Errores y Respuestas**: utilidades estandarizadas (`src/lib/errors.ts` y `src/lib/api-response.ts`) para respuestas de API consistentes. - - **Middleware**: interceptor de autenticación base vía Bearer Token en `src/middleware.ts`. - - **Testing**: integración con `node-mocks-http` y Vitest para pruebas de Route Handlers ultra veloces sin levantar servidores. -- **AI Workflow Injection**: todos los proyectos generados (`template/` y `template-api/`) ahora incluyen de fábrica archivos semilla para orquestar agentes de IA (`ROADMAP.md`, `AGENT_TASKS.md`, `HANDOFF.md`). -- **Prompt Kickoff**: prompt pre-configurado en `AGENTS.md` del template para que el usuario inicie de inmediato la Fase 1 del proyecto usando cualquier LLM (Cursor, OpenClaw, etc.). -- Decisiones técnicas fundacionales (ADR D004 y D005) inyectadas en los templates generados para prevenir amnesia contextual en la IA. -- **Soporte de Workspaces**: detección automática si el CLI se ejecuta dentro de un monorepo (Turborepo, pnpm workspaces, etc.), desactivando `git init` para evitar anidamiento accidental. -- **Pulido Visual del CLI**: `@clack/prompts` con spinners en tiempo real, colores semánticos, manejo limpio de cancelaciones (Ctrl+C) y bloque estilizado de "Próximos pasos". -- **CI/CD Pipeline**: flujo de trabajo de GitHub Actions nativo en el repositorio central para validar Linting, Tipos y tests de Playwright. - -### Changed - -- Tests de Vitest re-arquitectados para discriminar el directorio `src/` del directorio de plantillas `template/`, evitando colisiones fantasma. - -### Fixed - -- Se añadió `vitest.config.ts` en la raíz para incluir estrictamente `src/` e impedir que Vitest corra tests anidados dentro de `template/` y carpetas de prueba locales, arreglando conflictos con Playwright. - -## [0.5.0] - 2026-06-30 +## [0.7.0] — 2026-07-13 ### Added - -- `AGENT_TASKS.md` — guía de proceso para agentes de IA (Fase 1 del roadmap). -- `src/test/git.test.ts` — tests dedicados para funciones de Git (`isGitAvailable`, `getGitConfig`, `execCapture`). -- Tests exhaustivos en `cli.test.ts`: 27 tests (antes 13) cubriendo `validateProjectName` directo, edge cases de FS, combinaciones de flags. -- Tests exhaustivos en `copy-template.test.ts`: 11 tests (antes 2) cubriendo estructura completa de archivos, dotfiles, paths anidados, archivos sin placeholders. -- ADRs D002, D003, D004 en `docs/decisions.md`. - -### Changed - -- `cli.ts` refactorizado: funciones `validateProjectName`, `execInDir`, `execCapture`, `isGitAvailable`, `getGitConfig` ahora son exportadas para testabilidad directa. -- `validateProjectName` ahora acepta directorios existentes vacíos (antes rechazaba cualquier directorio existente). -- `validateProjectName` valida permisos de escritura en el directorio padre. -- `runGitInit` ahora detecta si Git está instalado y muestra warning amigable si no. -- `runGitInit` usa valores genéricos para el commit inicial cuando `user.name`/`user.email` no están configurados. -- Output de "Próximos pasos" ahora muestra el package manager seleccionado (antes siempre mostraba `npm`). - -## [0.3.1] - 2026-06-29 - -### Added - -- `CHANGELOG.md` base dentro de la carpeta `template/` para que los nuevos proyectos lo incluyan por defecto. -- Instrucción en `template/AGENTS.md` obligando a los agentes de IA a mantener actualizado el `CHANGELOG.md` de los proyectos generados. - -## [0.3.0] - 2026-06-28 - -### Changed - -- README completamente reescrito y pulido (M4 Lite). -- Mejor estructura, badges, tabla de diferencias con create-next-app. -- Sección de casos de uso y AI agents más visibles. -- Roadmap actualizado (M3 pausado + M4 Lite). - -### Added - -- `FUTURE.md` con proyectos futuros. - -## [0.2.0] - 2026-06-28 - -### Added - -- Soporte completo para AI agents (M2): - - Comando `npm run agent`. - - Carpeta `.openclaw/` con config y prompts (feature, refactor, bugfix, tests). - - Carpeta `.agents/` con config genérica y prompts compartidos. - - Carpeta `docs/` con `architecture.md`, `decisions.md` y `contributing.md`. -- Tests del scaffolder: - - `cli.test.ts` (parseo de flags y validación de nombres). - - `copy-template.test.ts` (reemplazo de placeholders). - - `integration.test.ts` (generación completa + checks de calidad). -- `AGENTS.md` actualizado con instrucciones para agentes. -- Formateo completo del template con Biome. - -### Changed - -- `README.md` actualizado para reflejar M2 cerrado. -- Justificación de `.openclaw/` y `.agents/` actualizada como "buena práctica". - -### Fixed - -- Varios archivos del template ahora pasan `biome check` correctamente. - ---- - -## [0.1.0] - 2026-06-28 - -### Added - -- Primera versión pública del scaffolder. -- Template de Next.js 15 con: - - TypeScript estricto. - - Biome. - - Vitest + Playwright. - - Zod. - - GitHub Actions (CI + e2e). - - `AGENTS.md`. -- Publicación en npm. -- Repo público en GitHub. - -[Unreleased]: https://github.com/gonzoblasco/create-stack-next/compare/v0.7.1...HEAD -[0.7.1]: https://github.com/gonzoblasco/create-stack-next/compare/v0.7.0...v0.7.1 -[0.7.0]: https://github.com/gonzoblasco/create-stack-next/compare/v0.6.2...v0.7.0 -[0.6.2]: https://github.com/gonzoblasco/create-stack-next/compare/v0.6.1...v0.6.2 -[0.6.1]: https://github.com/gonzoblasco/create-stack-next/compare/v0.6.0...v0.6.1 -[0.6.0]: https://github.com/gonzoblasco/create-stack-next/compare/v0.5.0...v0.6.0 -[0.5.0]: https://github.com/gonzoblasco/create-stack-next/compare/v0.3.1...v0.5.0 -[0.3.1]: https://github.com/gonzoblasco/create-stack-next/compare/v0.3.0...v0.3.1 -[0.3.0]: https://github.com/gonzoblasco/create-stack-next/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/gonzoblasco/create-stack-next/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/gonzoblasco/create-stack-next/releases/tag/v0.1.0 +- Spec-Driven Development integrado con OpenSpec +- Select interactivo para elegir herramientas IA +- Flag `--no-openspec` +- Próximos pasos incluye `/opsx:propose` diff --git a/FUTURE.md b/FUTURE.md index 2ecdbb0..fa7e7e2 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -1,26 +1,31 @@ # Proyectos futuros -Este documento lista ideas y posibles expansiones del ecosistema `create-stack-*` que **no están en el alcance actual**. +Este documento lista ideas y posibles expansiones del ecosistema `@gonzoblasco/create-stack` que **no están en el alcance actual**. --- -## Nuevos templates +## Nuevos stacks -- `--template cli` (scaffolder de CLIs en Node.js) -- `--template tanstack` (Next.js + TanStack Query + TanStack Start) +- `tanstack` — Next.js + TanStack Query + TanStack Start +- `astro` — Astro + Content Collections + integraciones +- `cli` — Scaffolder de CLIs en Node.js +- `remix` — Remix Run +- `sveltekit` — SvelteKit ## Expansión del ecosistema -- Familia de scaffolders: `create-stack-remix`, `create-stack-astro`, `create-stack-sveltekit`. - Sub-agentes de OpenClaw que ejecuten tareas de scaffolding o migración automáticamente. +- Plugins de IDE/extensiones para generar proyectos desde UI. +- Integración con registries de componentes (shadcn/ui, etc.). ## Otras ideas -- Plugins de IDE/extensiones para generar proyectos desde UI. -- Integración con registries de componentes (shadcn/ui, etc.). +- `create-stack` como CLI global instalable +- Modo interactivo sin argumentos (seleccionar stack + nombre con prompts) +- Template versionado (elegir versión de Next.js, etc.) --- -**Última actualización:** 2026-07-01 +**Última actualización:** 2026-07-20 Este documento es vivo. Se actualiza cuando surge una idea que no entra en el roadmap inmediato. diff --git a/HANDOFF.md b/HANDOFF.md index f3b79e0..0f732b0 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,98 +1,55 @@ -# Handover — create-stack-next +# Handover — @gonzoblasco/create-stack -**Fecha:** 2026-07-15 -**Última versión:** `0.7.1` +**Fecha:** 2026-07-20 +**Última versión:** `0.8.0` (v2-rebrand) +**Branch:** `v2-rebrand` --- -## Últimos cambios (2026-07-15) +## Resumen de la reidentificación -### Templates actualizados para compatibilidad con versiones modernas +`create-stack-next` → `@gonzoblasco/create-stack`. Se refactorizó la arquitectura a stacks modulares, se renombró el paquete, y se documentó con ADRs + OpenSpec specs. -Se actualizaron los templates (`template/` y `template-api/`) para resolver los pitfalls encontrados al scaffoldear SoporteML v2: +### Cambios principales -- **Biome 1.9 → 2.5.4:** - - Schema URL actualizada - - `organizeImports.enabled` → `assist.actions.source.organizeImports.level: "on"` - - `linter.rules.recommended: true` → `linter.rules.preset: "recommended"` - - `files.ignore` → `files.includes` (formato Biome 2.5+) - - Agregado `css.parser.tailwindDirectives: true` para Tailwind v4 -- **TypeScript:** templates mantienen `^5.6.x` (Next.js 15 no es compatible con TS 7) -- **Vitest:** bump de `^2.1.0` a `^4.0.0` -- **Agregado `src/global.d.ts`** con declaraciones de módulos para CSS/imágenes -- **`@types/react` y `@types/react-dom`** actualizados a `^19.0.0` en template-api +1. **Nombre npm:** `@gonzoblasco/create-stack` (scoped package público) +2. **CLI:** `npx @gonzoblasco/create-stack [nombre]` +3. **Arquitectura:** cada stack es un módulo en `src/stacks//` +4. **Stacks:** `next` (full app) y `api` (API-only) +5. **Compatibilidad:** `npx create-stack-next my-app` y `--template api` siguen funcionando -### Proyecto CSN actualizado +### Archivos modificados/creados -- `@biomejs/biome` bump a `^2.5.0`, `typescript` a `^7.0.0`, `vitest` a `^4.0.0` -- Agregado `"types": ["node"]` en tsconfig para compatibilidad con TS 7 -- Todos los 64 tests pasando ✅ +- `package.json` — renombrado, bin actualizado +- `src/parse-args.ts` — nueva API con stack posicional +- `src/cli.ts` — refactorizado para usar registry de stacks +- `src/stacks/` — nuevo: types, registry, next, api +- `src/stacks/next/template/` — template Next.js full (movido de `template/`) +- `src/stacks/api/template/` — template API-only (movido de `template-api/`) +- `src/test/*.test.ts` — tests actualizados para nueva API +- `vitest.config.ts` — exclude templates del runner +- `tsconfig.json` — exclude templates del build +- `README.md`, `ROADMAP.md`, `CHANGELOG.md`, `FUTURE.md` — actualizados +- `.knowledge/` — nuevo: ADRs, specs, BRIEF, STATUS -### Nota importante +### Tests -Los templates usan TypeScript 5.x porque Next.js 15 no es compatible con TS 7 (falla `next build` con `Cannot read properties of undefined (reading 'fileExists')`). El proyecto CSN en sí usa TS 7 sin problemas porque no depende de Next.js. +- 67 tests pasando (5 test files) +- Build compila sin errores +- Lint: Biome check ---- - -## Estado actual del proyecto - -### Versiones cerradas -- **Fase 1 (Robustez Absoluta):** ✅ Completada en v0.5.x -- **Fase 2 (Flexibilidad Interna y DX):** ✅ Completada en v0.6.x -- **Fase 3 (Adopción y Documentación):** ⏳ En progreso (SDD integrado) - -### Entregables de v0.7.0 completados -- ✅ **Spec-Driven Development integrado con OpenSpec**: todos los proyectos generados incluyen OpenSpec pre-configurado. -- ✅ Select interactivo para elegir herramientas IA (Claude Code, Cursor, Windsurf, GitHub Copilot, Cline, Codex) con Claude y Cursor pre-seleccionados. -- ✅ `openspec init --tools --force` se ejecuta post-install, antes de `git init`. -- ✅ Estructura `openspec/` pre-armada en `template/` y `template-api/` como fallback. -- ✅ `@fission-ai/openspec` agregado como `devDependency` en ambos templates. -- ✅ `AGENTS.md` de ambos templates actualizado con sección SDD. -- ✅ `README.md` de ambos templates + repo principal actualizado con sección SDD. -- ✅ Nuevo flag `--no-openspec` para saltear la inicialización. -- ✅ Próximos pasos del CLI incluye `/opsx:propose "tu primera feature"`. -- ✅ Fix de v0.6.2: `dotfiles: true` en `copy-template.ts` para copiar archivos ocultos. - -### Entregables de v0.6.x completados -- ✅ Template `--template api` (backend puro Next.js App Router, Drizzle ORM + SQLite, Zod, middleware Bearer/API Key, tests con `node-mocks-http`). -- ✅ AI-Native workflow: `AGENTS.md`, `ROADMAP.md`, `AGENT_TASKS.md`, `HANDOFF.md` y ADRs semilla inyectados en ambos templates. -- ✅ Soporte de workspaces con detección automática y desactivación inteligente de `git init`. -- ✅ Pulido visual del CLI con `@clack/prompts`. -- ✅ CI/CD de GitHub Actions en el repositorio central. - -### Tests del scaffolder -- 64 tests pasando con `npm run test:run`. -- 9 tests nuevos de OpenSpec (estructura, config, devDeps, runOpenSpecInit). -- `vitest.config.ts` aísla estrictamente `src/` para evitar colisiones con tests anidados en `template/`. - ---- +### Pendientes -## Dónde retomar +- [ ] Publicar v0.8.0 en npm (`npm login` + `npm publish`) +- [ ] Renombrar repo en GitHub a `create-stack` +- [ ] Mergear `v2-rebrand` a `main` cuando esté estable +- [ ] Sitio de documentación +- [ ] Video demo -Cuando se retome el proyecto, se recomienda revisar: - -1. `ROADMAP.md` — estado actual de fases y épicas pendientes. -2. `AGENT_TASKS.md` — sprint plan para la Fase 3. -3. `CHANGELOG.md` — historial de versiones. -4. `FUTURE.md` — ideas post-v1.0.0. - -### Pendientes de la Fase 3 -- [ ] Publicar v0.7.0 en npm (requiere `npm login` de Gonzo) -- [ ] Sitio Web de Documentación (VitePress o Nextra) -- [ ] Material de Onboarding (video demo de 2-4 min) -- [ ] Tests de integración multi-package manager (`pnpm`, `yarn`, `bun`) -- [ ] Polish de Release Candidate: beta testing comunitario - ---- - -## Notas - -- OpenSpec se ejecuta con `OPENSPEC_TELEMETRY=0` para desactivar telemetría. -- El flujo es: copiar template → personalizar → select herramientas IA → npm install → openspec init → git init. -- Si `openspec init` falla (sin internet, npm caído), la estructura base ya está copiada del template como fallback. -- Los prompts de agentes están en español (`.openclaw/prompts/` y `.agents/prompts/`). -- CSN es 100% en español — release notes, docs, todo. - ---- +### Notas -**Última actualización:** 2026-07-14 \ No newline at end of file +- Los templates usan TypeScript 5.x (Next.js 15 no es compatible con TS 7) +- El proyecto CSN en sí usa TS 7 sin problemas +- OpenSpec se ejecuta con `OPENSPEC_TELEMETRY=0` +- El flag `--template` legacy se mapea: `app → next`, `api → api` +- Si se pasa `--template` y también un stack posicional, gana `--template` diff --git a/README.md b/README.md index 2133355..55e3014 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# create-stack-next +# @gonzoblasco/create-stack -> Scaffolder opinado para Next.js 15. Un solo comando y tenés un proyecto moderno, testeado, lintado y listo para que un AI agent lo habite. +> Scaffolder opinado multi-stack. Un solo comando y tenés un proyecto moderno, testeado, lintado y listo para que un AI agent lo habite. -[![npm version](https://img.shields.io/npm/v/create-stack-next.svg)](https://www.npmjs.com/package/create-stack-next) +[![npm version](https://img.shields.io/npm/v/@gonzoblasco/create-stack.svg)](https://www.npmjs.com/package/@gonzoblasco/create-stack) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) --- @@ -10,17 +10,25 @@ ## Instalación ```bash -npx create-stack-next my-app +# Stack Next.js full (app + API routes + tests + AI agent) +npx @gonzoblasco/create-stack next my-app -# Si solo necesitás un Backend (API Route Handlers): -npx create-stack-next my-api --template api +# Stack Next.js API-only (Drizzle ORM + SQLite + middleware) +npx @gonzoblasco/create-stack api my-api ``` -Eso es todo. El comando genera un proyecto Next.js 15 listo para empezar a desarrollar. +Eso es todo. El comando genera un proyecto listo para empezar a desarrollar. --- -## Qué incluye +## Stacks disponibles + +| Stack | Descripción | +|-------|-------------| +| `next` | Next.js 15 + React 19 + TypeScript estricto + Biome + Vitest + Playwright + Zod + OpenSpec + AI agent config | +| `api` | Next.js 15 App Router (API-only) + Drizzle ORM + SQLite + middleware Bearer + tests con `node-mocks-http` | + +### Stack Next.js Full (`next`) El proyecto generado viene con todo preconfigurado: @@ -33,7 +41,8 @@ El proyecto generado viene con todo preconfigurado: - **AGENTS.md** + workflow de IA inyectado para desarrollo asistido - `docs/` con arquitectura, decisiones y guía de contribución -### Template API (`--template api`) +### Stack API-only (`api`) + Si preferís generar un backend puramente funcional sin React components: - **Next.js 15 App Router** (Rutas de API). - **Drizzle ORM** + SQLite pre-configurado para base de datos. @@ -45,8 +54,8 @@ Si preferís generar un backend puramente funcional sin React components: ## ¿Por qué este y no create-next-app? -| Aspecto | create-next-app | create-stack-next | -|---------|-----------------|-------------------| +| Aspecto | create-next-app | @gonzoblasco/create-stack | +|---------|-----------------|---------------------------| | Testing | Ninguno | Vitest + Playwright configurados | | Linter | ESLint + Prettier | Biome (un solo binario) | | TypeScript | Básico | Estricto + `noUncheckedIndexedAccess` | @@ -54,16 +63,33 @@ Si preferís generar un backend puramente funcional sin React components: | Spec-Driven Development | Ninguno | OpenSpec integrado (specs + slash commands) | | CI | Ninguno | GitHub Actions incluido | | Filosofía | Mínimo | Opinado con defaults 2026 | +| Multi-stack | Solo Next.js | `next`, `api`, y más por venir | **Ideal si:** arrancás proyectos con frecuencia y no querés repetir las mismas 40 decisiones cada vez. --- -## Casos de uso recomendados +## Opciones + +```bash +npx @gonzoblasco/create-stack [nombre] [opciones] + +Opciones: + --no-git No inicializa git ni hace commit inicial + --no-install No corre npm install después de generar + --no-openspec No inicializa OpenSpec (Spec-Driven Development) + --pm Package manager: npm, pnpm, yarn, bun (default: npm) + +Ejemplos: + npx @gonzoblasco/create-stack next my-app + npx @gonzoblasco/create-stack api my-api + npx @gonzoblasco/create-stack next my-app --pm pnpm --no-git +``` -- Proyectos personales o de equipo donde querés arrancar rápido con buena base -- Cuando usás (o querés usar) AI agents para desarrollar -- Cuando valorás tener tests, lint y CI desde el día 1 +### Compatibilidad hacia atrás + +El formato legacy `npx create-stack-next my-app` sigue funcionando (asume stack `next`). +El flag `--template api` también funciona, mapeado al stack `api`. --- @@ -79,7 +105,7 @@ El proyecto incluye: ### Spec-Driven Development con OpenSpec -Desde la v0.7.0, todos los proyectos generados incluyen [OpenSpec](https://github.com/Fission-AI/OpenSpec) pre-configurado. Esto significa que tu IA trabaja con especificaciones en vez de prompts vagos: +Todos los proyectos generados incluyen [OpenSpec](https://github.com/Fission-AI/OpenSpec) pre-configurado. Esto significa que tu IA trabaja con especificaciones en vez de prompts vagos: ```bash /opsx:propose "agregar autenticación" # la IA crea propuesta + specs + tasks @@ -91,18 +117,6 @@ Durante el scaffolding, el CLI te pregunta qué herramientas de IA usás (Claude --- -## Roadmap - -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). - -- **Fase 1: Robustez Absoluta** ✅ (Completada en v0.5.0) -- **Fase 2: Flexibilidad Interna y DX** ✅ (Completada en v0.6.0) -- **Fase 3: Adopción y Documentación** ⏳ En progreso - -Ver [`FUTURE.md`](FUTURE.md) para el registro de ideas futuras. - ---- - ## Comandos del proyecto generado ```bash @@ -118,9 +132,34 @@ npm run agent # sesión con OpenClaw --- -## Contribuir +## Arquitectura del CLI + +``` +src/ +├── index.ts # Entrypoint +├── cli.ts # Lógica principal del CLI +├── parse-args.ts # Parser de argumentos +├── copy-template.ts # Copia y personaliza templates +├── openspec-init.ts # Inicialización de OpenSpec +├── workspace.ts # Detección de workspaces +└── stacks/ + ├── types.ts # Tipos de StackConfig + ├── index.ts # Registry de stacks + ├── next/ + │ ├── index.ts # Config del stack next + │ └── template/ # Template Next.js full + └── api/ + ├── index.ts # Config del stack api + └── template/ # Template Next.js API-only +``` + +Cada stack es un módulo independiente. Para agregar uno nuevo, creá `src/stacks//` con su `index.ts` y `template/`, y registralo en `src/stacks/index.ts`. + +--- + +## Roadmap -Si querés contribuir, mirá [`docs/contributing.md`](docs/contributing.md) en el template generado. +Ver [`ROADMAP.md`](ROADMAP.md) y [`FUTURE.md`](FUTURE.md). --- @@ -130,4 +169,4 @@ MIT --- -**Creado con ❤️ por Gonzo y Kanam (OpenClaw)** \ No newline at end of file +**Creado con ❤️ por Gonzo y Kanam (OpenClaw)** diff --git a/ROADMAP.md b/ROADMAP.md index 6800484..c49c86f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,19 +1,23 @@ # Roadmap -## Estado actual (v0.7.0) +## Estado actual (v0.8.0 — Reidentificación) -- ✅ CLI robusto con validación de nombres, edge cases de FS y flujo Git -- ✅ Template app (Next.js 15 + React 19 + TS estricto + Biome + Vitest + Playwright + Zod) -- ✅ Template API (`--template api`) con Drizzle ORM + SQLite, middleware, error handling -- ✅ AI-Native: `AGENTS.md`, `.openclaw/`, `.agents/`, prompts y workflow inyectados +- ✅ CLI con stack posicional (`npx @gonzoblasco/create-stack next my-app`) +- ✅ Arquitectura modular de stacks (`src/stacks//`) +- ✅ Stacks: `next` (Next.js full) y `api` (Next.js API-only) +- ✅ Compatibilidad hacia atrás con `create-stack-next` y `--template` +- ✅ Template Next.js 15 + React 19 + TS estricto + Biome + Vitest + Playwright + Zod +- ✅ Template API con Drizzle ORM + SQLite + middleware + tests +- ✅ AI-Native: AGENTS.md, .openclaw/, .agents/, prompts y workflow inyectados - ✅ Soporte de workspaces (Turborepo, pnpm workspaces) con detección automática - ✅ CI/CD con GitHub Actions (lint, typecheck, tests, build en 4 package managers) -- ✅ Spec-Driven Development integrado con OpenSpec (v0.7.0) -- ✅ 64 tests automáticos +- ✅ Spec-Driven Development integrado con OpenSpec +- ✅ 67 tests automáticos ## Próximos pasos -- [ ] Publicar v0.7.0 en npm +- [ ] Publicar v0.8.0 en npm como `@gonzoblasco/create-stack` +- [ ] Renombrar repo en GitHub a `create-stack` - [ ] Sitio de documentación (VitePress o Nextra) - [ ] Video demo de onboarding (2-4 min) - [ ] Tests de integración multi-package manager en CI @@ -21,4 +25,4 @@ ## Ideas futuras -Ver [`FUTURE.md`](FUTURE.md). \ No newline at end of file +Ver [`FUTURE.md`](FUTURE.md). diff --git a/package-lock.json b/package-lock.json index 298d273..6364e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { - "name": "create-stack-next", - "version": "0.7.1", + "name": "@gonzoblasco/create-stack", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "create-stack-next", - "version": "0.7.1", + "name": "@gonzoblasco/create-stack", + "version": "0.8.0", "license": "MIT", "dependencies": { "@clack/prompts": "^0.9.0", "picocolors": "^1.1.0" }, "bin": { - "create-stack-next": "dist/index.js" + "create-stack": "dist/index.js" }, "devDependencies": { "@biomejs/biome": "^2.5.0", @@ -209,29 +209,6 @@ "sisteransi": "^1.0.5" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -592,6 +569,7 @@ "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1469,6 +1447,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -1676,6 +1655,7 @@ "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", diff --git a/package.json b/package.json index 759136e..4456962 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { - "name": "create-stack-next", - "version": "0.7.2", - "description": "Scaffolder opinado para proyectos Next.js 15 con TypeScript estricto, Biome, Vitest, Playwright y AI agent config preinstalado.", + "name": "@gonzoblasco/create-stack", + "version": "0.8.0", + "description": "Scaffolder opinado multi-stack. Un solo comando y tenés un proyecto moderno, testeado, lintado y listo para que un AI agent lo habite.", "type": "module", "bin": { - "create-stack-next": "dist/index.js" + "create-stack": "dist/index.js", + "@gonzoblasco/create-stack": "dist/index.js" }, "files": [ "dist", - "template", - "template-api" + "src/stacks" ], "scripts": { "build": "tsc", @@ -26,15 +26,17 @@ "node": ">=20.0.0" }, "keywords": [ - "create-stack-next", + "create-stack", "scaffolder", "next.js", "nextjs", + "tanstack", "typescript", "biome", "vitest", "playwright", - "ai-agent" + "ai-agent", + "openspec" ], "license": "MIT", "dependencies": { diff --git a/src/cli.ts b/src/cli.ts index 42a7e50..f6d9f4a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ import { spawn } from "node:child_process"; import { accessSync, constants, existsSync, readdirSync } from "node:fs"; -import { mkdir, writeFile } from "node:fs/promises"; +import { mkdir, writeFile, readFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import * as p from "@clack/prompts"; @@ -8,6 +8,7 @@ import pc from "picocolors"; import { copyTemplate } from "./copy-template.js"; import { runOpenSpecInit } from "./openspec-init.js"; import { type Args, parseArgs } from "./parse-args.js"; +import { getStack, listStacks } from "./stacks/index.js"; import { isInsideWorkspace } from "./workspace.js"; /** Herramientas IA soportadas por OpenSpec para el select interactivo. */ @@ -31,18 +32,22 @@ const DEFAULT_TOOLS = ["claude", "cursor"]; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -// En dist/ queda en dist/index.js, así que subimos un nivel -function getTemplateDir(templateType = "app"): string { - const folderName = templateType === "api" ? "template-api" : "template"; - return resolve(__dirname, "..", folderName); -} +const CLI_NAME = "@gonzoblasco/create-stack"; function printUsage(): void { + const stacks = listStacks(); + const stacksList = stacks + .map((s) => ` ${pc.green(s.id.padEnd(12))} ${s.label} — ${s.description}`) + .join("\n"); + console.log(` -${pc.cyan(pc.bold("create-stack-next"))} — scaffolder opinado para Next.js 15 +${pc.cyan(pc.bold(CLI_NAME))} — scaffolder opinado multi-stack ${pc.bold("Uso:")} - ${pc.cyan("npx create-stack-next")} ${pc.green("")} [opciones] + ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("")} [nombre-del-proyecto] [opciones] + +${pc.bold("Stacks disponibles:")} +${stacksList} ${pc.bold("Opciones:")} ${pc.yellow("--no-git")} No inicializa git ni hace commit inicial @@ -51,8 +56,9 @@ ${pc.bold("Opciones:")} ${pc.yellow("--pm ")} Package manager: ${pc.cyan("npm")}, ${pc.cyan("pnpm")}, ${pc.cyan("yarn")}, ${pc.cyan("bun")} (default: ${pc.cyan("npm")}) ${pc.bold("Ejemplos:")} - ${pc.cyan("npx create-stack-next")} ${pc.green("my-app")} - ${pc.cyan("npx create-stack-next")} ${pc.green("my-app")} ${pc.yellow("--pm pnpm --no-git")} + ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("next")} ${pc.green("my-app")} + ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("api")} ${pc.green("my-app")} + ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("next")} ${pc.green("my-app")} ${pc.yellow("--pm pnpm --no-git")} `); } @@ -74,7 +80,6 @@ export function validateProjectName( return { ok: false, reason: "Falta el nombre del proyecto" }; } - // Mismas reglas que npm init: lowercase, puede tener guión/underscore, no arrancar con . if (!/^[a-z0-9-_]+$/.test(name)) { return { ok: false, @@ -89,11 +94,9 @@ export function validateProjectName( }; } - // Si el directorio ya existe, verificar si está vacío if (existsSync(name)) { try { const entries = readdirSync(name); - // Ignorar archivos del sistema como .DS_Store const meaningfulEntries = entries.filter( (entry) => entry !== ".DS_Store" && entry !== "Thumbs.db", ); @@ -108,7 +111,6 @@ export function validateProjectName( } } - // Verificar permisos de escritura en el directorio padre const parentDir = dirname(resolve(name)); try { accessSync(parentDir, constants.W_OK); @@ -119,13 +121,8 @@ export function validateProjectName( return { ok: true }; } -function _logStep(emoji: string, message: string): void { - console.log(` ${emoji} ${message}`); -} - /** - * Ejecuta un comando en un directorio. Hereda stdout/stderr del proceso padre - * para que el output de npm/git sea visible. + * Ejecuta un comando en un directorio. Hereda stdout/stderr del proceso padre. */ export function execInDir( cmd: string, @@ -144,7 +141,7 @@ export function execInDir( } /** - * Ejecuta un comando y captura su stdout. Útil para leer valores de git config. + * Ejecuta un comando y captura su stdout. */ export function execCapture( cmd: string, @@ -180,7 +177,6 @@ export async function isGitAvailable(): Promise { /** * Lee un valor de la configuración global de git. - * Retorna null si no está configurado o si git no está disponible. */ export async function getGitConfig( key: string, @@ -195,7 +191,6 @@ export async function getGitConfig( } async function runGitInit(projectDir: string): Promise { - // 1. Verificar que git está disponible if (!(await isGitAvailable())) { p.log.warn("Git no encontrado. Saltando inicialización git."); p.log.info(`Instalá Git y corré ${pc.cyan("git init")} manualmente.`); @@ -205,7 +200,6 @@ async function runGitInit(projectDir: string): Promise { await execInDir("git", ["init", "-b", "main"], projectDir); await execInDir("git", ["add", "."], projectDir); - // 2. Verificar user.name/user.email const userName = await getGitConfig("user.name", projectDir); const userEmail = await getGitConfig("user.email", projectDir); @@ -217,12 +211,12 @@ async function runGitInit(projectDir: string): Promise { "git", [ "-c", - "user.name=create-stack-next", + "user.name=create-stack", "-c", - "user.email=noreply@create-stack-next", + "user.email=noreply@create-stack", "commit", "-m", - "chore: initial commit from create-stack-next", + "chore: initial commit from @gonzoblasco/create-stack", ], projectDir, ); @@ -232,7 +226,7 @@ async function runGitInit(projectDir: string): Promise { } else { await execInDir( "git", - ["commit", "-m", "chore: initial commit from create-stack-next"], + ["commit", "-m", "chore: initial commit from @gonzoblasco/create-stack"], projectDir, ); } @@ -244,7 +238,6 @@ async function runInstall(projectDir: string, pm: string): Promise { } export async function run(): Promise { - // Sin args o --help/-h → mostrar usage if ( process.argv.length <= 2 || process.argv.includes("--help") || @@ -256,16 +249,38 @@ export async function run(): Promise { const args: Args = parseArgs(process.argv.slice(2)); - const projectName = args._[0]; + // Validar stack + const stack = args.stack; + if (!stack) { + console.error(pc.red("❌"), "Falta el stack."); + console.error( + ` Uso: ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("")} ${pc.green("[nombre]")}`, + ); + console.error( + ` Stacks: ${listStacks().map((s) => pc.cyan(s.id)).join(", ")}`, + ); + process.exit(1); + } + + const stackConfig = getStack(stack); + if (!stackConfig) { + console.error( + pc.red("❌"), + `Stack "${stack}" no encontrado. Stacks disponibles: ${listStacks().map((s) => pc.cyan(s.id)).join(", ")}`, + ); + process.exit(1); + } + + // Validar nombre del proyecto + const projectName = args.projectName; if (!projectName) { console.error(pc.red("❌"), "Falta el nombre del proyecto."); console.error( - ` Uso: ${pc.cyan("npx create-stack-next")} ${pc.green("")}`, + ` Uso: ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green(stack)} ${pc.green("")}`, ); process.exit(1); } - // Validar nombre const validation = validateProjectName(projectName); if (!validation.ok) { console.error(pc.red("❌"), validation.reason); @@ -273,12 +288,12 @@ export async function run(): Promise { } const projectDir = resolve(process.cwd(), projectName); - const pm = args.pm ?? "npm"; + const pm = args.pm ?? stackConfig.defaultPM; const git = args.git !== false; const install = args.install !== false; const openspec = args.openspec !== false; - p.intro(pc.bgCyan(pc.black(" create-stack-next "))); + p.intro(pc.bgCyan(pc.black(` ${CLI_NAME} — ${stackConfig.label} `))); try { const s = p.spinner(); @@ -288,16 +303,17 @@ export async function run(): Promise { await mkdir(projectDir, { recursive: true }); s.stop(`Directorio creado: ${pc.cyan(projectName)}`); - // 2. Copiar template - s.start("Copiando template"); - const templateDir = getTemplateDir(args.template); - await copyTemplate(templateDir, projectDir, { projectName, pm }); + // 2. Copiar template del stack + s.start(`Copiando template: ${stackConfig.label}`); + await copyTemplate(stackConfig.templateDir, projectDir, { + projectName, + pm, + }); s.stop("Template copiado exitosamente"); - // 3. Personalizar package.json con el nombre correcto del proyecto + // 3. Personalizar package.json s.start("Configurando proyecto"); const packageJsonPath = join(projectDir, "package.json"); - const { readFile } = await import("node:fs/promises"); const packageJsonRaw = await readFile(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonRaw); packageJson.name = projectName; @@ -308,10 +324,9 @@ export async function run(): Promise { ); s.stop("Configuración lista"); - // 4. Seleccionar herramientas IA para OpenSpec (antes de install) + // 4. Seleccionar herramientas IA para OpenSpec let selectedTools: string[] = DEFAULT_TOOLS; if (openspec) { - // En entornos no interactivos (CI, pipes, etc.), saltar el select y usar defaults if (!process.stdin.isTTY) { p.log.info( `Modo no interactivo detectado. Usando herramientas por defecto: ${DEFAULT_TOOLS.join(", ")}`, @@ -353,9 +368,8 @@ export async function run(): Promise { } } - // 6. OpenSpec init (Spec-Driven Development) + // 6. OpenSpec init if (openspec && selectedTools.length > 0) { - // Si el usuario seleccionó "all", ignorar las demás y pasar solo "all" const toolsFlag = selectedTools.includes("all") ? ["all"] : selectedTools; s.start("Configurando OpenSpec (Spec-Driven Development)"); try { diff --git a/src/parse-args.ts b/src/parse-args.ts index dbd76ed..e8d67f9 100644 --- a/src/parse-args.ts +++ b/src/parse-args.ts @@ -1,11 +1,10 @@ -/** - * Parser de argumentos minimalista. No usamos commander/yargs porque solo - * necesitamos 3 flags. process.argv a mano es más simple y menos deps. - */ +import { isValidStack, listStacks } from "./stacks/index.js"; export type Args = { - /** Positional args: primer elemento es el nombre del proyecto */ - _: string[]; + /** Stack a usar (primer argumento posicional) */ + stack?: string; + /** Nombre del proyecto (segundo argumento posicional, opcional) */ + projectName?: string; /** Inicializar git (default: true). false si se pasa --no-git */ git?: boolean; /** Correr npm install (default: true). false si se pasa --no-install */ @@ -14,7 +13,7 @@ export type Args = { pm?: string; /** Inicializar OpenSpec (default: true). false si se pasa --no-openspec */ openspec?: boolean; - /** Template to use: app | api (default: app) */ + /** Template legacy (deprecado, usar stack posicional) */ template?: string; }; @@ -32,12 +31,18 @@ function isValidTemplate(value: string): value is ValidTemplate { return (VALID_TEMPLATES as readonly string[]).includes(value); } +/** Mapeo de templates legacy a stacks */ +const TEMPLATE_TO_STACK: Record = { + app: "next", + api: "api", +}; + export function parseArgs(argv: string[]): Args { - const args: Args = { _: [] }; + const args: Args = {}; + const positional: string[] = []; for (let i = 0; i < argv.length; i++) { const arg = argv[i]; - if (arg === undefined) continue; switch (arg) { @@ -74,19 +79,37 @@ export function parseArgs(argv: string[]): Args { `--template inválido: "${next}". Valores válidos: ${VALID_TEMPLATES.join(", ")}`, ); } + // Mapear template legacy a stack args.template = next; + args.stack = TEMPLATE_TO_STACK[next]; i++; break; } case "--help": case "-h": - // El caller ya maneja esto antes + // El caller maneja esto break; default: if (arg.startsWith("--")) { throw new Error(`Flag desconocida: ${arg}`); } - args._.push(arg); + positional.push(arg); + } + } + + // El primer argumento posicional es el stack + // Solo si no fue ya seteado por --template legacy + if (positional.length > 0 && args.stack === undefined) { + const first = positional[0]; + if (first && isValidStack(first)) { + args.stack = first; + // El segundo argumento posicional es el nombre del proyecto + args.projectName = positional[1]; + } else { + // Compatibilidad hacia atrás: si el primer arg no es un stack válido, + // asumimos que es el nombre del proyecto con stack "next" (legacy) + args.stack = "next"; + args.projectName = first; } } diff --git a/src/stacks/api/index.ts b/src/stacks/api/index.ts new file mode 100644 index 0000000..4c18bea --- /dev/null +++ b/src/stacks/api/index.ts @@ -0,0 +1,16 @@ +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import type { StackConfig } from "../types.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const apiStack: StackConfig = { + id: "api", + label: "Next.js API", + description: + "Next.js 15 App Router (API-only) + Drizzle ORM + SQLite + middleware Bearer + tests con node-mocks-http", + templateDir: resolve(__dirname, "template"), + defaultPM: "npm", + minNodeVersion: ">=20.0.0", +}; diff --git a/template-api/.vscode/extensions.json b/src/stacks/api/template/.vscode/extensions.json similarity index 100% rename from template-api/.vscode/extensions.json rename to src/stacks/api/template/.vscode/extensions.json diff --git a/template-api/.vscode/settings.json b/src/stacks/api/template/.vscode/settings.json similarity index 100% rename from template-api/.vscode/settings.json rename to src/stacks/api/template/.vscode/settings.json diff --git a/template-api/AGENTS.md b/src/stacks/api/template/AGENTS.md similarity index 100% rename from template-api/AGENTS.md rename to src/stacks/api/template/AGENTS.md diff --git a/template-api/AGENT_TASKS.md b/src/stacks/api/template/AGENT_TASKS.md similarity index 100% rename from template-api/AGENT_TASKS.md rename to src/stacks/api/template/AGENT_TASKS.md diff --git a/template-api/CHANGELOG.md b/src/stacks/api/template/CHANGELOG.md similarity index 100% rename from template-api/CHANGELOG.md rename to src/stacks/api/template/CHANGELOG.md diff --git a/template-api/HANDOFF.md b/src/stacks/api/template/HANDOFF.md similarity index 100% rename from template-api/HANDOFF.md rename to src/stacks/api/template/HANDOFF.md diff --git a/template-api/README.md b/src/stacks/api/template/README.md similarity index 100% rename from template-api/README.md rename to src/stacks/api/template/README.md diff --git a/template-api/ROADMAP.md b/src/stacks/api/template/ROADMAP.md similarity index 100% rename from template-api/ROADMAP.md rename to src/stacks/api/template/ROADMAP.md diff --git a/template-api/biome.json b/src/stacks/api/template/biome.json similarity index 100% rename from template-api/biome.json rename to src/stacks/api/template/biome.json diff --git a/template-api/docs/architecture.md b/src/stacks/api/template/docs/architecture.md similarity index 100% rename from template-api/docs/architecture.md rename to src/stacks/api/template/docs/architecture.md diff --git a/template-api/docs/contributing.md b/src/stacks/api/template/docs/contributing.md similarity index 100% rename from template-api/docs/contributing.md rename to src/stacks/api/template/docs/contributing.md diff --git a/template-api/docs/decisions.md b/src/stacks/api/template/docs/decisions.md similarity index 100% rename from template-api/docs/decisions.md rename to src/stacks/api/template/docs/decisions.md diff --git a/template-api/docs/workflow_ai.md b/src/stacks/api/template/docs/workflow_ai.md similarity index 100% rename from template-api/docs/workflow_ai.md rename to src/stacks/api/template/docs/workflow_ai.md diff --git a/template-api/drizzle.config.ts b/src/stacks/api/template/drizzle.config.ts similarity index 100% rename from template-api/drizzle.config.ts rename to src/stacks/api/template/drizzle.config.ts diff --git a/template-api/gitignore b/src/stacks/api/template/gitignore similarity index 100% rename from template-api/gitignore rename to src/stacks/api/template/gitignore diff --git a/template-api/openspec/changes/archive/.gitkeep b/src/stacks/api/template/openspec/changes/archive/.gitkeep similarity index 100% rename from template-api/openspec/changes/archive/.gitkeep rename to src/stacks/api/template/openspec/changes/archive/.gitkeep diff --git a/template-api/openspec/config.yaml b/src/stacks/api/template/openspec/config.yaml similarity index 100% rename from template-api/openspec/config.yaml rename to src/stacks/api/template/openspec/config.yaml diff --git a/template-api/openspec/specs/.gitkeep b/src/stacks/api/template/openspec/specs/.gitkeep similarity index 100% rename from template-api/openspec/specs/.gitkeep rename to src/stacks/api/template/openspec/specs/.gitkeep diff --git a/template-api/package.json b/src/stacks/api/template/package.json similarity index 100% rename from template-api/package.json rename to src/stacks/api/template/package.json diff --git a/template-api/src/app/api/health/route.ts b/src/stacks/api/template/src/app/api/health/route.ts similarity index 100% rename from template-api/src/app/api/health/route.ts rename to src/stacks/api/template/src/app/api/health/route.ts diff --git a/template-api/src/app/api/users/route.ts b/src/stacks/api/template/src/app/api/users/route.ts similarity index 100% rename from template-api/src/app/api/users/route.ts rename to src/stacks/api/template/src/app/api/users/route.ts diff --git a/template-api/src/lib/api-response.ts b/src/stacks/api/template/src/lib/api-response.ts similarity index 100% rename from template-api/src/lib/api-response.ts rename to src/stacks/api/template/src/lib/api-response.ts diff --git a/template-api/src/lib/db.ts b/src/stacks/api/template/src/lib/db.ts similarity index 100% rename from template-api/src/lib/db.ts rename to src/stacks/api/template/src/lib/db.ts diff --git a/template-api/src/lib/errors.ts b/src/stacks/api/template/src/lib/errors.ts similarity index 100% rename from template-api/src/lib/errors.ts rename to src/stacks/api/template/src/lib/errors.ts diff --git a/template-api/src/lib/schema.ts b/src/stacks/api/template/src/lib/schema.ts similarity index 100% rename from template-api/src/lib/schema.ts rename to src/stacks/api/template/src/lib/schema.ts diff --git a/template-api/src/middleware.ts b/src/stacks/api/template/src/middleware.ts similarity index 100% rename from template-api/src/middleware.ts rename to src/stacks/api/template/src/middleware.ts diff --git a/template-api/src/test/health.test.ts b/src/stacks/api/template/src/test/health.test.ts similarity index 100% rename from template-api/src/test/health.test.ts rename to src/stacks/api/template/src/test/health.test.ts diff --git a/template-api/tsconfig.json b/src/stacks/api/template/tsconfig.json similarity index 100% rename from template-api/tsconfig.json rename to src/stacks/api/template/tsconfig.json diff --git a/template-api/vitest.config.ts b/src/stacks/api/template/vitest.config.ts similarity index 100% rename from template-api/vitest.config.ts rename to src/stacks/api/template/vitest.config.ts diff --git a/src/stacks/index.ts b/src/stacks/index.ts new file mode 100644 index 0000000..aedbaae --- /dev/null +++ b/src/stacks/index.ts @@ -0,0 +1,34 @@ +import type { StackConfig } from "./types.js"; +import { nextStack } from "./next/index.js"; +import { apiStack } from "./api/index.js"; + +/** + * Registry de todos los stacks disponibles. + * Para agregar un stack nuevo, importalo acá y agregalo al array. + */ +const STACKS: StackConfig[] = [nextStack, apiStack]; + +/** Mapa id → stack para lookup rápido */ +const stackMap = new Map(STACKS.map((s) => [s.id, s])); + +/** + * Obtiene la config de un stack por su id. + * Retorna undefined si no existe. + */ +export function getStack(id: string): StackConfig | undefined { + return stackMap.get(id); +} + +/** + * Lista todos los stacks disponibles. + */ +export function listStacks(): StackConfig[] { + return STACKS; +} + +/** + * Valida que un stack id exista. + */ +export function isValidStack(id: string): boolean { + return stackMap.has(id); +} diff --git a/src/stacks/next/index.ts b/src/stacks/next/index.ts new file mode 100644 index 0000000..e2e237c --- /dev/null +++ b/src/stacks/next/index.ts @@ -0,0 +1,30 @@ +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export interface StackConfig { + /** Identificador del stack (ej: "next", "api") */ + id: string; + /** Nombre legible para mostrar en help */ + label: string; + /** Descripción breve */ + description: string; + /** Directorio del template (relativo a este archivo) */ + templateDir: string; + /** Package manager por defecto */ + defaultPM: string; + /** Versión de Node mínima */ + minNodeVersion: string; +} + +export const nextStack: StackConfig = { + id: "next", + label: "Next.js Full", + description: + "Next.js 15 + React 19 + TypeScript estricto + Biome + Vitest + Playwright + Zod + OpenSpec + AI agent config", + templateDir: resolve(__dirname, "template"), + defaultPM: "npm", + minNodeVersion: ">=20.0.0", +}; diff --git a/template/.agents/config.json b/src/stacks/next/template/.agents/config.json similarity index 100% rename from template/.agents/config.json rename to src/stacks/next/template/.agents/config.json diff --git a/template/.agents/prompts/bugfix.md b/src/stacks/next/template/.agents/prompts/bugfix.md similarity index 100% rename from template/.agents/prompts/bugfix.md rename to src/stacks/next/template/.agents/prompts/bugfix.md diff --git a/template/.agents/prompts/feature.md b/src/stacks/next/template/.agents/prompts/feature.md similarity index 100% rename from template/.agents/prompts/feature.md rename to src/stacks/next/template/.agents/prompts/feature.md diff --git a/template/.agents/prompts/refactor.md b/src/stacks/next/template/.agents/prompts/refactor.md similarity index 100% rename from template/.agents/prompts/refactor.md rename to src/stacks/next/template/.agents/prompts/refactor.md diff --git a/template/.agents/prompts/tests.md b/src/stacks/next/template/.agents/prompts/tests.md similarity index 100% rename from template/.agents/prompts/tests.md rename to src/stacks/next/template/.agents/prompts/tests.md diff --git a/template/.github/workflows/ci.yml b/src/stacks/next/template/.github/workflows/ci.yml similarity index 100% rename from template/.github/workflows/ci.yml rename to src/stacks/next/template/.github/workflows/ci.yml diff --git a/template/.github/workflows/playwright.yml b/src/stacks/next/template/.github/workflows/playwright.yml similarity index 100% rename from template/.github/workflows/playwright.yml rename to src/stacks/next/template/.github/workflows/playwright.yml diff --git a/template/.openclaw/README.md b/src/stacks/next/template/.openclaw/README.md similarity index 100% rename from template/.openclaw/README.md rename to src/stacks/next/template/.openclaw/README.md diff --git a/template/.openclaw/config.json b/src/stacks/next/template/.openclaw/config.json similarity index 100% rename from template/.openclaw/config.json rename to src/stacks/next/template/.openclaw/config.json diff --git a/template/.openclaw/prompts/bugfix.md b/src/stacks/next/template/.openclaw/prompts/bugfix.md similarity index 100% rename from template/.openclaw/prompts/bugfix.md rename to src/stacks/next/template/.openclaw/prompts/bugfix.md diff --git a/template/.openclaw/prompts/feature.md b/src/stacks/next/template/.openclaw/prompts/feature.md similarity index 100% rename from template/.openclaw/prompts/feature.md rename to src/stacks/next/template/.openclaw/prompts/feature.md diff --git a/template/.openclaw/prompts/refactor.md b/src/stacks/next/template/.openclaw/prompts/refactor.md similarity index 100% rename from template/.openclaw/prompts/refactor.md rename to src/stacks/next/template/.openclaw/prompts/refactor.md diff --git a/template/.openclaw/prompts/tests.md b/src/stacks/next/template/.openclaw/prompts/tests.md similarity index 100% rename from template/.openclaw/prompts/tests.md rename to src/stacks/next/template/.openclaw/prompts/tests.md diff --git a/template/.vscode/extensions.json b/src/stacks/next/template/.vscode/extensions.json similarity index 100% rename from template/.vscode/extensions.json rename to src/stacks/next/template/.vscode/extensions.json diff --git a/template/.vscode/settings.json b/src/stacks/next/template/.vscode/settings.json similarity index 100% rename from template/.vscode/settings.json rename to src/stacks/next/template/.vscode/settings.json diff --git a/template/AGENTS.md b/src/stacks/next/template/AGENTS.md similarity index 100% rename from template/AGENTS.md rename to src/stacks/next/template/AGENTS.md diff --git a/template/AGENT_TASKS.md b/src/stacks/next/template/AGENT_TASKS.md similarity index 100% rename from template/AGENT_TASKS.md rename to src/stacks/next/template/AGENT_TASKS.md diff --git a/template/CHANGELOG.md b/src/stacks/next/template/CHANGELOG.md similarity index 100% rename from template/CHANGELOG.md rename to src/stacks/next/template/CHANGELOG.md diff --git a/template/HANDOFF.md b/src/stacks/next/template/HANDOFF.md similarity index 100% rename from template/HANDOFF.md rename to src/stacks/next/template/HANDOFF.md diff --git a/template/LICENSE b/src/stacks/next/template/LICENSE similarity index 100% rename from template/LICENSE rename to src/stacks/next/template/LICENSE diff --git a/template/README.md b/src/stacks/next/template/README.md similarity index 100% rename from template/README.md rename to src/stacks/next/template/README.md diff --git a/template/ROADMAP.md b/src/stacks/next/template/ROADMAP.md similarity index 100% rename from template/ROADMAP.md rename to src/stacks/next/template/ROADMAP.md diff --git a/template/biome.json b/src/stacks/next/template/biome.json similarity index 100% rename from template/biome.json rename to src/stacks/next/template/biome.json diff --git a/template/docs/architecture.md b/src/stacks/next/template/docs/architecture.md similarity index 100% rename from template/docs/architecture.md rename to src/stacks/next/template/docs/architecture.md diff --git a/template/docs/contributing.md b/src/stacks/next/template/docs/contributing.md similarity index 100% rename from template/docs/contributing.md rename to src/stacks/next/template/docs/contributing.md diff --git a/template/docs/decisions.md b/src/stacks/next/template/docs/decisions.md similarity index 100% rename from template/docs/decisions.md rename to src/stacks/next/template/docs/decisions.md diff --git a/template/docs/workflow_ai.md b/src/stacks/next/template/docs/workflow_ai.md similarity index 100% rename from template/docs/workflow_ai.md rename to src/stacks/next/template/docs/workflow_ai.md diff --git a/template/gitignore b/src/stacks/next/template/gitignore similarity index 100% rename from template/gitignore rename to src/stacks/next/template/gitignore diff --git a/template/next.config.ts b/src/stacks/next/template/next.config.ts similarity index 100% rename from template/next.config.ts rename to src/stacks/next/template/next.config.ts diff --git a/template/openspec/changes/archive/.gitkeep b/src/stacks/next/template/openspec/changes/archive/.gitkeep similarity index 100% rename from template/openspec/changes/archive/.gitkeep rename to src/stacks/next/template/openspec/changes/archive/.gitkeep diff --git a/template/openspec/config.yaml b/src/stacks/next/template/openspec/config.yaml similarity index 100% rename from template/openspec/config.yaml rename to src/stacks/next/template/openspec/config.yaml diff --git a/template/openspec/specs/.gitkeep b/src/stacks/next/template/openspec/specs/.gitkeep similarity index 100% rename from template/openspec/specs/.gitkeep rename to src/stacks/next/template/openspec/specs/.gitkeep diff --git a/template/package.json b/src/stacks/next/template/package.json similarity index 100% rename from template/package.json rename to src/stacks/next/template/package.json diff --git a/template/playwright.config.ts b/src/stacks/next/template/playwright.config.ts similarity index 100% rename from template/playwright.config.ts rename to src/stacks/next/template/playwright.config.ts diff --git a/template/public/.gitkeep b/src/stacks/next/template/public/.gitkeep similarity index 100% rename from template/public/.gitkeep rename to src/stacks/next/template/public/.gitkeep diff --git a/template/src/app/api/health/route.ts b/src/stacks/next/template/src/app/api/health/route.ts similarity index 100% rename from template/src/app/api/health/route.ts rename to src/stacks/next/template/src/app/api/health/route.ts diff --git a/template/src/app/globals.css b/src/stacks/next/template/src/app/globals.css similarity index 100% rename from template/src/app/globals.css rename to src/stacks/next/template/src/app/globals.css diff --git a/template/src/app/layout.tsx b/src/stacks/next/template/src/app/layout.tsx similarity index 100% rename from template/src/app/layout.tsx rename to src/stacks/next/template/src/app/layout.tsx diff --git a/template/src/app/page.tsx b/src/stacks/next/template/src/app/page.tsx similarity index 100% rename from template/src/app/page.tsx rename to src/stacks/next/template/src/app/page.tsx diff --git a/template/src/components/ui/.gitkeep b/src/stacks/next/template/src/components/ui/.gitkeep similarity index 100% rename from template/src/components/ui/.gitkeep rename to src/stacks/next/template/src/components/ui/.gitkeep diff --git a/template/src/global.d.ts b/src/stacks/next/template/src/global.d.ts similarity index 100% rename from template/src/global.d.ts rename to src/stacks/next/template/src/global.d.ts diff --git a/template/src/lib/env.ts b/src/stacks/next/template/src/lib/env.ts similarity index 100% rename from template/src/lib/env.ts rename to src/stacks/next/template/src/lib/env.ts diff --git a/template/src/lib/utils.ts b/src/stacks/next/template/src/lib/utils.ts similarity index 100% rename from template/src/lib/utils.ts rename to src/stacks/next/template/src/lib/utils.ts diff --git a/template/src/test/example.test.ts b/src/stacks/next/template/src/test/example.test.ts similarity index 100% rename from template/src/test/example.test.ts rename to src/stacks/next/template/src/test/example.test.ts diff --git a/template/src/test/setup.ts b/src/stacks/next/template/src/test/setup.ts similarity index 100% rename from template/src/test/setup.ts rename to src/stacks/next/template/src/test/setup.ts diff --git a/template/tests/e2e/home.spec.ts b/src/stacks/next/template/tests/e2e/home.spec.ts similarity index 100% rename from template/tests/e2e/home.spec.ts rename to src/stacks/next/template/tests/e2e/home.spec.ts diff --git a/template/tsconfig.json b/src/stacks/next/template/tsconfig.json similarity index 100% rename from template/tsconfig.json rename to src/stacks/next/template/tsconfig.json diff --git a/template/vitest.config.ts b/src/stacks/next/template/vitest.config.ts similarity index 100% rename from template/vitest.config.ts rename to src/stacks/next/template/vitest.config.ts diff --git a/src/stacks/types.ts b/src/stacks/types.ts new file mode 100644 index 0000000..89d7e2c --- /dev/null +++ b/src/stacks/types.ts @@ -0,0 +1,14 @@ +export interface StackConfig { + /** Identificador del stack (ej: "next", "api") */ + id: string; + /** Nombre legible para mostrar en help */ + label: string; + /** Descripción breve */ + description: string; + /** Directorio del template */ + templateDir: string; + /** Package manager por defecto */ + defaultPM: string; + /** Versión de Node mínima */ + minNodeVersion: string; +} diff --git a/src/test/cli.test.ts b/src/test/cli.test.ts index 188c769..fbd53f8 100644 --- a/src/test/cli.test.ts +++ b/src/test/cli.test.ts @@ -6,63 +6,79 @@ import { validateProjectName } from "../cli.js"; import { parseArgs } from "../parse-args.js"; // ═══════════════════════════════════════════════════════════ -// Tests de parseArgs +// Tests de parseArgs (nueva API con stack posicional) // ═══════════════════════════════════════════════════════════ -describe("parseArgs", () => { - it("parsea el nombre del proyecto como argumento posicional", () => { +describe("parseArgs — nueva API con stack posicional", () => { + it("parsea stack + nombre del proyecto como argumentos posicionales", () => { + const args = parseArgs(["next", "my-app"]); + expect(args.stack).toBe("next"); + expect(args.projectName).toBe("my-app"); + }); + + it("parsea stack api + nombre", () => { + const args = parseArgs(["api", "my-api"]); + expect(args.stack).toBe("api"); + expect(args.projectName).toBe("my-api"); + }); + + it("compatibilidad hacia atrás: primer arg no-stack se asume nombre con stack next", () => { const args = parseArgs(["my-app"]); - expect(args._).toEqual(["my-app"]); + expect(args.stack).toBe("next"); + expect(args.projectName).toBe("my-app"); }); it("parsea --no-git correctamente", () => { - const args = parseArgs(["my-app", "--no-git"]); + const args = parseArgs(["next", "my-app", "--no-git"]); expect(args.git).toBe(false); }); it("parsea --no-install correctamente", () => { - const args = parseArgs(["my-app", "--no-install"]); + const args = parseArgs(["next", "my-app", "--no-install"]); expect(args.install).toBe(false); }); it("parsea --pm pnpm correctamente", () => { - const args = parseArgs(["my-app", "--pm", "pnpm"]); + const args = parseArgs(["next", "my-app", "--pm", "pnpm"]); expect(args.pm).toBe("pnpm"); }); it("parsea --pm yarn correctamente", () => { - const args = parseArgs(["my-app", "--pm", "yarn"]); + const args = parseArgs(["next", "my-app", "--pm", "yarn"]); expect(args.pm).toBe("yarn"); }); it("parsea --pm bun correctamente", () => { - const args = parseArgs(["my-app", "--pm", "bun"]); + const args = parseArgs(["next", "my-app", "--pm", "bun"]); expect(args.pm).toBe("bun"); }); it("parsea múltiples flags combinadas", () => { const args = parseArgs([ + "next", "my-app", "--no-git", "--no-install", "--pm", "pnpm", ]); - expect(args._).toEqual(["my-app"]); + expect(args.stack).toBe("next"); + expect(args.projectName).toBe("my-app"); expect(args.git).toBe(false); expect(args.install).toBe(false); expect(args.pm).toBe("pnpm"); }); it("lanza error si --pm tiene un valor inválido", () => { - expect(() => parseArgs(["my-app", "--pm", "deno"])).toThrow( + expect(() => parseArgs(["next", "my-app", "--pm", "deno"])).toThrow( /--pm inválido/, ); }); - it("parsea --template api correctamente", () => { + it("parsea --template api (legacy) correctamente", () => { const args = parseArgs(["my-app", "--template", "api"]); expect(args.template).toBe("api"); + expect(args.stack).toBe("api"); }); it("lanza error si --template tiene un valor inválido", () => { @@ -78,31 +94,34 @@ describe("parseArgs", () => { }); it("lanza error si --pm no tiene valor", () => { - expect(() => parseArgs(["my-app", "--pm"])).toThrow( + expect(() => parseArgs(["next", "my-app", "--pm"])).toThrow( /--pm requiere un valor/, ); }); it("lanza error con flag desconocida", () => { - expect(() => parseArgs(["my-app", "--foo"])).toThrow(/Flag desconocida/); + expect(() => parseArgs(["next", "my-app", "--foo"])).toThrow( + /Flag desconocida/, + ); }); - it("sin argumentos retorna array vacío de posicionales", () => { + it("sin argumentos retorna stack y projectName undefined", () => { const args = parseArgs([]); - expect(args._).toEqual([]); + expect(args.stack).toBeUndefined(); + expect(args.projectName).toBeUndefined(); }); it("ignora --help y -h sin error", () => { const args = parseArgs(["--help"]); - expect(args._).toEqual([]); + expect(args.stack).toBeUndefined(); const args2 = parseArgs(["-h"]); - expect(args2._).toEqual([]); + expect(args2.stack).toBeUndefined(); }); }); // ═══════════════════════════════════════════════════════════ -// Tests de validateProjectName (importación directa) +// Tests de validateProjectName // ═══════════════════════════════════════════════════════════ describe("validateProjectName", () => { @@ -116,8 +135,6 @@ describe("validateProjectName", () => { await rm(tempDir, { recursive: true, force: true }); }); - // --- Nombres válidos --- - it("acepta nombres válidos simples", () => { const result = validateProjectName("my-app"); expect(result.ok).toBe(true); @@ -138,8 +155,6 @@ describe("validateProjectName", () => { expect(result.ok).toBe(true); }); - // --- Nombres inválidos --- - it("rechaza nombre vacío", () => { const result = validateProjectName(""); expect(result.ok).toBe(false); @@ -189,14 +204,11 @@ describe("validateProjectName", () => { } }); - // --- Edge cases de file system --- - it("rechaza directorio existente NO vacío", async () => { const existingDir = join(tempDir, "existing-project"); await mkdir(existingDir, { recursive: true }); await writeFile(join(existingDir, "file.txt"), "content"); - // Cambiar cwd temporalmente al tempDir const originalCwd = process.cwd(); process.chdir(tempDir); try { diff --git a/src/test/copy-template.test.ts b/src/test/copy-template.test.ts index 764beba..14c0f14 100644 --- a/src/test/copy-template.test.ts +++ b/src/test/copy-template.test.ts @@ -4,10 +4,10 @@ import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { copyTemplate, listFiles } from "../copy-template.js"; -describe("copyTemplate", () => { +describe("copyTemplate — stack next", () => { let tempDir: string; let targetDir: string; - const templateDir = join(process.cwd(), "template"); + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "create-stack-test-")); @@ -18,8 +18,6 @@ describe("copyTemplate", () => { await rm(tempDir, { recursive: true, force: true }); }); - // ─── Reemplazo de placeholders ─────────────────────── - it("copia el template y reemplaza el nombre del proyecto en package.json", async () => { await copyTemplate(templateDir, targetDir, { projectName: "my-test-app", @@ -57,8 +55,6 @@ describe("copyTemplate", () => { expect(readme).toContain("custom-name"); }); - // ─── Estructura completa de archivos ───────────────── - it("copia todos los archivos del template (verificación exhaustiva)", async () => { await copyTemplate(templateDir, targetDir, { projectName: "my-test-app", @@ -68,8 +64,6 @@ describe("copyTemplate", () => { const templateFiles = await listFiles(templateDir); const targetFiles = await listFiles(targetDir); - // Cada archivo del template debe existir en el destino, - // con excepción de los dotfiles que se renombran (gitignore → .gitignore) const renames: Record = { gitignore: ".gitignore" }; for (const file of templateFiles) { const expectedFile = renames[file] ?? file; @@ -134,7 +128,6 @@ describe("copyTemplate", () => { pm: "npm", }); - // biome.json no debería tener placeholders ni ser modificado const biomeOriginal = await readFile( join(templateDir, "biome.json"), "utf-8", @@ -163,21 +156,60 @@ describe("copyTemplate", () => { }); }); +describe("copyTemplate — stack api", () => { + let tempDir: string; + let targetDir: string; + const templateDir = join(process.cwd(), "src", "stacks", "api", "template"); + + beforeEach(async () => { + tempDir = await mkdtemp(join(tmpdir(), "create-stack-api-test-")); + targetDir = join(tempDir, "my-test-api"); + }); + + afterEach(async () => { + await rm(tempDir, { recursive: true, force: true }); + }); + + it("copia el template API correctamente", async () => { + await copyTemplate(templateDir, targetDir, { + projectName: "my-test-api", + pm: "npm", + }); + + const packageJsonPath = join(targetDir, "package.json"); + const raw = await readFile(packageJsonPath, "utf-8"); + const pkg = JSON.parse(raw); + + expect(pkg.name).toBe("my-test-api"); + }); + + it("copia drizzle.config.ts en template API", async () => { + await copyTemplate(templateDir, targetDir, { + projectName: "my-test-api", + pm: "npm", + }); + + const drizzleConfig = await stat( + join(targetDir, "drizzle.config.ts"), + ); + expect(drizzleConfig.isFile()).toBe(true); + }); +}); + // ─── Tests de listFiles helper ─────────────────────────── describe("listFiles", () => { - it("devuelve la lista ordenada de archivos del template", async () => { - const templateDir = join(process.cwd(), "template"); + it("devuelve la lista ordenada de archivos del template next", async () => { + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); const files = await listFiles(templateDir); expect(files.length).toBeGreaterThan(0); - // Verificar que la lista está ordenada const sorted = [...files].sort(); expect(files).toEqual(sorted); }); it("incluye dotfiles en la lista", async () => { - const templateDir = join(process.cwd(), "template"); + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); const files = await listFiles(templateDir); expect(files).toContain("gitignore"); diff --git a/src/test/git.test.ts b/src/test/git.test.ts index d42e982..5dea459 100644 --- a/src/test/git.test.ts +++ b/src/test/git.test.ts @@ -1,13 +1,8 @@ import { describe, expect, it } from "vitest"; import { execCapture, getGitConfig, isGitAvailable } from "../cli.js"; -// ═══════════════════════════════════════════════════════════ -// Tests de utilidades Git (funciones exportadas de cli.ts) -// ═══════════════════════════════════════════════════════════ - describe("isGitAvailable", () => { it("retorna true cuando git está instalado", async () => { - // En el entorno de desarrollo, git siempre debería estar disponible const result = await isGitAvailable(); expect(result).toBe(true); }); @@ -16,14 +11,13 @@ describe("isGitAvailable", () => { describe("getGitConfig", () => { it("retorna null para una clave de git config inexistente", async () => { const result = await getGitConfig( - "create-stack-next.nonexistent-key-test", + "create-stack.nonexistent-key-test", process.cwd(), ); expect(result).toBeNull(); }); it("retorna un valor string para claves configuradas (si existen)", async () => { - // Solo validamos que la función no crashea y retorna string o null const userName = await getGitConfig("user.name", process.cwd()); expect(typeof userName === "string" || userName === null).toBe(true); }); diff --git a/src/test/integration.test.ts b/src/test/integration.test.ts index eabd83a..ab3ff0f 100644 --- a/src/test/integration.test.ts +++ b/src/test/integration.test.ts @@ -20,7 +20,7 @@ function runCommand(cmd: string, cwd: string): Promise { }); } -describe("Integration: full project generation", () => { +describe("Integration: full project generation — stack next", () => { let tempDir: string; let targetDir: string; @@ -34,26 +34,20 @@ describe("Integration: full project generation", () => { }); it("generates a project that passes lint, typecheck, test and build", async () => { - const templateDir = join(process.cwd(), "template"); + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); await copyTemplate(templateDir, targetDir, { projectName: "my-test-app", pm: "npm", }); - // Instalar dependencias await runCommand("npm install", targetDir); - - // Primero formateamos con Biome (el template puede tener archivos sin formatear) await runCommand("npm run lint:fix", targetDir); - - // Luego corremos los checks await runCommand("npm run lint", targetDir); await runCommand("npm run typecheck", targetDir); await runCommand("npm run test:run", targetDir); await runCommand("npm run build", targetDir); - // Si llegamos acá sin errores, el test pasa expect(true).toBe(true); - }, 120_000); // timeout de 2 minutos + }, 120_000); }); diff --git a/src/test/openspec.test.ts b/src/test/openspec.test.ts index 801a12e..1b632a0 100644 --- a/src/test/openspec.test.ts +++ b/src/test/openspec.test.ts @@ -5,10 +5,10 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { copyTemplate, listFiles } from "../copy-template.js"; import { runOpenSpecInit } from "../openspec-init.js"; -describe("OpenSpec — estructura en el template", () => { +describe("OpenSpec — estructura en el template next", () => { let tempDir: string; let targetDir: string; - const templateDir = join(process.cwd(), "template"); + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "csn-openspec-test-")); @@ -87,21 +87,16 @@ describe("runOpenSpecInit", () => { }); it("falla graceful si no hay proyecto válido", async () => { - // Pasamos un directorio inexistente — debería fallar sin crashear await expect( runOpenSpecInit(join(tempDir, "no-existe"), ["claude"]), ).rejects.toThrow(); }); - - // Nota: el test de integración real (correr openspec init en un proyecto - // con internet) está en integration.test.ts como test opcional que - // requiere conexión. }); -describe("OpenSpec — template-api", () => { +describe("OpenSpec — template api", () => { let tempDir: string; let targetDir: string; - const templateDir = join(process.cwd(), "template-api"); + const templateDir = join(process.cwd(), "src", "stacks", "api", "template"); beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "csn-openspec-api-test-")); @@ -133,9 +128,9 @@ describe("OpenSpec — template-api", () => { }); }); -describe("OpenSpec — template app", () => { - it("template app tiene @fission-ai/openspec en devDependencies", async () => { - const templateDir = join(process.cwd(), "template"); +describe("OpenSpec — template app (next)", () => { + it("template next tiene @fission-ai/openspec en devDependencies", async () => { + const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); const pkg = JSON.parse( await readFile(join(templateDir, "package.json"), "utf-8"), ); diff --git a/tsconfig.json b/tsconfig.json index 942c43a..da3db9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,5 +21,5 @@ "types": ["node"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "template"] + "exclude": ["node_modules", "dist", "src/stacks/*/template"] } diff --git a/vitest.config.ts b/vitest.config.ts index e036b1a..0b2e7fe 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,5 +3,10 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { include: ["src/**/*.{test,spec}.ts"], + exclude: [ + "src/stacks/*/template/**/*.{test,spec}.ts", + "node_modules", + "dist", + ], }, }); From b09c8c79d5661f3319e8b5e2b3be43047cdba08a Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:15:27 -0300 Subject: [PATCH 2/7] fix: regenerar package-lock.json y actualizar CI workflows - Regenerado package-lock.json (estaba desync con package.json renombrado) - CI actualizado: usa nuevo formato npx @gonzoblasco/create-stack next|api - CI ahora testea ambos stacks (next y api) en cada matrix - Agregado branch v2-rebrand a triggers de CI --- .github/workflows/ci.yml | 23 ++++++-- package-lock.json | 113 --------------------------------------- 2 files changed, 18 insertions(+), 118 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4b1fa9..5f08cd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: ["main"] + branches: ["main", "v2-rebrand"] pull_request: jobs: @@ -49,17 +49,30 @@ jobs: - name: Build CLI run: npm run build - - name: Test Project Generation (${{ matrix.pm }}) + - name: Test Project Generation — stack next (${{ matrix.pm }}) run: | mkdir -p /tmp/test-gen cd /tmp/test-gen - node $GITHUB_WORKSPACE/dist/index.js test-app --pm ${{ matrix.pm }} --no-git --no-openspec + node $GITHUB_WORKSPACE/dist/index.js next test-app --pm ${{ matrix.pm }} --no-git --no-openspec cd test-app - # Verificar que instaló dependencias (node_modules existe) if [ ! -d "node_modules" ]; then echo "❌ node_modules no fue creado por ${{ matrix.pm }}" exit 1 fi - echo "✅ Generación e instalación exitosa con ${{ matrix.pm }}" + echo "✅ Generación e instalación exitosa con ${{ matrix.pm }} (stack next)" + + - name: Test Project Generation — stack api (${{ matrix.pm }}) + run: | + mkdir -p /tmp/test-gen-api + cd /tmp/test-gen-api + node $GITHUB_WORKSPACE/dist/index.js api test-api --pm ${{ matrix.pm }} --no-git --no-openspec + cd test-api + + if [ ! -d "node_modules" ]; then + echo "❌ node_modules no fue creado por ${{ matrix.pm }}" + exit 1 + fi + + echo "✅ Generación e instalación exitosa con ${{ matrix.pm }} (stack api)" diff --git a/package-lock.json b/package-lock.json index 6364e7e..ab40f7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,8 +27,6 @@ }, "node_modules/@biomejs/biome": { "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.4.tgz", - "integrity": "sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -54,8 +52,6 @@ }, "node_modules/@biomejs/cli-darwin-arm64": { "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.4.tgz", - "integrity": "sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w==", "cpu": [ "arm64" ], @@ -190,8 +186,6 @@ }, "node_modules/@clack/core": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.1.tgz", - "integrity": "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==", "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -200,8 +194,6 @@ }, "node_modules/@clack/prompts": { "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz", - "integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==", "license": "MIT", "dependencies": { "@clack/core": "0.4.1", @@ -211,8 +203,6 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -222,8 +212,6 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -248,8 +236,6 @@ }, "node_modules/@oxc-project/types": { "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { @@ -275,8 +261,6 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -515,15 +499,11 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, @@ -540,8 +520,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -551,22 +529,16 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.20.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", - "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", "dev": true, "license": "MIT", "peer": true, @@ -593,8 +565,6 @@ }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", "cpu": [ "arm64" ], @@ -916,8 +886,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", - "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { @@ -934,8 +902,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", - "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -961,8 +927,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -974,8 +938,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", - "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { @@ -988,8 +950,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", - "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { @@ -1004,8 +964,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", - "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -1014,8 +972,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { @@ -1029,8 +985,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1039,8 +993,6 @@ }, "node_modules/chai": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -1049,15 +1001,11 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1066,15 +1014,11 @@ }, "node_modules/es-module-lexer": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", - "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1083,8 +1027,6 @@ }, "node_modules/expect-type": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1093,8 +1035,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1111,10 +1051,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1126,8 +1063,6 @@ }, "node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -1177,8 +1112,6 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -1387,8 +1320,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1397,8 +1328,6 @@ }, "node_modules/nanoid": { "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -1416,8 +1345,6 @@ }, "node_modules/obug": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -1430,21 +1357,15 @@ }, "node_modules/pathe": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "peer": true, @@ -1457,8 +1378,6 @@ }, "node_modules/postcss": { "version": "8.5.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", - "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -1486,8 +1405,6 @@ }, "node_modules/rolldown": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { @@ -1520,21 +1437,15 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1543,29 +1454,21 @@ }, "node_modules/stackback": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -1574,8 +1477,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -1591,8 +1492,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -1601,16 +1500,12 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD", "optional": true }, "node_modules/typescript": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1644,15 +1539,11 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { "version": "8.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", - "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", "dev": true, "license": "MIT", "peer": true, @@ -1730,8 +1621,6 @@ }, "node_modules/vitest": { "version": "4.1.10", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", - "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { @@ -1820,8 +1709,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { From 60bdc34a2f45480115ca0d8983351fe967dfd172 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:18:18 -0300 Subject: [PATCH 3/7] fix: cambiar npm ci por npm install en CI (lock platform-specific) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit El lockfile generado en macOS incluye deps nativas (@emnapi/*) que no existen en linux-x64. npm ci falla porque espera encontrarlas. Solución: usar npm install que resuelve dependencias al vuelo. --- .github/workflows/ci.yml | 2 +- package-lock.json | 238 +++++++++++++++++++++++++++++++-------- 2 files changed, 189 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f08cd2..832b810 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: # Core checks on all matrices (since it's cheap) - name: Install CLI dependencies - run: npm ci + run: npm install - name: Run Biome Lint run: npm run lint diff --git a/package-lock.json b/package-lock.json index ab40f7a..f47eaeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ }, "node_modules/@biomejs/biome": { "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.4.tgz", + "integrity": "sha512-xy5FNE5kQJKyK5MR1gJy6ztXYx4WBAbYGlK04lMEgmyPRWKybY9NFwiG9yo0XdzOU8Xvhj41u034J1ywfoWfMw==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -52,6 +54,8 @@ }, "node_modules/@biomejs/cli-darwin-arm64": { "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.4.tgz", + "integrity": "sha512-4o3NFRobXHynkgcFVrlZsoDAFtF2ldlEGN8sORSws5ZQqyY4PXnPUIylu4ksfyHuwkfvDREuWh3JK+niRwGq3w==", "cpu": [ "arm64" ], @@ -186,6 +190,8 @@ }, "node_modules/@clack/core": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.1.tgz", + "integrity": "sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==", "license": "MIT", "dependencies": { "picocolors": "^1.0.0", @@ -194,6 +200,8 @@ }, "node_modules/@clack/prompts": { "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.9.1.tgz", + "integrity": "sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==", "license": "MIT", "dependencies": { "@clack/core": "0.4.1", @@ -201,8 +209,35 @@ "sisteransi": "^1.0.5" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -212,6 +247,8 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, @@ -236,6 +273,8 @@ }, "node_modules/@oxc-project/types": { "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { @@ -261,6 +300,8 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -499,11 +540,15 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, @@ -520,6 +565,8 @@ }, "node_modules/@types/chai": { "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -529,16 +576,22 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "22.20.0", + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "peer": true, @@ -565,6 +618,8 @@ }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", "cpu": [ "arm64" ], @@ -886,6 +941,8 @@ }, "node_modules/@vitest/expect": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { @@ -902,6 +959,8 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -927,6 +986,8 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -938,6 +999,8 @@ }, "node_modules/@vitest/runner": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { @@ -950,6 +1013,8 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { @@ -964,6 +1029,8 @@ }, "node_modules/@vitest/spy": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -972,6 +1039,8 @@ }, "node_modules/@vitest/utils": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { @@ -985,6 +1054,8 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -993,6 +1064,8 @@ }, "node_modules/chai": { "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -1001,11 +1074,15 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1014,11 +1091,15 @@ }, "node_modules/es-module-lexer": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1027,6 +1108,8 @@ }, "node_modules/expect-type": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1035,6 +1118,8 @@ }, "node_modules/fdir": { "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1051,7 +1136,10 @@ }, "node_modules/fsevents": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1062,7 +1150,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -1076,23 +1166,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -1111,7 +1201,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -1130,9 +1222,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -1151,9 +1243,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -1172,9 +1264,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -1193,9 +1285,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -1214,9 +1306,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -1235,9 +1327,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -1256,9 +1348,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -1277,9 +1369,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -1298,9 +1390,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -1320,6 +1412,8 @@ }, "node_modules/magic-string": { "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1328,6 +1422,8 @@ }, "node_modules/nanoid": { "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -1344,7 +1440,9 @@ } }, "node_modules/obug": { - "version": "2.1.3", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -1357,15 +1455,21 @@ }, "node_modules/pathe": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "peer": true, @@ -1377,7 +1481,9 @@ } }, "node_modules/postcss": { - "version": "8.5.19", + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", "dev": true, "funding": [ { @@ -1395,7 +1501,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1405,6 +1511,8 @@ }, "node_modules/rolldown": { "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { @@ -1437,15 +1545,21 @@ }, "node_modules/siginfo": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, "node_modules/source-map-js": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1454,21 +1568,29 @@ }, "node_modules/stackback": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/std-env": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -1477,6 +1599,8 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -1492,6 +1616,8 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -1500,12 +1626,16 @@ }, "node_modules/tslib": { "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD", "optional": true }, "node_modules/typescript": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1539,19 +1669,23 @@ }, "node_modules/undici-types": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { - "version": "8.1.4", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", - "postcss": "^8.5.16", - "rolldown": "~1.1.4", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "bin": { @@ -1621,6 +1755,8 @@ }, "node_modules/vitest": { "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { @@ -1709,6 +1845,8 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { From 0e1f87658989840a88f9a5ef8841594729da3c42 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:21:21 -0300 Subject: [PATCH 4/7] fix: arreglar errores de Biome lint en CI - Eliminado import no usado de listStacks en parse-args.ts - Organizados imports en cli.ts, stacks/index.ts, api/index.ts, next/index.ts - Formateados test files (copy-template, integration, openspec) - Arreglado useOptionalChain en middleware.ts del template api - Eliminada variable no usada req en health.test.ts del template api - Lint 100% limpio, 67 tests pasando --- package-lock.json | 25 ------------------- src/cli.ts | 10 +++++--- src/parse-args.ts | 2 +- src/stacks/api/index.ts | 2 +- src/stacks/api/template/src/middleware.ts | 2 +- .../api/template/src/test/health.test.ts | 2 +- src/stacks/index.ts | 4 +-- src/stacks/next/index.ts | 2 +- src/test/copy-template.test.ts | 20 +++++++++++---- src/test/integration.test.ts | 8 +++++- src/test/openspec.test.ts | 8 +++++- 11 files changed, 43 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index f47eaeb..b5bdc98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,31 +209,6 @@ "sisteransi": "^1.0.5" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", diff --git a/src/cli.ts b/src/cli.ts index f6d9f4a..59a0efa 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,6 @@ import { spawn } from "node:child_process"; import { accessSync, constants, existsSync, readdirSync } from "node:fs"; -import { mkdir, writeFile, readFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import * as p from "@clack/prompts"; @@ -257,7 +257,9 @@ export async function run(): Promise { ` Uso: ${pc.cyan(`npx ${CLI_NAME}`)} ${pc.green("")} ${pc.green("[nombre]")}`, ); console.error( - ` Stacks: ${listStacks().map((s) => pc.cyan(s.id)).join(", ")}`, + ` Stacks: ${listStacks() + .map((s) => pc.cyan(s.id)) + .join(", ")}`, ); process.exit(1); } @@ -266,7 +268,9 @@ export async function run(): Promise { if (!stackConfig) { console.error( pc.red("❌"), - `Stack "${stack}" no encontrado. Stacks disponibles: ${listStacks().map((s) => pc.cyan(s.id)).join(", ")}`, + `Stack "${stack}" no encontrado. Stacks disponibles: ${listStacks() + .map((s) => pc.cyan(s.id)) + .join(", ")}`, ); process.exit(1); } diff --git a/src/parse-args.ts b/src/parse-args.ts index e8d67f9..6abec49 100644 --- a/src/parse-args.ts +++ b/src/parse-args.ts @@ -1,4 +1,4 @@ -import { isValidStack, listStacks } from "./stacks/index.js"; +import { isValidStack } from "./stacks/index.js"; export type Args = { /** Stack a usar (primer argumento posicional) */ diff --git a/src/stacks/api/index.ts b/src/stacks/api/index.ts index 4c18bea..24ff43d 100644 --- a/src/stacks/api/index.ts +++ b/src/stacks/api/index.ts @@ -1,4 +1,4 @@ -import { resolve, dirname } from "node:path"; +import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import type { StackConfig } from "../types.js"; diff --git a/src/stacks/api/template/src/middleware.ts b/src/stacks/api/template/src/middleware.ts index 76d3a1b..f2eb308 100644 --- a/src/stacks/api/template/src/middleware.ts +++ b/src/stacks/api/template/src/middleware.ts @@ -10,7 +10,7 @@ export function middleware(request: NextRequest) { request.nextUrl.pathname.startsWith('/api') && !request.nextUrl.pathname.startsWith('/api/health') ) { - if (!authHeader || !authHeader.startsWith('Bearer ')) { + if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json( { success: false, diff --git a/src/stacks/api/template/src/test/health.test.ts b/src/stacks/api/template/src/test/health.test.ts index 488ea1b..789dcbb 100644 --- a/src/stacks/api/template/src/test/health.test.ts +++ b/src/stacks/api/template/src/test/health.test.ts @@ -4,7 +4,7 @@ import { GET } from '@/app/api/health/route' describe('Healthcheck API', () => { it('should return 200 and ok status', async () => { - const { req } = createMocks({ + createMocks({ method: 'GET', }) diff --git a/src/stacks/index.ts b/src/stacks/index.ts index aedbaae..4f36630 100644 --- a/src/stacks/index.ts +++ b/src/stacks/index.ts @@ -1,6 +1,6 @@ -import type { StackConfig } from "./types.js"; -import { nextStack } from "./next/index.js"; import { apiStack } from "./api/index.js"; +import { nextStack } from "./next/index.js"; +import type { StackConfig } from "./types.js"; /** * Registry de todos los stacks disponibles. diff --git a/src/stacks/next/index.ts b/src/stacks/next/index.ts index e2e237c..280a284 100644 --- a/src/stacks/next/index.ts +++ b/src/stacks/next/index.ts @@ -1,4 +1,4 @@ -import { resolve, dirname } from "node:path"; +import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); diff --git a/src/test/copy-template.test.ts b/src/test/copy-template.test.ts index 14c0f14..036b497 100644 --- a/src/test/copy-template.test.ts +++ b/src/test/copy-template.test.ts @@ -189,9 +189,7 @@ describe("copyTemplate — stack api", () => { pm: "npm", }); - const drizzleConfig = await stat( - join(targetDir, "drizzle.config.ts"), - ); + const drizzleConfig = await stat(join(targetDir, "drizzle.config.ts")); expect(drizzleConfig.isFile()).toBe(true); }); }); @@ -200,7 +198,13 @@ describe("copyTemplate — stack api", () => { describe("listFiles", () => { it("devuelve la lista ordenada de archivos del template next", async () => { - const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); + const templateDir = join( + process.cwd(), + "src", + "stacks", + "next", + "template", + ); const files = await listFiles(templateDir); expect(files.length).toBeGreaterThan(0); @@ -209,7 +213,13 @@ describe("listFiles", () => { }); it("incluye dotfiles en la lista", async () => { - const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); + const templateDir = join( + process.cwd(), + "src", + "stacks", + "next", + "template", + ); const files = await listFiles(templateDir); expect(files).toContain("gitignore"); diff --git a/src/test/integration.test.ts b/src/test/integration.test.ts index ab3ff0f..63900dc 100644 --- a/src/test/integration.test.ts +++ b/src/test/integration.test.ts @@ -34,7 +34,13 @@ describe("Integration: full project generation — stack next", () => { }); it("generates a project that passes lint, typecheck, test and build", async () => { - const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); + const templateDir = join( + process.cwd(), + "src", + "stacks", + "next", + "template", + ); await copyTemplate(templateDir, targetDir, { projectName: "my-test-app", diff --git a/src/test/openspec.test.ts b/src/test/openspec.test.ts index 1b632a0..b830016 100644 --- a/src/test/openspec.test.ts +++ b/src/test/openspec.test.ts @@ -130,7 +130,13 @@ describe("OpenSpec — template api", () => { describe("OpenSpec — template app (next)", () => { it("template next tiene @fission-ai/openspec en devDependencies", async () => { - const templateDir = join(process.cwd(), "src", "stacks", "next", "template"); + const templateDir = join( + process.cwd(), + "src", + "stacks", + "next", + "template", + ); const pkg = JSON.parse( await readFile(join(templateDir, "package.json"), "utf-8"), ); From 25040890badcb73cac2ac33cde7a1d8a35cddb10 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:22:15 -0300 Subject: [PATCH 5/7] docs: agregar regla de pre-commit checks (lint:fix + typecheck + test:run) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AGENT_TASKS.md: workflow obligatorio actualizado - HANDOFF.md: lección aprendida documentada --- AGENT_TASKS.md | 5 +++-- HANDOFF.md | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/AGENT_TASKS.md b/AGENT_TASKS.md index a264caf..8e29564 100644 --- a/AGENT_TASKS.md +++ b/AGENT_TASKS.md @@ -9,8 +9,9 @@ Antes de escribir una sola línea de código, el agente DEBE: 2. Si la tarea implica una decisión arquitectónica nueva o cambio de diseño, documentarla como ADR en `docs/decisions.md`. 3. Planificar la implementación paso a paso. 4. Ejecutar cambios atómicos. -5. Correr linting (`npm run lint`), typecheck (`npm run typecheck`) y tests (`npm run test:run`). -6. Actualizar `CHANGELOG.md` en la sección `[Unreleased]` y, si corresponde, `ROADMAP.md`. +5. Correr `npm run lint:fix` (lint + format automático), `npm run typecheck` y `npm run test:run`. +6. **Solo si todo pasa**, hacer commit. Si algo falla, arreglarlo antes del commit — no hay commits de fixes. +7. Actualizar `CHANGELOG.md` en la sección `[Unreleased]` y, si corresponde, `ROADMAP.md`. --- diff --git a/HANDOFF.md b/HANDOFF.md index 0f732b0..f3dfcd7 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -53,3 +53,7 @@ - OpenSpec se ejecuta con `OPENSPEC_TELEMETRY=0` - El flag `--template` legacy se mapea: `app → next`, `api → api` - Si se pasa `--template` y también un stack posicional, gana `--template` + +### Lecciones aprendidas + +- **Siempre correr `lint:fix` + `typecheck` + `test:run` antes de commitear.** No hay commits de fixes. Si algo falla, se arregla antes del commit. Esto evita ruido en el historial y CI failures evitables. From f37b87632824ae45268cae5060916328a06d6f89 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:25:08 -0300 Subject: [PATCH 6/7] fix: copiar templates a dist/ durante el build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Los templates en src/stacks/*/template/ no se compilan con tsc pero el runtime los busca desde __dirname que apunta a dist/. Agregado script postbuild scripts/copy-templates.mjs que copia los templates a dist/stacks/*/template/ después de tsc. Esto soluciona el error ENOENT en CI: el CLI ahora encuentra los templates en dist/ al ejecutarse desde el build. --- package.json | 4 ++-- scripts/copy-templates.mjs | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 scripts/copy-templates.mjs diff --git a/package.json b/package.json index 4456962..47ffc1f 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ }, "files": [ "dist", - "src/stacks" + "scripts" ], "scripts": { - "build": "tsc", + "build": "tsc && node scripts/copy-templates.mjs", "dev": "tsc --watch", "start": "node ./dist/index.js", "lint": "biome check .", diff --git a/scripts/copy-templates.mjs b/scripts/copy-templates.mjs new file mode 100644 index 0000000..d3c6b78 --- /dev/null +++ b/scripts/copy-templates.mjs @@ -0,0 +1,42 @@ +#!/usr/bin/env node +// Copy templates from src/stacks/*/template/ to dist/stacks/*/template/ +// after TypeScript build. Templates are not compiled but need to be in dist/ +// for runtime resolution (__dirname points to dist/). + +import { cp, readdir } from "node:fs/promises"; +import { join, resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = resolve(__dirname, ".."); +const srcStacksDir = join(rootDir, "src", "stacks"); +const distStacksDir = join(rootDir, "dist", "stacks"); + +async function copyTemplates() { + const stackDirs = await readdir(srcStacksDir, { withFileTypes: true }); + + for (const entry of stackDirs) { + if (!entry.isDirectory()) continue; + + const srcTemplateDir = join(srcStacksDir, entry.name, "template"); + const distTemplateDir = join(distStacksDir, entry.name, "template"); + + try { + await cp(srcTemplateDir, distTemplateDir, { + recursive: true, + dotfiles: true, + }); + console.log(` ✓ Copiado template: ${entry.name}`); + } catch (err) { + const nodeErr = /** @type {import('node:fs').ErrnoException} */ (err); + if (nodeErr.code !== "ENOENT") { + console.error(` ✗ Error copiando template ${entry.name}:`, err); + } + } + } +} + +copyTemplates().catch((err) => { + console.error("Error copiando templates:", err); + process.exit(1); +}); From 1c208d44c806882583edd5ddc4d03879daf70d23 Mon Sep 17 00:00:00 2001 From: Gonzalo Blasco Date: Mon, 20 Jul 2026 14:27:32 -0300 Subject: [PATCH 7/7] fix: ordenar imports en scripts/copy-templates.mjs --- scripts/copy-templates.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/copy-templates.mjs b/scripts/copy-templates.mjs index d3c6b78..0c687e1 100644 --- a/scripts/copy-templates.mjs +++ b/scripts/copy-templates.mjs @@ -4,7 +4,7 @@ // for runtime resolution (__dirname points to dist/). import { cp, readdir } from "node:fs/promises"; -import { join, resolve, dirname } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url));