v0.2 — workspaces · audit · monorepos

npm install
un gestor de paquetes Node.js
que no te entregará un RAT.

npm, pnpm y bun ejecutan los lifecycle scripts con acceso completo al host. Un postinstall malicioso y se llevaron tu ~/.ssh. stil aísla cada script dentro de una microVM Hull, bloquea versiones recién publicadas, consulta el advisory bulk de npm en paralelo al install, y aún así corre ~9× más rápido que bun en un Next.js fresh.

// Benchmarks

Instalación en caliente — lockfile congelado, store con caché

197 paquetes · React 18 + Vite 6 + framer-motion + jspdf · Mac M-series

npm 11.6
npm ci
13.0 s
bun 1.2
bun
2.43 s
stil 0.2
0.21 s

stil es ~12× más rápido que bun y ~62× más rápido que npm.

Instalación en caliente — Next.js 16 fresh, sin lockfile

352 paquetes · Next 16.2 + React 19 + Tailwind 4 + TypeScript · resolución BFS + audit DB · Mac M-series

npm 11.6
npm install
14.5 s
bun 1.2
bun install
6.20 s
stil 0.2
stil install
0.68 s

stil ~9× más rápido que bun y ~21× más rápido que npm — y de paso reporta el advisory GHSA-qx2v-qp2m-jg93 que viene en una de las deps transitivas.

TanStack Router fresh — 222 paquetes, sin lockfile

React 19 + Vite 8 + TanStack Router · audit DB paralelo activo

bun 1.2
bun install
2.68 s
stil 0.2
stil install
0.35 s

stil ~8× más rápido que bun — overhead del audit ~12 ms gracias a la paralelización con la fase de link.

Monorepo — 3 workspaces, lockfile v2 congelado

apps/web depende de packages/ui y packages/utils vía workspace:*

stil 0.2
stil install --frozen-lockfile
21 ms

los workspaces se resuelven a symlinks sin tocar el registry; el resto sale del store con clonefile(2).

// Esta misma página corre en stil

Esta landing está construida con Vite + TypeScript en una carpeta llamada landing/. Para servirla durante desarrollo y compilarla para producción usamos stil mismo — es nuestro mejor test bed.

$ cd landing/
$ stil install                           # resuelve vite + transitive + binarios nativos de rollup
  186 packages planned

$ stil run dev                           # vite arranca en milisegundos
> dev
> vite

  VITE v6.4.2  ready in 117 ms

$ stil run build                         # tsc + vite build, dist/ listo para deploy
✓ built in 1.6s

// Caso real — migración npm → stil

Una app Next.js 15 en producción (reservas con seatsio, ~515 paquetes). Un único stil install migra el proyecto y dispara el audit DB integrado, que destapa 44 advisories que el equipo no conocía — incluido un RCE crítico en next antes del próximo deploy.

$ rm -rf node_modules
$ stil install                              # lee package.json, ignora package-lock.json
  515 packages installed in 1.30s          # warm, audit en paralelo con link

--- security advisories (44) ---
  [critical] next@15.3.2: RCE in React flight protocol
  [high]     axios@1.11.0: prototype pollution + header injection + DoS (×6)
  [high]     xlsx@0.18.5:  prototype pollution + ReDoS (×2)
  [high]     next@15.3.2:  DoS + middleware bypass + SSRF (×8)
  summary: 1 critical, 17 high, 22 moderate, 4 low

Tres bumps guiados por lo que mostró el audit y los críticos desaparecen:

$ stil add 'next@^15'                       # 15.3.2 → 15.5.18, parcha el RCE
$ stil add 'axios@^1.12'                    # 1.11.0 → 1.16.0, parcha 6 high
$ stil add 'seatsio@^87'                    # 85 → 87, sube axios nested a 1.15

$ stil install
  summary: 0 critical, 6 high, 9 moderate, 1 low   # −64% advisories

$ stil run build
   ▲ Next.js 15.5.18
 ✓ Compiled successfully in 4.7s          # 43 páginas, sin breaking changes

De 44 advisories detectados a 16 en una sola sesión — −64%, incluyendo el RCE crítico, sin tocar el código de la app. Las 6 high restantes son irreducibles sin acción de upstream (xlsx sin parche en npm, axios nested vía seatsio).

// Por qué stil existe

Lifecycle scripts aislados

Los scripts corren dentro de una microVM Hull con seccomp KILL_PROCESS sobre el perfil node (32 syscalls). Sin red, sin ~/.ssh, sin ~/.aws. Cualquier intento de escape mata el proceso. Por defecto se omiten directamente.

Política de edad de 7 días

Las versiones recién publicadas se bloquean por defecto — los mantenedores y los advisories necesitan tiempo para detectar un compromiso. El resolver hace fallback automático a la última versión que cumple el constraint y la política de edad. Incluso si tu package.json pinea "latest", stil retrocede a la última estable que pasa.

