Download the PHP package innodite/laravel-module-maker without Composer
On this page you can find all versions of the php package innodite/laravel-module-maker. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download innodite/laravel-module-maker
More information about innodite/laravel-module-maker
Files in innodite/laravel-module-maker
Package laravel-module-maker
Short Description Generador de módulos Laravel con arquitectura de contextos dinámicos (Central, Shared, Tenant). Genera controladores, servicios, repositorios, migraciones e inyección de rutas con un solo comando.
License MIT
Homepage https://github.com/innodite/laravel-module-maker
Informations about the package laravel-module-maker
🏗️ Innodite Laravel Module Maker
v5.2 — Generador de módulos Laravel para proyectos de una aplicación o multiinquilino. Genera el
backend completo, sus rutas con el permiso de cada una y sus vistas Vue 3, con un solo comando. Un
módulo agrupa varias subfuncionalidades, y cada una es autocontenida:
Modules/<Módulo>/<SubFuncionalidad>/<Capa>/<Contexto>/.
👉 La versión que se instala es la 5.2.0
Y cambia la forma de todo lo que el paquete escribe. Un módulo generado con una 4.x no coincide con lo que genera esta —ni en carpetas ni en namespaces—, así que actualizar sin mover nada deja el proyecto con dos formas dentro. Qué cambió exactamente, y en qué orden se mueve un módulo existente, está en el CHANGELOG.
El primer comando después de actualizar es el diagnóstico, que dice qué le falta al proyecto y con qué línea se arregla cada cosa:
⚠️ Versiones Deprecadas
Se consideran deprecados los tags históricos con referencias heredadas a software/proyecto externo. Para uso nuevo, instala la 5.1 (arriba) — esto es solo un registro de qué tags antiguos evitar si tienes que instalar contra una versión previa a la reescritura de la v5.
Tags deprecados:
v2.5.0v3.2.7av3.4.0
Nota: la deprecación es de soporte/uso recomendado. No se reescribe el historial Git publicado.
📋 Tabla de Contenidos
- La versión que se instala
- Requisitos
- Instalación
- Tabla comparativa de contextos
- Versiones Deprecadas
- Arquitectura Frontend
- Guía de comandos
- Archivos generados por contexto
- Flujo completo por contexto
- Composables Vue 3
- Stubs contextuales
- Bridge Frontend-Backend
- Estructura de contextos
- Estructura de árbol de un módulo generado
- Convenciones de nomenclatura
- Flujo de inyección de rutas
- Auditoría
- Pruebas
- Estándares de código
- Publicar en Packagist
- Documentación
- Changelog
- Licencia
Nuevos en la v4:
- Cómo se ejecutan las migraciones — quién decide el orden, y por qué son dos niveles
- Changelog — qué cambió, y qué hay que tocar al actualizar
innodite:doctor— el diagnóstico en cascada, que sustituye amodule-checkycheck-envinnodite:deploy— levanta el proyecto entero en el orden declaradoinnodite:crear-bd-test— clona el esquema real en la base_test, sin una sola fila
✅ Requisitos
| Dependencia | Versión mínima |
|---|---|
| PHP | 8.2+ |
| Laravel | 11.0+ |
| illuminate/support | ^11.0|^12.0 |
| illuminate/console | ^11.0|^12.0 |
| illuminate/filesystem | ^11.0|^12.0 |
| illuminate/routing | ^11.0|^12.0 |
| @inertiajs/vue3 | ^1.0 (frontend) |
| Vue | ^3.0 (frontend) |
Compatible opcionalmente con
stancl/tenancyyspatie/laravel-permission.
🚀 Instalación
Al instalar por primera vez, el paquete detecta la ausencia de configuración y sugiere el setup en consola.
Inicializar el proyecto (requerido)
Crea module-maker-config/ en la raíz del proyecto con:
contexts.json— Definición de contextos y tenantsstubs/contextual/— Plantillas PHP y Vue personalizables
Publicar assets manualmente
⚠️ Publicar las plantillas tiene un coste que conviene saber antes: una copia publicada se queda congelada en tu proyecto y deja de actualizarse con el paquete, así que sigue generando la forma antigua sin decir nada. Por eso el instalador ya no las publica y existe
innodite:publish-stubs, que te deja pedir solo las que vas a tocar y sin argumentos se limita a listarlas.
🗺️ Los dos contextos
En multiinquilino hay dos contextos y solo dos. Un proyecto que necesite otro lo declara en su
module-maker-config/contexts.json; el paquete no trae ninguno más de fábrica.
| Contexto | Prefijo de clase | Carpeta | Archivo de rutas | Prefijo de URL | Nombre de ruta | Conexión del modelo |
|---|---|---|---|---|---|---|
central |
Central |
…/Central/ |
Routes/web.php |
central-users |
central.users.index |
$connection = 'central' |
tenant |
Tenant |
…/Tenant/ |
Routes/tenant.php |
tenant-users |
tenant.users.index |
ninguna — la conmuta la tenencia |
⛔ Ningún archivo lleva el nombre de un cliente. Una carpeta y una conexión por cliente producen una copia por cliente de lógica idéntica, permisos que hay que sembrar y renombrar uno a uno, y rutas servidas bajo el prefijo de un cliente concreto.
⛔ Y el modelo del inquilino no declara conexión, nunca. La conmuta el paquete de tenencia al identificar la ruta; nombrarla ata el modelo a un cliente. Si el proyecto declara
tenancy.package = none, el modelo sale con una nota que dice quién tiene que conmutarla.
🖥️ Arquitectura Frontend
Regla fundamental — No negociable en este paquete.
| Responsabilidad | Tecnología |
|---|---|
| Navegación entre páginas | Inertia.js (router.visit(), router.get()) |
| Carga y mutación de datos | axios (GET, POST, PUT, DELETE) |
| Contexto activo y permisos | Props de Inertia — compartidos por InnoditeContextBridge |
Los controladores utilizan el trait RendersInertiaModule y el método renderModule() para devolver la vista Inertia correcta según el contexto. Nunca pasan datos de negocio por props de Inertia.
Las vistas Vue son shells que se autocargan al montarse vía axios. Inertia nunca transporta datos de negocio; solo gestiona la navegación SPA.
🛠️ Guía de comandos
innodite:make-module — Generador principal
Genera backend completo + vistas Vue en un solo comando.
Flags de componentes:
| Flag | Componente generado |
|---|---|
-M / --model |
Modelo Eloquent con $table definida |
-C / --controller |
Controlador con RendersInertiaModule + inyección de rutas CRUD |
-S / --service |
Servicio + Interface en Services/Contracts/ |
-R / --repository |
Repositorio + Interface en Repositories/Contracts/ |
-G / --migration |
Migración anónima contextualizada |
-Q / --request |
Form Request validado — el de alta y el de edición |
Validaciones de seguridad:
- Nombres no PascalCase son rechazados
- Palabras reservadas de PHP y Laravel bloqueadas:
class,model,auth,route, etc. - Módulos duplicados bloqueados con opción de añadir componentes
- En caso de error, se ofrece rollback para eliminar archivos generados
innodite:add-entity — Agregar entidad a módulo existente
Agrega una nueva entidad a un módulo ya existente, generando sus componentes dentro de la subcarpeta de entidad correspondiente. Diseñado para módulos multi-entidad como UserManagement (con User, Role, Permission, Module).
Firma:
| Argumento | Descripción |
|---|---|
module |
Nombre del módulo existente (ej: UserManagement) |
entity |
Nombre de la entidad nueva (ej: Role, Permission) |
--context= |
Contexto destino: central o tenant. En aplicación única no se pasa |
-M a -Q |
Mismos flags que make-module (sin flags = genera todos los componentes) |
Ejemplo de archivos generados — add-entity UserManagement Role --context=central:
La capa va dentro de la subfuncionalidad, y el contexto es la hoja. En aplicación única el
último tramo no existe (Role/Services/RoleService.php) y el resto es idéntico: los dos modos
comparten estructura en vez de parecerse.
Diferencia con make-module:
make-module |
add-entity |
|
|---|---|---|
| Crea módulo nuevo | ✅ | ❌ |
| Agrega a módulo existente | ❌ | ✅ |
| Valida que el módulo exista primero | — | ✅ |
| Sin flags = genera todos los componentes | ✅ | ✅ |
| Naming convention intacta | ✅ | ✅ |
innodite:module-setup — Configuración inicial
Crea la estructura de configuración del paquete en la raíz del proyecto. Debe ejecutarse una sola vez al inicializar un nuevo proyecto que use este paquete.
innodite:doctor — Diagnóstico, en cascada
Recorre tres etapas, y cada fallo trae en su propia línea qué hacer.
Etapa 1 · El entorno del generador
- El modo del proyecto — sin él, los comandos no generan
contexts.json— validez, estructura y el catálogo que exige ese modo- Permisos de escritura en
Modules/ystorage/logs/ - Colisiones de nombres entre módulos, y comandos del proyecto que tapen a los del paquete
config/make-module.phppublicada — el orden de despliegue vive ahí- Stubs publicados — detecta los que siguen en el formato de la v3
- Últimas entradas del log de eventos
Etapa 2 · El contrato del proyecto anfitrión
Verifica el bridge Inertia y, si algo falta, imprime el bloque de código exacto a copiar:
- Modelo User —
HasRoles(Spatie) oInnoditeUserPermissions HandleInertiaRequests—auth.permissionscompartidoInnoditeContextBridge— registrado en el grupo web
Etapa 3 · Lo que dice el criterio
Consulta al proveedor de reglas configurado y lista sus hallazgos.
⭐ Desde la 5.1.0, además recorre cada módulo buscando la estructura de pruebas compartida ya
retirada (Tests/Feature/Shared/, o una pieza de Central/Tenant que todavía incorpora un
trait con use <Trait> en vez de llevar el código inline). Si encuentra un caso, lo lista con la
ruta exacta y el arreglo: inlinear el cuerpo del trait en cada contexto y borrar la carpeta
compartida. Es detección pura — ningún comando mueve el código por su cuenta.
Viene de
innodite:module-checky deinnodite:check-env, que ya no existen. Eran dos comandos útiles cuyos nombres no decían lo que hacían, y que había que acordarse de lanzar por separado: uno miraba el entorno y el otro el contrato, cuando lo que se quiere saber es una sola cosa —si este proyecto puede generar y si lo generado va a encontrar su sitio—.
innodite:publish-frontend — ⛔ RETIRADO
El paquete no configura el frontend de tu proyecto: no publica composables ni componentes, y
no toca app.js ni el middleware de Inertia. Solo genera las vistas de cada subfuncionalidad,
y lo hace sin depender de ninguna biblioteca — el can() va escrito en la propia pantalla.
Contra qué se generan lo decide --frontend=default|innodite en el instalador.
innodite:migrate-plan — ⛔ RETIRADO
Aplicaba las migraciones del proyecto recorriendo el árbol. Usa innodite:deploy, que aplica el
esquema, los datos y los permisos juntos y en el orden que declaras tú:
El comando sigue registrado para decir esto mismo si lo ejecutas, y devuelve error —no éxito—, de modo que un script que lo tuviera escrito no lo dé por hecho.
Por qué se retiró. Ordenaba las subfuncionalidades por el nombre de sus carpetas, ignorando
el orden que el proyecto declara en deploy. Con Cart, Customer y Order en un módulo de
ventas, carts se migraba antes que las dos tablas a las que apunta. Y era el único sitio del
paquete que ejecutaba migraciones fuera del seeder, contra la regla que el propio paquete
escribe en cada trait que genera: el vehículo del despliegue es el seeder.
Dónde vive ahora cada mitad del orden, que es lo único que hay que recordar:
| Quién | Qué decide |
|---|---|
deploy, en config/make-module.php |
El orden entre subfuncionalidades — lo declaras tú |
El trait MigrationsList de cada una |
El orden dentro de una subfuncionalidad — lo genera el paquete |
Para una migración suelta sigue estando innodite:migrate-one.
innodite:migrate-one — Ejecutar una migración específica
Ejecuta una sola migración, nombrada por su coordenada Modulo:Contexto/archivo.php. La base de
datos sale de la propia coordenada, que ya lleva encima su carpeta de contexto.
| Opción | Descripción |
|---|---|
--context= |
Fuerza el contexto de ejecución en vez de derivarlo de la coordenada |
--yes |
Omite la confirmación |
--dry-run |
Muestra lo que haría sin ejecutar |
innodite:deploy — Levanta el proyecto entero
Esquema, datos y permisos, en el orden declarado en deploy (config/make-module.php), más el
webmaster al cerrar. Es el comando que sustituye a innodite:seed-one y a innodite:migration-sync.
| Argumento / opción | Descripción |
|---|---|
entorno |
Obligatorio. stage o production. Nada por defecto: stage puede reconstruir desde cero |
--context= |
Obligatorio en multitenant: central o tenant |
--force |
No pedir confirmación aunque SEEDER_DESTRUCTIVE esté activo |
El seeder que invoca lo escribe el instalador en database/seeders/, y es donde se lee y se amplía
la secuencia de pasos del despliegue.
Retirados en la v4:
innodite:seed-oneyinnodite:migration-sync. Los dos existían para mantener los manifiestos*.order.json, que ya no lee nadie: el orden de las migraciones lo declaran los traitsMigrationsListy el de los seeders el arraydeploy. Si tu proyecto todavía tiene esa carpeta, los comandos de migración te lo dirán al ejecutarse; puedes borrarla.
innodite:test — Ejecutar el contrato de una subfuncionalidad
Se ejecuta por subfuncionalidad, no por módulo, porque el contrato es de ella: son sus nueve temas, su manifiesto y sus seis piezas. Un módulo con cuatro subfuncionalidades no tiene un contrato, tiene cuatro — y ejecutarlos juntos mezcla el resultado de cosas que se despliegan y fallan por separado.
En cascada, y con corte temprano:
El orden es de dependencia: cada pieza da por supuesto lo que comprobó la anterior. Si el andamiaje no está, el esquema falla por lo mismo; si el esquema no está, los permisos fallan por lo mismo; y el comportamiento por HTTP falla de treinta formas distintas por la misma causa única.
Treinta fallos rojos de un solo problema no informan treinta veces mejor: informan peor, porque hay que leerlos todos para descubrir que eran el mismo. Por eso al primer fallo se para, y dice qué falló, qué cubría y qué queda sin ejecutar.
Antes de lanzar nada comprueba que el grupo esté entero. Faltar el manifiesto no es «una prueba menos»: sin él ninguna de las otras puede derivar rutas ni permisos, y lo que saldría serían fallos que describen el síntoma y esconden la causa.
⭐ Y desde la 5.1.0, comprueba también que la subfuncionalidad no tenga la estructura de pruebas
compartida ya retirada (Tests/Feature/Shared/, o un trait incorporado con use <Trait> en vez
de inline). Si la encuentra, el comando se niega a correr y dice el arreglo — el mismo chequeo que
hace innodite:doctor en cascada sobre todo el proyecto.
El tema 6 se nombra siempre, también cuando todo pasa. Un contrato «en verde» que se saltó un tema sin decirlo es la peor clase de silencio.
Qué tiene que traer tu proyecto para que el grupo corra
El grupo generado se escribe contra las tablas y contra el router, no contra las clases de un paquete concreto — así sobrevive a que cambies de versión, o de paquete de permisos. A cambio da por supuestas cinco cosas, y las cinco las pone el proyecto, no el módulo:
| Lo que hace falta | Por qué |
|---|---|
Tests\TestCase |
La base del grupo la extiende. Es la de Laravel de toda la vida |
Modules\ en el autoload PSR-4 de Composer |
Sin él las piezas no se cargan |
users y las tablas de permisos |
permissions (con description y module_id), modules, roles, model_has_permissions y role_has_permissions. El seeder generado llena description y module_id si existen |
| El alias del middleware de permiso, registrado | El que use tu proyecto: permission, central-permission, tenant-permission… El grupo lo lee de la propia ruta, así que reconoce cualquiera que termine en permission |
APP_KEY |
El grupo web cifra la sesión: sin clave, la petición revienta antes de llegar al middleware de permiso, y el fallo no se parece en nada a su causa |
Y una consecuencia del diseño de las migraciones: el trait MigrationsList declara sus rutas
relativas a la raíz del proyecto, porque así las recibe migrate --path. El módulo tiene que
vivir bajo esa raíz para que el seeder las encuentre.
Tres pruebas nacen saltadas, y es a propósito. Las de validación —entrada inválida, edición
inválida, y la de crear y volver a leer— se saltan mientras el FormRequest generado no declare
reglas: sin rules() no hay nada que pueda fallar la validación, y una prueba que pasa porque no
comprueba nada es peor que una que dice que no corrió. Escribe tus reglas y corren solas.
Viene de
innodite:test-module, que ya no existe. El comando anterior trabajaba por módulo y contexto leyendoModules/{Modulo}/Tests/test-config.json. Ese archivo era el último manifiesto JSON del paquete y se retiró junto coninnodite:test-sync, que lo generaba: el contexto lo dice ahora el comando (--context) y la forma del módulo la dice el modo. Un proyecto que aún tenga esos archivos recibe un aviso al ejecutarinnodite:test— no un error: el proyecto funciona sin ellos, y puedes borrarlos cuando hayas comprobado que no te falta nada de ellos.
📁 La forma de lo generado
La subfuncionalidad manda, la capa va dentro de ella y el contexto es la hoja. En aplicación única ese último tramo no existe y el resto es idéntico: los dos modos comparten estructura en vez de parecerse.
Qué vive dónde: si la pieza recorre todas las subfuncionalidades —Docs/, Routes/,
Providers/ y los tres maestros—, vive al nivel del módulo. Todo lo demás baja a la suya.
Las cuentas, medidas generando de verdad:
Módulo User con 2 subfuncionalidades × 2 contextos |
129 archivos + 16 carpetas |
| El mismo en aplicación única | 66 archivos + 8 carpetas |
| Una subfuncionalidad en un solo contexto | 29 archivos |
🔄 El ciclo, de principio a fin
⛔ add-entity deja exactamente lo mismo que make-module: las cuatro capas, la migración, las
seis piezas de seeder, la factory, las pantallas, las rutas, el provider de su contexto y el
grupo de pruebas. Las dos puertas por las que aparece una subfuncionalidad emiten lo mismo.
🧩 Composables Vue 3
⛔ El paquete ya no publica composables. Se retiraron en la 4.x junto con
innodite:publish-frontend: montar el andamiaje del frontend es del proyecto, o de la biblioteca de
interfaz que use. Lo que sigue describe el contrato que la vista generada espera de tu proyecto,
por si prefieres implementarlo con composables propios; la pantalla que genera el paquete no importa
ninguno — lleva su can() de dos líneas escrito dentro.
El contexto activo, y cómo lo lee la pantalla
Lee auth.context.route_prefix desde las props de Inertia compartidas por InnoditeContextBridge y antepone automáticamente el prefijo correcto a cualquier clave de ruta.
⛔ El paquete no publica ningún composable para esto. Lo hizo hasta la 4.x, con
useModuleContext; se retiró junto con el resto del andamiaje de frontend, porque montar el
frontend es del proyecto o de la biblioteca que lo instale, no del generador.
⭐ Y el nombre de la ruta ya no hay que resolverlo en cada petición: el generador lo escribe en la pantalla con el prefijo de su contexto ya puesto, porque lo que se sabe al generar no se calcula mil veces después. El contexto compartido sigue sirviendo para lo que sí depende de la sesión.
usePermissions — Verificación de permisos del usuario
Lee auth.permissions desde las props de Inertia y permite verificar permisos de forma declarativa en las plantillas Vue.
Estrategia dual: verifica {prefix}.{perm} y {perm} plano simultáneamente. El mismo componente funciona en cualquier contexto sin cambios.
Flujo de datos en las vistas Vue generadas
Ejemplo — CentralUserIndex.vue
Ejemplo — CentralUserCreate.vue
- Errores de validación Laravel 422 mostrados campo a campo
- Botón deshabilitado durante el envío (previene doble submit)
Ejemplo — CentralUserEdit.vue
- Recibe solo
idcomo prop de Inertia (nunca el objeto completo) - Carga el registro vía axios al montarse
🔧 Stubs contextuales
El paquete trae un solo juego de plantillas, en una carpeta plana. Tú decides cuáles personalizar, y puedes hacerlo para todo el proyecto o solo para un contexto.
Estructura de stubs
⚠️ El paquete ya NO envía copias por contexto. Llegó a llevar cuatro —Central, Shared,
TenantShared, TenantName—, idénticas byte a byte a la plantilla base y con prioridad sobre ella:
corregir la base sin tocar sus cuatro copias no cambiaba nada, porque ganaba la copia vieja. La
carpeta por contexto sigue existiendo en tu proyecto, que es donde puede diferir de verdad.
Publicar stubs para personalización
Copia las plantillas a module-maker-config/stubs/contextual/. A partir de ahí ganan a las del
paquete, y puedes borrar las que no vayas a tocar: lo que no esté ahí se sigue leyendo del paquete.
⚠️ Publicarlas tiene un coste: una plantilla copiada no se actualiza con el paquete. Si publicaste las 38 «por si acaso», borra las que no personalizaste — si no, seguirás generando con las de la versión en que las copiaste.
Stubs aportados por otro paquete instalado
Un paquete instalado puede aportar sus propios stubs, y el generador los usará sin que haya que configurar nada. El contrato es una ruta: cualquier paquete que traiga
participa en la resolución. Sirve para generar contra una biblioteca de interfaz concreta —su tabla, sus formularios— en vez de contra las plantillas genéricas del paquete.
Orden de resolución, de lo más específico a lo más genérico:
| # | Dónde | Quién manda |
|---|---|---|
| 1 | module-maker-config/stubs/contextual/{Contexto}/ |
El proyecto, para un contexto |
| 2 | module-maker-config/stubs/contextual/ |
El proyecto |
| 3 | vendor/*/*/stubs/module-maker/contextual/ |
Un paquete instalado |
| 4 | Los del propio laravel-module-maker |
El paquete |
El proyecto siempre gana: es el único que puede tener la última palabra sobre su propio código. Si
dos paquetes aportan el mismo stub, gana el primero por orden alfabético y make-module lo dice al
terminar, junto con el nombre de quién aportó qué.
provider-boot.stub — el punto de enganche del módulo
Es un stub opcional: no existe en el paquete y solo se usa si el proyecto o un paquete instalado
lo aportan. Su contenido se escribe dentro del boot() del ServiceProvider del módulo generado, y
sin él ese boot() sale vacío.
Está pensado para enganchar el módulo en el menú de la aplicación, que es lo que el generador no puede escribir por su cuenta: la línea que lo hace depende de cómo se declare un menú en cada proyecto. Recibe dos variables:
| Variable | Qué trae | Ejemplo |
|---|---|---|
{{{ moduleName }}} |
El nombre del módulo | Invoice |
{{{ functionality }}} |
La funcionalidad, como la nombran sus rutas | invoices |
texts-trait.stub — la séptima pieza del seeder: los textos de la subfuncionalidad
También opcional, y por el mismo motivo: el paquete no sabe sembrar un texto —en qué tabla
se guarda, qué archivo se sirve después— porque eso lo decide la biblioteca que lo lee. Si el
proyecto o un paquete instalado lo aportan, make-module y add-entity escriben
{Prefijo}{Módulo}{SubFunc}Texts.php junto a las otras seis piezas de Database/Seeders/, los
seeders de stage y de producción la incorporan con use …Texts; y la llaman al final de run()
—$this->safe('seedTexts', fn () => $this->seedTexts()), después de los permisos y antes del
reporte—, y el módulo nace con resources/lang/.gitkeep: sin esa carpeta el proveedor no registra
el espacio de nombres de traducción del módulo y el texto sembrado no llega a ninguna pantalla.
Sin nadie que lo aporte, los seeders salen exactamente como siempre. Y como las otras seis, la pieza no se sobrescribe si ya existe. Recibe:
| Variable | Qué trae | Ejemplo |
|---|---|---|
{{{ namespace }}} |
El de las piezas de seeder de esa subfuncionalidad y contexto | Modules\Invoice\Invoice\Database\Seeders\Central |
{{{ traitName }}} |
El nombre de la pieza | CentralInvoiceInvoiceTexts |
{{{ moduleName }}} · {{{ subFeature }}} |
El módulo y la subfuncionalidad | Invoice · Invoice |
{{{ textsGroup }}} |
El grupo de traducción, compuesto como el proveedor registra resources/lang: snake_case del módulo y del archivo |
invoice::invoice · person_catalog::person_catalog |
⚠️ stage-seeder.stub y production-seeder.stub llevan desde la 5.2.0 los placeholders
{{{ textsTraitUse }}} y {{{ textsStep }}}. Si los tienes publicados de una versión anterior,
tu copia gana y el seeder generado no incorpora la pieza: republícalos o añádeselos.
Variables disponibles en los stubs
⛔ Se escriben con TRIPLE llave: {{{ nombre }}}, no {{ nombre }}. No es capricho: los stubs
generan código que a su vez lleva llaves —Blade, Vue, arrays de PHP—, y con doble llave el
sustituidor se comía trozos que no eran suyos. Un stub personalizado con doble llave no se
sustituye y sale literal en el archivo generado; el doctor lo detecta y lo dice.
Las que verás en casi todos:
| Variable | Descripción | Ejemplo |
|---|---|---|
{{{ moduleName }}} |
Nombre del módulo | Invoice |
{{{ modelName }}} |
Nombre del modelo | Invoice |
{{{ namespace }}} |
Namespace de la clase que se genera | Modules\Invoice\Http\Controllers |
{{{ className }}} |
Nombre de la clase generada | InvoiceController |
{{{ tableName }}} |
Nombre de la tabla | invoices |
{{{ subFeature }}} |
Subfuncionalidad | Payment |
{{{ subFeatureLabel }}} |
Etiqueta legible de la subfuncionalidad | Payment |
{{{ routeBase }}} |
La funcionalidad, como la nombran las rutas | invoices |
{{{ routePrefix }}} |
Prefijo de ruta del contexto | central |
{{{ connection }}} |
Conexión declarada, cuando el modo la lleva | tenant_one |
Y en las cuatro vistas Vue, además:
| Variable | Descripción | Ejemplo |
|---|---|---|
{{{ vueComponentName }}} |
Componente de esta vista | InvoiceIndex |
{{{ vueComponentBase }}} |
Nombre sin el sufijo de vista | Invoice |
{{{ permViewStore }}} · {{{ permViewShow }}} · {{{ permViewUpdate }}} · {{{ permViewDestroy }}} · {{{ permViewRestore }}} |
El permiso que decide si se muestra cada elemento | |
{{{ textsGroup }}} |
El grupo de traducción de la subfuncionalidad, el mismo que recibe texts-trait.stub |
invoice::invoice |
⭐ La lista completa la tiene cada stub: abre el que vayas a personalizar y mira qué variables usa. Son las que su generador le entrega, y no todas están en todos.
🌉 Bridge Frontend-Backend
Middleware InnoditeContextBridge
Intercepta cada request e inyecta vía Inertia::share():
| Prop | Valor ejemplo |
|---|---|
auth.context.route_prefix |
central, tenant — null en aplicación única |
auth.context.permission_prefix |
central, tenant — null en aplicación única |
auth.permissions |
['central.users.edit', 'users.view', ...] |
Cadena de resolución de permisos:
- Spatie Permission →
$user->getAllPermissions()->pluck('name') InnoditeUserPermissions→$user->getInnoditePermissions()- Fail-safe →
[]+ Warning en log
Registrar en bootstrap/app.php (Laravel 11+):
Alias para rutas específicas:
Interfaz InnoditeUserPermissions
⚙️ El catálogo de contextos (contexts.json)
Lo publica el instalador en module-maker-config/contexts.json, y en multiinquilino trae dos:
⛔ El inquilino no declara connection_key, y no es un olvido: la conmuta el paquete de tenencia
al identificar la ruta. Nombrarla ataría cada modelo a un cliente.
Un contexto propio —una segunda central, un almacén aparte— se declara aquí con su carpeta, su prefijo y su archivo de rutas, y el generador lo acepta. Ese sí tiene que declarar su conexión: no la conmuta nadie.
📐 Convenciones de nomenclatura
| Contexto | Prefijo de clase | Ejemplo Vue | Ejemplo PHP |
|---|---|---|---|
central |
Central |
CentralUserIndex.vue |
CentralUserController.php |
tenant |
Tenant |
TenantUserIndex.vue |
TenantUserController.php |
| aplicación única | — | UserIndex.vue |
UserController.php |
Reglas adicionales:
- El nombre del módulo siempre va en PascalCase (ej:
User,InvoiceItem,TaxReport) - Las migraciones son anónimas (
return new class extends Migration) para evitar colisiones de nombres - Los ServiceProviders llevan el nombre del módulo sin prefijo de contexto (
UserServiceProvider, noCentralUserServiceProvider) - Los Seeders, Jobs, Notifications y Commands sí llevan prefijo de contexto a partir de v3.1.0
🔀 Flujo de inyección de rutas
Marcadores en routes/web.php
Marcadores en routes/tenant.php
Lo decide el ARCHIVO, no el contexto. Cada archivo de rutas sirve a un contexto y solo a uno, así que dentro hay una sección y un solo sitio donde crece: ahí entra la siguiente subfuncionalidad, dentro del grupo que le da dominio y middleware.
Proceso interno de inyección
Un contexto por archivo de rutas
| Contexto | Archivo destino | Prefijo URL | Nombre de ruta |
|---|---|---|---|
central |
routes/web.php |
central-{funcionalidad} |
central. |
tenant |
routes/tenant.php |
tenant-{funcionalidad} |
tenant. |
El archivo declara las rutas de su contexto y de ninguno más. Hasta la 4.x un archivo podía recibir un bloque por cada inquilino del catálogo, importando controladores que nadie había escrito: el módulo salía sin una sola ruta utilizable y el fallo no se veía leyendo el código.
📋 Resumen de todos los comandos
| Comando | Descripción |
|---|---|
innodite:make-module {Name} |
Genera módulo completo con backend, vistas Vue y rutas |
innodite:add-entity {Module} {Entity} |
Agrega una entidad a un módulo existente |
innodite:module-setup |
Inicializa configuración del paquete en el proyecto |
innodite:doctor |
Diagnóstico en cascada: el entorno del generador y el contrato del proyecto |
innodite:crear-bd-test |
Clona el esquema real en la base _test, sin una sola fila |
innodite:publish-stubs |
Exporta las plantillas que quieras personalizar |
innodite:migrate-plan |
Retirado — el esquema lo aplica innodite:deploy, a través de los seeders |
innodite:migrate-one |
Ejecuta una migración puntual por coordenada |
innodite:deploy {stage\|production} |
Levanta el proyecto: esquema, datos, permisos y webmaster |
innodite:test |
Ejecuta el contrato de una subfuncionalidad, en cascada y con corte temprano |
vendor:publish --tag=module-maker-config |
Publica make-module.php |
vendor:publish --tag=module-maker-stubs |
Publica stubs contextuales personalizables |
vendor:publish --tag=module-maker-contexts |
Publica contexts.json de ejemplo |
Son tres los tags publicables, y son los que el proveedor de servicios declara. El de
module-maker-frontendque esta tabla listaba no existe: se fue con el andamiaje de frontend.
📊 Auditoría
storage/logs/module_maker.log — formato NDJSON (una entrada JSON por línea):
| Evento | Cuándo se registra |
|---|---|
module.created |
Módulo completo generado correctamente |
module.components |
Componentes individuales añadidos a módulo existente |
routes.injected |
Rutas inyectadas exitosamente en el proyecto |
module.rollback |
Rollback ejecutado tras error durante la generación |
🧪 Pruebas
Los tests generados se ubican todos en Modules/{Name}/Tests/Feature/{Context}/{SubFuncionalidad}/,
como un grupo por subfuncionalidad: un manifiesto ({SubFunc}Contract.php), su base
({SubFunc}TestCase.php) y las piezas de los nueve temas —ScaffoldTest, SchemaTest,
PermissionsTest, DeploymentTest y HttpTest—. La del tema 6 la ejecuta Vitest y vive con el
JavaScript, en resources/js/__tests__/{Context}/{SubFuncionalidad}/.
Los emiten por igual make-module y add-entity: una subfuncionalidad nace con su grupo entero
venga por donde venga.
📏 Estándares de código
El paquete incluye configuración de PHP CS Fixer compatible con PSR-12. Todos los archivos PHP generados incluyen declare(strict_types=1) por defecto.
📦 Publicar en Packagist / repositorio privado
Repositorio público (Packagist)
Luego registrar el repositorio en packagist.org con la URL del repositorio.
⚠️ La etiqueta se empuja sola, por su referencia completa. --tags arrastra todas las que tengas
en local, incluidas las de pruebas que no querías publicar — y una etiqueta publicada no se corrige:
hay que retirarla y rehacerla, cuando Packagist ya la ha servido.
⭐ Y etiqueta después de cerrar el CHANGELOG.md, no antes: lo que se publica es el árbol tal y
como está en ese commit, así que una versión etiquetada con su changelog aún en «sin publicar» sale
diciendo que no publica nada de lo que trae.
Repositorio privado (VCS)
Agregar en el composer.json del proyecto consumidor:
📚 Documentación
El manual de uso vive en docs/, dentro del propio paquete, y se declara en el composer.json
con extra.innodite-docs:
👉 Leerlo aquí — instalación, modificaciones manuales del proyecto anfitrión, y una ficha por comando con pestañas: qué hace, qué parámetros recibe, qué archivos o cambios genera, personalización de stubs (en los generadores) y ejemplos.
⭐ Vive aquí a propósito: quien cambia un comando actualiza su ficha en el mismo commit. Una documentación que se escribe en otro sitio se actualiza «después», y «después» es como se llega a un manual que anuncia comandos que ya no existen.
La página publicada se genera, no se edita
manual.html sale de las fichas, así que no se toca a mano: se cambia el .md y se vuelve a
armar. index.html es la portada gerencial (aparte, no generada) — es lo primero que ve quien
entra al sitio. Los dos, junto con assets/, viven en la raíz del repositorio —no en
docs/— a propósito: así el hosting del subdominio los sirve directo, sin ninguna subcarpeta.
Así la fuente sigue siendo una sola —los .md, que son también lo que consume el portal por
extra.innodite-docs— y la página publicada no puede decir algo distinto de lo que dice el manual.
📝 Changelog
Ver CHANGELOG.md para el historial completo de versiones.
📄 Licencia
MIT — Anthony Filgueira
All versions of laravel-module-maker with dependencies
illuminate/support Version ^11.0|^12.0|^13.0
illuminate/console Version ^11.0|^12.0|^13.0
illuminate/filesystem Version ^11.0|^12.0|^13.0
illuminate/routing Version ^11.0|^12.0|^13.0