@herowcode/cli 0.1.0 → 0.2.1

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 (2) hide show
  1. package/dist/index.js +192 -485
  2. package/package.json +32 -26
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import chalk5 from "chalk";
8
8
  import chalk2 from "chalk";
9
9
  import ora2 from "ora";
10
10
  import prompts from "prompts";
11
- import fs3 from "fs-extra";
12
- import path3 from "path";
11
+ import fs4 from "fs-extra";
12
+ import path4 from "path";
13
13
 
14
14
  // src/utils/detect-project.ts
15
15
  import fs from "fs-extra";
@@ -226,442 +226,92 @@ async function ensureShadcnForm(config, cwd) {
226
226
  await installShadcnComponent("form", config, cwd);
227
227
  }
228
228
 
229
- // src/registry/components/select-estado.ts
230
- var selectEstadoComponent = {
231
- name: "select-estado",
232
- description: "Combobox para sele\xE7\xE3o de estados brasileiros com fetch din\xE2mico da API do IBGE",
233
- dependencies: [],
234
- devDependencies: [],
235
- shadcnDeps: ["command", "popover", "button"],
236
- variants: {
237
- withForm: {
238
- dependencies: ["react-hook-form", "@hookform/resolvers", "zod"]
239
- }
240
- },
241
- getCode: (config, options) => {
242
- const isNextjs = config.type === "nextjs";
243
- const useAlias = config.srcDir ? "@/" : "../";
244
- if (isNextjs) {
245
- return getNextjsCode(useAlias, config.isTypeScript);
246
- } else {
247
- return getViteCode(useAlias, config.isTypeScript);
248
- }
249
- },
250
- getServerAction: (config) => {
251
- return getServerActionCode(config.isTypeScript);
252
- },
253
- getFormWrapper: (config) => {
254
- const useAlias = config.srcDir ? "@/" : "../";
255
- return getFormWrapperCode(useAlias, config.isTypeScript);
256
- },
257
- usageExample: (withForm) => {
258
- if (withForm) {
259
- return `
260
- import { SelectEstadoField } from "@/components/ui/select-estado-field"
261
-
262
- // Dentro do seu FormProvider:
263
- <SelectEstadoField
264
- name="estado"
265
- label="Estado"
266
- description="Selecione o estado"
267
- />`;
229
+ // src/registry/index.ts
230
+ import fs3 from "fs-extra";
231
+ import path3 from "path";
232
+ import { fileURLToPath } from "url";
233
+ var __filename = fileURLToPath(import.meta.url);
234
+ var __dirname = path3.dirname(__filename);
235
+ async function getRegistryPath() {
236
+ const possiblePaths = [
237
+ // Development: running from packages/cli/dist
238
+ path3.resolve(__dirname, "../../../registry"),
239
+ // Development: running from packages/cli/src
240
+ path3.resolve(__dirname, "../../registry"),
241
+ // Monorepo root
242
+ path3.resolve(__dirname, "../../../../packages/registry"),
243
+ // Installed via npm (registry bundled)
244
+ path3.resolve(__dirname, "../registry")
245
+ ];
246
+ for (const registryPath of possiblePaths) {
247
+ if (await fs3.pathExists(registryPath)) {
248
+ return registryPath;
268
249
  }
269
- return `
270
- import { SelectEstado } from "@/components/ui/select-estado"
271
-
272
- <SelectEstado
273
- value={estado}
274
- onValueChange={setEstado}
275
- placeholder="Selecione um estado"
276
- />`;
277
250
  }
278
- };
279
- function getNextjsCode(aliasPrefix, isTypeScript) {
280
- const ts = isTypeScript;
281
- return `"use client";
282
-
283
- import * as React from "react";
284
- import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
285
- import { cn } from "${aliasPrefix}lib/utils";
286
- import { Button } from "${aliasPrefix}components/ui/button";
287
- import {
288
- Command,
289
- CommandEmpty,
290
- CommandGroup,
291
- CommandInput,
292
- CommandItem,
293
- CommandList,
294
- } from "${aliasPrefix}components/ui/command";
295
- import {
296
- Popover,
297
- PopoverContent,
298
- PopoverTrigger,
299
- } from "${aliasPrefix}components/ui/popover";
300
- import { getEstados } from "${aliasPrefix}actions/select-estado";
301
-
302
- ${ts ? `interface Estado {
303
- id: number;
304
- sigla: string;
305
- nome: string;
306
- }
307
-
308
- interface SelectEstadoProps {
309
- value?: string;
310
- onValueChange?: (value: string) => void;
311
- placeholder?: string;
312
- disabled?: boolean;
313
- className?: string;
314
- }` : ""}
315
-
316
- export function SelectEstado({
317
- value,
318
- onValueChange,
319
- placeholder = "Selecione um estado...",
320
- disabled = false,
321
- className,
322
- }${ts ? ": SelectEstadoProps" : ""}) {
323
- const [open, setOpen] = React.useState(false);
324
- const [estados, setEstados] = React.useState${ts ? "<Estado[]>" : ""}([]);
325
- const [loading, setLoading] = React.useState(true);
326
- const [error, setError] = React.useState${ts ? "<string | null>" : ""}(null);
327
-
328
- React.useEffect(() => {
329
- async function fetchEstados() {
330
- try {
331
- setLoading(true);
332
- setError(null);
333
- const data = await getEstados();
334
- setEstados(data);
335
- } catch (err) {
336
- setError("Erro ao carregar estados");
337
- console.error(err);
338
- } finally {
339
- setLoading(false);
340
- }
341
- }
342
-
343
- fetchEstados();
344
- }, []);
345
-
346
- const selectedEstado = estados.find((estado) => estado.sigla === value);
347
-
348
- return (
349
- <Popover open={open} onOpenChange={setOpen}>
350
- <PopoverTrigger asChild>
351
- <Button
352
- variant="outline"
353
- role="combobox"
354
- aria-expanded={open}
355
- disabled={disabled || loading}
356
- className={cn("w-full justify-between", className)}
357
- >
358
- {loading ? (
359
- <span className="flex items-center gap-2">
360
- <Loader2 className="h-4 w-4 animate-spin" />
361
- Carregando...
362
- </span>
363
- ) : error ? (
364
- <span className="text-destructive">{error}</span>
365
- ) : selectedEstado ? (
366
- <span>{selectedEstado.nome} ({selectedEstado.sigla})</span>
367
- ) : (
368
- <span className="text-muted-foreground">{placeholder}</span>
369
- )}
370
- <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
371
- </Button>
372
- </PopoverTrigger>
373
- <PopoverContent className="w-full p-0" align="start">
374
- <Command>
375
- <CommandInput placeholder="Buscar estado..." />
376
- <CommandList>
377
- <CommandEmpty>Nenhum estado encontrado.</CommandEmpty>
378
- <CommandGroup>
379
- {estados.map((estado) => (
380
- <CommandItem
381
- key={estado.id}
382
- value={\`\${estado.nome} \${estado.sigla}\`}
383
- onSelect={() => {
384
- onValueChange?.(estado.sigla === value ? "" : estado.sigla);
385
- setOpen(false);
386
- }}
387
- >
388
- <Check
389
- className={cn(
390
- "mr-2 h-4 w-4",
391
- value === estado.sigla ? "opacity-100" : "opacity-0"
392
- )}
393
- />
394
- {estado.nome} ({estado.sigla})
395
- </CommandItem>
396
- ))}
397
- </CommandGroup>
398
- </CommandList>
399
- </Command>
400
- </PopoverContent>
401
- </Popover>
251
+ throw new Error(
252
+ `Registry n\xE3o encontrado. Paths tentados:
253
+ ${possiblePaths.map((p) => ` - ${p}`).join("\n")}`
402
254
  );
403
255
  }
404
- `;
405
- }
406
- function getViteCode(aliasPrefix, isTypeScript) {
407
- const ts = isTypeScript;
408
- return `import * as React from "react";
409
- import { Check, ChevronsUpDown, Loader2 } from "lucide-react";
410
- import { cn } from "${aliasPrefix}lib/utils";
411
- import { Button } from "${aliasPrefix}components/ui/button";
412
- import {
413
- Command,
414
- CommandEmpty,
415
- CommandGroup,
416
- CommandInput,
417
- CommandItem,
418
- CommandList,
419
- } from "${aliasPrefix}components/ui/command";
420
- import {
421
- Popover,
422
- PopoverContent,
423
- PopoverTrigger,
424
- } from "${aliasPrefix}components/ui/popover";
425
-
426
- const IBGE_API_URL = "https://servicodados.ibge.gov.br/api/v1/localidades/estados?orderBy=nome";
427
-
428
- ${ts ? `interface Estado {
429
- id: number;
430
- sigla: string;
431
- nome: string;
432
- }
433
-
434
- interface SelectEstadoProps {
435
- value?: string;
436
- onValueChange?: (value: string) => void;
437
- placeholder?: string;
438
- disabled?: boolean;
439
- className?: string;
440
- }` : ""}
441
-
442
- export function SelectEstado({
443
- value,
444
- onValueChange,
445
- placeholder = "Selecione um estado...",
446
- disabled = false,
447
- className,
448
- }${ts ? ": SelectEstadoProps" : ""}) {
449
- const [open, setOpen] = React.useState(false);
450
- const [estados, setEstados] = React.useState${ts ? "<Estado[]>" : ""}([]);
451
- const [loading, setLoading] = React.useState(true);
452
- const [error, setError] = React.useState${ts ? "<string | null>" : ""}(null);
453
-
454
- React.useEffect(() => {
455
- const controller = new AbortController();
456
-
457
- async function fetchEstados() {
458
- try {
459
- setLoading(true);
460
- setError(null);
461
-
462
- const response = await fetch(IBGE_API_URL, {
463
- signal: controller.signal,
464
- });
465
-
466
- if (!response.ok) {
467
- throw new Error("Erro ao buscar estados");
468
- }
469
-
470
- const data${ts ? ": Estado[]" : ""} = await response.json();
471
- setEstados(data);
472
- } catch (err) {
473
- if (err${ts ? " instanceof Error && " : " && "}err.name !== "AbortError") {
474
- setError("Erro ao carregar estados");
475
- console.error(err);
476
- }
477
- } finally {
478
- setLoading(false);
479
- }
256
+ async function listComponents() {
257
+ const registryPath = await getRegistryPath();
258
+ const entries = await fs3.readdir(registryPath, { withFileTypes: true });
259
+ const components = [];
260
+ for (const entry of entries) {
261
+ if (!entry.isDirectory() || entry.name === "node_modules") continue;
262
+ const manifestPath = path3.join(registryPath, entry.name, "manifest.json");
263
+ if (await fs3.pathExists(manifestPath)) {
264
+ const manifest = await fs3.readJson(manifestPath);
265
+ components.push({
266
+ name: manifest.name,
267
+ description: manifest.description,
268
+ hasFormVariant: !!manifest.frameworks.nextjs?.withForm || !!manifest.frameworks.vite?.withForm
269
+ });
480
270
  }
481
-
482
- fetchEstados();
483
-
484
- return () => controller.abort();
485
- }, []);
486
-
487
- const selectedEstado = estados.find((estado) => estado.sigla === value);
488
-
489
- return (
490
- <Popover open={open} onOpenChange={setOpen}>
491
- <PopoverTrigger asChild>
492
- <Button
493
- variant="outline"
494
- role="combobox"
495
- aria-expanded={open}
496
- disabled={disabled || loading}
497
- className={cn("w-full justify-between", className)}
498
- >
499
- {loading ? (
500
- <span className="flex items-center gap-2">
501
- <Loader2 className="h-4 w-4 animate-spin" />
502
- Carregando...
503
- </span>
504
- ) : error ? (
505
- <span className="text-destructive">{error}</span>
506
- ) : selectedEstado ? (
507
- <span>{selectedEstado.nome} ({selectedEstado.sigla})</span>
508
- ) : (
509
- <span className="text-muted-foreground">{placeholder}</span>
510
- )}
511
- <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
512
- </Button>
513
- </PopoverTrigger>
514
- <PopoverContent className="w-full p-0" align="start">
515
- <Command>
516
- <CommandInput placeholder="Buscar estado..." />
517
- <CommandList>
518
- <CommandEmpty>Nenhum estado encontrado.</CommandEmpty>
519
- <CommandGroup>
520
- {estados.map((estado) => (
521
- <CommandItem
522
- key={estado.id}
523
- value={\`\${estado.nome} \${estado.sigla}\`}
524
- onSelect={() => {
525
- onValueChange?.(estado.sigla === value ? "" : estado.sigla);
526
- setOpen(false);
527
- }}
528
- >
529
- <Check
530
- className={cn(
531
- "mr-2 h-4 w-4",
532
- value === estado.sigla ? "opacity-100" : "opacity-0"
533
- )}
534
- />
535
- {estado.nome} ({estado.sigla})
536
- </CommandItem>
537
- ))}
538
- </CommandGroup>
539
- </CommandList>
540
- </Command>
541
- </PopoverContent>
542
- </Popover>
543
- );
544
- }
545
- `;
546
- }
547
- function getServerActionCode(isTypeScript) {
548
- const ts = isTypeScript;
549
- return `"use server";
550
-
551
- const IBGE_API_URL = "https://servicodados.ibge.gov.br/api/v1/localidades/estados?orderBy=nome";
552
-
553
- ${ts ? `interface Estado {
554
- id: number;
555
- sigla: string;
556
- nome: string;
557
- }` : ""}
558
-
559
- export async function getEstados()${ts ? ": Promise<Estado[]>" : ""} {
560
- try {
561
- const response = await fetch(IBGE_API_URL, {
562
- next: { revalidate: 86400 }, // Cache por 24 horas
563
- });
564
-
565
- if (!response.ok) {
566
- throw new Error("Erro ao buscar estados do IBGE");
567
- }
568
-
569
- const data${ts ? ": Estado[]" : ""} = await response.json();
570
- return data;
571
- } catch (error) {
572
- console.error("Erro ao buscar estados:", error);
573
- throw new Error("N\xE3o foi poss\xEDvel carregar a lista de estados");
574
271
  }
272
+ return components;
575
273
  }
576
- `;
274
+ async function getComponentManifest(componentName) {
275
+ const registryPath = await getRegistryPath();
276
+ const manifestPath = path3.join(registryPath, componentName, "manifest.json");
277
+ if (!await fs3.pathExists(manifestPath)) {
278
+ return null;
279
+ }
280
+ return fs3.readJson(manifestPath);
577
281
  }
578
- function getFormWrapperCode(aliasPrefix, isTypeScript) {
579
- const ts = isTypeScript;
580
- return `"use client";
581
-
582
- import * as React from "react";
583
- import { useFormContext${ts ? ", FieldPath, FieldValues" : ""} } from "react-hook-form";
584
- import {
585
- FormControl,
586
- FormDescription,
587
- FormField,
588
- FormItem,
589
- FormLabel,
590
- FormMessage,
591
- } from "${aliasPrefix}components/ui/form";
592
- import { SelectEstado } from "./select-estado";
593
-
594
- ${ts ? `interface SelectEstadoFieldProps<T extends FieldValues> {
595
- name: FieldPath<T>;
596
- label?: string;
597
- description?: string;
598
- placeholder?: string;
599
- disabled?: boolean;
600
- className?: string;
601
- }` : ""}
602
-
603
- export function SelectEstadoField${ts ? "<T extends FieldValues>" : ""}({
604
- name,
605
- label,
606
- description,
607
- placeholder,
608
- disabled,
609
- className,
610
- }${ts ? ": SelectEstadoFieldProps<T>" : ""}) {
611
- const form = useFormContext${ts ? "<T>" : ""}();
612
-
613
- return (
614
- <FormField
615
- control={form.control}
616
- name={name}
617
- render={({ field }) => (
618
- <FormItem className={className}>
619
- {label && <FormLabel>{label}</FormLabel>}
620
- <FormControl>
621
- <SelectEstado
622
- value={field.value}
623
- onValueChange={field.onChange}
624
- placeholder={placeholder}
625
- disabled={disabled}
626
- />
627
- </FormControl>
628
- {description && <FormDescription>{description}</FormDescription>}
629
- <FormMessage />
630
- </FormItem>
631
- )}
632
- />
633
- );
282
+ async function readComponentFile(componentName, framework, fileName) {
283
+ const registryPath = await getRegistryPath();
284
+ const filePath = path3.join(registryPath, componentName, framework, fileName);
285
+ if (!await fs3.pathExists(filePath)) {
286
+ throw new Error(`Arquivo n\xE3o encontrado: ${filePath}`);
287
+ }
288
+ return fs3.readFile(filePath, "utf-8");
634
289
  }
635
- `;
290
+ function transformAliases(content, aliasPrefix) {
291
+ return content.replace(/@\//g, aliasPrefix);
636
292
  }
637
293
 
638
- // src/registry/index.ts
639
- var COMPONENT_REGISTRY = {
640
- "select-estado": selectEstadoComponent
641
- };
642
-
643
294
  // src/commands/add.ts
644
295
  async function addCommand(componentName, options) {
645
296
  const cwd = process.cwd();
646
297
  console.log(chalk2.cyan(`
647
298
  \u{1F4E6} Adicionando componente: ${componentName}
648
299
  `));
649
- const component = COMPONENT_REGISTRY[componentName];
650
- if (!component) {
300
+ const manifest = await getComponentManifest(componentName);
301
+ if (!manifest) {
651
302
  console.error(
652
303
  chalk2.red(`\u274C Componente "${componentName}" n\xE3o encontrado.`)
653
304
  );
654
305
  console.log(chalk2.dim("\nComponentes dispon\xEDveis:"));
655
- Object.keys(COMPONENT_REGISTRY).forEach((name) => {
656
- console.log(chalk2.dim(` - ${name}`));
306
+ const components = await listComponents();
307
+ components.forEach((c) => {
308
+ console.log(chalk2.dim(` - ${c.name}`));
657
309
  });
658
310
  process.exit(1);
659
311
  }
660
312
  const config = await getConfig(cwd);
661
313
  if (!config) {
662
- console.error(
663
- chalk2.red("\u274C herow.config.json n\xE3o encontrado.")
664
- );
314
+ console.error(chalk2.red("\u274C herow.config.json n\xE3o encontrado."));
665
315
  console.log(chalk2.dim("Execute primeiro: herow init"));
666
316
  process.exit(1);
667
317
  }
@@ -669,15 +319,24 @@ async function addCommand(componentName, options) {
669
319
  let projectConfig;
670
320
  try {
671
321
  projectConfig = await detectProject(cwd);
672
- spinner.succeed(
673
- `Projeto detectado: ${projectConfig.type === "nextjs" ? "Next.js" : "Vite"}`
674
- );
322
+ const projectLabel = projectConfig.type === "nextjs" ? "Next.js" : projectConfig.type === "vite" ? "Vite" : "Desconhecido";
323
+ spinner.succeed(`Projeto detectado: ${projectLabel}`);
675
324
  } catch (error) {
676
325
  spinner.fail("Erro ao detectar projeto");
677
326
  throw error;
678
327
  }
328
+ const framework = projectConfig.type === "nextjs" ? "nextjs" : "vite";
329
+ const frameworkConfig = manifest.frameworks[framework];
330
+ if (!frameworkConfig) {
331
+ console.error(
332
+ chalk2.red(
333
+ `\u274C Componente "${componentName}" n\xE3o suporta ${framework}.`
334
+ )
335
+ );
336
+ process.exit(1);
337
+ }
679
338
  let withForm = options.withForm ?? false;
680
- if (component.variants?.withForm && !options.yes && !options.withForm) {
339
+ if (frameworkConfig.withForm && !options.yes && !options.withForm) {
681
340
  const response = await prompts({
682
341
  type: "confirm",
683
342
  name: "withForm",
@@ -690,17 +349,25 @@ async function addCommand(componentName, options) {
690
349
  }
691
350
  withForm = response.withForm;
692
351
  }
693
- const allDeps = [...component.dependencies];
694
- if (withForm && component.variants?.withForm) {
695
- allDeps.push(...component.variants.withForm.dependencies);
352
+ const filesToInstall = [...frameworkConfig.files];
353
+ if (withForm && frameworkConfig.withForm) {
354
+ filesToInstall.push(...frameworkConfig.withForm.files);
355
+ }
356
+ const allDeps = [...manifest.dependencies];
357
+ const allRegistryDeps = [...manifest.registryDependencies];
358
+ if (withForm && frameworkConfig.withForm) {
359
+ allDeps.push(...frameworkConfig.withForm.dependencies);
360
+ allRegistryDeps.push(...frameworkConfig.withForm.registryDependencies);
696
361
  }
697
362
  console.log(chalk2.dim("\nSer\xE1 instalado:"));
698
363
  console.log(chalk2.dim(` Componente: ${componentName}`));
364
+ console.log(chalk2.dim(` Framework: ${framework}`));
365
+ console.log(chalk2.dim(` Arquivos: ${filesToInstall.length}`));
699
366
  if (allDeps.length > 0) {
700
- console.log(chalk2.dim(` Depend\xEAncias: ${allDeps.join(", ")}`));
367
+ console.log(chalk2.dim(` Depend\xEAncias npm: ${allDeps.join(", ")}`));
701
368
  }
702
- if (withForm) {
703
- console.log(chalk2.dim(` Integra\xE7\xE3o: React Hook Form + Zod`));
369
+ if (allRegistryDeps.length > 0) {
370
+ console.log(chalk2.dim(` Depend\xEAncias shadcn: ${allRegistryDeps.join(", ")}`));
704
371
  }
705
372
  if (!options.yes) {
706
373
  const { confirm } = await prompts({
@@ -714,8 +381,8 @@ async function addCommand(componentName, options) {
714
381
  process.exit(0);
715
382
  }
716
383
  }
717
- if (component.shadcnDeps && component.shadcnDeps.length > 0) {
718
- for (const dep of component.shadcnDeps) {
384
+ if (allRegistryDeps.length > 0) {
385
+ for (const dep of allRegistryDeps) {
719
386
  await installShadcnComponent(dep, projectConfig, cwd);
720
387
  }
721
388
  }
@@ -726,51 +393,82 @@ async function addCommand(componentName, options) {
726
393
  await installDependencies(allDeps, projectConfig, cwd);
727
394
  }
728
395
  const writeSpinner = ora2("Escrevendo arquivos...").start();
396
+ const writtenFiles = [];
397
+ const aliasPrefix = config.aliases.ui?.startsWith("@/") ? "@/" : "../";
729
398
  try {
730
- const uiPath = resolveAlias(config.aliases.ui || config.aliases.components, cwd);
731
- const mainFile = component.getCode(projectConfig, { withForm });
732
- const mainFilePath = path3.join(
733
- uiPath,
734
- `${componentName}.${projectConfig.isTypeScript ? "tsx" : "jsx"}`
735
- );
736
- await fs3.ensureDir(path3.dirname(mainFilePath));
737
- await fs3.writeFile(mainFilePath, mainFile, "utf-8");
738
- if (projectConfig.type === "nextjs" && component.getServerAction) {
739
- const actionCode = component.getServerAction(projectConfig);
740
- const actionsPath = path3.join(
741
- cwd,
742
- projectConfig.actionsPath || (projectConfig.srcDir ? "src/actions" : "actions")
743
- );
744
- const actionFilePath = path3.join(
745
- actionsPath,
746
- `${componentName}.${projectConfig.isTypeScript ? "ts" : "js"}`
747
- );
748
- await fs3.ensureDir(path3.dirname(actionFilePath));
749
- await fs3.writeFile(actionFilePath, actionCode, "utf-8");
750
- writeSpinner.succeed(`Arquivos escritos!`);
751
- console.log(chalk2.dim(` ${mainFilePath}`));
752
- console.log(chalk2.dim(` ${actionFilePath}`));
753
- } else {
754
- writeSpinner.succeed(`Arquivo escrito: ${mainFilePath}`);
755
- }
756
- if (withForm && component.getFormWrapper) {
757
- const formFile = component.getFormWrapper(projectConfig);
758
- const formFilePath = path3.join(
759
- uiPath,
760
- `${componentName}-field.${projectConfig.isTypeScript ? "tsx" : "jsx"}`
761
- );
762
- await fs3.writeFile(formFilePath, formFile, "utf-8");
763
- console.log(chalk2.dim(` ${formFilePath}`));
399
+ for (const file of filesToInstall) {
400
+ const content = await readComponentFile(componentName, framework, file.name);
401
+ const transformedContent = transformAliases(content, aliasPrefix);
402
+ let targetDir;
403
+ let targetFileName = file.targetName || file.name;
404
+ switch (file.target) {
405
+ case "components":
406
+ targetDir = resolveAlias(
407
+ config.aliases.ui || config.aliases.components,
408
+ cwd
409
+ );
410
+ break;
411
+ case "actions":
412
+ targetDir = path4.join(
413
+ cwd,
414
+ projectConfig.actionsPath || (projectConfig.srcDir ? "src/actions" : "actions")
415
+ );
416
+ break;
417
+ case "hooks":
418
+ targetDir = path4.join(
419
+ cwd,
420
+ projectConfig.srcDir ? "src/hooks" : "hooks"
421
+ );
422
+ break;
423
+ case "lib":
424
+ targetDir = path4.join(cwd, projectConfig.srcDir ? "src/lib" : "lib");
425
+ break;
426
+ default:
427
+ targetDir = resolveAlias(config.aliases.components, cwd);
428
+ }
429
+ const targetPath = path4.join(targetDir, targetFileName);
430
+ await fs4.ensureDir(path4.dirname(targetPath));
431
+ await fs4.writeFile(targetPath, transformedContent, "utf-8");
432
+ writtenFiles.push(targetPath);
764
433
  }
434
+ writeSpinner.succeed("Arquivos escritos!");
435
+ writtenFiles.forEach((f) => console.log(chalk2.dim(` ${f}`)));
765
436
  } catch (error) {
766
437
  writeSpinner.fail("Erro ao escrever arquivos");
767
438
  throw error;
768
439
  }
769
- console.log(chalk2.green(`
770
- \u2705 Componente ${componentName} adicionado com sucesso!`));
771
- if (component.usageExample) {
772
- console.log(chalk2.dim("\nExemplo de uso:"));
773
- console.log(chalk2.cyan(component.usageExample(withForm)));
440
+ console.log(
441
+ chalk2.green(`
442
+ \u2705 Componente ${componentName} adicionado com sucesso!`)
443
+ );
444
+ console.log(chalk2.dim("\nExemplo de uso:"));
445
+ if (framework === "nextjs") {
446
+ console.log(
447
+ chalk2.cyan(`
448
+ // Em um Server Component (page.tsx, layout.tsx):
449
+ import { SelectEstado } from "${aliasPrefix}components/ui/select-estado"
450
+
451
+ export default async function Page() {
452
+ return <SelectEstado />
453
+ }
454
+
455
+ // Em um Client Component:
456
+ import { SelectEstadoClient } from "${aliasPrefix}components/ui/select-estado-client"
457
+ // Passe os estados como prop
458
+ `)
459
+ );
460
+ } else {
461
+ console.log(
462
+ chalk2.cyan(`
463
+ import { SelectEstado } from "${aliasPrefix}components/ui/select-estado"
464
+
465
+ <SelectEstado
466
+ value={estado}
467
+ onValueChange={setEstado}
468
+ placeholder="Selecione um estado"
469
+ />
470
+ `)
471
+ );
774
472
  }
775
473
  }
776
474
 
@@ -778,8 +476,8 @@ async function addCommand(componentName, options) {
778
476
  import chalk3 from "chalk";
779
477
  import ora3 from "ora";
780
478
  import prompts2 from "prompts";
781
- import fs4 from "fs-extra";
782
- import path4 from "path";
479
+ import fs5 from "fs-extra";
480
+ import path5 from "path";
783
481
  async function initCommand(options) {
784
482
  const cwd = process.cwd();
785
483
  console.log(chalk3.cyan("\n\u{1F680} Inicializando Herow Components...\n"));
@@ -876,15 +574,15 @@ async function initCommand(options) {
876
574
  writeSpinner.fail("Erro ao salvar configura\xE7\xE3o");
877
575
  throw error;
878
576
  }
879
- const uiDir = path4.join(cwd, projectConfig.componentsPath);
880
- if (!await fs4.pathExists(uiDir)) {
881
- await fs4.ensureDir(uiDir);
577
+ const uiDir = path5.join(cwd, projectConfig.componentsPath);
578
+ if (!await fs5.pathExists(uiDir)) {
579
+ await fs5.ensureDir(uiDir);
882
580
  console.log(chalk3.dim(` Criado: ${projectConfig.componentsPath}`));
883
581
  }
884
582
  if (projectConfig.actionsPath) {
885
- const actionsDir = path4.join(cwd, projectConfig.actionsPath);
886
- if (!await fs4.pathExists(actionsDir)) {
887
- await fs4.ensureDir(actionsDir);
583
+ const actionsDir = path5.join(cwd, projectConfig.actionsPath);
584
+ if (!await fs5.pathExists(actionsDir)) {
585
+ await fs5.ensureDir(actionsDir);
888
586
  console.log(chalk3.dim(` Criado: ${projectConfig.actionsPath}`));
889
587
  }
890
588
  }
@@ -898,23 +596,32 @@ async function initCommand(options) {
898
596
  import chalk4 from "chalk";
899
597
  async function listCommand() {
900
598
  console.log(chalk4.cyan("\n\u{1F4E6} Componentes dispon\xEDveis:\n"));
901
- for (const [name, component] of Object.entries(COMPONENT_REGISTRY)) {
902
- console.log(
903
- ` ${chalk4.green("\u25CF")} ${chalk4.bold(name)}`
904
- );
905
- console.log(chalk4.dim(` ${component.description}`));
906
- if (component.dependencies.length > 0) {
907
- console.log(
908
- chalk4.dim(` Deps: ${component.dependencies.join(", ")}`)
909
- );
599
+ try {
600
+ const components = await listComponents();
601
+ if (components.length === 0) {
602
+ console.log(chalk4.dim(" Nenhum componente encontrado no registry."));
603
+ return;
910
604
  }
911
- if (component.variants?.withForm) {
912
- console.log(chalk4.yellow(` \u{1F4DD} Suporta integra\xE7\xE3o com React Hook Form`));
605
+ for (const component of components) {
606
+ console.log(` ${chalk4.green("\u25CF")} ${chalk4.bold(component.name)}`);
607
+ console.log(chalk4.dim(` ${component.description}`));
608
+ if (component.hasFormVariant) {
609
+ console.log(
610
+ chalk4.yellow(` \u{1F4DD} Suporta integra\xE7\xE3o com React Hook Form`)
611
+ );
612
+ }
613
+ console.log();
913
614
  }
914
- console.log();
615
+ console.log(chalk4.dim("Para adicionar um componente:"));
616
+ console.log(chalk4.cyan(" herow add <nome-do-componente>\n"));
617
+ } catch (error) {
618
+ console.error(
619
+ chalk4.red(
620
+ `\u274C Erro ao listar componentes: ${error instanceof Error ? error.message : "Erro desconhecido"}`
621
+ )
622
+ );
623
+ process.exit(1);
915
624
  }
916
- console.log(chalk4.dim("Para adicionar um componente:"));
917
- console.log(chalk4.cyan(" herow add <nome-do-componente>\n"));
918
625
  }
919
626
 
920
627
  // src/index.ts
package/package.json CHANGED
@@ -1,35 +1,41 @@
1
1
  {
2
- "name" : "@herowcode/cli",
3
- "version" : "0.1.0",
4
- "description" : "CLI para instalar componentes Herow diretamente do GitHub",
5
- "type" : "module",
6
- "bin" : {"herow": "./dist/index.js"},
7
- "main" : "./dist/index.js",
8
- "types" : "./dist/index.d.ts",
9
- "files" : ["dist"],
10
- "scripts" : {
11
- "dev" : "tsup src/index.ts --format esm --watch",
12
- "build" : "tsup src/index.ts --format esm --dts --clean",
13
- "start" : "node dist/index.js",
14
- "typecheck" : "tsc --noEmit",
2
+ "name": "@herowcode/cli",
3
+ "version": "0.2.1",
4
+ "description": "CLI para instalar componentes Herow diretamente do GitHub",
5
+ "type": "module",
6
+ "bin": {
7
+ "herow": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "dev": "tsup src/index.ts --format esm --watch",
16
+ "build": "tsup src/index.ts --format esm --dts --clean",
17
+ "start": "node dist/index.js",
18
+ "typecheck": "tsc --noEmit",
15
19
  "prepublishOnly": "pnpm build"
16
20
  },
17
- "dependencies" : {
18
- "chalk" : "^5.3.0",
19
- "commander" : "^11.1.0",
20
- "execa" : "^8.0.1",
21
- "fs-extra" : "^11.2.0",
21
+ "dependencies": {
22
+ "chalk": "^5.3.0",
23
+ "commander": "^11.1.0",
24
+ "execa": "^8.0.1",
25
+ "fs-extra": "^11.2.0",
22
26
  "node-fetch": "^3.3.2",
23
- "ora" : "^7.0.1",
24
- "prompts" : "^2.4.2",
25
- "zod" : "^3.22.4"
27
+ "ora": "^7.0.1",
28
+ "prompts": "^2.4.2",
29
+ "zod": "^3.22.4"
26
30
  },
27
31
  "devDependencies": {
28
32
  "@types/fs-extra": "^11.0.4",
29
- "@types/node" : "^20.10.0",
30
- "@types/prompts" : "^2.4.9",
31
- "tsup" : "^8.0.1",
32
- "typescript" : "^5.3.0"
33
+ "@types/node": "^20.10.0",
34
+ "@types/prompts": "^2.4.9",
35
+ "tsup": "^8.0.1",
36
+ "typescript": "^5.3.0"
33
37
  },
34
- "publishConfig" : {"access": "public"}
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
35
41
  }