Advisory bulk en cada install

Tras resolver el plan, stil POST-ea la lista (nombre, versión) al bulk endpoint de npm. Reporta cualquier advisory con severidad y URL — y --audit-level=high aborta el install con exit 1 si hay CVE crítico. El POST corre en paralelo con la fase de link, así que el overhead final es ~12 ms.

Store con verificación de integridad

sha512 / sha256 / sha1 verificados antes de que el tarball toque el disco. Almacenado una vez, hardlinkeado en cada proyecto — clonefile(2) en macOS, ~200 syscalls en lugar de los ~10k típicos.

Fetch HTTP/2 multiplexado + cache con ETag

Handle Multi de libcurl, 8 conexiones paralelas a npmjs, decenas de streams por conexión. Caché persistente de packuments en ~/.stil-cache/ con TTL de 24h y revalidación If-None-Match — entradas viejas se confirman con un 304 casi sin payload en lugar de re-descargarse.

Drop-in para Vite / Next / Astro

Resuelve optionalDependencies con filtros de os / cpu: los binarios nativos de plataforma (@rollup/rollup-darwin-arm64, esbuild, etc.) caen en disco y vite build funciona sin tocar nada.

Zig, no JavaScript

Un único binario estático de ~1 MB (~10 MB en Linux). Sin runtime de Node.js, sin bootstrap de npm, sin overhead de arranque. Boot en milisegundos, no medio segundo como tarda npm en cargarse.

Deploy en un comando

stil mentat init autodetecta el stack (Next.js, Vite, Java, Node) y genera el up.yaml de Mentat listo para mt up. Lee los nombres de tus env vars de .env y los deja como <SET_ME> — nunca copia secrets al manifiesto.

// Monorepos & workspaces

Workspaces nativos al estilo npm/yarn. Cada paquete consume al otro vía workspace:* y stil resuelve la dependencia a un symlink local — sin round-trip al registry, sin entry duplicado en el store.

# package.json del monorepo
{
  "name": "myrepo",
  "private": true,
  "workspaces": ["packages/*", "apps/*"]
}

# packages/ui/package.json
{
  "name": "@myrepo/ui",
  "dependencies": {
    "@myrepo/utils": "workspace:*",
    "react": "^18"
  }
}

$ stil install                            # crea symlinks + hardlinks
  monorepo: 3 workspaces discovered
  4 packages planned

$ cd packages/utils && stil install      # desde un sub-workspace, encuentra el root solo
stil: in workspace 'utils', running install from monorepo root '/path/to/myrepo'

$ stil run dev --filter '@myrepo/*'      # corre dev en cada workspace que matchee
[apps/web]
> dev
> vite

// Instalar

macOS — Apple Silicon o Intel

$ curl -fsSL https://stil.dev/install.sh | bash
# o desde source:
$ scripts/build.sh                           # Zig 0.15.2 en ~/.local/zig15
$ cp zig-out/bin/stil ~/.local/bin/stil
$ ln -sf stil ~/.local/bin/stilr             # `stilr dev` ≡ `stil run dev`

Linux — x86_64-musl (ELF estático, ~10 MB)

$ curl -fsSL https://stil.dev/install.sh | bash
# o desde source:
$ TARGET=x86_64-linux-musl scripts/build.sh
# v0.2 ships con backend HTTP de Zig stdlib.
# Path libcurl vendoreado (paridad macOS) viene en v0.2.1.

Uso diario

$ stil install                              # respeta política de 7 días
$ stil install --frozen-lockfile           # CI: salta el BFS, instala lo del lockfile
$ stil add react@^18 -D                    # devDependency
$ stil add chalk -w '@my/utils'             # a un workspace específico
$ stil update                               # sube todo a la última válida
$ stil run dev                              # scripts.dev del package.json
$ stil run dev --filter '@my/*'           # corre en cada workspace
$ stil mentat init                          # genera up.yaml de deploy según el stack

Tip multi-volumen (macOS)

# clonefile(2) y los hardlinks no cruzan volúmenes APFS.
# Si tus proyectos viven en /Volumes/Foo, apuntá el store ahí también:
$ export STIL_STORE=/Volumes/Foo/.stil-store

// vs otros gestores

Característicanpmpnpmbunstil
Lifecycle scripts aislados✓ Hull microVM
Política de edad por defecto✓ 7 días
Advisory DB en cada install~ npm audit (aparte)✓ paralelo
Store content-addressed
Fetch HTTP/2 multiplexado~
Workspaces / monorepos
Registries privados / authv0.3
Instalación en caliente (197 pkg, frozen)13.0 s~3 s2.43 s0.21 s
Next.js 16 fresh (352 pkg, sin lockfile)14.5 s6.20 s0.68 s