@herowcode/cli 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.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,928 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk5 from "chalk";
6
+
7
+ // src/commands/add.ts
8
+ import chalk2 from "chalk";
9
+ import ora2 from "ora";
10
+ import prompts from "prompts";
11
+ import fs3 from "fs-extra";
12
+ import path3 from "path";
13
+
14
+ // src/utils/detect-project.ts
15
+ import fs from "fs-extra";
16
+ import path from "path";
17
+ async function detectPackageManager(cwd) {
18
+ if (await fs.pathExists(path.join(cwd, "bun.lockb"))) return "bun";
19
+ if (await fs.pathExists(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
20
+ if (await fs.pathExists(path.join(cwd, "yarn.lock"))) return "yarn";
21
+ return "npm";
22
+ }
23
+ async function detectProject(cwd) {
24
+ const pkgPath = path.join(cwd, "package.json");
25
+ if (!await fs.pathExists(pkgPath)) {
26
+ throw new Error(
27
+ "package.json n\xE3o encontrado. Execute este comando na raiz do seu projeto."
28
+ );
29
+ }
30
+ const pkg = await fs.readJson(pkgPath);
31
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
32
+ let type = "unknown";
33
+ if ("next" in deps) {
34
+ type = "nextjs";
35
+ } else if ("vite" in deps) {
36
+ type = "vite";
37
+ }
38
+ const isTypeScript = await fs.pathExists(path.join(cwd, "tsconfig.json")) || "typescript" in deps;
39
+ const srcDir = await fs.pathExists(path.join(cwd, "src"));
40
+ let componentsPath;
41
+ if (type === "nextjs") {
42
+ const hasAppDir = await fs.pathExists(path.join(cwd, "app")) || await fs.pathExists(path.join(cwd, "src", "app"));
43
+ if (srcDir) {
44
+ componentsPath = "src/components/ui";
45
+ } else {
46
+ componentsPath = "components/ui";
47
+ }
48
+ } else {
49
+ componentsPath = srcDir ? "src/components/ui" : "components/ui";
50
+ }
51
+ let actionsPath;
52
+ if (type === "nextjs") {
53
+ if (srcDir) {
54
+ actionsPath = "src/actions";
55
+ } else {
56
+ actionsPath = "actions";
57
+ }
58
+ }
59
+ const hasReactHookForm = "react-hook-form" in deps;
60
+ const hasShadcnForm = hasReactHookForm && "@hookform/resolvers" in deps && "zod" in deps;
61
+ const hasTailwind = "tailwindcss" in deps || await fs.pathExists(path.join(cwd, "tailwind.config.js")) || await fs.pathExists(path.join(cwd, "tailwind.config.ts"));
62
+ const packageManager = await detectPackageManager(cwd);
63
+ return {
64
+ type,
65
+ isTypeScript,
66
+ srcDir,
67
+ componentsPath,
68
+ actionsPath,
69
+ hasReactHookForm,
70
+ hasShadcnForm,
71
+ hasTailwind,
72
+ packageManager
73
+ };
74
+ }
75
+ function getInstallCommand(packageManager, packages) {
76
+ const pkgList = packages.join(" ");
77
+ switch (packageManager) {
78
+ case "pnpm":
79
+ return `pnpm add ${pkgList}`;
80
+ case "yarn":
81
+ return `yarn add ${pkgList}`;
82
+ case "bun":
83
+ return `bun add ${pkgList}`;
84
+ default:
85
+ return `npm install ${pkgList}`;
86
+ }
87
+ }
88
+ function getExecCommand(packageManager, command) {
89
+ switch (packageManager) {
90
+ case "pnpm":
91
+ return `pnpm dlx ${command}`;
92
+ case "yarn":
93
+ return `yarn dlx ${command}`;
94
+ case "bun":
95
+ return `bunx ${command}`;
96
+ default:
97
+ return `npx ${command}`;
98
+ }
99
+ }
100
+
101
+ // src/utils/config.ts
102
+ import fs2 from "fs-extra";
103
+ import path2 from "path";
104
+ import { z } from "zod";
105
+ var configSchema = z.object({
106
+ $schema: z.string().optional(),
107
+ style: z.enum(["default", "new-york"]).default("default"),
108
+ typescript: z.boolean().default(true),
109
+ tailwind: z.object({
110
+ config: z.string(),
111
+ css: z.string(),
112
+ baseColor: z.string()
113
+ }),
114
+ aliases: z.object({
115
+ components: z.string(),
116
+ utils: z.string(),
117
+ ui: z.string().optional(),
118
+ lib: z.string().optional(),
119
+ hooks: z.string().optional()
120
+ }),
121
+ registry: z.string().optional()
122
+ });
123
+ var CONFIG_FILE = "herow.config.json";
124
+ async function getConfig(cwd) {
125
+ const configPath = path2.join(cwd, CONFIG_FILE);
126
+ if (!await fs2.pathExists(configPath)) {
127
+ return null;
128
+ }
129
+ try {
130
+ const config = await fs2.readJson(configPath);
131
+ return configSchema.parse(config);
132
+ } catch (error) {
133
+ return null;
134
+ }
135
+ }
136
+ async function writeConfig(cwd, config) {
137
+ const configPath = path2.join(cwd, CONFIG_FILE);
138
+ await fs2.writeJson(configPath, config, { spaces: 2 });
139
+ }
140
+ function getDefaultConfig(options) {
141
+ const { isTypeScript, srcDir } = options;
142
+ const prefix = srcDir ? "@/" : "./";
143
+ return {
144
+ $schema: "https://herow.dev/schema.json",
145
+ style: "default",
146
+ typescript: isTypeScript,
147
+ tailwind: {
148
+ config: srcDir ? "tailwind.config.ts" : "tailwind.config.js",
149
+ css: srcDir ? "src/app/globals.css" : "app/globals.css",
150
+ baseColor: "slate"
151
+ },
152
+ aliases: {
153
+ components: `${prefix}components`,
154
+ utils: `${prefix}lib/utils`,
155
+ ui: `${prefix}components/ui`,
156
+ lib: `${prefix}lib`,
157
+ hooks: `${prefix}hooks`
158
+ }
159
+ };
160
+ }
161
+ function resolveAlias(alias, cwd) {
162
+ if (alias.startsWith("@/")) {
163
+ return path2.join(cwd, "src", alias.slice(2));
164
+ }
165
+ if (alias.startsWith("./")) {
166
+ return path2.join(cwd, alias.slice(2));
167
+ }
168
+ return path2.join(cwd, alias);
169
+ }
170
+
171
+ // src/utils/dependencies.ts
172
+ import { execa } from "execa";
173
+ import ora from "ora";
174
+ import chalk from "chalk";
175
+ async function installDependencies(dependencies, config, cwd) {
176
+ if (dependencies.length === 0) return;
177
+ const spinner = ora({
178
+ text: `Instalando depend\xEAncias: ${chalk.cyan(dependencies.join(", "))}`
179
+ }).start();
180
+ try {
181
+ const command = getInstallCommand(config.packageManager, dependencies);
182
+ const [cmd, ...args] = command.split(" ");
183
+ await execa(cmd, args, { cwd, stdio: "pipe" });
184
+ spinner.succeed(
185
+ `Depend\xEAncias instaladas: ${chalk.green(dependencies.join(", "))}`
186
+ );
187
+ } catch (error) {
188
+ spinner.fail(`Falha ao instalar depend\xEAncias`);
189
+ throw error;
190
+ }
191
+ }
192
+ async function installShadcnComponent(componentName, config, cwd) {
193
+ const spinner = ora({
194
+ text: `Instalando shadcn/ui: ${chalk.cyan(componentName)}`
195
+ }).start();
196
+ try {
197
+ const command = getExecCommand(
198
+ config.packageManager,
199
+ `shadcn@latest add ${componentName} -y`
200
+ );
201
+ const [cmd, ...args] = command.split(" ");
202
+ await execa(cmd, args, { cwd, stdio: "pipe" });
203
+ spinner.succeed(
204
+ `shadcn/ui instalado: ${chalk.green(componentName)}`
205
+ );
206
+ } catch (error) {
207
+ spinner.fail(`Falha ao instalar shadcn/ui ${componentName}`);
208
+ throw error;
209
+ }
210
+ }
211
+ async function ensureShadcnForm(config, cwd) {
212
+ if (config.hasShadcnForm) {
213
+ return;
214
+ }
215
+ console.log(
216
+ chalk.yellow("\n\u26A0\uFE0F shadcn/form n\xE3o detectado. Instalando...")
217
+ );
218
+ const deps = ["react-hook-form", "@hookform/resolvers", "zod"];
219
+ await installDependencies(
220
+ deps.filter(
221
+ (d) => !config.hasReactHookForm || d !== "react-hook-form" && d !== "@hookform/resolvers" && d !== "zod"
222
+ ),
223
+ config,
224
+ cwd
225
+ );
226
+ await installShadcnComponent("form", config, cwd);
227
+ }
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
+ />`;
268
+ }
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
+ }
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>
402
+ );
403
+ }
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
+ }
480
+ }
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
+ }
575
+ }
576
+ `;
577
+ }
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
+ );
634
+ }
635
+ `;
636
+ }
637
+
638
+ // src/registry/index.ts
639
+ var COMPONENT_REGISTRY = {
640
+ "select-estado": selectEstadoComponent
641
+ };
642
+
643
+ // src/commands/add.ts
644
+ async function addCommand(componentName, options) {
645
+ const cwd = process.cwd();
646
+ console.log(chalk2.cyan(`
647
+ \u{1F4E6} Adicionando componente: ${componentName}
648
+ `));
649
+ const component = COMPONENT_REGISTRY[componentName];
650
+ if (!component) {
651
+ console.error(
652
+ chalk2.red(`\u274C Componente "${componentName}" n\xE3o encontrado.`)
653
+ );
654
+ console.log(chalk2.dim("\nComponentes dispon\xEDveis:"));
655
+ Object.keys(COMPONENT_REGISTRY).forEach((name) => {
656
+ console.log(chalk2.dim(` - ${name}`));
657
+ });
658
+ process.exit(1);
659
+ }
660
+ const config = await getConfig(cwd);
661
+ if (!config) {
662
+ console.error(
663
+ chalk2.red("\u274C herow.config.json n\xE3o encontrado.")
664
+ );
665
+ console.log(chalk2.dim("Execute primeiro: herow init"));
666
+ process.exit(1);
667
+ }
668
+ const spinner = ora2("Detectando projeto...").start();
669
+ let projectConfig;
670
+ try {
671
+ projectConfig = await detectProject(cwd);
672
+ spinner.succeed(
673
+ `Projeto detectado: ${projectConfig.type === "nextjs" ? "Next.js" : "Vite"}`
674
+ );
675
+ } catch (error) {
676
+ spinner.fail("Erro ao detectar projeto");
677
+ throw error;
678
+ }
679
+ let withForm = options.withForm ?? false;
680
+ if (component.variants?.withForm && !options.yes && !options.withForm) {
681
+ const response = await prompts({
682
+ type: "confirm",
683
+ name: "withForm",
684
+ message: "Deseja integrar com React Hook Form?",
685
+ initial: false
686
+ });
687
+ if (response.withForm === void 0) {
688
+ console.log(chalk2.yellow("\nInstala\xE7\xE3o cancelada."));
689
+ process.exit(0);
690
+ }
691
+ withForm = response.withForm;
692
+ }
693
+ const allDeps = [...component.dependencies];
694
+ if (withForm && component.variants?.withForm) {
695
+ allDeps.push(...component.variants.withForm.dependencies);
696
+ }
697
+ console.log(chalk2.dim("\nSer\xE1 instalado:"));
698
+ console.log(chalk2.dim(` Componente: ${componentName}`));
699
+ if (allDeps.length > 0) {
700
+ console.log(chalk2.dim(` Depend\xEAncias: ${allDeps.join(", ")}`));
701
+ }
702
+ if (withForm) {
703
+ console.log(chalk2.dim(` Integra\xE7\xE3o: React Hook Form + Zod`));
704
+ }
705
+ if (!options.yes) {
706
+ const { confirm } = await prompts({
707
+ type: "confirm",
708
+ name: "confirm",
709
+ message: "Continuar?",
710
+ initial: true
711
+ });
712
+ if (!confirm) {
713
+ console.log(chalk2.yellow("\nInstala\xE7\xE3o cancelada."));
714
+ process.exit(0);
715
+ }
716
+ }
717
+ if (component.shadcnDeps && component.shadcnDeps.length > 0) {
718
+ for (const dep of component.shadcnDeps) {
719
+ await installShadcnComponent(dep, projectConfig, cwd);
720
+ }
721
+ }
722
+ if (withForm) {
723
+ await ensureShadcnForm(projectConfig, cwd);
724
+ }
725
+ if (options.deps !== false && allDeps.length > 0) {
726
+ await installDependencies(allDeps, projectConfig, cwd);
727
+ }
728
+ const writeSpinner = ora2("Escrevendo arquivos...").start();
729
+ 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}`));
764
+ }
765
+ } catch (error) {
766
+ writeSpinner.fail("Erro ao escrever arquivos");
767
+ throw error;
768
+ }
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)));
774
+ }
775
+ }
776
+
777
+ // src/commands/init.ts
778
+ import chalk3 from "chalk";
779
+ import ora3 from "ora";
780
+ import prompts2 from "prompts";
781
+ import fs4 from "fs-extra";
782
+ import path4 from "path";
783
+ async function initCommand(options) {
784
+ const cwd = process.cwd();
785
+ console.log(chalk3.cyan("\n\u{1F680} Inicializando Herow Components...\n"));
786
+ const existingConfig = await getConfig(cwd);
787
+ if (existingConfig && !options.yes) {
788
+ const { overwrite } = await prompts2({
789
+ type: "confirm",
790
+ name: "overwrite",
791
+ message: "herow.config.json j\xE1 existe. Deseja sobrescrever?",
792
+ initial: false
793
+ });
794
+ if (!overwrite) {
795
+ console.log(chalk3.yellow("Inicializa\xE7\xE3o cancelada."));
796
+ return;
797
+ }
798
+ }
799
+ const spinner = ora3("Detectando configura\xE7\xE3o do projeto...").start();
800
+ let projectConfig;
801
+ try {
802
+ projectConfig = await detectProject(cwd);
803
+ spinner.succeed("Projeto detectado!");
804
+ } catch (error) {
805
+ spinner.fail("Erro ao detectar projeto");
806
+ console.error(
807
+ chalk3.red(error instanceof Error ? error.message : "Erro desconhecido")
808
+ );
809
+ process.exit(1);
810
+ }
811
+ console.log(
812
+ chalk3.dim(`
813
+ Tipo: ${projectConfig.type === "nextjs" ? "Next.js" : projectConfig.type === "vite" ? "Vite" : "Desconhecido"}
814
+ TypeScript: ${projectConfig.isTypeScript ? "Sim" : "N\xE3o"}
815
+ Diret\xF3rio src: ${projectConfig.srcDir ? "Sim" : "N\xE3o"}
816
+ Tailwind: ${projectConfig.hasTailwind ? "Sim" : "N\xE3o"}
817
+ Package Manager: ${projectConfig.packageManager}
818
+ `)
819
+ );
820
+ let config = getDefaultConfig({
821
+ isTypeScript: projectConfig.isTypeScript,
822
+ srcDir: projectConfig.srcDir,
823
+ hasTailwind: projectConfig.hasTailwind
824
+ });
825
+ if (!options.yes) {
826
+ const responses = await prompts2([
827
+ {
828
+ type: "select",
829
+ name: "style",
830
+ message: "Qual estilo voc\xEA prefere?",
831
+ choices: [
832
+ { title: "Default", value: "default" },
833
+ { title: "New York", value: "new-york" }
834
+ ],
835
+ initial: 0
836
+ },
837
+ {
838
+ type: "text",
839
+ name: "componentsAlias",
840
+ message: "Alias para componentes:",
841
+ initial: config.aliases.components
842
+ },
843
+ {
844
+ type: "text",
845
+ name: "uiAlias",
846
+ message: "Alias para componentes UI:",
847
+ initial: config.aliases.ui
848
+ },
849
+ {
850
+ type: "text",
851
+ name: "registry",
852
+ message: "URL do registry customizado (deixe vazio para usar o padr\xE3o):",
853
+ initial: ""
854
+ }
855
+ ]);
856
+ if (responses.componentsAlias === void 0) {
857
+ console.log(chalk3.yellow("\nInicializa\xE7\xE3o cancelada."));
858
+ process.exit(0);
859
+ }
860
+ config = {
861
+ ...config,
862
+ style: responses.style,
863
+ aliases: {
864
+ ...config.aliases,
865
+ components: responses.componentsAlias,
866
+ ui: responses.uiAlias
867
+ },
868
+ registry: responses.registry || void 0
869
+ };
870
+ }
871
+ const writeSpinner = ora3("Salvando configura\xE7\xE3o...").start();
872
+ try {
873
+ await writeConfig(cwd, config);
874
+ writeSpinner.succeed("Configura\xE7\xE3o salva em herow.config.json");
875
+ } catch (error) {
876
+ writeSpinner.fail("Erro ao salvar configura\xE7\xE3o");
877
+ throw error;
878
+ }
879
+ const uiDir = path4.join(cwd, projectConfig.componentsPath);
880
+ if (!await fs4.pathExists(uiDir)) {
881
+ await fs4.ensureDir(uiDir);
882
+ console.log(chalk3.dim(` Criado: ${projectConfig.componentsPath}`));
883
+ }
884
+ if (projectConfig.actionsPath) {
885
+ const actionsDir = path4.join(cwd, projectConfig.actionsPath);
886
+ if (!await fs4.pathExists(actionsDir)) {
887
+ await fs4.ensureDir(actionsDir);
888
+ console.log(chalk3.dim(` Criado: ${projectConfig.actionsPath}`));
889
+ }
890
+ }
891
+ console.log(chalk3.green("\n\u2705 Herow Components inicializado com sucesso!"));
892
+ console.log(chalk3.dim("\nPr\xF3ximos passos:"));
893
+ console.log(chalk3.cyan(" herow add select-estado"));
894
+ console.log(chalk3.cyan(" herow list\n"));
895
+ }
896
+
897
+ // src/commands/list.ts
898
+ import chalk4 from "chalk";
899
+ async function listCommand() {
900
+ 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
+ );
910
+ }
911
+ if (component.variants?.withForm) {
912
+ console.log(chalk4.yellow(` \u{1F4DD} Suporta integra\xE7\xE3o com React Hook Form`));
913
+ }
914
+ console.log();
915
+ }
916
+ console.log(chalk4.dim("Para adicionar um componente:"));
917
+ console.log(chalk4.cyan(" herow add <nome-do-componente>\n"));
918
+ }
919
+
920
+ // src/index.ts
921
+ var program = new Command();
922
+ program.name("herow").description(
923
+ chalk5.cyan("\u{1F680} Herow Components CLI") + "\n Instale componentes React diretamente do GitHub"
924
+ ).version("0.1.0");
925
+ program.command("init").description("Inicializa a configura\xE7\xE3o do Herow no seu projeto").option("-y, --yes", "Pular prompts e usar configura\xE7\xF5es padr\xE3o").action(initCommand);
926
+ program.command("add").description("Adiciona um componente ao seu projeto").argument("<component>", "Nome do componente (ex: select-estado)").option("-y, --yes", "Pular prompts de confirma\xE7\xE3o").option("--with-form", "Incluir integra\xE7\xE3o com React Hook Form").option("--no-deps", "N\xE3o instalar depend\xEAncias automaticamente").action(addCommand);
927
+ program.command("list").alias("ls").description("Lista todos os componentes dispon\xEDveis").action(listCommand);
928
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,35 @@
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",
15
+ "prepublishOnly": "pnpm build"
16
+ },
17
+ "dependencies" : {
18
+ "chalk" : "^5.3.0",
19
+ "commander" : "^11.1.0",
20
+ "execa" : "^8.0.1",
21
+ "fs-extra" : "^11.2.0",
22
+ "node-fetch": "^3.3.2",
23
+ "ora" : "^7.0.1",
24
+ "prompts" : "^2.4.2",
25
+ "zod" : "^3.22.4"
26
+ },
27
+ "devDependencies": {
28
+ "@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
+ },
34
+ "publishConfig" : {"access": "public"}
35
+ }