Proyectos: estructura, apps nuevas y publicación
Qué guarda todo proyecto de Remy en el mismo lugar, cómo empezar una app nueva, la publicación de paquetes, las traducciones, Cloudflare y los planes. Todos los comandos están en Comandos.
La herramienta es remy (y remy-check), los bins de @joeblew999/remy-cli (packages/remy-cli/src, un
módulo de TypeScript por comando), publicados junto con @joeblew999/remy-ui bajo la misma versión. Una app obtiene
ambos con vp install, en la versión de su catálogo, ejecuta el comando como pnpm remy … y lo nombra en los
scripts de su package.json y en las tareas de su vite.config.ts, los mismos en todas las apps ("ci": "vp run ready && vp run test:browser && vp run test:audits").
Cada valor vive en el archivo de la herramienta que lo lee, nunca en uno nuestro: dónde se despliegan la app y su
documentación es el .env de Vite (la línea de comandos lo lee con el loadEnv de Vite), los ajustes de cada Worker
su wrangler.jsonc, y el entorno de cada despliegue los vars de Wrangler (producción, la sección previews, un
.dev.vars local):
# .env
VITE_DEPLOY_ORIGIN=https://your-app.your-subdomain.workers.dev
VITE_DOCS_ORIGIN=https://your-app-docs.your-subdomain.workers.devremy.config.ts es solo para un repositorio que publica paquetes: su release (el nombre de remy-auth es
@joeblew999/remy-ui), tipado por @joeblew999/remy-cli/project.
La estructura
Toda app de Remy tiene los mismos archivos en los mismos lugares; lo que hay dentro de los archivos del
producto es diferente. La estructura son las rutas que leen los comandos y el paquete, así que un archivo
en cualquier otro lugar simplemente no se encuentra; ninguna comprobación propia la mantiene. Una app es
una carpeta: en su propio repositorio, la raíz del repositorio. remy-auth, que además es dueño de los
paquetes, es un workspace con las mismas piezas lado a lado: los archivos de la app que se listan abajo
están en apps/remy-auth/, la documentación en apps/remy-auth-docs/ (no docs/), los paquetes en packages/* (remy-ui, remy-cli, el contrato y el showcase), cada carpeta con el nombre de su
paquete, y la raíz contiene los scripts de package.json, pnpm-workspace.yaml (los paquetes y el catálogo), remy.config.ts, el vite.config.ts raíz (el estilo compartido y las comprobaciones del repositorio, ready) y
los archivos del repositorio (.plans/, .github/, template/), tal como vp create vite:monorepo organiza un workspace. Los comandos encuentran la app por su
wrangler.jsonc y la documentación por su docs.config.ts, así que se ejecutan desde la raíz en ambos
casos.
| Ruta | Leído por | Contiene |
|---|---|---|
package.json's scripts, .node-version (el de remy-auth) | Vite Plus (vp run) | los scripts que componen las tareas (ci, translate, release, setup; el upgrade de una app); la versión de Node |
package.json, pnpm-workspace.yaml, pnpm-lock.yaml, .npmrc | pnpm (a través de vp install), remy | lo que importa cada paquete (catalog:, workspace:*), la versión de pnpm (devEngines); los paquetes del workspace, el catálogo de versiones, los overrides de copia única; GitHub Packages como registro de @joeblew999 |
.env | Vite (import.meta.env), remy (loadEnv) | dónde se despliegan la app y su documentación (VITE_DEPLOY_ORIGIN, VITE_DOCS_ORIGIN) |
remy.config.ts | remy packages, remy release | el paquete de release, en un repositorio que publica paquetes |
wrangler.jsonc | wrangler, el plugin de Vite de Cloudflare, los builds Git de Cloudflare, remy cloudflare | el nombre del Worker, los bindings, los assets y el entorno (vars, previews) |
vite.config.ts, tsconfig.json, playwright.config.ts | Vite Plus (las tareas escritas ahí, vp check), Playwright (vp run test:browser) | el build (remyApp()), cada tarea, los tipos y las comprobaciones de navegador |
.vite-hooks/ (pre-commit, pre-push) | Git, a través de Vite Plus (vp config, el script prepare) | los hooks: vp staged antes de un commit, vp run ci antes de un push (VP_GIT_HOOKS=0 los omite); los copia remy upgrade |
fnox.toml, skills-lock.json | fnox, remy skills | los nombres de los secretos; las skills fijadas |
src/routes/, src/routeTree.gen.ts | TanStack Start (el árbol se genera) | las páginas del producto |
src/parts.json | el remyParts() del paquete | las partes compartidas que lista la app (ninguna sin el archivo; la app en blanco lista seo-routes) |
src/api/ | la ruta api.$ de la app | los procedimientos del producto, si tiene API |
tests/ (smoke.spec.ts por nombre) | remy test (remy test live: el smoke contra un despliegue) | las comprobaciones compartidas con los ajustes de la app, y las propias |
public/ | Vite | favicon, _headers |
dist/ (dist/client/assets) | Wrangler (el despliegue, su simulacro), las comprobaciones | resultado del build, nunca en commit |
docs/ (docs.config.ts con su contract, collections.ts, wrangler.jsonc, vite.config.ts, tsconfig.json, package.json, content/{users,dev,ui}/ con i18n.json y meta.json, content/questions.json; dist/ generado) | sus propias tareas, remy docs, remy i18n | la documentación de la app: su identidad y sus páginas, las colecciones de Fumadocs (en commit), los ajustes del Worker de la documentación; el código del Worker es del paquete |
packages/<name>/ con README.md | remy plans, remy packages | los paquetes que publica el repositorio, si los hay |
messages/, project.inlang/ | el preset de la app, la tarea generate de la app, remy i18n | las cadenas propias de la app, si las hay (compiladas en src/paraglide/) |
.plans/ (now.md, done/, parked/) | remy plans | los planes del repositorio |
AGENTS.md, .github/workflows/ | agentes; GitHub | el puntero de los agentes a la documentación; CI |
packages/remy-cli/src/, template/, fixtures/consumer/ | el paquete; giget; vp run new-app | solo remy-auth: la propia línea de comandos, la app en blanco desde la que arranca un repositorio nuevo, y el fixture de consumidor que prueba ambos tal como los recibiría otro repositorio |
Un consumidor nuevo
La receta única. Una app nueva arranca desde la app en blanco, template/
en remy-auth en la tag de la versión: la estructura de arriba con solo los archivos propios del repositorio
dentro (sus nombres, una página de inicio en cada idioma, un índice de documentación por sitio, los
archivos de configuración de la estructura con apenas unas líneas cada uno). Todo lo demás es el paquete,
que un solo cambio de versión actualiza.
- Requisitos: el
vpde la máquina en modo gestionado (herramientas para desarrollo), Node 26,gh auth logincon un token que tengaread:packages, y Google Chrome (lo usan las comprobaciones). vpx giget@3.3.1 gh:joeblew999/remy-auth/template#vX.Y.Z <name>, luegocd <name>ygit init(las herramientas de traducción y de planes leen el historial de git;vp run setuplo indica si falta).- Nombra la aplicación: sustituye
my-appyMy app(grep -rn -i "my.app" --exclude-dir=node_modules .):package.json,wrangler.jsonc,src/service.ts, los orígenes de.env,docs/wrangler.jsonc,docs/docs.config.ts(el nombre y la fuente del producto, que también lee el marco de la app) ymessages/en.json. - El token de GitHub Packages en la configuración de pnpm de esta máquina, una sola vez (pnpm no lee ningún token del
.npmrcde un repositorio, que solo nombra el registro):pnpm config set --location user //npm.pkg.github.com/:_authToken "$(gh auth token)". Luegovp installuna vez para escribirpnpm-lock.yaml, y despuésvp run setup(la instalación desde el lockfile, las skills fijadas y la skillremy, el bloque de reglas deAGENTS.md, el registro MCP, las comprobaciones de la instalación y, por último,vp run ready). - Haz commit de todo,
pnpm-lock.yaml,skills-lock.jsonysrc/routeTree.gen.tsincluidos. Las cadenas de la app en blanco ya vienen traducidas; cuando el inglés cambie (messages/en.json, la documentación), ejecutavp run translate, que escribe los demás idiomas (nunca hace commit: haz commit de ellos junto con el resto); a partir de entonces se ejecuta antes de cada commit que cambie texto. - Desplegar es un push: conecta el repositorio a los builds Git de Cloudflare, uno por Worker (la app en la raíz, la
documentación en
docs/), con los ajustes de build de remy-auth (en Cloudflare;remy upgradeimprime el comando de build). Crear las bases de datos y los secretos que nombra un binding corresponde a quien es dueño (pnpm remy cloudflare provisionedindica qué falta). A partir de ahí el trabajo pasa por el flujo: cada cambio terminado se sube amain, y cada push amaindespliega. - CI:
.github/workflows/ci.ymlllama al flujo de trabajo compartido de remy-auth en la misma tag. Da al repositorio nuevo acceso de lectura en la configuración de los paquetes@joeblew999/remy-uiy@joeblew999/remy-cli("Manage Actions access"), o su instalación falla allí.
Después, la app crece en sus propios archivos: páginas en src/routes/ (una página junto a la ruta de un
padre, como videos.$videoId al lado de videos, es videos_.$videoId.tsx: TanStack anida
videos.$videoId dentro de videos, que entonces necesita un <Outlet />), sus enlaces en
src/remy-app.tsx, cadenas en messages/en.json (m desde src/paraglide/messages.js), una API como
paquete de contrato y src/api/ (todo procedimiento que recibe entrada documenta un error, o apiChecks
falla: la regla api/coverage del paquete), documentación en docs/content/. Pasa a una versión nueva con
vp run upgrade <version>: @joeblew999/remy-ui y @joeblew999/remy-cli en esa versión (vp add), la tag del
flujo de trabajo de CI, los scripts, los archivos de .vite-hooks/ y vite.config.ts de la app en blanco (uno antiguo se conserva como
vite.config.ts.before-upgrade, para volver a mover a él sus propios plugins y opciones), pnpm-workspace.yaml, el
collections.ts y el wrangler.jsonc de la documentación donde la app no los tenga, los orígenes antiguos de
remy.config.ts movidos a .env, y luego las skills, las reglas y las comprobaciones de la instalación; imprime el
comando de build de Cloudflare, que vive en Cloudflare. Una app en la 0.15.1 instala primero a mano los dos paquetes
en la versión nueva, porque el upgrade de la 0.15.1 no puede. Dos ejecuciones de navegador locales a la vez comparten
PREVIEW_PORT y la segunda falla al iniciar su servidor: dale a cada shell el suyo (PREVIEW_PORT=4232 vp run …).
Un repositorio que publica paquetes
Un repositorio también puede publicar paquetes propios (un contrato, un paquete de UI), como hace
remy-auth; eso no cuesta ningún comando propio. Sus paquetes son los del workspace (packages/<name>/, listados en
pnpm-workspace.yaml), y cada uno que no es private se publica mediante remy packages (el pack y el publish de pnpm,
que escriben cada versión de catalog: y workspace:), cuyos subcomandos están en Comandos.
En remy.config.ts: release.package (el paquete cuya versión da nombre al release) y
release.title. El exports de un paquete nombra cada subruta que sus usuarios importan
("./video": "./src/video.ts"); cualquier otra cosa se rechaza en tiempo de build y en Playwright, cuyo
mensaje nombra el archivo que importa, no el mapa de exports. Las cadenas propias de un paquete son su
propio proyecto inlang, compilado por su build, cuyo runtime sigue el idioma de la plataforma con una sola
llamada, followLocale(runtime) (locale); las herramientas de traducción encuentran el proyecto inlang del repositorio a través de git. Las
comprobaciones que el repositorio añade a un release son pasos de su propio script release, después de vp run ci
(remy-auth: vp run template:check, vp run ui:verify). remy-auth fija la versión de un release con vp run ui:version X.Y.Z,
que la escribe en todos los lugares donde aparece (el paquete y los pines de la app en blanco).
Elegir la versión: publicada, de desarrollo o local
El vp run release de remy-auth comprueba y etiqueta el commit como vX.Y.Z; después el job de release de GitHub publica
@joeblew999/remy-ui X.Y.Z y, a su lado, la línea de comandos, @joeblew999/remy-cli (los comandos remy y remy-check),
siempre en la misma versión, junto con el showcase y el contrato cuando se subió su versión. Todavía no se
publica ninguno de los dos de esta forma, y publicar es cosa del job de release, nunca de una sesión en la nube. Quien consume
fija ambos en su catálogo (pnpm-workspace.yaml, y la tag de su flujo de trabajo de CI), y vp run upgrade X.Y.Z los mueve, junto con los
scripts, los hooks y la configuración de la app en blanco en esa tag. Para probar el checkout propio de remy-auth en una app, haz lo que
hace la prueba de app nueva: pnpm pack de los paquetes en remy-auth y apunta el catálogo y los overrides de la app a los
tarballs (file:./vendor/remy-ui.tgz).
Cada paquete lista lo que importa, en la versión del catálogo (catalog:): el package.json de una app nombra los dos
paquetes de la plataforma, los paquetes del framework que importan sus propios archivos, las herramientas que llaman sus tareas (Wrangler, Playwright,
Vite Plus), sus propios paquetes y lo que añada su producto. Las herramientas que ejecutan las tareas compartidas son
dependencias de remy-cli. Un paquete que se rompe si se instala por duplicado (React, TanStack Router y Query, el stringifier de MDX:
remy.singleCopy en el package.json de remy-ui) tiene una sola copia mediante los overrides de pnpm-workspace.yaml, en la versión
del catálogo; la parte de copias únicas de la comprobación (check:single-copies de vp run ready) falla cuando el store de pnpm
contiene más de una versión de cualquiera de ellos. La preparación propia de una app es una tarea en su propio vite.config.ts: una tarea local (el estado local que necesita antes de ejecutarse, que sus
dev y build nombran primero; remy-auth escribe ahí su .dev.vars y migra su D1 local) y su generate
(su propio catálogo, project.inlang, compilado con los tipos del Worker). Sus propias comprobaciones de release son pasos de su
script release.
El flujo son los scripts y las tareas de Vite Plus; cada paso y quién lo ejecuta: cómo trabajamos; todos los comandos: Comandos.
vp run ci ejecuta todas las comprobaciones, las de navegador, las auditorías de Google y, en remy-auth, la prueba de app nueva
(vp run new-app, fixtures/consumer/run.sh): los demás repositorios a los que este sirve, probados tal como los reciben. Copia
template/ a una carpeta temporal y le superpone fixtures/consumer/files (archivos terminados: un paquete propio de la app,
su API y una parte del showcase; una comprobación unitaria los contrasta con la app en blanco), empaqueta remy-ui, remy-cli y el
showcase en su vendor/ con pnpm pack (cada uno con una versión que ningún registro tiene), y luego instala y ejecuta las
skills (instaladas una vez por lista fijada y luego reutilizadas), los invariantes de las herramientas, el vp run ready de la app, las comprobaciones rápidas de
navegador y las de la documentación (unos 93 s); Vite Plus lo guarda en caché según de qué está hecha una app nueva, así que un cambio en
la app propia de remy-auth o en la documentación de su producto ya no lo ejecuta.
Un cambio que rompería otro repositorio falla ahí, no en ese repositorio. Las pruebas se comprueban con tipos junto
con la app (tsconfig.json incluye tests/). Las auditorías de Google son
vp run test:audits (Lighthouse, local; parte de vp run ci, así que de cada push) y Core Web Vitals es
pnpm remy test cwv (en un despliegue: producción, o el Preview de una rama); el release ejecuta ambas.
Dónde se ejecutan las comprobaciones: el comportamiento propio del paquete compartido se comprueba una vez, en remy-auth, antes de cada
release. Una app construida sobre el paquete ejecuta un conjunto de contrato (sus páginas renderizan, las páginas de sitio y de app se mantienen
separadas, sus propias funcionalidades funcionan) más el nivel de Google en sus propias páginas de sitio, no toda la suite del paquete
de nuevo. Mantén las comprobaciones baratas en lugar de quitarlas (el reloj de Playwright en lugar de esperas
reales, una página de navegador por comprobación, workers en paralelo). Las comprobaciones recorren checkedLocales de
@joeblew999/remy-ui/checks: un idioma por cada forma en que la app dibuja el texto por defecto, todos los idiomas con
CHECK_LOCALES=all (el release), quick para el idioma base y el primero de derecha a izquierda, o una lista separada por comas; las propias comprobaciones determinan los idiomas, así que ningún comando los nombra (cuáles y por qué). Cada paso indica cuánto tardó.
Traducciones
Una misma estructura en cada app, para que los mismos comandos remy i18n funcionen en todas partes (la regla sobre quién traduce es
en el cambio):
| Qué | Inglés | Traducción |
|---|---|---|
| Docs | docs/content/<site>/<page>.md o .mdx (estructura de Fumadocs) | a su lado: <page>.<locale>.md o .mdx |
| Catálogos de UI | el catálogo del idioma base de cada proyecto inlang (<dir>/project.inlang) | el catálogo de cada idioma, según el pathPattern propio del proyecto (p. ej. messages/<locale>.json) |
Dos flujos, uno para cada tipo de texto, con el mismo patrón en ambos: detectar sin conexión a partir del
registro de cada traducción sobre lo que se tradujo, traducir con el Claude Code de la máquina (headless, sin
herramientas) y escribir las traducciones y sus registros (el commit del cambio los incluye). Ambos son
TypeScript en el paquete (packages/remy-cli/src/i18n.ts, translate.ts: remy i18n check,
remy i18n translate); la tarea check:translations y el script translate los ejecutan.
- Mensajes de UI (Paraglide): los catálogos, los idiomas y el idioma base vienen del propio
settings.jsonde inlang (el único proyecto que git conoce). Se detectan (packages/remy-cli/src/i18n.ts): catálogos y claves que faltan,{placeholders}perdidos o inventados, claves cuyo inglés difiere del registro del catálogo (translated-from/<locale>.jsona su lado), y las categorías de plural que necesita el idioma (Intl.PluralRules: ninguna herramienta externa las comprueba). - Docs (Fumadocs): la carpeta es el
content/del paquete de documentación (docs/content, oapps/remy-auth-docs/contenten remy-auth); cada sitio con uni18n.jsontraduce cada página en inglés a cada uno de sus idiomas. Se detectan: páginas que faltan, desactualizadas (su frontmattertranslatedFrom: una huella por sección en inglés), huérfanas, y el propio texto de Fumadocs UI (ui/<lang>.jsonfrente aui/en.json). - El traductor es el Claude Code en modo headless de la máquina (
claude -p), sin herramientas, sin servidores MCP, skills ni ajustes de proyecto, y con salida estructurada: las claves de un catálogo vuelven como JSON y se combinan en el orden de claves del inglés; de una página desactualizada vuelven las secciones que cambiaron y se insertan en su sitio (una página que ya no encaja con su registro, o una que falta, vuelve completa), y solo se escribe cuando es una página (page.ts). Los prompts están enpackages/remy-cli/src/prompts.ts. Una llamada por idioma (mensajes) o por página (docs), ocho a la vez (--jobs). - Las comprobaciones no necesitan clave, ni red, ni historial de git (un clon superficial da la misma respuesta). Traducir usa el login de Claude de la máquina.
- Una app sin proyecto inlang ni sitios de documentación recibe "nothing to check" y sale con 0.
Los dos comandos, pnpm remy i18n check y pnpm remy i18n translate, con sus opciones: Comandos.
Cloudflare
Todos los comandos pnpm remy cloudflare …, y pnpm remy test remote, pnpm remy observe y pnpm remy upgrade, con lo que hace y lo que rechaza cada uno: Comandos. Cada uno indica en su comentario de cabecera si es local o remoto (y de producción).
Las credenciales vienen del login de Wrangler, o del llavero a través de fnox (herramientas).
Planes
La regla de los planes (cómo trabajamos) como comandos (pnpm remy plans …), igual en
todos los proyectos: .plans/now.md es la única lista ordenada, .plans/*.md los pocos archivos de plan abiertos,
.plans/done/ y .plans/parked/ el resto, .plans/stability-log.md opcional. Un proyecto sin
.plans/ pasa plans check y no tiene nada que listar.
Los comandos, con lo que hace cada uno, están en Comandos.
close y park cambian archivos y preparan el movimiento, pero nunca hacen commit: lee git diff y luego vp run ready.
Inicio de sesión y permisos
Cómo una app de Remy obtiene el inicio de sesión, los roles y los permisos basados en relaciones de remy-auth y su paquete: el guard, el motor de relaciones, el componente Allowed, los entornos, el correo y las comprobaciones que aporta cada uno.
Escribir la documentación
Cómo se organiza y se escribe la documentación de una app de Remy con Fumadocs: carpetas, frontmatter, medios, componentes, traducciones y la CLI.