@herowcode/cli 0.1.0 → 0.2.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/dist/index.js +192 -485
- 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
|
|
12
|
-
import
|
|
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/
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
280
|
-
|
|
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
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
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
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
|
650
|
-
if (!
|
|
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
|
-
|
|
656
|
-
|
|
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
|
-
|
|
673
|
-
|
|
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 (
|
|
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
|
|
694
|
-
if (withForm &&
|
|
695
|
-
|
|
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 (
|
|
703
|
-
console.log(chalk2.dim(`
|
|
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 (
|
|
718
|
-
for (const dep of
|
|
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
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
);
|
|
762
|
-
await
|
|
763
|
-
|
|
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(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
|
782
|
-
import
|
|
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 =
|
|
880
|
-
if (!await
|
|
881
|
-
await
|
|
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 =
|
|
886
|
-
if (!await
|
|
887
|
-
await
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
|
|
912
|
-
console.log(chalk4.
|
|
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"
|
|
3
|
-
"version"
|
|
4
|
-
"description"
|
|
5
|
-
"type"
|
|
6
|
-
"bin"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
"name": "@herowcode/cli",
|
|
3
|
+
"version": "0.2.0",
|
|
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"
|
|
19
|
-
"commander"
|
|
20
|
-
"execa"
|
|
21
|
-
"fs-extra"
|
|
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"
|
|
24
|
-
"prompts"
|
|
25
|
-
"zod"
|
|
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"
|
|
30
|
-
"@types/prompts"
|
|
31
|
-
"tsup"
|
|
32
|
-
"typescript"
|
|
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"
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
35
41
|
}
|