create-lexy 0.1.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/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # create-lexy
2
+
3
+ CLI del **Lexy Design System**. Crea proyectos React + Vite + TS con el theme Lexy y trae componentes a demanda desde el registry (`@lexydesign/registry`, modelo shadcn): el código vive en tu proyecto, lo ves antes de instalarlo y lo editas con libertad.
4
+
5
+ ```bash
6
+ npx create-lexy create mi-app # proyecto vacío con theme + .lexy
7
+ npx create-lexy view button # código + doc antes de instalar
8
+ npx create-lexy add button # lo instala local y editable, con sus deps
9
+ npx create-lexy diff button # copia local vs registry vigente
10
+ npx create-lexy doctor # drift de todo lo instalado (exit 1 para CI)
11
+ ```
12
+
13
+ Todos los comandos funcionan con TUI interactiva para humanos y con flags para agentes/CI (`create mi-app -t feature -w crm`, `add button --overwrite`, `doctor --strict`).
@@ -0,0 +1,93 @@
1
+ Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ https://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "button",
4
+ "type": "registry:ui",
5
+ "title": "Button",
6
+ "description": "Botón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add button` y edítalo con libertad.",
7
+ "dependencies": [
8
+ "@radix-ui/react-slot",
9
+ "class-variance-authority"
10
+ ],
11
+ "registryDependencies": [],
12
+ "files": [
13
+ {
14
+ "path": "components/base/Button.tsx",
15
+ "type": "registry:ui",
16
+ "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap select-none rounded text-sm font-medium transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80\",\n outline:\n \"border border-border bg-background text-foreground hover:border-primary hover:text-primary active:bg-accent active:text-accent-foreground\",\n secondary:\n \"bg-background text-secondary-foreground hover:bg-muted active:bg-accent active:text-accent-foreground\",\n ghost: \"text-secondary-foreground hover:bg-muted active:bg-muted\",\n link: \"text-primary underline-offset-4 hover:underline active:text-primary/80\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-8 px-3\",\n lg: \"h-12 px-5\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n /** Renderiza el hijo como el elemento raíz (patrón Slot de Radix): `<Button asChild><Link …/></Button>`. */\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
17
+ },
18
+ {
19
+ "path": "components/base/Button.md",
20
+ "type": "registry:file",
21
+ "target": "components/base/Button.md",
22
+ "content": "# Button — guía de uso\n\nBotón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add button` y edítalo con libertad.\n\n## Import\n\n```tsx\nimport { Button } from \"@/components/base/Button\";\n```\n\n## Uso básico\n\n```tsx\n<Button onClick={save}>Guardar cambios</Button>\n```\n\n## Variantes\n\n```tsx\n<Button variant=\"default\">Guardar</Button> {/* acción primaria */}\n<Button variant=\"secondary\">Cancelar</Button> {/* secundaria */}\n<Button variant=\"outline\">Filtrar</Button> {/* secundaria, menos peso */}\n<Button variant=\"ghost\">Ver más</Button> {/* terciaria, sin fondo */}\n<Button variant=\"link\">Olvidé mi contraseña</Button> {/* navegación inline */}\n<Button variant=\"destructive\">Eliminar</Button> {/* borrar o revertir */}\n```\n\n## Tamaños\n\n```tsx\n<Button size=\"sm\">Compacto</Button>\n<Button size=\"default\">Normal</Button>\n<Button size=\"lg\">Grande</Button>\n<Button size=\"icon\" aria-label=\"Buscar\"><Search /></Button> {/* solo icono */}\n```\n\n## asChild — navegación con apariencia de botón\n\n```tsx\n<Button asChild variant=\"outline\">\n <Link to=\"/cuenta\">Ir a mi cuenta</Link>\n</Button>\n```\n\nCon `asChild` el hijo se vuelve el elemento raíz (patrón Slot de Radix): el botón aporta estilos y props, el hijo aporta la semántica (`<a>`, `Link`). Es la vía preferida para enlaces con apariencia de botón; `buttonVariants` queda para casos donde no puedas envolver.\n\n## Props\n\n| Prop | Tipo | Default | Descripción |\n|---|---|---|---|\n| `variant` | `\"default\" \\| \"destructive\" \\| \"outline\" \\| \"secondary\" \\| \"ghost\" \\| \"link\"` | `\"default\"` | Jerarquía/intención visual. |\n| `size` | `\"default\" \\| \"sm\" \\| \"lg\" \\| \"icon\"` | `\"default\"` | Tamaño. `icon` para botón cuadrado solo-icono. |\n| `asChild` | `boolean` | `false` | Renderiza el hijo como elemento raíz (Slot). |\n| `disabled` | `boolean` | `false` | Deshabilita e ignora clics. |\n| ...resto | `ButtonHTMLAttributes` | — | `onClick`, `type`, `form`, `aria-*`, etc. |\n\n`buttonVariants` se exporta: úsalo para dar estilo de botón a un `<a>` real cuando no puedas usar `asChild` — `<a className={buttonVariants({ variant: \"outline\" })}>`.\n\n## Reglas\n\n- **Una sola** acción primaria (`default`) por vista o sección: si todo es primario, nada lo es.\n- Usa `destructive` solo para acciones que borran o revierten; combínalo con confirmación (`AppDialog`).\n- `size=\"icon\"` **requiere** `aria-label`: sin texto visible, es la única etiqueta accesible.\n- `variant=\"link\"` es para navegación inline dentro de texto, no para la acción principal de un formulario.\n- No juntes muchas acciones de alto peso; baja a `ghost`/`link` las de menor prioridad.\n\n## Cuándo NO usar\n\n- **Navegación entre páginas/rutas** → usa `asChild` con un `<a>`/`Link` real. Un `<button>` no es un enlace.\n- **Activar/desactivar un estado** → `Switch` (ajustes) o `Checkbox` (selección en formulario).\n- **Elegir entre opciones** → `RadioGroup`, `Select` o `Tabs`, no varios botones.\n\n## Para IA\n\n1. Define la jerarquía: ¿esta acción es la principal de la vista, una alternativa o algo terciario?\n2. Asigna la variante por intención, no por color: primaria=`default`, secundaria=`secondary`/`outline`, terciaria=`ghost`, navegación inline=`link`, irreversible=`destructive`.\n3. Mantén una sola acción primaria visible por sección.\n4. Si es solo-icono, agrega `aria-label` con el verbo de la acción.\n5. Si la acción navega a otra ruta, usa `asChild` con un enlace real en vez de un `<button>`.\n6. Para acciones destructivas, envuélvelas en `AppDialog` con la consecuencia explícita.\n"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
3
+ "name": "lexy",
4
+ "homepage": "https://lexy.cl",
5
+ "items": [
6
+ {
7
+ "name": "button",
8
+ "type": "registry:ui",
9
+ "title": "Button",
10
+ "description": "Botón de acción. Renderiza un `<button>` nativo; acepta todos sus atributos (`onClick`, `type`, `disabled`, `aria-*`). El componente vive en tu proyecto: instálalo con `create-lexy add button` y edítalo con libertad.",
11
+ "dependencies": [
12
+ "@radix-ui/react-slot",
13
+ "class-variance-authority"
14
+ ],
15
+ "registryDependencies": [],
16
+ "files": [
17
+ {
18
+ "path": "components/base/Button.tsx",
19
+ "type": "registry:ui"
20
+ },
21
+ {
22
+ "path": "components/base/Button.md",
23
+ "type": "registry:file",
24
+ "target": "components/base/Button.md"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Tema Lexy — fuente única de verdad.
3
+ * Lo consumen tanto Storybook (src/styles/storybook.css) como el proyecto
4
+ * generado (escrito por src/services/installAddons.ts). No editar copias:
5
+ * cualquier cambio de tokens va aquí.
6
+ *
7
+ * Convención shadcn/Tailwind: tokens semánticos (no escalas numéricas).
8
+ * Para colores crudos puntuales se usa la paleta por defecto de Tailwind
9
+ * (bg-blue-500, text-zinc-600, …). Fondos sutiles de estado vía opacidad
10
+ * (bg-success/10, border-warning/20, …).
11
+ */
12
+
13
+ /*
14
+ * Noto Sans (variable, SIL OFL) — tipografía principal de Lexy, auto-hospedada
15
+ * para garantizar un render idéntico en todos los sistemas (Windows, Linux,
16
+ * macOS). Es muy cercana a Segoe UI, aunque algo más geométrica y con mayor
17
+ * avance horizontal; se ajusta con size-adjust y un tracking sutil (ver
18
+ * @layer base) para igualar densidad y tamaño aparente. Un solo woff2 variable
19
+ * cubre los pesos 400–700. Licencia en /fonts/OFL-NotoSans.txt.
20
+ */
21
+ @font-face {
22
+ font-family: "Noto Sans";
23
+ font-style: normal;
24
+ font-weight: 400 700;
25
+ font-display: swap;
26
+ size-adjust: 96%;
27
+ src: url("/fonts/noto-sans-latin.woff2") format("woff2");
28
+ }
29
+
30
+ @theme {
31
+ /* ── Color ──
32
+ Valores cromáticos alineados con LexyDesign (src/app/tokens.css), la fuente
33
+ de verdad de la marca, que sigue Fluent 2. Se conserva la NOMENCLATURA
34
+ shadcn/Tailwind (tokens semánticos consumidos como bg-primary,
35
+ text-muted-foreground, …): solo cambian los hex, no los nombres. El token
36
+ Fluent de origen se anota a la derecha de cada valor. */
37
+
38
+ /* ── Superficies y texto ── */
39
+ --color-background: #ffffff; /* Fluent NeutralBackground1 */
40
+ --color-foreground: #242424; /* Fluent NeutralForeground1 */
41
+ --color-card: #fafafa; /* Fluent NeutralCardBackground */
42
+ --color-card-foreground: #242424;
43
+ --color-popover: #ffffff;
44
+ --color-popover-foreground: #242424;
45
+
46
+ /* ── Acción ── */
47
+ --color-primary: #4648d4; /* índigo de marca · Fluent BrandBackground */
48
+ --color-primary-foreground: #ffffff;
49
+ --color-primary-hover: #4b41e1; /* Fluent BrandBackgroundHover */
50
+ --color-primary-active: #313396; /* Fluent BrandBackgroundPressed */
51
+ --color-secondary: #f5f5f5; /* Fluent NeutralBackground3 */
52
+ --color-secondary-foreground: #424242; /* Fluent NeutralForeground2 */
53
+ --color-accent: #ebf3fc; /* tinte de marca · Fluent BrandBackground2 */
54
+ --color-accent-foreground: #4648d4; /* Fluent BrandForeground1 */
55
+ --color-muted: #f5f5f5; /* Fluent NeutralBackground3 */
56
+ --color-muted-foreground: #616161; /* Fluent NeutralForeground3 */
57
+
58
+ /* ── Bordes y foco ── */
59
+ --color-border: #d1d1d1; /* Fluent NeutralStroke1 */
60
+ --color-input: #d1d1d1;
61
+ --color-ring: #4648d4; /* Fluent BrandStroke1 */
62
+
63
+ /* ── Radius — contrato de geometría: `rounded` en controles,
64
+ `rounded-md`/`lg` en superficies. Todo deriva de la perilla --radius;
65
+ los valores preservan los defaults de Tailwind (4/6/8px). ── */
66
+ --radius: 0.25rem; /* 4px · controles */
67
+ --radius-sm: var(--radius);
68
+ --radius-md: calc(var(--radius) + 0.125rem); /* 6px · superficies */
69
+ --radius-lg: calc(var(--radius) + 0.25rem); /* 8px · superficies grandes */
70
+
71
+ /* ── Sidebar shadcn ── */
72
+ --color-sidebar: #ffffff;
73
+ --color-sidebar-foreground: #242424;
74
+ --color-sidebar-primary: #4648d4;
75
+ --color-sidebar-primary-foreground: #ffffff;
76
+ --color-sidebar-accent: #ededfb;
77
+ --color-sidebar-accent-foreground: #4648d4;
78
+ --color-sidebar-border: #d1d1d1;
79
+ --color-sidebar-ring: #4648d4;
80
+
81
+ /* ── Series de datos (charts) ──
82
+ Paleta categórica anclada en el índigo de marca, pensada para que series
83
+ contiguas se distingan sin competir. Se consumen vía la prop `color` del
84
+ ChartConfig (color: "var(--color-chart-1)") o como bg-chart-1 en Tailwind. */
85
+ --color-chart-1: #4648d4; /* índigo de marca */
86
+ --color-chart-2: #0f6cbd; /* azul de comunicación */
87
+ --color-chart-3: #1aab8a; /* teal */
88
+ --color-chart-4: #f7630c; /* naranja */
89
+ --color-chart-5: #b14ad1; /* magenta */
90
+
91
+ /* ── Estados semánticos (paleta Fluent 2) ── */
92
+ --color-destructive: #d13438; /* Fluent PaletteRedBackground3 */
93
+ --color-destructive-foreground: #ffffff;
94
+ --color-success: #107c10; /* Fluent PaletteGreenBackground3 */
95
+ --color-success-foreground: #ffffff;
96
+ --color-warning: #f7630c; /* Fluent StatusWarningBackground3 */
97
+ --color-warning-foreground: #ffffff;
98
+ --color-info: #0f6cbd; /* azul de comunicación Fluent 2 */
99
+ --color-info-foreground: #ffffff;
100
+
101
+ /* ── Tipografía — rampa oficial de Fluent 2 (Microsoft), nomenclatura
102
+ Tailwind. Cada tamaño viene pareado con su line-height (px de
103
+ referencia). Los tamaños de cuerpo (xs–lg) usan tracking nativo
104
+ (normal); a partir de los titulares (xl–5xl) se aplica un tracking
105
+ negativo progresivo para cerrar el espaciado óptico, igual que la
106
+ rampa de Display/Title de Fluent. ── */
107
+ --text-xs: 0.75rem; /* 12 / 16 · Caption */
108
+ --text-xs--line-height: 1rem;
109
+ --text-xs--letter-spacing: normal;
110
+ --text-sm: 0.875rem; /* 14 / 20 · Body */
111
+ --text-sm--line-height: 1.25rem;
112
+ --text-sm--letter-spacing: normal;
113
+ --text-base: 1rem; /* 16 / 22 · Body large */
114
+ --text-base--line-height: 1.375rem;
115
+ --text-base--letter-spacing: normal;
116
+ --text-lg: 1.25rem; /* 20 / 26 · Subtitle */
117
+ --text-lg--line-height: 1.625rem;
118
+ --text-lg--letter-spacing: normal;
119
+ --text-xl: 1.5rem; /* 24 / 32 · Title 3 */
120
+ --text-xl--line-height: 2rem;
121
+ --text-xl--letter-spacing: -0.01em;
122
+ --text-2xl: 1.75rem; /* 28 / 36 · Title 2 */
123
+ --text-2xl--line-height: 2.25rem;
124
+ --text-2xl--letter-spacing: -0.014em;
125
+ --text-3xl: 2rem; /* 32 / 40 · Title 1 */
126
+ --text-3xl--line-height: 2.5rem;
127
+ --text-3xl--letter-spacing: -0.018em;
128
+ --text-4xl: 2.5rem; /* 40 / 52 · Large title */
129
+ --text-4xl--line-height: 3.25rem;
130
+ --text-4xl--letter-spacing: -0.022em;
131
+ --text-5xl: 4.25rem; /* 68 / 92 · Display */
132
+ --text-5xl--line-height: 5.75rem;
133
+ --text-5xl--letter-spacing: -0.028em;
134
+
135
+ /* ── Pesos — estándar de Fluent/Segoe UI. ── */
136
+ --font-weight-normal: 400;
137
+ --font-weight-medium: 500;
138
+ --font-weight-semibold: 600;
139
+ --font-weight-bold: 700;
140
+
141
+ /* ── Fuente ──
142
+ Noto Sans (auto-hospedada, OFL) es la única fuente de marca; se carga vía
143
+ @font-face, así el render es idéntico en todos los sistemas. Los genéricos
144
+ finales solo actúan si el woff2 no llegara a cargar. */
145
+ --font-sans: "Noto Sans", system-ui, -apple-system, BlinkMacSystemFont,
146
+ Roboto, "Helvetica Neue", Arial, sans-serif;
147
+
148
+ /* ── Animaciones (Accordion de Radix) ── */
149
+ --animate-accordion-down: accordion-down 0.2s ease-out;
150
+ --animate-accordion-up: accordion-up 0.2s ease-out;
151
+
152
+ /* ── Spacing semántico de layout ──
153
+ Mismos valores que LexyDesign (Fluent 2 / tailwind.config.ts), con naming
154
+ Tailwind. Se consumen como gap-gutter, p-section-gap, max-w-container, etc.
155
+ La unidad base sigue siendo la escala numérica de Tailwind (múltiplos de
156
+ 4px); estos tokens añaden vocabulario intencional para layouts. */
157
+ --spacing-stack-sm: 0.5rem; /* 8px · separación entre items pequeños */
158
+ --spacing-stack-md: 1rem; /* 16px · separación entre grupos */
159
+ --spacing-stack-lg: 2rem; /* 32px · separación entre secciones */
160
+ --spacing-gutter: 1.5rem; /* 24px · margen interior de columna */
161
+ --spacing-section-gap: 2.5rem; /* 40px · separación entre bloques de página */
162
+ --spacing-page-bottom: 8rem; /* 128px · padding inferior de página */
163
+
164
+ /* ── Márgenes de contenedor ── */
165
+ --spacing-margin-mobile: 1rem; /* 16px · margen lateral en móvil */
166
+ --spacing-margin-desktop: 3rem; /* 48px · margen lateral en desktop */
167
+ --spacing-container-max: 80rem; /* 1280px · ancho máximo de contenedor */
168
+ }
169
+
170
+ @layer utilities {
171
+ .hide-scrollbar::-webkit-scrollbar { display: none; }
172
+ .hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
173
+ }
174
+
175
+ @keyframes accordion-down {
176
+ from { height: 0; }
177
+ to { height: var(--radix-accordion-content-height); }
178
+ }
179
+
180
+ @keyframes accordion-up {
181
+ from { height: var(--radix-accordion-content-height); }
182
+ to { height: 0; }
183
+ }
184
+
185
+ /* Fuente global del sistema de diseño. La carga (@import de la fuente) vive en
186
+ el entrypoint CSS porque debe ir antes de @import "tailwindcss". */
187
+ @layer base {
188
+ :root {
189
+ --sidebar-background: 0 0% 100%;
190
+ --sidebar-foreground: 0 0% 14%;
191
+ --sidebar-primary: 239 62% 55%;
192
+ --sidebar-primary-foreground: 0 0% 100%;
193
+ --sidebar-accent: 239 63% 96%;
194
+ --sidebar-accent-foreground: 239 62% 55%;
195
+ --sidebar-border: 0 0% 82%;
196
+ --sidebar-ring: 239 62% 55%;
197
+ }
198
+
199
+ html {
200
+ font-family: var(--font-sans);
201
+ font-weight: var(--font-weight-normal);
202
+ /* Noto Sans tiene mayor avance horizontal; un tracking negativo sutil
203
+ compacta su densidad y la acerca al ritmo de Segoe UI. */
204
+ letter-spacing: -0.007em;
205
+ -webkit-font-smoothing: antialiased;
206
+ -moz-osx-font-smoothing: grayscale;
207
+ }
208
+ }
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import{readFileSync as it}from"fs";import xe from"path";import{fileURLToPath as at}from"url";import{Command as ct}from"commander";import lt from"picocolors";import{spawn as Ee}from"child_process";import q from"fs/promises";import H from"path";import{confirm as Le,isCancel as Ie,spinner as ce}from"@clack/prompts";import y from"picocolors";import Z from"path";var ve=(e,t)=>t.architecture!=="feature"?e:e.replace(/from ["']@\/lib\/utils\/cn["']/g,'from "@/shared/lib/utils/cn"'),j=(e,t)=>e.files.filter(o=>typeof o.content=="string").map(o=>({relPath:Z.posix.join(t.paths.components,Z.posix.basename(o.path)),content:ve(o.content,t)}));import ee from"fs/promises";import be from"path";var te=e=>be.join(e,".lexy"),P=async e=>{let t;try{t=await ee.readFile(te(e),"utf-8")}catch{throw new Error("No se encontr\xF3 el archivo .lexy. \xBFEst\xE1s en la ra\xEDz de un proyecto Lexy? (cr\xE9alo con `npx create-lexy create`)")}let o=JSON.parse(t);if(!o.paths?.components)throw new Error("El .lexy no tiene paths.components \u2014 archivo corrupto o de una versi\xF3n incompatible.");return o.installed??={},o},oe=async(e,t)=>{await ee.writeFile(te(e),JSON.stringify(t,null,2)+`
3
+ `)};import O from"fs/promises";import je from"os";import $ from"path";import{fileURLToPath as $e}from"url";var re="@lexydesign/registry",A=`https://cdn.jsdelivr.net/npm/${re}`,Ce=8e3,U=$.resolve($.dirname($e(import.meta.url)),"../assets"),Pe=$.join(je.homedir(),".cache","create-lexy","registry"),V=async e=>{let t=await fetch(e,{signal:AbortSignal.timeout(Ce)});if(!t.ok){let o=new Error(`HTTP ${t.status} al pedir ${e}`);throw o.status=t.status,o}return await t.json()},ne=async e=>{try{return JSON.parse(await O.readFile($.join(U,"r",e),"utf-8"))}catch{return null}},B=async()=>{try{return(await O.readFile($.join(U,"registry-version"),"utf-8")).trim()}catch{return"bundle"}},k=null,J=async()=>{if(k)return k;try{k=(await V(`https://registry.npmjs.org/${re}/latest`)).version}catch{try{k=(await V(`${A}@latest/package.json`)).version}catch{k=await B()}}return k},x=async(e,t)=>{let o=t??await J(),r=$.join(Pe,o,`${e}.json`);try{return{item:JSON.parse(await O.readFile(r,"utf-8")),version:o,source:"cache"}}catch{}try{let n=await V(`${A}@${o}/r/${e}.json`);return await O.mkdir($.dirname(r),{recursive:!0}),await O.writeFile(r,JSON.stringify(n)),{item:n,version:o,source:"cdn"}}catch(n){if(n.status===404)throw new Error(`El componente "${e}" no existe en el registry (versi\xF3n ${o}). Mira el cat\xE1logo con \`create-lexy view --list\`.`);let i=await ne(`${e}.json`);if(i)return{item:i,version:await B(),source:"bundle"};throw new Error(`No se pudo alcanzar el registry (\xBFsin red?) y "${e}" tampoco est\xE1 en el bundle local del CLI.`)}},se=async()=>{try{let e=await J();return{index:await V(`${A}@${e}/r/registry.json`),version:e,source:"cdn"}}catch{let e=await ne("registry.json");if(e)return{index:e,version:await B(),source:"bundle"};throw new Error("No se pudo alcanzar el registry ni leer el bundle local del CLI.")}},ie=$.join(U,"theme/lexy-theme.css"),ae=$.join(U,"fonts");var Fe=(e,t)=>new Promise((o,r)=>{Ee("pnpm",["add",...e],{cwd:t,stdio:"ignore",shell:!0}).on("close",i=>i===0?o():r(new Error(`pnpm add fall\xF3 con c\xF3digo ${i}`)))}),Te=async e=>{try{return await q.readFile(e,"utf-8")}catch{return null}},le=async(e,t)=>{let o=process.cwd(),r=await P(o),n=ce();n.start("Resolviendo componentes en el registry...");let i=new Map,s=async l=>{let f=l.toLowerCase();if(i.has(f))return;let h=await x(f);i.set(f,h);for(let w of h.item.registryDependencies??[])await s(w)};try{for(let l of e)await s(l)}catch(l){throw n.stop(y.red("No se pudo resolver el pedido.")),l}let c=new Map;for(let[l,{item:f}]of i){let h=[];for(let w of j(f,r)){let T=await Te(H.join(o,w.relPath));h.push({...w,status:T===null?"new":T===w.content?"same":"differs"})}c.set(l,h)}let a=Array.from(c.values()).flat().filter(l=>l.status==="differs");if(n.stop(`Resueltos: ${Array.from(i.keys()).join(", ")}`),a.length>0&&!t.overwrite){let l=a.map(f=>` ${f.relPath}`).join(`
4
+ `);if(process.stdout.isTTY&&process.stdin.isTTY){let f=await Le({message:`Estos archivos existen con cambios locales y ser\xE1n sobrescritos:
5
+ ${l}
6
+ \xBFContinuar?`});if(Ie(f)||f!==!0){console.log(y.yellow("Operaci\xF3n cancelada \u2014 nada fue modificado.")),process.exitCode=1;return}}else{console.error(y.red(`Estos archivos existen con cambios locales:
7
+ `)+l+y.dim(`
8
+ Usa --overwrite para sobrescribirlos.`)),process.exitCode=1;return}}for(let[l,f]of c){let{item:h,version:w,source:T}=i.get(l);for(let g of f){if(g.status==="same")continue;let S=H.join(o,g.relPath);await q.mkdir(H.dirname(S),{recursive:!0}),await q.writeFile(S,g.content)}r.installed[h.name]=w;let D=e.map(g=>g.toLowerCase()).includes(l)?"":y.dim(" (dependencia)"),b=T==="bundle"?y.yellow(" [bundle local \u2014 sin red]"):"";console.log(y.green(`${y.bold(h.title??h.name)} instalado`)+D+y.cyan(` \u2192 ${r.paths.components}/`)+b);for(let g of f){let S=g.status==="same"?y.dim(" (sin cambios)"):"";console.log(y.dim(` ${g.relPath}${S}`))}}await oe(o,r);let d=new Set;for(let{item:l}of i.values())for(let f of l.dependencies??[])d.add(f);let u=JSON.parse(await q.readFile(H.join(o,"package.json"),"utf-8")),m={...u.dependencies,...u.devDependencies},p=Array.from(d).filter(l=>!m[l]);if(p.length>0)if(t.install===!1)console.log(y.yellow(`Dependencias pendientes (inst\xE1lalas t\xFA): ${p.join(", ")}`));else{let l=ce();l.start(`Instalando dependencias: ${p.join(", ")}...`),await Fe(p,o),l.stop("Dependencias instaladas.")}};import{spawn as Xe}from"child_process";import{note as Ke,outro as Qe,spinner as Ze}from"@clack/prompts";import I from"picocolors";import{cancel as Se,intro as Ne,isCancel as ke,select as de,text as De}from"@clack/prompts";import me from"picocolors";var Oe=`
9
+ ###### ######
10
+ ##::+#### ####+::##
11
+ #-..:+################+:..:#
12
+ #+...=################=...+#
13
+ #=-*##################*-=#
14
+ ########################
15
+ ==-:.:=*##########*=:.::==
16
+ **#####*+--*####*--+*#####**
17
+ ##########%@@#:-##=.#@@%##########
18
+ #######*+*#%%*:-@@=.+%%#*+*#######
19
+ #+:..........:*#:..........:=#
20
+ ...........:====:...........
21
+ ......................
22
+ ..............
23
+ `,Y=e=>{ke(e)&&(Se("Operaci\xF3n cancelada por el usuario."),process.exit(0))},pe=async e=>{console.clear(),Ne(`${me.cyan(Oe)}
24
+ ${me.bold(" Bienvenido al generador de proyectos Lexy")}`);let t=e;if(!t){let n=await De({message:"\xBFCu\xE1l es el nombre de tu proyecto?",placeholder:"lexy-app",validate:i=>{if(!i||i.trim()==="")return"El nombre es obligatorio.";if(!/^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(i))return"El nombre debe estar en min\xFAsculas, sin espacios ni caracteres especiales."}});Y(n),t=n}let o=await de({message:"\xBFPara qu\xE9 mundo es este proyecto?",options:[{label:"Cliente",value:"cliente",hint:"Producto de cara al cliente final"},{label:"CRM / equipo interno",value:"crm",hint:"Herramienta densa para el equipo Lexy"},{label:"Mixto",value:"mixto",hint:"Ambos o a\xFAn sin definir"}],initialValue:"mixto"});Y(o);let r=await de({message:"\xBFQu\xE9 tipo de arquitectura utilizar\xE1 este proyecto?",options:[{label:"Por Feature",value:"feature",hint:"Recomendado para apps escalables"},{label:"Por Capas",value:"layer",hint:"Cl\xE1sica"}],initialValue:"feature"});return Y(r),{projectName:t,architecture:r,world:o}};import E from"fs/promises";import L from"path";var Ae={feature:["src/app","src/features","src/shared/assets","src/shared/components/base","src/shared/hooks","src/shared/services","src/shared/lib","src/shared/types"],layer:["src/assets","src/components/base","src/hooks","src/services","src/lib","src/types","src/views","src/stores"]},Re=e=>JSON.stringify({name:e.projectName,private:!0,version:"0.0.0",type:"module",scripts:{dev:"vite",build:"tsc -b && vite build",preview:"vite preview"},dependencies:{clsx:"^2.1.1",react:"^19.2.6","react-dom":"^19.2.6","tailwind-merge":"^3.3.1"},devDependencies:{"@tailwindcss/vite":"^4.3.0","@types/node":"^24.12.3","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^6.0.1",tailwindcss:"^4.3.0",typescript:"~5.9.3",vite:"^8.0.12"}},null,2)+`
25
+ `,Me=`import path from "node:path";
26
+ import { defineConfig } from "vite";
27
+ import react from "@vitejs/plugin-react";
28
+ import tailwindcss from "@tailwindcss/vite";
29
+
30
+ export default defineConfig({
31
+ plugins: [react(), tailwindcss()],
32
+ resolve: {
33
+ alias: {
34
+ "@": path.resolve(import.meta.dirname, "./src"),
35
+ },
36
+ },
37
+ });
38
+ `,ze=`{
39
+ "files": [],
40
+ "references": [
41
+ { "path": "./tsconfig.app.json" },
42
+ { "path": "./tsconfig.node.json" }
43
+ ]
44
+ }
45
+ `,_e=`{
46
+ "compilerOptions": {
47
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
48
+ "target": "ES2022",
49
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
50
+ "module": "ESNext",
51
+ "types": ["vite/client"],
52
+ "skipLibCheck": true,
53
+ "paths": {
54
+ "@/*": ["./src/*"]
55
+ },
56
+ "moduleResolution": "bundler",
57
+ "allowImportingTsExtensions": true,
58
+ "verbatimModuleSyntax": true,
59
+ "moduleDetection": "force",
60
+ "noEmit": true,
61
+ "jsx": "react-jsx",
62
+ "strict": true,
63
+ "noUnusedLocals": true,
64
+ "noUnusedParameters": true,
65
+ "noFallthroughCasesInSwitch": true
66
+ },
67
+ "include": ["src"]
68
+ }
69
+ `,Ve=`{
70
+ "compilerOptions": {
71
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
72
+ "target": "ES2023",
73
+ "lib": ["ES2023"],
74
+ "module": "ESNext",
75
+ "types": ["node"],
76
+ "skipLibCheck": true,
77
+ "moduleResolution": "bundler",
78
+ "allowImportingTsExtensions": true,
79
+ "verbatimModuleSyntax": true,
80
+ "moduleDetection": "force",
81
+ "noEmit": true,
82
+ "strict": true
83
+ },
84
+ "include": ["vite.config.ts"]
85
+ }
86
+ `,Ue=e=>`<!doctype html>
87
+ <html lang="es">
88
+ <head>
89
+ <meta charset="UTF-8" />
90
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
91
+ <title>${e.projectName}</title>
92
+ </head>
93
+ <body>
94
+ <div id="root"></div>
95
+ <script type="module" src="/src/main.tsx"></script>
96
+ </body>
97
+ </html>
98
+ `,Je=`@import "tailwindcss";
99
+ @import "./lexy-theme.css";
100
+ `,He=e=>`import { StrictMode } from "react";
101
+ import { createRoot } from "react-dom/client";
102
+ import "./index.css";
103
+ import { App } from "${e.architecture==="feature"?"./app/App":"./views/App"}";
104
+
105
+ createRoot(document.getElementById("root")!).render(
106
+ <StrictMode>
107
+ <App />
108
+ </StrictMode>,
109
+ );
110
+ `,qe=`export const App = () => {
111
+ return (
112
+ <main className="flex min-h-screen items-center justify-center bg-background text-foreground">
113
+ <div className="text-center">
114
+ <h1 className="text-2xl font-semibold">Proyecto Lexy listo</h1>
115
+ <p className="mt-2 text-sm text-muted-foreground">
116
+ Trae tu primer componente: <code>npx create-lexy add button</code>
117
+ </p>
118
+ </div>
119
+ </main>
120
+ );
121
+ };
122
+ `,Ge=`import { clsx, type ClassValue } from "clsx";
123
+ import { twMerge } from "tailwind-merge";
124
+
125
+ export function cn(...inputs: ClassValue[]) {
126
+ return twMerge(clsx(inputs));
127
+ }
128
+ `,We=`node_modules/
129
+ dist/
130
+ *.log
131
+ .env
132
+ `,Be=e=>`# ${e.projectName}
133
+
134
+ Proyecto Lexy (React + Vite + TS + theme Lexy). Los componentes viven en tu proyecto: m\xEDralos con \`view\`, inst\xE1lalos con \`add\`, ed\xEDtalos con libertad.
135
+
136
+ \`\`\`bash
137
+ pnpm dev # desarrollo
138
+ npx create-lexy view button # ver un componente antes de instalarlo
139
+ npx create-lexy add button # instalarlo (local y editable)
140
+ npx create-lexy doctor # divergencia con el registry
141
+ \`\`\`
142
+ `,Ye=e=>{let t=e.architecture==="feature";return{version:"2.0.0",generatedAt:new Date().toISOString(),architecture:e.architecture,world:e.world,registry:{package:"@lexydesign/registry",cdn:A},componentImportPattern:t?"@/shared/components/base/{Component}":"@/components/base/{Component}",paths:{components:t?"src/shared/components/base":"src/components/base",hooks:t?"src/shared/hooks":"src/hooks",services:t?"src/shared/services":"src/services",lib:t?"src/shared/lib":"src/lib",views:t?"src/features":"src/views"},installed:{}}},fe=async(e,t)=>{let o=["package.json","src","index.html",".lexy"],r=[];for(let a of o)try{await E.access(L.join(t,a)),r.push(a)}catch{}if(r.length>0)throw new Error(`El directorio actual ya contiene archivos de un proyecto (${r.join(", ")}). Ejecuta el comando dentro de una carpeta vac\xEDa.`);for(let a of Ae[e.architecture])await E.mkdir(L.join(t,a),{recursive:!0});let n=(a,d)=>E.writeFile(L.join(t,a),d);await n("package.json",Re(e)),await n("vite.config.ts",Me),await n("tsconfig.json",ze),await n("tsconfig.app.json",_e),await n("tsconfig.node.json",Ve),await n("index.html",Ue(e)),await n(".gitignore",We),await n("README.md",Be(e)),await n("src/index.css",Je),await n("src/main.tsx",He(e));let i=e.architecture==="feature"?"src/app":"src/views";await n(`${i}/App.tsx`,qe);let s=Ye(e);await n(".lexy",JSON.stringify(s,null,2)+`
143
+ `),await E.copyFile(ie,L.join(t,"src/lexy-theme.css")),await E.mkdir(L.join(t,"public/fonts"),{recursive:!0}),await E.cp(ae,L.join(t,"public/fonts"),{recursive:!0});let c=L.join(t,s.paths.lib,"utils");await E.mkdir(c,{recursive:!0}),await E.writeFile(L.join(c,"cn.ts"),Ge)};var et=["feature","layer"],tt=["cliente","crm","mixto"],ot=e=>new Promise((t,o)=>{Xe("pnpm",["install"],{cwd:e,stdio:"ignore",shell:!0}).on("close",n=>n===0?t():o(new Error(`pnpm install fall\xF3 con c\xF3digo ${n}`)))}),ue=async(e,t)=>{let o;!!(e&&t.type)?(et.includes(t.type)||(console.error(I.red(`Error: la arquitectura "${t.type}" no es v\xE1lida. Usa "feature" o "layer".`)),process.exit(1)),t.world&&!tt.includes(t.world)&&(console.error(I.red(`Error: el mundo "${t.world}" no es v\xE1lido. Usa "cliente", "crm" o "mixto".`)),process.exit(1)),o={projectName:e,architecture:t.type,world:t.world??"mixto"},console.log(I.blue("Modo automatizado detectado. Saltando interfaz interactiva..."))):o=await pe(e),Ke(`Nombre: ${o.projectName}
144
+ Arquitectura: ${o.architecture}
145
+ Mundo: ${o.world}`,"Configuraci\xF3n de Proyecto");let n=Ze();n.start("Construyendo tu proyecto...");let i=process.cwd();try{await fe(o,i)}catch(s){throw n.stop(I.red("No se pudo crear el proyecto.")),s}t.install!==!1&&(n.message("Instalando dependencias base..."),await ot(i)),n.stop("\xA1Proyecto construido con \xE9xito!"),Qe(I.green(`Todo listo en el directorio actual
146
+
147
+ `)+I.dim(`Siguientes pasos:
148
+ `)+I.cyan(` pnpm dev
149
+ `)+I.cyan(" npx create-lexy add button"))};import rt from"fs/promises";import nt from"path";import F from"picocolors";import X from"picocolors";var R=(e,t)=>{let o=e.split(`
150
+ `),r=t.split(`
151
+ `),n=o.length,i=r.length,s=Array.from({length:n+1},()=>new Array(i+1).fill(0));for(let u=n-1;u>=0;u--)for(let m=i-1;m>=0;m--)s[u][m]=o[u]===r[m]?s[u+1][m+1]+1:Math.max(s[u+1][m],s[u][m+1]);let c=[],a=0,d=0;for(;a<n&&d<i;)o[a]===r[d]?(c.push({type:"same",text:o[a]}),a++,d++):s[a+1][d]>=s[a][d+1]?c.push({type:"del",text:o[a++]}):c.push({type:"add",text:r[d++]});for(;a<n;)c.push({type:"del",text:o[a++]});for(;d<i;)c.push({type:"add",text:r[d++]});return c},G=e=>e.some(t=>t.type!=="same"),ge=(e,t=3)=>{if(!G(e))return null;let o=new Array(e.length).fill(!1);e.forEach((i,s)=>{if(i.type!=="same")for(let c=Math.max(0,s-t);c<=Math.min(e.length-1,s+t);c++)o[c]=!0});let r=[],n=!1;for(let i=0;i<e.length;i++){if(!o[i]){n||(r.push(X.dim(" \u22EF")),n=!0);continue}n=!1;let s=e[i];s.type==="same"?r.push(` ${s.text}`):s.type==="del"?r.push(X.red(`- ${s.text}`)):r.push(X.green(`+ ${s.text}`))}return r.join(`
152
+ `)};var ye=async e=>{let t=process.cwd(),o=await P(t),r=e.toLowerCase(),{item:n,version:i}=await x(r),s=o.installed[r];console.log(F.bold(`
153
+ ${n.title??r}`)+F.dim(` \xB7 instalado con registry@${s??"?"} \xB7 vigente registry@${i}
154
+ `));let c=!1;for(let a of j(n,o)){let d=await rt.readFile(nt.join(t,a.relPath),"utf-8").catch(()=>null);if(d===null){console.log(F.red(`\u2717 ${a.relPath} no existe localmente`)+F.dim(` \u2014 cr\xE9alo con \`create-lexy add ${r}\``)),c=!0;continue}let u=ge(R(d,a.content));u===null?console.log(F.green(`\u2713 ${a.relPath}`)+F.dim(" sin diferencias")):(c=!0,console.log(F.yellow(`\xB1 ${a.relPath}`)+F.dim(" (\u2212 local \xB7 + registry)")),console.log(u))}c&&(process.exitCode=1)};import W from"fs/promises";import M from"path";import C from"picocolors";var he=async e=>{let t=process.cwd(),o=0,r=m=>console.log(C.green(" \u2713 ")+m),n=m=>console.log(C.yellow(" \u2022 ")+m),i=(m,p)=>{o=1,console.log(C.red(" \u2717 ")+m),p&&console.log(C.dim(` \u2192 ${p}`))};console.log(C.bold(`
155
+ Diagn\xF3stico del proyecto Lexy
156
+ `));let s;try{s=await P(t),r(`.lexy v\xE1lido (arquitectura ${s.architecture}${s.world?`, mundo ${s.world}`:""})`)}catch(m){return i(m.message),1}await W.access(M.join(t,"src/lexy-theme.css")).then(()=>!0,()=>!1)?r("theme presente (src/lexy-theme.css)"):i("falta src/lexy-theme.css","re-cr\xE9alo desde un proyecto nuevo o copia el theme del registry");let a=M.join(s.paths.lib,"utils/cn.ts");await W.access(M.join(t,a)).then(()=>!0,()=>!1)?r(`helper cn presente (${a})`):i(`falta el helper cn (${a})`);let u=Object.entries(s.installed);if(u.length===0)console.log(C.dim(`
157
+ Sin componentes instalados a\xFAn (create-lexy add <nombre>).`));else{let m=await J();console.log(C.bold(`
158
+ Componentes instalados (registry vigente: ${m})
159
+ `));for(let[p,l]of u){let f;try{f=await x(p)}catch(b){i(`${p}: ${b.message}`);continue}let h=j(f.item,s),w=[],T=!1;for(let b of h){let g=await W.readFile(M.join(t,b.relPath),"utf-8").catch(()=>null);g===null?w.push(b.relPath):G(R(g,b.content))&&(T=!0)}if(w.length>0){i(`${p}: archivos faltantes (${w.join(", ")})`,`create-lexy add ${p}`);continue}if(!T){r(`${p} al d\xEDa (${f.version})`);continue}let D=!0;if(l&&l!==f.version)try{let b=await x(p,l),g=j(b.item,s);D=!1;for(let S of g){let Q=await W.readFile(M.join(t,S.relPath),"utf-8").catch(()=>null);(Q===null||G(R(Q,S.content)))&&(D=!0)}}catch{}D?e.strict?i(`${p}: editado localmente (difiere del registry vigente)`,`create-lexy diff ${p}`):n(`${p}: editado localmente \u2014 divergencia deliberada (m\xEDrala con \`create-lexy diff ${p}\`)`):i(`${p}: desactualizado (instalado con ${l}, vigente ${f.version})`,`create-lexy diff ${p} \xB7 create-lexy add ${p} --overwrite`)}}return console.log(o===0?C.green(`
160
+ Todo en orden.
161
+ `):C.red(`
162
+ Hay problemas que atender (exit 1).
163
+ `)),o};import st from"fs/promises";import K from"path";import v from"picocolors";var z=e=>console.log(v.bold(v.cyan(`
164
+ \u2500\u2500 ${e} \u2500\u2500
165
+ `))),we=async(e,t)=>{if(t.list||!e){let{index:s,version:c,source:a}=await se();z(`Cat\xE1logo Lexy \xB7 @lexydesign/registry@${c}${a==="bundle"?" (bundle local)":""}`);for(let d of s.items)console.log(` ${v.bold(d.name.padEnd(20))} ${v.dim(d.description??"")}`);console.log(v.dim(`
166
+ ${s.items.length} componente(s). Instala con: create-lexy add <nombre>`));return}let o=e.toLowerCase();if(t.installed){let s=process.cwd(),c=await P(s),a=c.installed[o];if(!a){console.error(v.red(`"${o}" no figura como instalado en .lexy. Inst\xE1lalo con \`create-lexy add ${o}\`.`)),process.exitCode=1;return}let{item:d}=await x(o,a).catch(()=>x(o));z(`${d.title??o} \xB7 copia local (instalado con registry@${a})`);for(let u of j(d,c)){let m=await st.readFile(K.join(s,u.relPath),"utf-8").catch(()=>null);z(m===null?`${u.relPath} (FALTANTE)`:u.relPath),m!==null&&console.log(m)}return}let{item:r,version:n,source:i}=await x(o);z(`${r.title??r.name} \xB7 @lexydesign/registry@${n}${i==="bundle"?" (bundle local)":""}`),r.description&&console.log(r.description+`
167
+ `),console.log(v.dim("tipo: ")+r.type),console.log(v.dim("deps npm: ")+((r.dependencies??[]).join(", ")||"\u2014")),console.log(v.dim("deps registry: ")+((r.registryDependencies??[]).join(", ")||"\u2014")),console.log(v.dim("archivos: ")+r.files.map(s=>K.posix.basename(s.path)).join(", ")),console.log(v.dim("instalar: ")+`create-lexy add ${r.name}`);for(let s of r.files)typeof s.content=="string"&&(z(K.posix.basename(s.path)),console.log(s.content))};var dt=xe.dirname(at(import.meta.url)),mt=JSON.parse(it(xe.join(dt,"../package.json"),"utf-8")),N=new ct,_=e=>async()=>{try{await e()}catch(t){console.error(lt.red(`
168
+ Error: `)+t.message),process.exit(1)}};N.name("create-lexy").description("CLI del Lexy Design System: crea proyectos y trae componentes a demanda.\nLos componentes viven en tu proyecto \u2014 m\xEDralos con `view`, inst\xE1lalos con `add`, ed\xEDtalos con libertad.").version(mt.version);N.command("create",{isDefault:!0}).alias("init").description("Crea un proyecto Lexy vac\xEDo (React + Vite + TS + theme); los componentes llegan con `add`").argument("[project-name]","Nombre del proyecto").option("-t, --type <architecture>","Arquitectura (feature o layer) \u2014 activa el modo no interactivo").option("-w, --world <world>","Mundo de dise\xF1o (cliente, crm o mixto)").option("--no-install","No correr pnpm install al final").action((e,t)=>_(()=>ue(e,t))());N.command("add").description("Instala componentes del registry en tu proyecto (local y editable), con sus dependencias").argument("<components...>","Nombres en el registry (ej: button card)").option("--overwrite","Sobrescribir copias locales con cambios sin preguntar").option("--no-install","No instalar dependencias npm").action((e,t)=>_(()=>le(e,t))());N.command("view").description("Muestra c\xF3digo + doc + metadata de un componente antes de instalarlo").argument("[component]","Nombre en el registry (sin nombre: lista el cat\xE1logo)").option("--installed","Mostrar la copia local instalada en vez de la del registry").option("--list","Listar el cat\xE1logo completo").action((e,t)=>_(()=>we(e,t))());N.command("diff").description("Copia local vs versi\xF3n vigente del registry (exit 1 si hay diferencias)").argument("<component>","Nombre en el registry").action(e=>_(()=>ye(e))());N.command("doctor").description("Drift de todo lo instalado contra el registry (solo lectura; exit 1 con problemas)").option("--strict","Las ediciones locales tambi\xE9n hacen fallar").action(e=>_(async()=>{process.exitCode=await he(e)})());N.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "create-lexy",
3
+ "version": "0.1.0",
4
+ "description": "CLI del Lexy Design System — crea proyectos y trae componentes a demanda desde el registry",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-lexy": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "assets"
12
+ ],
13
+ "scripts": {
14
+ "build": "node scripts/prepare-assets.mjs && tsup",
15
+ "dev": "tsx src/index.ts"
16
+ },
17
+ "dependencies": {
18
+ "@clack/prompts": "^1.0.1",
19
+ "commander": "^14.0.3",
20
+ "picocolors": "^1.1.1"
21
+ },
22
+ "devDependencies": {
23
+ "tsup": "^8.5.0"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/oscarmbn-ing/lexy-kickoff.git",
31
+ "directory": "packages/cli"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "keywords": [
37
+ "lexy",
38
+ "design-system",
39
+ "cli",
40
+ "scaffold",
41
+ "registry"
42
+ ]
43
+ }