openprompt-lang 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +483 -32
  2. package/bin/cli.js +232 -41
  3. package/bin/create.js +135 -0
  4. package/bin/lint.js +20 -21
  5. package/docs/COMMANDS.md +200 -127
  6. package/docs/COMMITS/INDEX.md +1 -0
  7. package/docs/PROMPT_AI_CONTEXT.md +99 -0
  8. package/docs/langs/dotnet.md +36 -0
  9. package/docs/langs/java-spring.md +45 -0
  10. package/docs/langs/python-fastapi.md +35 -0
  11. package/docs/langs/unity.md +30 -0
  12. package/docs/langs/vue-nuxt.md +36 -0
  13. package/package.json +31 -3
  14. package/scaffolds/.cursorrules +6 -0
  15. package/scaffolds/AGENTS.md +27 -0
  16. package/scaffolds/Dockerfile +11 -0
  17. package/scaffolds/capacitor.config.ts +17 -0
  18. package/scaffolds/netlify.toml +8 -0
  19. package/scaffolds/prompt-lang.json +15 -0
  20. package/scaffolds/railway.json +12 -0
  21. package/scaffolds/tailwind.config.js +8 -0
  22. package/scaffolds/tauri.conf.json +26 -0
  23. package/schemas/language-module.json +116 -0
  24. package/schemas/prompt-lang.json +38 -3
  25. package/schemas/structures.json +145 -0
  26. package/src/ai/prompt-builder.js +184 -0
  27. package/src/ai/providers.js +247 -0
  28. package/src/annotations/registry.js +39 -0
  29. package/src/annotations/tags.json +24 -0
  30. package/src/commands/ai-gen.js +161 -0
  31. package/src/commands/component.js +242 -212
  32. package/src/commands/context.js +184 -109
  33. package/src/commands/extract.js +242 -0
  34. package/src/commands/figma.js +15 -15
  35. package/src/commands/init.js +197 -93
  36. package/src/commands/integrate.js +406 -0
  37. package/src/commands/lang.js +148 -0
  38. package/src/commands/qa-gen.js +139 -0
  39. package/src/commands/scaffold.js +127 -0
  40. package/src/commands/suggest.js +24 -14
  41. package/src/commands/teach.js +110 -0
  42. package/src/commands/validate.js +143 -83
  43. package/src/commands/wizard.js +456 -0
  44. package/src/generators/figma-prompt.js +20 -12
  45. package/src/language-service/plugin.cjs +94 -0
  46. package/src/language-service/plugin.d.ts +6 -0
  47. package/src/mcp-server.js +605 -0
  48. package/src/templates/langs/react/INDEX.json +262 -0
  49. package/src/templates/langs/react/MODULE.json +166 -0
  50. package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
  51. package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
  52. package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
  53. package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
  54. package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
  55. package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
  56. package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
  57. package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
  58. package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
  59. package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
  60. package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
  61. package/src/templates/langs/vue/INDEX.json +246 -0
  62. package/src/templates/langs/vue/MODULE.json +105 -0
  63. package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
  64. package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
  65. package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
  66. package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
  67. package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
  68. package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
  69. package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
  70. package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
  71. package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
  72. package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
  73. package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
  74. package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
  75. package/src/templates/scripts/log-actividad.sh +32 -0
  76. package/src/templates/scripts/log-commit.sh +35 -0
  77. package/src/templates/scripts/log-error.sh +45 -0
  78. package/src/templates/scripts/validate.sh +23 -0
  79. package/src/ts-transformer/index.cjs +86 -0
  80. package/src/utils/ai.js +35 -53
  81. package/src/utils/annotations.js +260 -214
  82. package/src/utils/config.js +61 -13
  83. package/src/utils/error-learner.js +203 -0
  84. package/src/utils/file-utils.js +119 -0
  85. package/src/utils/language-loader.js +167 -0
  86. package/src/utils/template-utils.js +45 -0
  87. package/src/vite-plugin/index.js +54 -0
  88. package/vscode-extension/package.json +23 -2
  89. package/vscode-extension/snippets/promptlang.json +1 -3
  90. package/vscode-extension/syntaxes/annotations-code.tmGrammar.json +15 -0
