Guía CLI
Guía CLI
Sección titulada «Guía CLI»Veloce-TS incluye una CLI integrada para crear proyectos y generar código. Todos los archivos generados superan npm run typecheck sin ningún cambio, y desde v3.1.0 todo lo que produce la CLI funciona tanto en Node como en Bun.
Instalación
Sección titulada «Instalación»Los binarios veloce y veloce-ts están disponibles tras instalar el paquete:
npm install veloce-ts# o de forma globalnpm install -g veloce-ts
# Con Bun es igualbun add veloce-tsveloce new <nombre>
Sección titulada «veloce new <nombre>»Genera un proyecto completo.
veloce new mi-apiEstructura generada (template rest; cada uno escribe solo los directorios que llena):
mi-api/├── src/│ ├── index.ts # entrada de la app con llamadas usePlugin│ ├── controllers/│ │ └── user.controller.ts # inyecta UserService con @Inject│ └── services/│ └── user.service.ts # resuelto por el contenedor de DI├── public/│ └── docs.html # Swagger UI independiente├── package.json # los scripts pasan por el binario veloce├── tsconfig.json # decoradores Stage-2 + reflect-metadata├── .env.example # cada variable que lee el código generado├── .gitignore└── README.mdLos templates graphql y fullstack añaden src/resolvers/, websocket y fullstack añaden src/websockets/, y todos salvo websocket añaden src/services/.
Opciones:
| Opción | Por defecto | Qué hace |
|---|---|---|
-t, --template <template> | rest | rest, graphql, websocket o fullstack |
--install | off | Instala dependencias (Bun si está disponible, npm si no) |
--git | off | git init más un commit inicial — se salta si el destino ya está dentro de un repositorio |
Los ejemplos usan el contenedor de DI. El controlador y el resolver generados piden su UserService con @Inject(UserService) en vez de construirlo, así que reemplazar el array en memoria por una base de datos no toca a ningún llamador. Un scaffolding que construye sus dependencias a mano enseña justo lo contrario de lo que ofrece el framework.
Templates:
veloce new mi-api # REST (por defecto)veloce new mi-api --template restveloce new mi-api --template graphql # resolver GraphQL + playgroundveloce new mi-api --template websocket # gateway WebSocketveloce new mi-api --template fullstack # REST + GraphQL + WebSocket:::note Los cuatro templates corren en Node
Incluidos websocket y fullstack, que antes lanzaban al arrancar ahí. Declaran
@hono/node-ws (sin uso bajo Bun y Deno, que hacen el upgrade nativamente). CI
genera cada template y lo instala, typechequea, compila y arranca bajo Node en cada
ejecución.
:::
El package.json generado enruta sus scripts por el binario veloce en vez de fijar
un runtime, así que el mismo proyecto funciona de las dos formas:
{ "scripts": { "dev": "veloce dev", "build": "veloce build", "start": "node dist/index.js", "typecheck": "tsc --noEmit" }}veloce dev
Sección titulada «veloce dev»Arranca la app con recarga en caliente.
veloce devveloce dev --port 8080veloce dev --runtime node # fuerza el camino de NodeCon Bun usa bun --watch. Con Node ejecuta tsx, que transpila los decoradores
Stage-2 que el type stripping propio de Node no sabe manejar; tsx viene como
devDependency en los proyectos generados.
| Opción | Por defecto | Qué hace |
|---|---|---|
-p, --port <port> | 3000 | Puerto que se pasa a la app como PORT |
-w, --watch <path> | src | Rutas extra a vigilar |
-r, --runtime <runtime> | auto | auto, bun o node |
veloce build
Sección titulada «veloce build»Compila el proyecto para producción.
veloce buildveloce build --runtime nodeveloce build --outdir buildCon Bun empaqueta con Bun.build y emite ESM y CJS. Con Node compila usando el
tsc del propio proyecto, respetando tu tsconfig.json, que es lo que emite los
metadatos de decoradores que el framework lee.
:::note El camino de Bun exige que la propia CLI corra bajo Bun
Bun.build es una API en proceso, así que tener el binario bun en el PATH no
basta: npm run build en una máquina con Bun instalado igual toma el camino de Node,
porque npm arranca la CLI con Node. Usa bun run build, o pasa --runtime bun (que
falla si el proceso no es Bun).
:::
| Opción | Por defecto | Qué hace |
|---|---|---|
-o, --outdir <dir> | dist | Directorio de salida |
-m, --minify | off | Minifica (camino Bun) |
-s, --sourcemap | off | Emite sourcemaps. Desactivado por defecto: publicarlos es una decisión de tamaño de despliegue, y el paquete del propio framework era 73% sourcemaps antes de que esa lección aterrizara en 3.1.2 |
-f, --format <format> | both | esm, cjs o both — solo camino Bun |
-r, --runtime <runtime> | auto | auto, bun o node |
veloce generate
Sección titulada «veloce generate»Genera piezas individuales de tu aplicación. Todos los comandos aceptan un argumento <nombre>.
generate module <nombre>
Sección titulada «generate module <nombre>»Genera una carpeta de módulo completa: controlador + servicio + DTO + barrel.
veloce generate module usersSalida en src/modules/users/:
users.controller.ts # @Controller('/users') con stubs CRUDusers.service.ts # clase de servicio planausers.dto.ts # CreateUsersDto, UpdateUsersDto (Zod)users.module.ts # barrel: re-exporta controlador + serviciogenerate controller <nombre>
Sección titulada «generate controller <nombre>»veloce generate controller productsimport { Controller, Get, Post, Put, Delete, Param, Body } from 'veloce-ts';import { z } from 'zod';
const CreateProductsSchema = z.object({ name: z.string().min(1),});
@Controller('/products')export class ProductsController { @Get('/') async findAll() { return []; }
@Get('/:id') async findOne(@Param('id') id: string) { return { id }; }
@Post('/') async create(@Body(CreateProductsSchema) body: z.infer<typeof CreateProductsSchema>) { return body; }
@Put('/:id') async update(@Param('id') id: string, @Body() body: unknown) { return { id, ...body as object }; }
@Delete('/:id') async remove(@Param('id') id: string) { return { id, deleted: true }; }}generate service <nombre>
Sección titulada «generate service <nombre>»veloce generate service productsexport class ProductsService { async findAll() { return []; }
async findOne(id: string) { return { id }; }
async create(data: unknown) { return data; }
async update(id: string, data: unknown) { return { id, ...data as object }; }
async remove(id: string) { return { id, deleted: true }; }}generate dto <nombre>
Sección titulada «generate dto <nombre>»veloce generate dto create-productimport { z } from 'zod';
export const CreateProductSchema = z.object({ name: z.string().min(1),});
export type CreateProductDto = z.infer<typeof CreateProductSchema>;generate resolver <nombre>
Sección titulada «generate resolver <nombre>»veloce generate resolver userimport { Resolver, GQLQuery, GQLMutation, Arg } from 'veloce-ts/graphql';import { z } from 'zod';
@Resolver('user')export class UserResolver { @GQLQuery('getUsers') async getAll() { return []; }
@GQLQuery('getUser') async getOne(@Arg('id', z.string()) id: string) { return { id }; }
@GQLMutation('createUser') async create(@Arg('name', z.string()) name: string) { return { id: Date.now().toString(), name }; }}generate middleware <nombre>
Sección titulada «generate middleware <nombre>»veloce generate middleware authimport type { Context, Next } from 'hono';
export async function authMiddleware(c: Context, next: Next) { const token = c.req.header('authorization'); if (!token) { return c.json({ error: 'No autorizado' }, 401); } await next();}generate gateway <nombre>
Sección titulada «generate gateway <nombre>»Un gateway @WebSocket con los tres hooks de ciclo de vida y un schema Zod de mensajes.
veloce generate gateway presenceCrea src/websockets/presence.websocket.ts con @OnConnect, @OnMessage y @OnDisconnect, uniéndose a una sala con el nombre del gateway. Se registra como un controlador:
app.include(PresenceGateway);app.usePlugin(new WebSocketPlugin());Solo un @OnMessage por gateway — la metadata guarda un único manejador de mensajes — así que modela los distintos payloads del cliente como una unión discriminada sobre type.
generate listener <nombre>
Sección titulada «generate listener <nombre>»Una clase de listeners @On.
veloce generate listener auditCrea src/listeners/audit.listener.ts. Los listeners se registran con app.include() y se resuelven por el contenedor de DI, así que inyectan servicios igual que un controlador. Las suscripciones se enganchan en compile() y se sueltan en shutdown().
generate plugin <nombre>
Sección titulada «generate plugin <nombre>»veloce generate plugin analyticsimport type { Plugin, VeloceTS } from 'veloce-ts';
export class AnalyticsPlugin implements Plugin { name = 'analytics';
async register(app: VeloceTS): Promise<void> { // registra rutas o middleware aquí }}Reglas de colocación
Sección titulada «Reglas de colocación»| Comando | Ruta de salida |
|---|---|
generate module <nombre> | src/modules/<nombre>/ |
generate controller <nombre> | src/controllers/<nombre>.controller.ts |
generate service <nombre> | src/services/<nombre>.service.ts |
generate dto <nombre> | src/dto/<nombre>.dto.ts |
generate resolver <nombre> | src/resolvers/<nombre>.resolver.ts |
generate middleware <nombre> | src/middleware/<nombre>.middleware.ts |
generate plugin <nombre> | src/plugins/<nombre>.plugin.ts |
generate gateway <nombre> | src/websockets/<nombre>.websocket.ts |
generate listener <nombre> | src/listeners/<nombre>.listener.ts |
La CLI detecta un directorio src/ existente y coloca los archivos allí. Si no se encuentra src/, los archivos se colocan en el directorio actual.
Normalización de nombres
Sección titulada «Normalización de nombres»El argumento <nombre> se normaliza automáticamente:
| Entrada | PascalCase | kebab-case |
|---|---|---|
user | User | user |
UserProfile | UserProfile | user-profile |
create-product | CreateProduct | create-product |