@wu-framework/cli 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +149 -32
- package/README.md +69 -11
- package/bin/wu-darwin-arm64 +0 -0
- package/bin/wu-darwin-x64 +0 -0
- package/bin/wu-linux-arm64 +0 -0
- package/bin/wu-linux-x64 +0 -0
- package/bin/wu-win32-x64.exe +0 -0
- package/bin/wu.js +61 -61
- package/package.json +4 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,40 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
Todos los cambios notables de wu-cli. Formato basado en [Keep a Changelog](https://keepachangelog.com/) y [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## [0.4.0] — 2026-08-27 — cabeceras, CSP generada y ficheros que no debían salir
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Todo lo de aquí sale de un informe de escaneo contra un sitio wu: `.env` servido, sin CSP, sin `X-Frame-Options`, `package.json` público, `Access-Control-Allow-Origin: *`. Nueve de los once hallazgos los producía wu, no el hosting.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Corregido
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
11
|
+
- **Parser del proxy estricto y con DNS real.** Los targets documentados como `http://localhost:8080` o `http://internal.corp:8081` fallaban porque el handler solo aceptaba IPs. Ahora resuelve hostnames, acepta IPv6 entre corchetes y rechaza puertos inválidos, credenciales, paths y esquemas ajenos. `https://` también falla explícitamente: antes elegía el puerto 443 pero enviaba HTTP plano, una falsa promesa de TLS. El parser tiene un target de fuzzing.
|
|
12
|
+
- **Path traversal en `wu serve`.** El servidor de producción concatenaba `dist/{path}` sin normalizar: `GET /../.env` leía el `.env` del proyecto, y `/../package.json` su árbol de dependencias. Bastaba un cliente que no normalice la ruta (`curl --path-as-is`, cualquier escáner por socket). Ahora la ruta se percent-decodifica **antes** de comprobarse, y `..`, `\`, `:` y NUL la descartan.
|
|
13
|
+
- **`wu dev` servía `package.json`.** `isServableProjectFile` lo aceptaba por su extensión `.json`. También servía cualquier fichero bajo el directorio del shell —`shell/.env`, `shell/.git/config`— porque el fallback del shell no filtraba nada: es el handler que recibe todo lo que no casó con ninguna app, es decir, exactamente lo que pide un escáner.
|
|
14
|
+
- **`Access-Control-Allow-Origin: *` hardcodeado** en todas las respuestas de `wu serve`. Ahora no se emite ninguna cabecera CORS salvo que `security.cors` lo pida; con un origen concreto se añade `Vary: Origin`, sin el cual una caché compartida sirve la respuesta de un origen a otro.
|
|
15
|
+
- **Un asset que falta ya no devuelve el `index.html`.** El fallback SPA respondía 200 + HTML a `/assets/x.js`, y el navegador fallaba con «unexpected token '<'». Ahora sólo cae al shell lo que puede ser una ruta de cliente.
|
|
16
|
+
- **`HEAD` se responde como tal**: cabeceras del `GET`, sin cuerpo. Antes escribía el cuerpo igualmente y desincronizaba el keep-alive.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
### Agregado
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
- **Shells Astro y Next.js.** `wu create` los ofrece en el menú del shell (y en `--shell astro|next`); compilan con su propio bundler y wu sirve la salida — `shell/dist/` (Astro 7) u `shell/out/` (export estático de Next 16). `wu dev` construye el shell al arrancar si falta la salida, lo **reconstruye automáticamente** cuando el watcher ve cambiar sus fuentes (antes de emitir el reload, para que el navegador no recargue el build viejo; `out/` y `.astro/` entraron a la lista de directorios ignorados para que el propio build no se re-dispare en bucle), y el dev server aprendió a sondear `out/` además de `dist/` (root, assets `/_next/`, sub-rutas y fallback SPA). `wu build` usa el build por defecto del framework (`npx astro build` / `npx next build`) y hoista desde cualquiera de los dos directorios. `wu doctor` D2 comprueba la salida del build en vez de exigir un `index.html` que estos shells no tienen. El escritor de `wu.config.json` ahora emite `shell.dev_cmd`/`build_cmd` (antes los parseaba y los tiraba al reescribir — el mismo fallo que borraba las reglas de proxy). Sólo shell: una micro-app wu es un mount CSR de Vite, así que `--template astro` se rechaza con el mensaje que lo explica.
|
|
21
|
+
- **Diseño premium para los shells Astro y Next.** Una sola hoja (`templates/wu-shell.css`) que `wu create` copia a los dos scaffolds, así ambos shells son exactamente la misma interfaz: sidebar con vidrio esmerilado y barra de acento en el ítem activo, halos de luz ambiental, tarjetas con hairline del color del framework y glow al hover, cabecera pegajosa `wu / <app>` con chip de framework en cada micro-app montada, atajos de teclado (1–9 abre esa app, H vuelve al overview), sidebar que colapsa a top bar en móvil y respeto de `prefers-reduced-motion`. Disciplina CSP heredada del shell vanilla: cero atributos `style` en el markup estático (los colores viajan como variable `--c` vía CSSOM) — el shell Next pasó de aflojar la política por 15 atributos inline a no necesitar ninguna excepción.
|
|
22
|
+
- **Versiones de scaffolding al día.** react `^19.2`, lit `^3.3`, preact `^10.29`, alpine `^3.16`, stencil `^4.44`, wu-framework `^2.7`. Vite se queda en `^7` a propósito: el optimizer de Qwik declara `vite >=5 <8`, y subir a 8 rompería cada proyecto Qwik.
|
|
23
|
+
- **CI nativa en Windows y macOS**, además de Linux. Compila, ejecuta toda la
|
|
24
|
+
suite, prueba el launcher npm y arranca el binario de cada sistema; Windows
|
|
25
|
+
también valida que el Job Object de cierre forzado se pueda crear de verdad.
|
|
26
|
+
- **Fuzzing continuo de parsers.** CI entrega 25.000 mutaciones a los targets de
|
|
27
|
+
HTTP, percent-decoding/rutas y URLs de proxy mediante `std.testing.fuzz`.
|
|
28
|
+
- **`src/runtime/security.zig`** — la política en un solo sitio, compartida por los dos servidores, el build, el doctor y el sentinel. Reglas de ruta, cabeceras, generación de CSP, escáner de contenido en línea y auditoría de directorios. 15 tests, uno de ellos contra el vector de ejemplo de la spec de CSP.
|
|
29
|
+
- **Cabeceras de seguridad en toda respuesta.** `wu serve`: CSP, `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy`, `Permissions-Policy`, HSTS. `wu dev`: `SAMEORIGIN`, `nosniff` y `Referrer-Policy` — sin CSP (sirve fuente recién transformada, ningún hash duraría) y con CORS abierto (cada app en su puerto).
|
|
30
|
+
- **CSP calculada, no copiada.** `wu build` y `wu serve` hashean cada bloque en línea del `dist/index.html` final —cliente del sentinel, runtime wu-lite, lógica del shell, `<style>`— y los escriben como `'sha256-…'`. Sin `'unsafe-inline'` ni `'unsafe-eval'`. Verificado en Chrome: los tres scripts ejecutan y la navegación, el tema y el CSS del shell siguen funcionando bajo la política.
|
|
31
|
+
- **El shell generado ya no necesita excepciones.** Los `onclick=` pasaron a un único listener delegado sobre `data-wu-action`/`data-section`, los `style="background:…"` a reglas `.fw-icon[data-fw="…"]`, y el `display:none` en línea a una clase. De paso, `showError` construye nodos y usa `textContent` en vez de interpolar el mensaje del bundler en `innerHTML`.
|
|
32
|
+
- **`wu build` escribe la política para tu host**: `dist/_headers` (Netlify, Cloudflare Pages), `dist/vercel.json` (con el rewrite SPA) y `dist/wu-security.conf` (nginx, con `always` y los `deny` de dotfiles). La mayoría de deploys no corre `wu serve`; una política que sólo existe en un README no la tiene nadie.
|
|
33
|
+
- **Auditoría de lo que se va a publicar.** `wu build` recorre `dist/` y `wu doctor` recorre además los `public/` de cada app y del shell, nombrando credenciales, lockfiles y `.git/` antes de que suban. Un `.env` en `public/` lo copia verbatim cualquier bundler: así es como aparece en la URL de producción.
|
|
34
|
+
- **Bloque `security` en `wu.config.json`** — `csp`, `frameOptions`, `referrerPolicy`, `permissionsPolicy`, `hsts`, `nosniff`, `cors`, `appOrigins`, `connectOrigins`. `""` = wu decide, `"off"` = no emitir. Ver [docs/configuration.md](docs/configuration.md#security).
|
|
35
|
+
- **El sentinel corre en `wu serve`.** Toda la maquinaria anti-scraping vivía sólo en el dev server, donde está deshabilitada a propósito; el que mira a internet no la usaba. Además comparte la lista de rutas prohibidas: pedir `/.env` o `/.git/config` puntúa como bot en vez de costar un 404 barato.
|
|
36
|
+
- **`wu doctor`** gana D6 (credenciales en lo que se publica) y D7 (el build lleva cabeceras aplicables y el shell no necesita excepciones de CSP).
|
|
18
37
|
|
|
19
|
-
|
|
38
|
+
---
|
|
20
39
|
|
|
21
|
-
|
|
22
|
-
- **SFC `.vue`/`.svelte` importados por specifier bare/aliased servidos crudos**. Al importar p.ej. `@shared/components/Modal.vue` (resuelto a `/@modules/...`), el dev server solo compilaba `.ts`/`.mts` vía daemon y mandaba el texto del SFC tal cual → el browser fallaba con *"module does not provide an export named 'default'"*. Ahora también se compilan los SFCs que pasan por esa ruta (`src/runtime/dev_server.zig`).
|
|
23
|
-
- **Procesos hijo huérfanos en Windows**. El daemon compilador y los procesos Vite quedaban vivos cuando `wu` moría de forma abrupta (taskkill, crash, cerrar la terminal) — el handler de señales no llega a correr. Nuevo `src/util/jobobject.zig`: ata cada hijo a un Job Object con `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, así mueren con el padre pase lo que pase. Cableado en `dev`, `serve` y el supervisor Vite.
|
|
24
|
-
- **Shells react/vue/svelte no buildeaban**. `wu create` generaba el shell sin `vite.config.js` ni las devDependencies (`vite` + plugin del framework), así que `wu build` fallaba. Ahora se generan ambos para los tres shells (`src/commands/create.zig`).
|
|
25
|
-
- **Shells Vue y Svelte incompletos**. El shell Vue ahora monta vía `createApp` + `App.vue` usando `WuSlot` de `wu-framework/adapters/vue`; el shell Svelte usa el `mount` de Svelte 5 + `App.svelte` con loop imperativo en `onMount`.
|
|
26
|
-
- **Rutas absolutas del shell rotas en build**. Los scripts del `index.html` del shell usaban `/shell/src/main.tsx` y `/shell/main.js`; con Vite usando el dir del shell como root esos paths no resolvían. Cambiados a root-relativos (`/src/main.tsx`, `/main.js`).
|
|
27
|
-
- **`@angular/build` faltante** en el `package.json` mergeado, rompía el build de proyectos Angular con *"Cannot find module '@angular/build/private'"*. Agregado a las devDeps del framework.
|
|
28
|
-
- **`wu dev` ignoraba `proxy.port`**. El puerto estaba hardcoded a 3000 y el campo de `wu.config.json` no tenía efecto. Ahora: `--port` gana, si no se respeta `proxy.port`, y solo si no, 3000 (`src/commands/dev.zig`).
|
|
29
|
-
- **Cuatro memory leaks** detectados con `DebugAllocator`: buffers de `std.Io.Writer.Allocating.fromArrayListAligned` que no se reclamaban en `config.zig` (`writeConfigTo`), `install.zig` (`writeAliasMap`, `generateMergedPackageJson`) y las strings de `findAsset` en `build.zig`. Reclamados con `aw.toArrayList()` / `defer free`.
|
|
40
|
+
## [0.3.0 – 0.3.6] — 2026-05-17 → 2026-06-10
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
> Esta sección agrega todo lo liberado en la serie 0.3 (siete versiones en npm). El desglose exacto por parche se perdió junto con el árbol original de 0.3.5/0.3.6 (ver la nota de reconstrucción más abajo); este repo es ahora la única fuente.
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
- **`generateRootPackageJsonNamed` recibe el framework del shell** para emitir las devDeps correctas; `wu add` actualizado a la nueva firma.
|
|
44
|
+
Sesión grande de hardening, refactor de UX y documentación. Sin cambios breaking para usuarios que ya tienen `wu.config.json` válido — pero **una excepción**: el campo `cors` del proxy ahora default `false` (antes hardcoded `true`).
|
|
35
45
|
|
|
36
|
-
|
|
46
|
+
### Corregido (infra de tests)
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
- **`zig build test` no ejecutaba ningún test.** El root del `addTest` es `src/main.zig` y Zig solo recolecta bloques `test` de los archivos que analiza semánticamente; importar un módulo al tope de `main.zig` no basta. La suite compilaba, reportaba éxito y corría **0 de 87** tests. Un bloque `test { _ = @import(...) }` en `main.zig` los engancha; añadir archivos nuevos ahí es ahora obligatorio.
|
|
49
|
+
- **Dos tests de `hypervisor/vfs.zig` llevaban rotos** desde que `VFS` ganó el parámetro `io`, sin que nadie lo notara porque nunca se compilaban. Actualizados.
|
|
50
|
+
- **`resolveConditionObject` respeta el orden de claves del `package.json`.** Recorría su propia lista de prioridades (`import > module > browser > default`) en lugar del orden en que el paquete escribió las condiciones, que es lo que dice la spec de Node: el consumidor declara qué condiciones acepta, el paquete decide el orden. En preact —que lista `browser` antes que `import`, apuntando a builds distintos— resolvía a `preact.mjs` donde Node y Vite resuelven a `preact.module.js`. Nuevo `FieldIterator` que itera claves a profundidad 1 en orden.
|
|
51
|
+
- `require` queda fuera de la primera pasada: siempre resolvemos para un `import`, así que un paquete que liste `require` primero no debe entregarnos su build CommonJS. Se prueba solo si nada más hizo match, para que los paquetes CJS-only sigan resolviendo.
|
|
52
|
+
- Esto lo destapó el arreglo de la suite: el test `browser priority` llevaba en rojo desde siempre, tapado porque no se ejecutaba.
|
|
39
53
|
|
|
40
54
|
### Seguridad
|
|
41
55
|
|
|
@@ -49,8 +63,116 @@ Sesión grande de hardening, refactor de UX y documentación. Sin cambios breaki
|
|
|
49
63
|
- **CORS opt-in en proxy** (**breaking**): el campo `cors: bool` se agrega a `ProxyRule` con default `false`. Antes el proxy inyectaba `Access-Control-Allow-Origin: *` en TODAS las respuestas. Migración: agregá `"cors": true` explícito a las reglas que lo necesiten.
|
|
50
64
|
- **`looksLikeBrowser` reforzado**: ahora exige `Mozilla/5.0` + engine (`AppleWebKit`/`Gecko`/`Trident`) + product token (`Chrome/`, `Firefox/`, `Safari/`, `Edg/`, `OPR/`, `Brave/`, `Vivaldi/`, `SamsungBrowser/`). Antes "Mozilla/5.0" solo era suficiente — cualquier scraper moderno pasaba.
|
|
51
65
|
|
|
66
|
+
### Agregado (prevención)
|
|
67
|
+
|
|
68
|
+
- **`wu doctor`** — diagnostica un proyecto buscando justo los fallos que no se ven mientras ocurren: directorio de app que no existe, `index.html` del shell apuntando a una entrada que nadie escribió, rangos de `wu-framework` desincronizados entre `package.json`, overrides de `.wu-deps.json` cuya clave no casa con ningún directorio, y puertos repetidos. Cuando la clave está mal dice además *por qué*: «eso es el **nombre** de la app; el dev server busca por su directorio». Verificado reintroduciendo los tres bugs reales: los tres salen.
|
|
69
|
+
- **`src/util/doc.zig`** — retira el patrón que más bugs ha producido en este repo. `Writer.Allocating.fromArrayList` se queda la memoria de la `ArrayList` hasta que llamas a `toArrayList()`; hasta entonces `.items` es un slice vacío, y como `createFile` trunca antes de escribir el resultado no es «no escribió nada» sino «destruyó lo que había». Eso llegó a publicarse **cinco veces**: `shell/package.json`, `shell/index.html`, los dos escritores de `wu install` y `config.zig`. El `ArrayList` nunca hizo falta: `Allocating.init` + `written()` dan lo mismo sin paso que olvidar. Migrados 18 sitios en 7 archivos; los 3 que necesitan interoperar con un `ArrayList` ajeno llevan un `// allow-fromArrayList:` con su motivo.
|
|
70
|
+
- **`scripts/check-invariants.sh`** — cuatro propiedades que ningún test unitario expresa, una por bug publicado: **I1** `fromArrayList` confinado a `doc.zig`; **I2** todo campo de config leído por alguien fuera de `config.zig`; **I3** el rango de `wu-framework` en una sola constante; **I4** las tres versiones (`build.zig.zon`, `main.zig`, `package.json`) sincronizadas.
|
|
71
|
+
- **`scripts/smoke-hmr.sh`** — `wu dev` no tenía **ninguna** cobertura automática: el humo solo ejercitaba `create`/`install`/`build`, así que un dev server sirviendo bytes rancios para siempre habría pasado todo lo del repo. Comprueba que compila en vez de servir el fuente crudo, que reescribe los specifiers a `/@modules/`, que **una edición se refleja en lo servido**, que el canal SSE de `/__wu_hmr` emite, que `--no-open` se respeta y que no fuga en toda una sesión. En CI sobre react, vue y svelte (las dos rutas de compilación).
|
|
72
|
+
- **CI ampliado a 6 trabajos** y el mínimo de tests subido de 90 a 100.
|
|
73
|
+
|
|
74
|
+
### Agregado (siete shells nativos)
|
|
75
|
+
|
|
76
|
+
- **Shells nativos para Solid, Preact, Lit y Qwik**, además de React, Vue y Angular. `wu create --template <fw>` genera un shell escrito en ese framework para los siete; los otros seis siguen con el agnóstico de JavaScript plano.
|
|
77
|
+
- Todos montan **imperativamente** con `wu.mount()` tras existir la vista, no con el componente de slot del adapter. Solid, Preact y Lit sí exponen uno (`createWuSlot`, `createWuSlotElement`), pero un solo patrón es un solo sitio donde equivocarse, y es el que ya estaba probado con Angular. El shell sigue siendo del framework: su UI, su reactividad y su modelo de componentes son suyos.
|
|
78
|
+
- **Qwik no tiene componente de slot** — su adapter solo exporta `register`, `getWuInstance` y `waitForWu`. Con montaje imperativo da igual.
|
|
79
|
+
- **Lit necesita `createRenderRoot() { return this; }`**, y esto no es cosmético: Lit renderiza en su propio shadow root por defecto, y `wu.mount()` resuelve el destino con `document.querySelector`, que no cruza esa frontera — todos los montajes fallarían al no encontrar su contenedor. Cada micro-app conserva su propio shadow root; esto solo saca del shadow al marcado del shell.
|
|
80
|
+
- **Svelte sigue sin shell nativo a propósito**, igual que Alpine, HTMX y Stimulus: los tres últimos son capas sobre HTML, no frameworks de aplicación.
|
|
81
|
+
- **`scripts/smoke-shells.sh` + trabajo de CI con matriz de 7.** El humo normal empareja un shell con una micro-app de su mismo framework, así que **no puede distinguir un shell que funciona de uno que solo se monta a sí mismo**. Este construye cada shell nativo con dos micro-apps ajenas (React y Vue) al lado de la suya y comprueba que las tres queden interactivas en la misma página. Esa combinación es literalmente lo que wu vende, y hasta ahora no se había ejecutado nunca para ningún shell salvo a mano. **7/7 en verde.**
|
|
82
|
+
- El aserto A2b del humo **deriva la lista de shells nativos de `create.zig`** en vez de llevar una copia. Una segunda copia de una lista es exactamente como el rango de `wu-framework` acabó en cuatro sitios y se desincronizó.
|
|
83
|
+
|
|
84
|
+
### Agregado (shell de Angular + versiones al día)
|
|
85
|
+
|
|
86
|
+
- **Tercer shell nativo: Angular.** `wu create --template angular` genera ahora un shell escrito en Angular 22 (componente standalone, `bootstrapApplication`, `@for` en plantilla) en vez del agnóstico. Verificado orquestando Angular + React + Vue en la misma página: las tres montan, los tres contadores responden, cero errores.
|
|
87
|
+
- **Los dos `tsconfig` son imprescindibles y su ausencia falla sin nombrarlos.** Sin `tsconfig.json` y `tsconfig.app.json` el plugin de Angular se salta la compilación AOT, deja el componente para el compilador JIT — y JIT no va en un bundle de producción. La página muere al arrancar con `JIT compiler unavailable`, un mensaje que no menciona tsconfig por ningún lado. Lo descubrí construyéndolo a mano y fallando primero.
|
|
88
|
+
- Nuevo predicado `create.hasNativeShell()` y `shellEntry()`, en lugar de repartir la lista de shells nativos por el archivo. La ruta del entry sale de un solo sitio, así que la etiqueta `<script>` y el archivo en disco no pueden volver a divergir.
|
|
89
|
+
- **Svelte sigue con el shell agnóstico a propósito**: su adapter no exporta componente de slot, así que un «shell Svelte» sería `wu.mount()` a mano, el mismo código disfrazado.
|
|
90
|
+
- Nuevo aserto **A2b** en el humo: comprueba que cada framework tenga el tipo de shell que le corresponde, y en Angular que estén los dos tsconfig.
|
|
91
|
+
- **Dependencias de plantilla actualizadas**: `vite ^6 → ^7`, `@vitejs/plugin-vue ^5.2 → ^6`, `@preact/preset-vite ^2.9 → ^2.10`, `@builder.io/qwik ^1.12 → ^1.20`, `esbuild ^0.25 → ^0.28`.
|
|
92
|
+
- **Vite se queda en 7, no en 8, y hay motivo:** `@builder.io/qwik@1.20` declara `peer vite@">=5 <8"`. Y vite no se puede aliasar por app como una librería —es un binario que se invoca como `npx vite`—, así que su versión la capa el framework más restrictivo del conjunto. Por arrastre, `@vitejs/plugin-react` se queda en ^5.2 y `@sveltejs/vite-plugin-svelte` en ^6.2: sus versiones 6 y 7 exigen vite ^8.
|
|
93
|
+
- Las tres retenciones están documentadas en `check-template-drift.sh`, así que el informe semanal las marca como «retenido» con su motivo en vez de como deriva.
|
|
94
|
+
- El conflicto de Qwik lo cazó `wu install` saliendo con código 1 — el arreglo de hace unas horas. Antes habría sido un «Done!» verde y un error de build incomprensible.
|
|
95
|
+
|
|
96
|
+
### Corregido (manejo de señales)
|
|
97
|
+
|
|
98
|
+
- **El manejador de Ctrl+C de `wu dev` hacía trabajo ilegal en contexto de señal.** Llamaba a `server.shutdown()` directamente, que toma el mutex del daemon compilador (`compile_mod.shutdownDaemon`) y hace varios `free`. Nada de eso es async-signal-safe: si la señal cae mientras otro hilo tiene ese mutex, o en mitad de una asignación, el comportamiento es indefinido.
|
|
99
|
+
- Ahora el manejador hace **solo** dos cosas, ambas en la lista POSIX de seguras: marca una bandera atómica y hace `shutdown()` del socket que escucha, para despertar al hilo parado en `accept()`. `shutdown` y no `close`, a propósito: hace que `accept()` retorne sin liberar el descriptor, así que el `defer listener.deinit()` del hilo principal sigue siendo correcto y no puede cerrar un descriptor que entretanto se haya reasignado a otro hilo.
|
|
100
|
+
- La limpieza real ocurre ahora donde debía: en el hilo principal, por el `defer server.shutdown(io)` de `dev.zig`, cuando `run()` retorna con normalidad.
|
|
101
|
+
- **Honestidad sobre la verificación:** vi un `SIGABRT` con volcado una sola vez con el código antiguo y **no logré reproducirlo en 50 intentos**, ni siquiera matando en mitad de compilaciones concurrentes. El código antiguo pasa la misma prueba de estrés que el nuevo. Este cambio se justifica **por corrección** —la regla POSIX es explícita— y no porque haya un fallo reproducido. El nuevo manejador es correcto por construcción; el antiguo era una carrera latente.
|
|
102
|
+
|
|
103
|
+
### Agregado (deriva de versiones)
|
|
104
|
+
|
|
105
|
+
- **`scripts/check-template-drift.sh` + trabajo semanal en CI.** Compara cada pin de las plantillas con el `latest` de npm. La plantilla de Angular se quedó **tres majors** por detrás sin que nada avisara; el primer síntoma fue alguien preguntando si la versión actual estaba soportada. Es **consultivo a propósito**: nunca tumba el build, porque un major nuevo aguas arriba es una noticia, no una regresión — el pin debe moverse cuando alguien haya ejecutado `./scripts/smoke.sh <framework>` y lo haya visto construir y renderizar.
|
|
106
|
+
- Lleva excepciones documentadas: `typescript` aparece «por detrás» de la 7 y **no debe subir**, porque `@angular/build@22` declara `peer typescript@">=6.0 <6.1"`. Un informe que marca pines correctos se deja de leer, y entonces se pierde los de verdad.
|
|
107
|
+
- Encontró de entrada que `vite` está 2 majors por detrás (^6 contra 8.2.0).
|
|
108
|
+
|
|
109
|
+
### Agregado (Angular junto a otros frameworks)
|
|
110
|
+
|
|
111
|
+
- **Bloque D en el test de integración: Angular + React + Vue en un mismo proyecto.** `smoke.sh angular` solo construye un shell Angular con una micro-app Angular; Angular compartiendo página con otro framework —zone.js, su propia detección de cambios y sus manías de bundling, todo dentro de un Shadow DOM ajeno— **no se había ejecutado nunca**, y es justo el escenario de migración en el que wu es mejor. Comprueba que los tres instalan, construyen, y que **cada bundle lleva su propio runtime**.
|
|
112
|
+
- Detectar Angular por `defineComponent` sería un error: Angular emite `ɵɵdefineComponent`, así que una comprobación ingenua de Vue lo captura también. Me pasó al verificarlo a mano.
|
|
113
|
+
- Verificado en Chrome: Angular 22.1.0, React 19.2.8 y Vue 3.5.40 montando en la misma página, contadores independientes, estado conservado al navegar entre ellas, consola sin un solo error.
|
|
114
|
+
|
|
115
|
+
### Corregido (Angular 22 y errores de instalación)
|
|
116
|
+
|
|
117
|
+
- **`wu install` terminaba en 0 aunque `npm install` fallara.** Imprimía la línea roja y seguía hasta un «Done!» verde, así que ningún script, CI ni el propio test de humo podía distinguir un `node_modules` vacío de una instalación correcta — el fallo solo aparecía después, como un error de build confuso. `runNpmInstall` y `runNpmInstallLegacy` devuelven ahora `bool` y `wu install` sale con 1. Descubierto porque mi propia prueba de Angular 22 dijo «ok» sin haber instalado nada.
|
|
118
|
+
- **La plantilla de Angular emitía dos veces `@angular/build` y `typescript`.** En JSON gana la última clave, así que los pines viejos del final anulaban en silencio a los de arriba. Ahora la rama escribe un solo juego.
|
|
119
|
+
- **Plantilla de Angular subida a 22** (antes 19, tres majors por detrás): `@angular/* ^22.0.0`, `@analogjs/vite-plugin-angular ^2.6.4` (el `^1.14.0` anterior no soporta 22), `zone.js ^0.16.0` y **`typescript ^6.0.0`**. Este último es el que no se ve venir: `@angular/build@22` declara `peer typescript@">=6.0 <6.1"` y npm se niega a resolver el árbol contra 5.x. Verificado: `wu create --template angular` → `wu install` → `wu build` → render headless con Angular **22.1.0**, contador 0→3, cero errores.
|
|
120
|
+
|
|
121
|
+
### Corregido (versiones por app en producción)
|
|
122
|
+
|
|
123
|
+
- **`wu build` ignoraba `.wu-deps.json` por completo — dev y producción discrepaban en silencio.** El alias por app estaba implementado solo en el dev server; `build.zig` ni siquiera abría el archivo. Consecuencia medida: una app con `"react": "^18.3.1"` desarrollaba contra React 18 y **su bundle de producción llevaba React 19**, con build en verde y sin un solo aviso. `react--18@18.3.1` quedaba instalado en disco y sin usar por nadie. Eso es peor que no tener la función: si no existiera, lo sabrías.
|
|
124
|
+
- La envoltura de Vite que ya generaba `shared.zig` para inyectar `external` inyecta ahora también `resolve.alias`. Un `find` de tipo string en `@rollup/plugin-alias` casa el id exacto **o como prefijo de ruta**, así que una entrada para `react-dom` redirige también `react-dom/client` — los subpaths no necesitan entradas propias.
|
|
125
|
+
- Nuevo `src/config/deps.zig`: carga y valida el mapa, con 6 tests, incluido uno que fija el contrato de que **se indexa por directorio y no por nombre**.
|
|
126
|
+
- **Cuando el alias no se puede aplicar el aviso es rojo, no amarillo.** Perder el compartir de dependencias cuesta bytes; perder un alias cuesta correctitud, y el mensaje dice exactamente qué paquete va a resolver a la versión izada.
|
|
127
|
+
- Verificado de punta a punta: `mf-dual` (pide 19) → 19.2.8 en su bundle, `mf-legacy` (pide 18) → 18.3.1, ambas montan en la misma página y sus contadores responden, cero errores de consola. Nuevo aserto **C5**, comprobado en negativo desactivando el arreglo.
|
|
128
|
+
- Nota sobre React 17: no se puede fijar a 17 usando el adapter React de wu-framework, porque el adapter importa `react-dom/client`, que llegó en la 18. Antes eso «funcionaba» enviando React 19 en silencio; ahora el build falla y dice por qué.
|
|
129
|
+
|
|
130
|
+
### Empaquetado
|
|
131
|
+
|
|
132
|
+
- **`package.json` restaurado y versiones sincronizadas en `0.4.0`.** El repo tenía `bin/wu.js` y `scripts/build-all.sh` versionados pero **no el `package.json`**, así que publicar desde este árbol era imposible. Reconstruido desde el tarball de `@wu-framework/cli@0.3.6`. Además `build.zig.zon` y `src/main.zig` decían `0.2.1`, por debajo de lo publicado: publicar habría sido una regresión de versión. Las tres fuentes van ahora sincronizadas. Minor y no patch por el cambio de comportamiento de `open_browser`.
|
|
133
|
+
- **Linux ARM64 distribuido por npm.** `build-all.sh` cruza-compila ahora `aarch64-linux-gnu` y el shim selecciona `wu-linux-arm64` cuando Node reporta `linux-arm64`. El mapa de cinco plataformas tiene una prueba con `node:test` que corre en CI; además, el mensaje de recuperación usa el nombre real del paquete, `@wu-framework/cli`.
|
|
134
|
+
|
|
135
|
+
### Corregido (reconstruido de 0.3.5/0.3.6)
|
|
136
|
+
|
|
137
|
+
> El código de 0.3.5 y 0.3.6 se perdió: se publicó desde una máquina cuyo trabajo nunca llegó a GitHub (`origin/master` es del 17 de mayo, 0.3.5 salió el 29). Estos arreglos están reconstruidos a partir de las notas del CHANGELOG publicado y verificados uno a uno contra este árbol. **Este repo es ahora la única fuente que existe.**
|
|
138
|
+
|
|
139
|
+
- **CSS extraído no se enlazaba con shell custom.** Vite separa el CSS de cada micro-app en su propio archivo, enlazado solo desde el `index.html` de la app — que el build descarta. `injectAppsIntoUserShell` escribía la ruta del CSS en `window.__wu_apps` **como dato** y no emitía ningún `<link>`; `generateProductionShell` sí lo hacía, así que solo se rompía para proyectos con shell propio: montaban sin estilos y todo lo demás pasaba en verde. Nuevo aserto **A15** en el test de humo, que inyecta un `.css` real en la app antes de construir — sin eso el aserto pasaría en vacío, porque las plantillas de serie estilan todo inline.
|
|
140
|
+
- **Estilos de Vue pisándose entre micro-apps.** El daemon inyectaba `<style data-wu-vue="{basename}">`, y como todas las apps tienen `src/App.vue` compartían la clave `App.vue`: la última en montar borraba los estilos de las anteriores. Ahora la clave se deriva de la ruta completa del proyecto. **Segunda colisión encontrada de paso:** `id: 'wu'` y `scopeId = 'data-v-wu'` eran constantes literales, así que *todos* los componentes con `<style scoped>` compartían un único scope global — `scoped` no aislaba nada. Ambos ids se derivan ahora del path.
|
|
141
|
+
- **SFC importado por specifier bare/aliased servido en crudo.** `import Modal from '@shared/components/Modal.vue'` resuelve por `/@modules/`, y esa rama solo compilaba `.ts`/`.mts`: llegaba el texto del SFC al navegador y fallaba con *"does not provide an export named 'default'"*. Arreglado localmente en esa rama, sin tocar `needsTransform()`, que comparte camino con los archivos de app donde los SFC ya se compilan antes.
|
|
142
|
+
- **`proxy.open_browser` tampoco se leía nunca — mismo patrón, encontrado auditando la clase entera.** `wu create` lo escribe como `true` en todos los proyectos y `docs/configuration.md` lo documenta como «`wu dev` abre el navegador al iniciar», pero `dev.zig` lo fijaba a `false` y solo `--open` lo cambiaba: una promesa que el código nunca cumplió. Ahora la precedencia es `--open`/`--no-open` > `proxy.open_browser` > `true`. **Cambio de comportamiento:** los proyectos existentes tienen `"open_browser": true` en su config, así que `wu dev` pasa a abrir el navegador. Para volver atrás: `"open_browser": false` o `--no-open`.
|
|
143
|
+
- **`wu dev` ignoraba `proxy.port`.** El puerto se inicializaba a 3000 y solo lo cambiaba `--port`; el campo se parseaba del `wu.config.json`, se guardaba y se volvía a escribir, pero nunca se leía. Ahora la precedencia es `--port` > `proxy.port` > 3000.
|
|
144
|
+
- **Procesos hijo huérfanos en Windows.** `taskkill /F`, un crash o cerrar la terminal no dan opción a que corra el manejador de señales, y el supervisor de Vite y el daemon compilador sobrevivían reteniendo sus puertos. Nuevo `src/util/jobobject.zig`: un Job Object con `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` mueve la garantía al kernel. No-op en POSIX. **Verificado solo por cruza-compilación** (`-Dtarget=x86_64-windows-gnu`), no en ejecución — no hay Windows en esta máquina.
|
|
145
|
+
|
|
146
|
+
### Corregido
|
|
147
|
+
|
|
148
|
+
- **`wu create` generaba proyectos que `wu build` no podía construir — 12 de los 13 frameworks.** Verificado creando, instalando y construyendo un proyecto por framework: antes pasaba solo React, ahora pasan los 13. Eran seis defectos independientes:
|
|
149
|
+
1. **El `index.html` del shell y su archivo de entrada no coincidían.** Eran dos literales mantenidos por separado en ramas distintas: el HTML pedía `/shell/main.js` (10 frameworks) o `/src/main.js` (Svelte) mientras el archivo estaba en `shell/main.js`. Vite resuelve eso contra el directorio del shell, así que morían con "Failed to resolve" antes de compilar nada. Ahora la ruta se decide una vez y se usa en los dos sitios.
|
|
150
|
+
2. **El shell de Vue no tenía archivo de entrada.** La rama era literalmente `// Vue template placeholder` + `createDir`: creaba `shell/src/` vacío y el `index.html` apuntaba a un archivo que nunca se escribía. Ahora genera `main.js` + `App.vue` reales, con `app.use(wuVuePlugin, { Vue })` — sin ese argumento el adapter no puede construir refs reactivos y falla en silencio.
|
|
151
|
+
3. **El shell no recibía `vite.config.js`.** Sin plugin, esbuild compilaba el JSX a `React.createElement`. Esto dejaba muerta la feature de "shell custom respetado" para todo proyecto recién creado.
|
|
152
|
+
4. **El plugin de Vite del shell seguía al framework nominal, no a su contenido.** Solo React y Vue producen un shell escrito en su framework; los otros once reciben el shell agnóstico, que es JavaScript plano. Darle a eso un plugin es inútil, y con Qwik es fatal: `qwikVite` se niega a arrancar sin un `src/` y abortaba con `Qwik srcDir ... not found`. Ahora el shell agnóstico lleva config sin plugins.
|
|
153
|
+
5. **El `package.json` raíz agregaba dependencias solo de las apps, nunca del shell.** Un shell Vue con micro-apps React no instalaba ni `vue` ni `@vitejs/plugin-vue`. Solo parecía sano porque `wu create --template <fw>` hace shell y micro-app del mismo framework. Aplica también a `wu add`, que regeneraba el archivo con el mismo criterio.
|
|
154
|
+
6. **La barra lateral del shell agnóstico estaba vacía.** El HTML traía el rótulo "Micro-apps" y un `<div id="wu-nav-apps">` que nadie rellenaba, y el botón Home traía `data-section` pero ningún listener: una vez abrías una app no había forma de volver. El `<p id="wu-app-count">` tampoco se escribía. Ahora el entry genera un botón por app con el color de su framework, mueve el resaltado `.active` al navegar, conecta Home y pone el contador. Encontrado mirando una captura, no un test — los 13 proyectos "pasaban" con la barra vacía.
|
|
155
|
+
7. **Angular no declaraba `@angular/build`.** `@analogjs/vite-plugin-angular` lo hace `require()` al cargarse sin declararlo, y el build moría con `Cannot find module '@angular/build/private'`.
|
|
156
|
+
- **Las plantillas pedían `wu-framework: "^1.1.17"` y nunca llegaban a 2.x.** El rango estaba repetido como literal en cuatro escritores —el shell, cada micro-app, el `package.json` raíz agregado y `wu install`— y se desincronizaron: tres seguían en `^1.1.17` cuando los otros ya no. npm iza el rango de la raíz, así que el literal viejo ganaba y **todo proyecto recién creado resolvía a 1.2.1** por mucho que las hojas pidieran otra cosa. De ahí venían los dos fallos del shell de Vue que se achacaban a "falta publicar": 1.2.1 expone su API solo por el default export (no hay `export { WuSlot }` plano) y su `WuSlot` se pisa el propio destino de montaje. Ninguno hacía falta arreglarlo: **2.6.0 lleva ambos resueltos y está publicado desde hace tiempo.** Ahora el rango vive en una sola constante (`create.wu_framework_range`, `^2.6.0`) que usan los cuatro escritores, y el shell de Vue vuelve a `<WuSlot>` con `import { wuVuePlugin } from 'wu-framework/adapters/vue'`. Verificado creando, instalando y construyendo los 13 frameworks contra 2.6.0 y renderizando cada uno en Chrome headless: los 13 montan, el contador responde al clic y no hay ni un error en consola.
|
|
157
|
+
- **`wu install` escribía `package.json` y `.wu-deps.json` vacíos — y se comía el `package.json` del proyecto.** Los dos escritores volcaban `buf.items` a disco, pero `buf` sigue siendo el `ArrayList` vacío que era antes de que el `Io.Writer.Allocating` tomara el control; hay que recuperarlo con `toArrayList()` primero, como sí hace `create.zig`. Como `createFile` trunca antes de escribir, el modo de fallo no era "install no hizo nada" sino "install destruyó el proyecto", y la siguiente ejecución moría leyendo el JSON corrupto. Consecuencia grave y silenciosa: **`.wu-deps.json` salía siempre vacío, así que el soporte de versiones distintas por app no se activaba nunca** aunque `wu install` detectara los conflictos y npm instalara los alias correctamente. Ahora ambos escritores hacen `toArrayList()`; verificado con dos micro-apps React (17 y 19) conviviendo.
|
|
158
|
+
- **Fuga de memoria en `wu install`.** Los nombres de alias (`react--17`) y las referencias npm (`npm:react@^17.0.2`) se alocaban con `allocPrint` y no se liberaban: el DebugAllocator escupía un stack trace al final de cada ejecución. No bastaba con liberarlos desde los mapas —un nombre de alias vive a la vez como clave en `final_deps` y como valor en `aliases`, y ambos mapas contienen además slices prestados que no deben liberarse—, así que ahora una lista `owned` es su único dueño.
|
|
159
|
+
- **`wu create` mandaba a `npm install` teniendo `wu install`.** En un proyecto recién creado npm funciona de casualidad, porque el `package.json` raíz ya viene fusionado; pero es el hábito equivocado: en cuanto alguien toca las dependencias de un micro-app, solo `wu install` vuelve a fusionarlas, detecta conflictos de major y escribe el `.wu-deps.json`. Además, apuntar a npm dejó todo el camino de `wu install` sin ejercitar — de ahí que los dos defectos anteriores sobrevivieran a una verificación de los 13 frameworks.
|
|
160
|
+
- **Las versiones distintas por app no se aplicaban nunca, por una segunda razón.** Aun con el `.wu-deps.json` ya escrito bien, el archivo se indexaba por **nombre** de app (`legacy`) mientras el dev server busca por **directorio** (`mf-legacy`), que es lo único que aparece en la URL de la que saca el `Referer`. Toda búsqueda fallaba en silencio. El propio comentario de cabecera de `install.zig` documentaba la clave correcta —`{ "mf-legacy": ... }`— y el código escribía otra. `DepClaim` lleva ahora `app_dir` además de `app_name`: el nombre para los mensajes, el directorio para el mapa. Comprobado sirviendo de verdad: `mf-dual` recibe React 19 (48 KB) y `mf-legacy` el envoltorio de `react--17` (73 KB, con su `object-assign`).
|
|
161
|
+
- **Fuga de memoria en `wu build`.** Las rutas de `entry` y `css` que devuelve `findAsset` se alocaban por app y no se liberaban nunca; el DebugAllocator las reportaba con stack trace al final de cada build. Ahora `manifest_apps` las libera, incluido el camino en que `append` falla.
|
|
162
|
+
|
|
52
163
|
### Agregado
|
|
53
164
|
|
|
165
|
+
- **Test de humo end-to-end en CI** (`.github/workflows/ci.yml`, `scripts/smoke.sh`, `scripts/smoke-integration.sh`, `scripts/smoke-probe.js`). Ejecuta los comandos que ejecuta una persona real —`wu create` → `wu install` → `wu build` → renderizar en un navegador headless— para los 13 frameworks en paralelo, más un trabajo aparte para `wu add` y para dos majors de React conviviendo. En local: `./scripts/smoke.sh` (todos), `./scripts/smoke.sh react` (uno), `./scripts/smoke-integration.sh`.
|
|
166
|
+
- Cada aserto corresponde a un defecto que llegó a publicarse, no a un supuesto: entrada del shell inexistente, rango de versión desincronizado, `wu install` truncando el `package.json`, fugas de memoria, y el contador que no sube porque los exports del bundle son todos `undefined`.
|
|
167
|
+
- **Usa `wu install`, nunca `npm install` a secas.** Que la verificación anterior usara npm es exactamente lo que dejó que dos bugs graves de `wu install` sobrevivieran a una pasada por los 13 frameworks.
|
|
168
|
+
- **Compila en Debug a propósito.** `init.gpa` solo es `DebugAllocator` en Debug; con `ReleaseSafe` el aserto de «sin fugas» no comprobaría nada.
|
|
169
|
+
- El trabajo de tests unitarios **exige un mínimo de 90 tests ejecutados**, no solo salida cero. `zig build test` estuvo mucho tiempo informando éxito mientras corría 0 de 87, porque Zig solo recoge bloques `test` de archivos que ha analizado. Un tick verde no es evidencia; un número sí.
|
|
170
|
+
- La sonda desciende por shadow roots anidados (Lit, Angular y Stencil montan dentro de un shadow root que a su vez está dentro del que crea wu) y vuelve a consultar el DOM antes de cada clic, porque las plantillas vanilla y Stencil se reconstruyen enteras con `innerHTML`. Ambas cosas produjeron mediciones falsas antes de estar así.
|
|
171
|
+
|
|
172
|
+
- **Dependencias compartidas en `wu build`** (`"shared": [...]` en `wu.config.json`). Cada specifier listado se bundlea una vez a `dist/wu-shared/<pkg>-<hash>.js`, se marca `external` en el build de cada app, y el shell recibe un `<script type="importmap">` que lo mapea a esa URL. Es el mismo mecanismo que `wu dev` ya usaba —identidad por URL, no negociación de versiones— llevado a producción. Medido en un proyecto shell React + 2 micro-apps React 19: `dist/` de 807 KB a 425 KB, cada micro-app de 207 KB a 12 KB, una sola descarga de React para tres consumidores. Nuevo módulo `src/commands/shared.zig`.
|
|
173
|
+
- Los subpaths son specifiers distintos: `react-dom` no incluye `react-dom/client`.
|
|
174
|
+
- Degradaciones explícitas, todas avisadas por consola: app sin `vite.config.*`, `build_cmd` que no es un `vite build` directo, specifier que no resuelve, o bundle compartido que falla. En el peor caso el resultado es el comportamiento previo — nunca un build roto.
|
|
175
|
+
- Los nombres de export de un paquete CommonJS se enumeran cargándolo en Node antes de generar el shim, igual que hace el optimizador de Vite. `export * from` un módulo CJS solo re-exporta `default`, y un bundle así deja `import { useState } from "react"` en `undefined`.
|
|
54
176
|
- **Wizard interactivo con TUI** (`wu create`). Raw mode cross-platform, navegación con ↑↓/jk, Esc/Ctrl+C para cancelar con cleanup del terminal. Sin defaults escondidos en la elección del shell. Confirm explícito antes de `npm install`.
|
|
55
177
|
- **Nuevo módulo `src/cli/tui.zig`** — primitivas: `enterRawMode`, `forceRestore`, `readKey`, ANSI helpers (cursor up, clear line, hide/show cursor).
|
|
56
178
|
- **Nuevo módulo `src/cli/prompts.zig`** — `Menu`, `Text`, `Confirm`. Re-render in-place sin flicker.
|
|
@@ -93,9 +215,9 @@ Sesión grande de hardening, refactor de UX y documentación. Sin cambios breaki
|
|
|
93
215
|
|
|
94
216
|
---
|
|
95
217
|
|
|
96
|
-
## [0.2.1] — 2026-
|
|
218
|
+
## [0.2.1 – 0.2.5] — 2026-03-09 → 2026-03-18
|
|
97
219
|
|
|
98
|
-
|
|
220
|
+
Versiones publicadas previas a esta sesión. Reconstruidas del README.md anterior.
|
|
99
221
|
|
|
100
222
|
### Agregado
|
|
101
223
|
|
|
@@ -114,9 +236,9 @@ Versión publicada previa a esta sesión. Reconstruida del README.md anterior.
|
|
|
114
236
|
|
|
115
237
|
---
|
|
116
238
|
|
|
117
|
-
## [0.2.0] — 2026-03-
|
|
239
|
+
## [0.2.0] — 2026-03-06
|
|
118
240
|
|
|
119
|
-
Primera versión
|
|
241
|
+
Primera versión con el pipeline completo. (La 0.1.0, del 2026-02-27, fue el primer paquete publicado en npm.)
|
|
120
242
|
|
|
121
243
|
### Agregado
|
|
122
244
|
|
|
@@ -131,8 +253,3 @@ Primera versión empaquetada en npm.
|
|
|
131
253
|
|
|
132
254
|
---
|
|
133
255
|
|
|
134
|
-
[0.3.6]: https://github.com/LuisPadre25/wu-cli/compare/v0.3.5...v0.3.6
|
|
135
|
-
[0.3.5]: https://github.com/LuisPadre25/wu-cli/compare/v0.3.0...v0.3.5
|
|
136
|
-
[0.3.0]: https://github.com/LuisPadre25/wu-cli/compare/v0.2.1...v0.3.0
|
|
137
|
-
[0.2.1]: https://github.com/LuisPadre25/wu-cli/compare/v0.2.0...v0.2.1
|
|
138
|
-
[0.2.0]: https://github.com/LuisPadre25/wu-cli/releases/tag/v0.2.0
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<a href="docs/configuration.md">Configuración</a> ·
|
|
22
22
|
<a href="docs/deployment.md">Deployment</a> ·
|
|
23
23
|
<a href="docs/security.md">Seguridad</a> ·
|
|
24
|
+
<a href="docs/testing.md">Pruebas</a> ·
|
|
24
25
|
<a href="docs/contributing.md">Contribuir</a> ·
|
|
25
26
|
<a href="CHANGELOG.md">Changelog</a>
|
|
26
27
|
</p>
|
|
@@ -31,26 +32,33 @@
|
|
|
31
32
|
|
|
32
33
|
**Wu CLI** es un CLI + dev-server nativo en **Zig 0.16** para arquitecturas microfrontend ([wu-framework](https://www.wu-framework.com)). Reemplaza **N procesos Vite** (uno por micro-app) con **un único proceso en un solo puerto** que descubre, compila, sirve y hace HMR. Sin dependencias Zig, binario de ~250-460 KB por plataforma, distribuido vía npm.
|
|
33
34
|
|
|
34
|
-
##
|
|
35
|
+
## Estado actual (código v0.4.0)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
Las versiones de `package.json`, `src/main.zig` y `build.zig.zon` están
|
|
38
|
+
sincronizadas y una invariante de CI evita que vuelvan a divergir. Consulta el
|
|
39
|
+
[changelog](CHANGELOG.md) para distinguir lo publicado de lo aún no liberado.
|
|
37
40
|
|
|
38
41
|
**Build pipeline:**
|
|
39
42
|
|
|
43
|
+
- **Dependencias compartidas en producción** — `"shared": ["react", "react-dom"]` en `wu.config.json` bundlea cada specifier una sola vez a `dist/wu-shared/` y emite un import map en el shell. `wu dev` ya compartía (todas las apps piden `/@modules/react` al mismo origen); ahora `wu build` también. En un proyecto de prueba con shell + 2 apps React: 807 KB → 425 KB. Ver [configuración](docs/configuration.md#shared).
|
|
40
44
|
- **Shell custom respetado** — si tu `shell/` buildea, su `dist/*` se hoist al root de `dist/` y se inyecta `window.__wu_apps` automáticamente. Antes `wu build` sobreescribía tu shell con un vanilla generado.
|
|
41
45
|
- **`apps.json` runtime-swappable** — emitido en `dist/apps.json`. Cambia el archivo por entorno (dev/staging/prod) para repointar las apps sin rebuild del shell.
|
|
42
46
|
- **`wu.json` por app** — `dist/<app>/wu.json` con el entry exacto (`assets/index-HASH.js`). Evita el probing de 8 paths que rompe en static hosting con MIME errors.
|
|
43
47
|
- **`shell.build_cmd` defaulteado** — si está vacío y `framework != "html"`, se usa `npx vite build`. Antes era silent no-op (el shell "buildeaba" sin producir nada).
|
|
44
48
|
- **Vite plugins completos** — Solid, Preact y Qwik ahora generan vite.config con plugin correcto. Antes JSX se compilaba a `React.createElement` y crasheaba en runtime.
|
|
45
49
|
|
|
46
|
-
**
|
|
50
|
+
**Base incorporada desde v0.2.2:**
|
|
47
51
|
|
|
48
52
|
- **TUI interactiva** — `wu create` con menús navegables (↑↓ enter), sin defaults escondidos, raw mode cross-platform.
|
|
49
|
-
- **DoS hardening (dev server)** — read/write timeouts (30s), límite de body (16 MB), detección de peer-close en SSE/WebSocket. `wu dev` ya no se cuelga con slowloris. (
|
|
53
|
+
- **DoS hardening (dev server)** — read/write timeouts (30s), límite de body (16 MB), detección de peer-close en SSE/WebSocket. `wu dev` ya no se cuelga con slowloris. (`wu serve` sigue sin heredar timeouts ni cap de body; sí hereda ya la política de rutas, las cabeceras y el sentinel.)
|
|
50
54
|
- **Sentinel real** — anti-bot con IP del cliente real (antes hardcoded 127.0.0.1), allowlist de paths legítimos (`/robots.txt`, `/.well-known/*`), challenge HTML libre de XSS.
|
|
51
55
|
- **Cache LRU + cap de disco** — eviction inteligente en memoria, `.wu-cache/` capado a 500 MB con sweep periódico.
|
|
52
56
|
- **Configuración robusta** — parser `wu.config.json` migrado a `std.json` (escape sequences correctos, sin desincronizaciones).
|
|
53
57
|
- **CORS opt-in en proxy** — el `Access-Control-Allow-Origin: *` ya no se inyecta por defecto; ahora es por regla (`cors: true`).
|
|
58
|
+
- **Proxy honesto y estricto** — acepta HTTP con DNS, IPv4 e IPv6; rechaza
|
|
59
|
+
credenciales, paths, puertos inválidos y `https://` hasta que exista TLS real.
|
|
60
|
+
- **CI nativa y fuzzing** — Windows/macOS compilan y ejecutan sus binarios; Linux
|
|
61
|
+
entrega 25.000 mutaciones a HTTP, rutas y URLs de proxy.
|
|
54
62
|
- **Modularización** — `dev_server.zig` adelgazó 10 % extrayendo `handlers/proxy.zig` y `handlers/hmr.zig`.
|
|
55
63
|
- **`build.zig.zon`** — manifest oficial de paquete Zig (consumible como dependencia, valida versión mínima).
|
|
56
64
|
|
|
@@ -109,12 +117,41 @@ my-shop/
|
|
|
109
117
|
|---|---|
|
|
110
118
|
| `wu create [name]` | Wizard interactivo (TUI). Con `--template <fw>` modo no-interactivo. |
|
|
111
119
|
| `wu dev` | Servidor de desarrollo nativo. Un solo proceso, un solo puerto. |
|
|
112
|
-
| `wu build` | Compila todos los micro-apps secuencialmente (un Vite a la vez) → `dist/`. |
|
|
113
|
-
| `wu serve` | Static server minimal para validar `dist/` localmente. Sin
|
|
120
|
+
| `wu build` | Compila todos los micro-apps secuencialmente (un Vite a la vez) → `dist/`. Con `shared`, bundlea las dependencias comunes una vez y emite un import map. |
|
|
121
|
+
| `wu serve` | Static server minimal para validar `dist/` localmente, con cabeceras de seguridad y CSP generada. Sin TLS ni cap de body: para producción real, un CDN o nginx delante. |
|
|
114
122
|
| `wu add <framework> <name>` | Agrega un micro-app a un proyecto existente. |
|
|
115
123
|
| `wu install` | Instala dependencias de todos los micro-apps. |
|
|
124
|
+
| `wu doctor` | Diagnostica el proyecto: entradas rotas, versiones desincronizadas, overrides que no aplican, puertos repetidos. |
|
|
116
125
|
| `wu info` | Estado actual del proyecto (apps detectados, puertos, framework del shell). |
|
|
117
126
|
|
|
127
|
+
### Pruebas y CI
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
zig build test --summary all # 138 tests descubiertos
|
|
131
|
+
npm run test:wrapper # dispatch npm por OS/arquitectura
|
|
132
|
+
./scripts/check-invariants.sh # invariantes estructurales
|
|
133
|
+
zig build test --fuzz=25K # Linux/macOS: HTTP, rutas y proxy
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Smokes de proyectos generados:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
./scripts/smoke.sh # los 13 frameworks, de punta a punta
|
|
140
|
+
./scripts/smoke.sh react # uno solo
|
|
141
|
+
./scripts/smoke-integration.sh # wu add + dos majors de React conviviendo
|
|
142
|
+
./scripts/smoke-hmr.sh # wu dev: compila, reescribe imports, recarga
|
|
143
|
+
./scripts/smoke-shells.sh # cada shell nativo con micro-apps de otros frameworks
|
|
144
|
+
./scripts/check-invariants.sh # invariantes del código (sin compilar)
|
|
145
|
+
./scripts/check-template-drift.sh # pines de plantillas vs npm latest
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Cada framework pasa por `wu create` → `wu install` → `wu build` → render en Chrome
|
|
149
|
+
headless, donde se pulsa el contador y se comprueba que sube. Requiere Node,
|
|
150
|
+
Python 3 y Chrome/Chromium. Corre igual en CI (`.github/workflows/ci.yml`).
|
|
151
|
+
|
|
152
|
+
La matriz también ejecuta build y tests nativos en Windows y macOS. Alcance
|
|
153
|
+
completo: **[docs/testing.md](docs/testing.md)**.
|
|
154
|
+
|
|
118
155
|
Referencia completa con todas las flags: **[docs/cli.md](docs/cli.md)**.
|
|
119
156
|
|
|
120
157
|
## Configuración
|
|
@@ -134,6 +171,7 @@ Referencia completa con todas las flags: **[docs/cli.md](docs/cli.md)**.
|
|
|
134
171
|
{ "name": "catalog", "dir": "mf-catalog", "framework": "react", "port": 5001 },
|
|
135
172
|
{ "name": "cart", "dir": "mf-cart", "framework": "vue", "port": 5002 }
|
|
136
173
|
],
|
|
174
|
+
"shared": ["react", "react-dom", "react-dom/client"],
|
|
137
175
|
"proxy": {
|
|
138
176
|
"port": 3000,
|
|
139
177
|
"open_browser": true,
|
|
@@ -157,7 +195,7 @@ Schema completo, defaults, ejemplos de proxy: **[docs/configuration.md](docs/con
|
|
|
157
195
|
| Vue | .vue | Daemon (esbuild) | `@vitejs/plugin-vue` | ✓ |
|
|
158
196
|
| Svelte | .svelte | Daemon (esbuild) | `@sveltejs/vite-plugin-svelte` | ✓ |
|
|
159
197
|
| Solid.js | .jsx, .tsx | Daemon (babel-preset-solid) | `vite-plugin-solid` | ✓ |
|
|
160
|
-
| Angular | .ts | Daemon (esbuild bundle) | `@analogjs/vite-plugin-angular` | ✓ |
|
|
198
|
+
| Angular | .ts | Daemon (esbuild bundle + transforms de JIT) | `@analogjs/vite-plugin-angular` | ✓ |
|
|
161
199
|
| Lit | .ts, .js | Daemon (esbuild TS) | sin plugin | ✓ |
|
|
162
200
|
| Stencil | .js, .tsx | Daemon (JSX/TS) | sin plugin | ✓ |
|
|
163
201
|
| Alpine.js | .js | Passthrough | sin plugin | – |
|
|
@@ -166,6 +204,17 @@ Schema completo, defaults, ejemplos de proxy: **[docs/configuration.md](docs/con
|
|
|
166
204
|
| Stimulus | .js | Passthrough | sin plugin | – |
|
|
167
205
|
| Vanilla | .js, .ts | Daemon (esbuild TS para `.ts`) | sin plugin | ✓ |
|
|
168
206
|
|
|
207
|
+
> **Sobre Angular:** en `wu dev` no hay compilador AOT — esbuild quita los
|
|
208
|
+
> tipos y Angular arranca en modo JIT. Eso deja fuera dos cosas que el AOT
|
|
209
|
+
> deduce leyendo el código: los tipos de los parámetros del constructor (falla
|
|
210
|
+
> con **NG0202**) y las entradas de señal `input()` / `input.required()`
|
|
211
|
+
> (fallan con **NG0950**, «Input is required but no value is available»). El
|
|
212
|
+
> daemon aplica ahora `angularJitApplicationTransform` de `@angular/compiler-cli`
|
|
213
|
+
> antes de compilar, que genera esa metadata; el código de la app es Angular
|
|
214
|
+
> idiomático y no necesita adaptarse a `wu dev`. Si el proyecto no tiene
|
|
215
|
+
> `@angular/compiler-cli` instalado, la compilación sigue adelante sin los
|
|
216
|
+
> transforms y el motivo queda anotado en `.wu-cache/wu-compiler.log`.
|
|
217
|
+
|
|
169
218
|
> **Sobre Qwik:** un comentario antiguo en `create.zig` mantenía Qwik fuera del wizard por incompatibilidad con Shadow DOM. Como los shells actuales **no usan Shadow DOM**, la razón ya no aplica y Qwik está disponible en todos los modos (wizard, `--template`, `wu add`).
|
|
170
219
|
|
|
171
220
|
## Arquitectura (resumen)
|
|
@@ -176,6 +225,7 @@ src/
|
|
|
176
225
|
commands/ dev, build, create, add, install, serve, info
|
|
177
226
|
templates/ 13 plantillas embebidas en el binario
|
|
178
227
|
config/ loader (std.json) + auto-discovery
|
|
228
|
+
commands/shared.zig bundle compartido + import map para `wu build`
|
|
179
229
|
runtime/ ← núcleo del dev server
|
|
180
230
|
dev_server.zig thread-per-connection HTTP server (~3.3k LOC)
|
|
181
231
|
handlers/ proxy.zig, hmr.zig (extraídos del monolítico)
|
|
@@ -196,19 +246,27 @@ Detalles: **[docs/contributing.md](docs/contributing.md)**.
|
|
|
196
246
|
|
|
197
247
|
Wu integra varias defensas de bajo nivel:
|
|
198
248
|
|
|
249
|
+
- **Cabeceras en toda respuesta**: CSP, `X-Frame-Options`, `nosniff`, `Referrer-Policy`, `Permissions-Policy`, HSTS. Configurables en `security` de `wu.config.json`.
|
|
250
|
+
- **CSP generada del build**: `wu build` y `wu serve` hashean los scripts en línea que el propio wu emite, así que la política no lleva `'unsafe-inline'` ni `'unsafe-eval'`. Y `wu build` la escribe también para tu host: `dist/_headers`, `dist/vercel.json`, `dist/wu-security.conf`.
|
|
251
|
+
- **Nunca-servir**: `.env`, `.git/`, `package.json`, lockfiles, claves — 404 en `wu serve`, 403 en `wu dev`, y `wu build`/`wu doctor` los nombran si aparecen en lo que vas a publicar.
|
|
199
252
|
- **Anti-slowloris**: timeouts de 30 s en sockets aceptados.
|
|
200
253
|
- **Body-size cap**: 16 MB máx por request, 413 si excede.
|
|
201
|
-
- **
|
|
202
|
-
|
|
203
|
-
- **
|
|
254
|
+
- **Parser HTTP estricto**: máximo 32 KiB de cabeceras, nombres RFC-token y sin
|
|
255
|
+
caracteres de control en target o valores.
|
|
256
|
+
- **Path-traversal**: validación post-URL-decode (`isPathSafe`), en los dos servidores.
|
|
257
|
+
- **Sentinel**: clasificador zero-alloc para bots, ahora también en `wu serve`; AI agents redirigidos a ACP (`/.well-known/acp.json`).
|
|
258
|
+
- **CORS opt-in**: ni el proxy ni `wu serve` relajan CORS sin consentimiento explícito.
|
|
259
|
+
- **Targets de proxy validados**: DNS/IPv4/IPv6 por HTTP; sin credenciales,
|
|
260
|
+
paths arbitrarios ni falsa conexión HTTPS sobre TCP plano.
|
|
204
261
|
|
|
205
262
|
Configuración para producción y modelo de amenazas: **[docs/security.md](docs/security.md)**.
|
|
206
263
|
|
|
207
264
|
## Estadísticas
|
|
208
265
|
|
|
209
|
-
- **
|
|
266
|
+
- **47** archivos Zig fuente, **~18.4k LOC** totales en `src/` (runtime + cli + config + commands)
|
|
210
267
|
- **~250-460 KB** binario release por plataforma (con `-Doptimize=ReleaseFast`)
|
|
211
268
|
- **0** dependencias Zig externas
|
|
269
|
+
- **138** tests Zig descubiertos, más launcher npm, invariantes, fuzzing y smokes
|
|
212
270
|
- **0** procesos Vite simultáneos en `wu dev` (modo nativo). `wu dev --vite` legacy sí spawnea N procesos.
|
|
213
271
|
|
|
214
272
|
## Requisitos
|
package/bin/wu-darwin-arm64
CHANGED
|
Binary file
|
package/bin/wu-darwin-x64
CHANGED
|
Binary file
|
|
Binary file
|
package/bin/wu-linux-x64
CHANGED
|
Binary file
|
package/bin/wu-win32-x64.exe
CHANGED
|
Binary file
|
package/bin/wu.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
|
|
8
|
-
const PLATFORM_MAP = {
|
|
9
|
-
'win32-x64': 'wu-win32-x64.exe',
|
|
10
|
-
'linux-x64': 'wu-linux-x64',
|
|
11
|
-
'darwin-x64': 'wu-darwin-x64',
|
|
12
|
-
'darwin-arm64': 'wu-darwin-arm64',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const platform = os.platform();
|
|
16
|
-
const arch = os.arch();
|
|
17
|
-
const key = `${platform}-${arch}`;
|
|
18
|
-
const binaryName = PLATFORM_MAP[key];
|
|
19
|
-
|
|
20
|
-
if (!binaryName) {
|
|
21
|
-
console.error(`wu-cli: unsupported platform ${platform}-${arch}`);
|
|
22
|
-
console.error(`Supported: ${Object.keys(PLATFORM_MAP).join(', ')}`);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const binaryPath = path.join(__dirname, binaryName);
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(binaryPath)) {
|
|
29
|
-
console.error(`wu-cli: binary not found at ${binaryPath}`);
|
|
30
|
-
console.error('This may mean the package was not built for your platform.');
|
|
31
|
-
console.error('Try reinstalling: npm install -g wu-cli');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const args = process.argv.slice(2);
|
|
36
|
-
|
|
37
|
-
let result = spawnSync(binaryPath, args, {
|
|
38
|
-
stdio: 'inherit',
|
|
39
|
-
env: process.env,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Handle EACCES — try chmod +x and retry (Linux/macOS)
|
|
43
|
-
if (result.error && result.error.code === 'EACCES') {
|
|
44
|
-
try {
|
|
45
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
46
|
-
result = spawnSync(binaryPath, args, {
|
|
47
|
-
stdio: 'inherit',
|
|
48
|
-
env: process.env,
|
|
49
|
-
});
|
|
50
|
-
} catch (chmodErr) {
|
|
51
|
-
console.error(`wu-cli: permission denied and could not chmod: ${chmodErr.message}`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (result.error) {
|
|
57
|
-
console.error(`wu-cli: failed to execute binary: ${result.error.message}`);
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
process.exit(result.status ?? 1);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const PLATFORM_MAP = {
|
|
9
|
+
'win32-x64': 'wu-win32-x64.exe',
|
|
10
|
+
'linux-x64': 'wu-linux-x64',
|
|
11
|
+
'darwin-x64': 'wu-darwin-x64',
|
|
12
|
+
'darwin-arm64': 'wu-darwin-arm64',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const platform = os.platform();
|
|
16
|
+
const arch = os.arch();
|
|
17
|
+
const key = `${platform}-${arch}`;
|
|
18
|
+
const binaryName = PLATFORM_MAP[key];
|
|
19
|
+
|
|
20
|
+
if (!binaryName) {
|
|
21
|
+
console.error(`wu-cli: unsupported platform ${platform}-${arch}`);
|
|
22
|
+
console.error(`Supported: ${Object.keys(PLATFORM_MAP).join(', ')}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const binaryPath = path.join(__dirname, binaryName);
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(binaryPath)) {
|
|
29
|
+
console.error(`wu-cli: binary not found at ${binaryPath}`);
|
|
30
|
+
console.error('This may mean the package was not built for your platform.');
|
|
31
|
+
console.error('Try reinstalling: npm install -g wu-cli');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
|
|
37
|
+
let result = spawnSync(binaryPath, args, {
|
|
38
|
+
stdio: 'inherit',
|
|
39
|
+
env: process.env,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Handle EACCES — try chmod +x and retry (Linux/macOS)
|
|
43
|
+
if (result.error && result.error.code === 'EACCES') {
|
|
44
|
+
try {
|
|
45
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
46
|
+
result = spawnSync(binaryPath, args, {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
env: process.env,
|
|
49
|
+
});
|
|
50
|
+
} catch (chmodErr) {
|
|
51
|
+
console.error(`wu-cli: permission denied and could not chmod: ${chmodErr.message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (result.error) {
|
|
57
|
+
console.error(`wu-cli: failed to execute binary: ${result.error.message}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
process.exit(result.status ?? 1);
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu-framework/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Native Zig dev server + CLI for wu-framework microfrontends. One binary, one port, all your micro-apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "
|
|
6
|
+
"author": "Wu Framework Team",
|
|
7
7
|
"homepage": "https://www.wu-framework.com",
|
|
8
|
-
"bugs": {
|
|
9
|
-
"url": "https://github.com/LuisPadre25/wu-cli/issues"
|
|
10
|
-
},
|
|
11
8
|
"bin": {
|
|
12
9
|
"wu": "bin/wu.js"
|
|
13
10
|
},
|
|
@@ -18,7 +15,8 @@
|
|
|
18
15
|
"LICENSE"
|
|
19
16
|
],
|
|
20
17
|
"scripts": {
|
|
21
|
-
"prepublishOnly": "bash scripts/build-all.sh"
|
|
18
|
+
"prepublishOnly": "bash scripts/build-all.sh",
|
|
19
|
+
"test:wrapper": "node --test \"tests/*.test.cjs\""
|
|
22
20
|
},
|
|
23
21
|
"keywords": [
|
|
24
22
|
"micro-frontend",
|
|
@@ -42,10 +40,6 @@
|
|
|
42
40
|
"htmx",
|
|
43
41
|
"stimulus"
|
|
44
42
|
],
|
|
45
|
-
"repository": {
|
|
46
|
-
"type": "git",
|
|
47
|
-
"url": "git+https://github.com/LuisPadre25/wu-cli.git"
|
|
48
|
-
},
|
|
49
43
|
"engines": {
|
|
50
44
|
"node": ">=16"
|
|
51
45
|
},
|