@@ -1,143 +1,208 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, cpSync } from "fs";
2
- import { join, relative, basename } from "path";
3
- import chalk from "chalk";
4
-
5
- const MANIFEST_FILE = "componentes.json";
1
+ import {
2
+ readFileSync,
3
+ writeFileSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ readdirSync,
7
+ statSync,
8
+ copyFileSync,
9
+ } from "fs"
10
+ import { join, relative, basename, extname, dirname } from "path"
11
+ import { fileURLToPath } from "url"
12
+ import chalk from "chalk"
13
+ import {
14
+ getLanguages,
15
+ getLanguageIndex,
16
+ searchTemplates,
17
+ getTemplate,
18
+ } from "../utils/language-loader.js"
19
+
20
+ const MANIFEST_FILE = "componentes.json"
6
21
 
7
22
  export async function componentList(options) {
8
- const cwd = process.cwd();
9
- const config = loadProjectConfig(cwd);
10
- const libPath = options.library || config?.components?.library || null;
11
-
12
- console.log(chalk.cyan("\n📦 Componentes disponibles\n"));
13
-
14
- // Componentes del proyecto
15
- const sourceDir = config?.components?.source || join(cwd, "src/components/ui");
16
- if (existsSync(sourceDir)) {
17
- console.log(chalk.blue(`📁 Proyecto (${relative(cwd, sourceDir)}/):`));
18
- const files = listComponents(sourceDir);
19
- if (files.length === 0) {
20
- console.log(" (vacío)");
21
- } else {
22
- for (const f of files) {
23
- const tags = extractTags(join(sourceDir, f));
24
- console.log(` ${chalk.bold(f.replace(".tsx", ""))}${tags ? ` — ${tags}` : ""}`);
25
- }
23
+ const cwd = process.cwd()
24
+ const config = loadProjectConfig(cwd)
25
+ const libPath = options.library || config?.components?.library || null
26
+ const langId = config?.lenguaje || "react"
27
+
28
+ console.log(chalk.cyan("\n📦 Available components\n"))
29
+
30
+ // 1. From language module templates
31
+ const index = getLanguageIndex(langId)
32
+ if (index?.templates) {
33
+ console.log(chalk.blue(`📚 Templates (${langId} module):`))
34
+ for (const tmpl of index.templates) {
35
+ const teachBadge = tmpl.hasTeachMe ? chalk.green(" [teach]") : ""
36
+ const darkBadge = tmpl.darkMode ? " 🌙" : ""
37
+ console.log(` 📦 ${chalk.bold(tmpl.id)}${teachBadge}${darkBadge}`)
38
+ console.log(` ${tmpl.description}`)
39
+ console.log(` deps: ${tmpl.dependencies?.join(", ") || "none"}`)
40
+ console.log("")
26
41
  }
27
- } else {
28
- console.log(chalk.yellow(` ⚠️ No existe ${relative(cwd, sourceDir)}/`));
29
42
  }
30
43
 
31
- // Componentes de la biblioteca
44
+ // 2. From library (diseno-componentes)
32
45
  if (libPath && existsSync(libPath)) {
33
- const manifestPath = join(libPath, MANIFEST_FILE);
46
+ const manifestPath = join(libPath, MANIFEST_FILE)
34
47
  if (existsSync(manifestPath)) {
35
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
36
- console.log(chalk.blue(`\n📚 Biblioteca (${relative(cwd, libPath)}/):`));
48
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"))
49
+ console.log(chalk.blue(`📚 Library (${relative(cwd, libPath)}/):`))
37
50
  for (const [name, info] of Object.entries(manifest.componentes)) {
38
- const tags = info.tags ? info.tags.join(", ") : "";
39
- console.log(` 📦 ${chalk.bold(name)}${tags ? ` — ${tags}` : ""}`);
40
- if (info.variants) console.log(` Variants: ${info.variants.join(", ")}`);
51
+ const tags = info.tags ? info.tags.join(", ") : ""
52
+ console.log(` 📦 ${chalk.bold(name)}${tags ? ` — ${tags}` : ""}`)
53
+ if (info.variants) console.log(` Variants: ${info.variants.join(", ")}`)
54
+ console.log("")
41
55
  }
42
56
  }
43
57
  } else if (libPath) {
44
- console.log(chalk.yellow(`\n ⚠️ Biblioteca no encontrada: ${libPath}`));
58
+ console.log(chalk.yellow(` ⚠️ Library not found: ${libPath}`))
59
+ }
60
+
61
+ // 3. Project components
62
+ const sourceDir = config?.components?.source || join(cwd, "src/components/ui")
63
+ if (existsSync(sourceDir)) {
64
+ console.log(chalk.blue(`📁 Project (${relative(cwd, sourceDir)}/):`))
65
+ const files = listComponents(sourceDir)
66
+ if (files.length === 0) {
67
+ console.log(" (empty)\n")
68
+ } else {
69
+ for (const f of files) {
70
+ const tags = extractTags(join(sourceDir, f))
71
+ console.log(` ✅ ${chalk.bold(f.replace(/\.(tsx|ts)$/, ""))}${tags ? ` — ${tags}` : ""}`)
72
+ }
73
+ console.log("")
74
+ }
45
75
  }
46
76
 
47
- console.log("");
77
+ console.log(chalk.gray(` Tip: npx openPrompt-Lang component add <id> --template`))
78
+ console.log(chalk.gray(` Tip: npx openPrompt-Lang teach <id> for component lessons\n`))
48
79
  }
49
80
 
50
81
  export async function componentAdd(name, options) {
51
- const cwd = process.cwd();
52
- const config = loadProjectConfig(cwd);
53
- const libPath = options.library || config?.components?.library || null;
82
+ const cwd = process.cwd()
83
+ const config = loadProjectConfig(cwd)
84
+ const langId = config?.lenguaje || "react"
85
+
86
+ // Try language module template first
87
+ if (options.template) {
88
+ const template = getTemplate(name, langId)
89
+ if (!template) {
90
+ console.log(chalk.red(`\n❌ Template "${name}" not found in ${langId} module\n`))
91
+ console.log(chalk.cyan(` Run: npx openPrompt-Lang lang index ${langId}\n`))
92
+ return
93
+ }
94
+
95
+ const targetDir = config?.components?.source || join(cwd, "src/components/ui")
96
+ mkdirSync(targetDir, { recursive: true })
97
+
98
+ const ext = extname(template.metadata.file) || ".tsx"
99
+ const outputName = options.output || name
100
+ const targetFile = join(targetDir, `${outputName}${ext}`)
101
+
102
+ let content = template.content
103
+
104
+ // Process variants if specified
105
+ if (options.variants) {
106
+ const variants = options.variants.split(",").map((v) => v.trim())
107
+ content = injectVariants(content, variants)
108
+ }
109
+
110
+ if (existsSync(targetFile) && !options.force) {
111
+ console.log(chalk.yellow(` ⚠️ ${outputName}${ext} already exists. Skipping.\n`))
112
+ console.log(chalk.cyan(` Use --force to overwrite.\n`))
113
+ return
114
+ }
54
115
 
116
+ writeFileSync(targetFile, content, "utf-8")
117
+ console.log(chalk.green(`\n ✅ ${outputName} added from "${name}" template\n`))
118
+
119
+ if (template.metadata.hasTeachMe) {
120
+ console.log(
121
+ chalk.cyan(` 💡 Tip: npx openPrompt-Lang teach ${name} to learn about this component`)
122
+ )
123
+ }
124
+ if (template.metadata.fixHistory?.length > 0) {
125
+ console.log(
126
+ chalk.yellow(` 🐛 This component has ${template.metadata.fixHistory.length} learned fixes`)
127
+ )
128
+ console.log(chalk.cyan(` npx openPrompt-Lang lang errors ${langId} --template ${name}\n`))
129
+ }
130
+ return
131
+ }
132
+
133
+ // Fallback to legacy library
134
+ const libPath = options.library || config?.components?.library || null
55
135
  if (!libPath || !existsSync(libPath)) {
56
- console.log(chalk.red(`\n❌ Biblioteca no encontrada: ${libPath || "no configurada"}`));
57
- console.log(chalk.cyan(" Configura components.library en prompt-lang.json o usa --library\n"));
58
- return;
136
+ console.log(chalk.red(`\n❌ Library not found: ${libPath || "not configured"}`))
137
+ console.log(
138
+ chalk.cyan(" Configure components.library in prompt-lang.json or use --template\n")
139
+ )
140
+ return
59
141
  }
60
142
 
61
- const manifestPath = join(libPath, MANIFEST_FILE);
143
+ const manifestPath = join(libPath, MANIFEST_FILE)
62
144
  if (!existsSync(manifestPath)) {
63
- console.log(chalk.red("\n❌ La biblioteca no tiene componentes.json\n"));
64
- return;
145
+ console.log(chalk.red("\n❌ Library has no componentes.json\n"))
146
+ return
65
147
  }
66
148
 
67
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
68
- const compInfo = manifest.componentes?.[name];
149
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"))
150
+ const compInfo = manifest.componentes?.[name]
69
151
  if (!compInfo) {
70
- console.log(chalk.red(`\n❌ Componente "${name}" no encontrado en la biblioteca`));
71
- console.log(chalk.cyan(` Componentes disponibles: ${Object.keys(manifest.componentes).join(", ")}\n`));
72
- return;
152
+ console.log(chalk.red(`\n❌ Component "${name}" not found in library\n`))
153
+ return
73
154
  }
74
155
 
75
- const srcFile = join(libPath, compInfo.path);
156
+ const srcFile = join(libPath, compInfo.path)
76
157
  if (!existsSync(srcFile)) {
77
- console.log(chalk.red(`\n❌ Archivo no encontrado: ${compInfo.path}\n`));
78
- return;
158
+ console.log(chalk.red(`\n❌ File not found: ${compInfo.path}\n`))
159
+ return
79
160
  }
80
161
 
81
- const targetDir = config?.components?.source || join(cwd, "src/components/ui");
82
- mkdirSync(targetDir, { recursive: true });
162
+ const targetDir = config?.components?.source || join(cwd, "src/components/ui")
163
+ mkdirSync(targetDir, { recursive: true })
83
164
 
84
- const targetFile = join(targetDir, basename(srcFile));
165
+ const targetFile = join(targetDir, basename(srcFile))
85
166
  if (existsSync(targetFile)) {
86
- console.log(chalk.yellow(`\n ⚠️ ${basename(srcFile)} ya existe en el proyecto. Sobrescrito.\n`));
167
+ console.log(chalk.yellow(` ⚠️ ${basename(srcFile)} already exists. Overwritten.\n`))
87
168
  }
88
169
 
89
- const content = readFileSync(srcFile, "utf-8");
90
- writeFileSync(targetFile, content, "utf-8");
91
- console.log(chalk.green(`\n ✅ ${name} agregado a src/components/ui/${basename(srcFile)}\n`));
170
+ copyFileSync(srcFile, targetFile)
171
+ console.log(chalk.green(`\n ✅ ${name} added to src/components/ui/${basename(srcFile)}\n`))
92
172
  }
93
173
 
94
174
  export async function componentInit(options) {
95
- const libPath = options.library || join(process.cwd(), "diseno-componentes");
175
+ const libPath = options.library || join(process.cwd(), "diseno-componentes")
96
176
 
97
177
  if (existsSync(libPath) && readdirSync(libPath).length > 0) {
98
- console.log(chalk.yellow(`\n ⚠️ El directorio ${libPath} ya existe y no está vacío.\n`));
99
- return;
178
+ console.log(chalk.yellow(`\n ⚠️ ${libPath} already exists and is not empty.\n`))
179
+ return
100
180
  }
101
181
 
102
- mkdirSync(join(libPath, "ui"), { recursive: true });
103
- mkdirSync(join(libPath, "layouts"), { recursive: true });
104
-
105
- // UI Components
106
- const buttonContent = `import { cva, type VariantProps } from "class-variance-authority"
107
- import { memo } from "react"
108
-
109
- const buttonVariants = cva(
110
- "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
111
- {
112
- variants: {
113
- variant: {
114
- primary: "bg-blue-600 text-white hover:bg-blue-700",
115
- secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
116
- danger: "bg-red-600 text-white hover:bg-red-700",
117
- ghost: "hover:bg-gray-100 hover:text-gray-900",
118
- },
119
- size: {
120
- default: "h-10 px-4 py-2",
121
- sm: "h-9 rounded-md px-3",
122
- lg: "h-11 rounded-md px-8",
123
- icon: "h-10 w-10",
124
- },
125
- },
126
- defaultVariants: { variant: "primary", size: "default" },
127
- }
128
- )
182
+ mkdirSync(join(libPath, "ui"), { recursive: true })
183
+ mkdirSync(join(libPath, "layouts"), { recursive: true })
129
184
 
130
- interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
185
+ const buttonContent =
186
+ readFromModule("react", "templates/ui/Button.template.tsx") ||
187
+ `import { memo } from "react"
188
+
189
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
190
+ variant?: "primary" | "secondary" | "danger" | "ghost"
191
+ size?: "sm" | "default" | "lg"
192
+ loading?: boolean
193
+ }
131
194
 
132
- const Button = memo<ButtonProps>(({ className, variant, size, ...props }) => (
133
- <button className={buttonVariants({ variant, size, className })} {...props} />
195
+ const Button = memo<ButtonProps>(({ variant = "primary", size = "default", loading, children, ...rest }) => (
196
+ <button type="button" className={\`btn btn-\${variant} btn-\${size}\`} disabled={loading} {...rest}>
197
+ {loading && <span className="spinner" />}
198
+ {children}
199
+ </button>
134
200
  ))
135
201
  Button.displayName = "Button"
136
-
137
- export { Button, buttonVariants }
202
+ export { Button }
138
203
  `
139
204
 
140
- const confirmModalContent = `import { useEffect, useRef, type ReactNode } from "react"
205
+ const confirmModalContent = `import { useEffect, useRef } from "react"
141
206
  import { Button } from "./Button"
142
207
 
143
208
  interface ConfirmModalProps {
@@ -146,54 +211,20 @@ interface ConfirmModalProps {
146
211
  onCancel: () => void
147
212
  title: string
148
213
  message: string
149
- confirmLabel?: string
150
- cancelLabel?: string
151
214
  variant?: "danger" | "primary"
152
215
  loading?: boolean
153
- children?: ReactNode
154
216
  }
155
217
 
156
- export function ConfirmModal({
157
- open,
158
- onConfirm,
159
- onCancel,
160
- title,
161
- message,
162
- confirmLabel = "Confirmar",
163
- cancelLabel = "Cancelar",
164
- variant = "danger",
165
- loading = false,
166
- children,
167
- }: ConfirmModalProps) {
168
- const overlayRef = useRef<HTMLDivElement>(null)
169
-
170
- useEffect(() => {
171
- const handleEscape = (e: KeyboardEvent) => {
172
- if (e.key === "Escape" && !loading) onCancel()
173
- }
174
- if (open) document.addEventListener("keydown", handleEscape)
175
- return () => document.removeEventListener("keydown", handleEscape)
176
- }, [open, loading, onCancel])
177
-
218
+ export function ConfirmModal({ open, onConfirm, onCancel, title, message, variant = "danger", loading }: ConfirmModalProps) {
178
219
  if (!open) return null
179
-
180
220
  return (
181
- <div
182
- ref={overlayRef}
183
- className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
184
- onClick={(e) => { if (e.target === overlayRef && !loading) onCancel() }}
185
- >
186
- <div className="w-full max-w-md rounded-lg bg-white p-6 shadow-xl" role="dialog" aria-modal="true">
187
- <h3 className="text-lg font-semibold text-gray-900">{title}</h3>
188
- {message && <p className="mt-2 text-sm text-gray-600">{message}</p>}
189
- {children && <div className="mt-3">{children}</div>}
190
- <div className="mt-6 flex justify-end gap-3">
191
- <Button variant="ghost" onClick={onCancel} disabled={loading}>
192
- {cancelLabel}
193
- </Button>
194
- <Button variant={variant} onClick={onConfirm} disabled={loading}>
195
- {loading ? "Procesando..." : confirmLabel}
196
- </Button>
221
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={onCancel}>
222
+ <div className="rounded-lg bg-white p-6 shadow-xl" onClick={(e) => e.stopPropagation()}>
223
+ <h3>{title}</h3>
224
+ <p>{message}</p>
225
+ <div className="mt-4 flex gap-2 justify-end">
226
+ <Button variant="ghost" onClick={onCancel}>Cancel</Button>
227
+ <Button variant={variant} onClick={onConfirm} loading={loading}>Confirm</Button>
197
228
  </div>
198
229
  </div>
199
230
  </div>
@@ -201,126 +232,125 @@ export function ConfirmModal({
201
232
  }
202
233
  `
203
234
 
204
- const inputContent = `import { forwardRef, type InputHTMLAttributes } from "react"
205
-
206
- interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
207
- label?: string
208
- error?: string
209
- }
210
-
211
- const Input = forwardRef<HTMLInputElement, InputProps>(({ label, error, className, ...props }, ref) => (
212
- <div className="w-full">
213
- {label && <label className="mb-1 block text-sm font-medium text-gray-700">{label}</label>}
214
- <input
215
- ref={ref}
216
- className={\`flex h-10 w-full rounded-md border px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 \${
217
- error ? "border-red-500 focus:ring-red-500" : "border-gray-300"
218
- } \${className || ""}\`}
219
- {...props}
220
- />
221
- {error && <p className="mt-1 text-xs text-red-600">{error}</p>}
222
- </div>
223
- ))
224
- Input.displayName = "Input"
225
-
226
- export { Input }
227
- `
228
-
229
- writeFileSync(join(libPath, "ui/Button.tsx"), buttonContent, "utf-8");
230
- writeFileSync(join(libPath, "ui/ConfirmModal.tsx"), confirmModalContent, "utf-8");
231
- writeFileSync(join(libPath, "ui/Input.tsx"), inputContent, "utf-8");
235
+ writeFileSync(join(libPath, "ui/Button.tsx"), buttonContent, "utf-8")
236
+ writeFileSync(join(libPath, "ui/ConfirmModal.tsx"), confirmModalContent, "utf-8")
232
237
 
233
- // Barrel export
234
- writeFileSync(join(libPath, "ui/index.ts"), `export { Button, buttonVariants } from "./Button"
238
+ writeFileSync(
239
+ join(libPath, "ui/index.ts"),
240
+ `export { Button } from "./Button"
235
241
  export { ConfirmModal } from "./ConfirmModal"
236
- export { Input } from "./Input"
237
- `, "utf-8");
242
+ `,
243
+ "utf-8"
244
+ )
238
245
 
239
- // Manifest
240
246
  const manifest = {
241
247
  name: options.name || "mi-diseno",
242
248
  version: "1.0.0",
243
- descripcion: "Biblioteca de componentes reutilizables",
249
+ description: "Reusable component library",
244
250
  componentes: {
245
251
  Button: {
246
252
  path: "ui/Button.tsx",
247
- tags: ["ui", "form", "accion"],
253
+ tags: ["ui", "form", "action"],
248
254
  variants: ["primary", "secondary", "danger", "ghost"],
249
- descripcion: "Botón con variantes de color y tamaño",
255
+ description: "Button with variants",
250
256
  },
251
257
  ConfirmModal: {
252
258
  path: "ui/ConfirmModal.tsx",
253
- tags: ["ui", "modal", "confirmacion", "destructivo"],
254
- descripcion: "Modal centrado para confirmar acciones destructivas con soporte de teclado",
255
- },
256
- Input: {
257
- path: "ui/Input.tsx",
258
- tags: ["ui", "form", "input"],
259
- descripcion: "Input con label, error y forwardRef",
259
+ tags: ["ui", "modal", "confirmation"],
260
+ description: "Confirmation modal dialog",
260
261
  },
261
262
  },
262
- };
263
+ }
263
264
 
264
- writeFileSync(join(libPath, MANIFEST_FILE), JSON.stringify(manifest, null, 2), "utf-8");
265
- console.log(chalk.green(`\n ✅ Biblioteca inicializada en ${libPath}/`));
266
- console.log(chalk.cyan(` 3 componentes creados: Button, ConfirmModal, Input`));
267
- console.log(chalk.cyan(` Agrega a prompt-lang.json: "components": { "library": "${libPath}" }\n`));
265
+ writeFileSync(join(libPath, MANIFEST_FILE), JSON.stringify(manifest, null, 2), "utf-8")
266
+ console.log(chalk.green(`\n ✅ Library initialized at ${libPath}/`))
267
+ console.log(chalk.cyan(` 2 components created: Button, ConfirmModal\n`))
268
268
  }
269
269
 
270
270
  export async function componentManifest(options) {
271
- const cwd = process.cwd();
272
- const config = loadProjectConfig(cwd);
273
- const libPath = options.library || config?.components?.library || null;
271
+ const cwd = process.cwd()
272
+ const config = loadProjectConfig(cwd)
273
+ const libPath = options.library || config?.components?.library || null
274
274
 
275
275
  if (!libPath || !existsSync(libPath)) {
276
- console.log(chalk.red("\n❌ Biblioteca no encontrada\n"));
277
- return;
276
+ console.log(chalk.red("\n❌ Library not found\n"))
277
+ return
278
278
  }
279
279
 
280
- const manifestPath = join(libPath, MANIFEST_FILE);
280
+ const manifestPath = join(libPath, MANIFEST_FILE)
281
281
  if (!existsSync(manifestPath)) {
282
- console.log(chalk.yellow("\n ⚠️ No hay componentes.json en la biblioteca\n"));
283
- return;
282
+ console.log(chalk.yellow("\n ⚠️ No componentes.json in library\n"))
283
+ return
284
284
  }
285
285
 
286
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
287
- console.log(chalk.cyan(`\n📚 ${manifest.name} v${manifest.version}`));
288
- console.log(` ${manifest.descripcion || ""}`);
289
- console.log(`\n Componentes (${Object.keys(manifest.componentes || {}).length}):`);
286
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"))
287
+ console.log(chalk.cyan(`\n📚 ${manifest.name} v${manifest.version}`))
288
+ console.log(` ${manifest.description || ""}`)
289
+ console.log(`\n Components (${Object.keys(manifest.componentes || {}).length}):`)
290
290
  for (const [name, info] of Object.entries(manifest.componentes || {})) {
291
- console.log(` 📦 ${chalk.bold(name)} → ${info.path}`);
292
- if (info.descripcion) console.log(` ${info.descripcion}`);
293
- if (info.tags) console.log(` Tags: ${info.tags.join(", ")}`);
291
+ console.log(` 📦 ${chalk.bold(name)} → ${info.path}`)
292
+ if (info.description) console.log(` ${info.description}`)
293
+ if (info.tags) console.log(` Tags: ${info.tags.join(", ")}`)
294
294
  }
295
- console.log("");
295
+ console.log("")
296
296
  }
297
297
 
298
298
  // ─── Helpers ─────────────────────────────────────────────────────────────────
299
299
 
300
300
  function loadProjectConfig(cwd) {
301
- const configPath = join(cwd, "prompt-lang.json");
302
- if (!existsSync(configPath)) return null;
301
+ const configPath = join(cwd, "prompt-lang.json")
302
+ if (!existsSync(configPath)) return null
303
303
  try {
304
- return JSON.parse(readFileSync(configPath, "utf-8"));
304
+ return JSON.parse(readFileSync(configPath, "utf-8"))
305
305
  } catch {
306
- return null;
306
+ return null
307
307
  }
308
308
  }
309
309
 
310
310
  function listComponents(dir) {
311
- const entries = readdirSync(dir, { withFileTypes: true });
311
+ const entries = readdirSync(dir, { withFileTypes: true })
312
312
  return entries
313
- .filter(e => e.isFile() && (e.name.endsWith(".tsx") || e.name.endsWith(".tsx")))
314
- .map(e => e.name)
315
- .sort();
313
+ .filter((e) => e.isFile() && (e.name.endsWith(".tsx") || e.name.endsWith(".ts")))
314
+ .map((e) => e.name)
315
+ .sort()
316
316
  }
317
317
 
318
318
  function extractTags(filePath) {
319
319
  try {
320
- const content = readFileSync(filePath, "utf-8");
321
- const match = content.match(/\/\/\s*@kind\((\w+)\)/);
322
- return match ? match[1] : null;
320
+ const content = readFileSync(filePath, "utf-8")
321
+ const match = content.match(/\/\/\s*@kind\((\w+)\)/)
322
+ return match ? match[1] : null
323
+ } catch {
324
+ return null
325
+ }
326
+ }
327
+
328
+ function injectVariants(content, variants) {
329
+ // Find the variants section in cva or the variant enum and filter
330
+ const variantMatch = content.match(/variant:\s*\{([^}]+)\}/s)
331
+ if (!variantMatch) return content
332
+
333
+ const variantBlock = variantMatch[1]
334
+ const variantLines = variantBlock.split("\n").map((l) => l.trim())
335
+ const kept = variantLines.filter((l) =>
336
+ variants.some((v) => l.startsWith(v + ":") || l.startsWith(`"${v}"`))
337
+ )
338
+
339
+ if (kept.length === 0) return content
340
+
341
+ return content.replace(
342
+ /variant:\s*\{[^}]+\}/s,
343
+ `variant: {\n${kept.map((l) => ` ${l}`).join("\n")}\n }`
344
+ )
345
+ }
346
+
347
+ function readFromModule(langId, relativePath) {
348
+ try {
349
+ const __dirname = dirname(fileURLToPath(import.meta.url))
350
+ const fullPath = join(__dirname, "..", "templates", "langs", langId, relativePath)
351
+ if (!existsSync(fullPath)) return null
352
+ return readFileSync(fullPath, "utf-8")
323
353
  } catch {
324
- return null;
354
+ return null
325
355
  }
326
356
  }