expo-bbase 1.1.1 → 1.3.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 +1425 -267
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
|
+
addModule: () => addModule,
|
|
33
34
|
createProject: () => createProject,
|
|
34
|
-
run: () => run
|
|
35
|
+
run: () => run,
|
|
36
|
+
upgradeProject: () => upgradeProject
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(src_exports);
|
|
37
39
|
var import_chalk = __toESM(require("chalk"));
|
|
@@ -47,6 +49,16 @@ function registerCreateCommand(program) {
|
|
|
47
49
|
await createProject(projectName);
|
|
48
50
|
});
|
|
49
51
|
}
|
|
52
|
+
function registerUpgradeCommand(program) {
|
|
53
|
+
program.command("upgrade").description("Upgrade an existing expo-bbase project to the latest module versions").option("--dir <path>", "Project directory (default: current directory)", process.cwd()).action(async (options) => {
|
|
54
|
+
await upgradeProject(options.dir);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function registerAddCommand(program) {
|
|
58
|
+
program.command("add [modules...]").description("Add one or more modules to an existing expo-bbase project").option("--dir <path>", "Project directory (default: current directory)", process.cwd()).action(async (moduleIds, options) => {
|
|
59
|
+
await addModule(moduleIds, options.dir);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
50
62
|
|
|
51
63
|
// src/utils/lines.ts
|
|
52
64
|
function lines(...args) {
|
|
@@ -2508,267 +2520,594 @@ var flashlist_default = flashlistModule;
|
|
|
2508
2520
|
var uiReusablesModule = {
|
|
2509
2521
|
id: "ui-reusables",
|
|
2510
2522
|
name: "reactnative.reusables UI",
|
|
2511
|
-
description: "\u9884\u7F6E UI \u7EC4\u4EF6",
|
|
2523
|
+
description: "\u9884\u7F6E UI \u7EC4\u4EF6 (Button, AlertDialog, Card, Input, Label, Text, Separator)",
|
|
2512
2524
|
defaultChecked: false,
|
|
2513
2525
|
dependencies: {
|
|
2514
|
-
"
|
|
2515
|
-
"
|
|
2526
|
+
"class-variance-authority": "^0.7.1",
|
|
2527
|
+
"clsx": "^2.1.1",
|
|
2528
|
+
"tailwind-merge": "^2.6.0",
|
|
2516
2529
|
"@rn-primitives/slot": "^1.1.0",
|
|
2517
|
-
"@rn-primitives/types": "^1.1.0"
|
|
2530
|
+
"@rn-primitives/types": "^1.1.0",
|
|
2531
|
+
"@rn-primitives/label": "^1.1.0",
|
|
2532
|
+
"@rn-primitives/separator": "^1.1.0",
|
|
2533
|
+
"@rn-primitives/alert-dialog": "^1.1.0",
|
|
2534
|
+
"react-native-svg": "^15.8.0"
|
|
2518
2535
|
},
|
|
2519
2536
|
devDependencies: {},
|
|
2520
2537
|
files: [
|
|
2538
|
+
// ─── lib/utils.ts ────────────────────────────────────────────────────
|
|
2521
2539
|
{
|
|
2522
|
-
path: "src/
|
|
2540
|
+
path: "src/lib/utils.ts",
|
|
2523
2541
|
content: lines(
|
|
2524
|
-
'import
|
|
2525
|
-
'import {
|
|
2526
|
-
'import { Slot } from "@rn-primitives/slot";',
|
|
2527
|
-
"",
|
|
2528
|
-
'type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";',
|
|
2529
|
-
'type ButtonSize = "default" | "sm" | "lg" | "icon";',
|
|
2542
|
+
'import { type ClassValue, clsx } from "clsx";',
|
|
2543
|
+
'import { twMerge } from "tailwind-merge";',
|
|
2530
2544
|
"",
|
|
2531
|
-
"
|
|
2532
|
-
"
|
|
2533
|
-
" size?: ButtonSize;",
|
|
2534
|
-
" asChild?: boolean;",
|
|
2535
|
-
" style?: StyleProp<ViewStyle>;",
|
|
2536
|
-
" textStyle?: StyleProp<TextStyle>;",
|
|
2537
|
-
" children: React.ReactNode;",
|
|
2545
|
+
"export function cn(...inputs: ClassValue[]) {",
|
|
2546
|
+
" return twMerge(clsx(inputs));",
|
|
2538
2547
|
"}",
|
|
2548
|
+
""
|
|
2549
|
+
)
|
|
2550
|
+
},
|
|
2551
|
+
// ─── components/ui/text.tsx ──────────────────────────────────────────
|
|
2552
|
+
{
|
|
2553
|
+
path: "src/components/ui/text.tsx",
|
|
2554
|
+
content: lines(
|
|
2555
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2556
|
+
'import { Slot } from "@rn-primitives/slot";',
|
|
2557
|
+
'import { cva, type VariantProps } from "class-variance-authority";',
|
|
2558
|
+
'import * as React from "react";',
|
|
2559
|
+
'import { Platform, Text as RNText, type Role } from "react-native";',
|
|
2560
|
+
"",
|
|
2561
|
+
"const textVariants = cva(",
|
|
2562
|
+
" cn(",
|
|
2563
|
+
' "text-foreground text-base",',
|
|
2564
|
+
" Platform.select({",
|
|
2565
|
+
' web: "select-text",',
|
|
2566
|
+
" })",
|
|
2567
|
+
" ),",
|
|
2568
|
+
" {",
|
|
2569
|
+
" variants: {",
|
|
2570
|
+
" variant: {",
|
|
2571
|
+
' default: "",',
|
|
2572
|
+
" h1: cn(",
|
|
2573
|
+
' "text-center text-4xl font-extrabold tracking-tight",',
|
|
2574
|
+
' Platform.select({ web: "scroll-m-20 text-balance" })',
|
|
2575
|
+
" ),",
|
|
2576
|
+
" h2: cn(",
|
|
2577
|
+
' "border-border border-b pb-2 text-3xl font-semibold tracking-tight",',
|
|
2578
|
+
' Platform.select({ web: "scroll-m-20 first:mt-0" })',
|
|
2579
|
+
" ),",
|
|
2580
|
+
" h3: cn(",
|
|
2581
|
+
' "text-2xl font-semibold tracking-tight",',
|
|
2582
|
+
' Platform.select({ web: "scroll-m-20" })',
|
|
2583
|
+
" ),",
|
|
2584
|
+
" h4: cn(",
|
|
2585
|
+
' "text-xl font-semibold tracking-tight",',
|
|
2586
|
+
' Platform.select({ web: "scroll-m-20" })',
|
|
2587
|
+
" ),",
|
|
2588
|
+
' p: "mt-3 leading-7 sm:mt-6",',
|
|
2589
|
+
' blockquote: "mt-4 border-l-2 pl-3 italic sm:mt-6 sm:pl-6",',
|
|
2590
|
+
" code: cn(",
|
|
2591
|
+
' "bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"',
|
|
2592
|
+
" ),",
|
|
2593
|
+
' lead: "text-muted-foreground text-xl",',
|
|
2594
|
+
' large: "text-lg font-semibold",',
|
|
2595
|
+
' small: "text-sm font-medium leading-none",',
|
|
2596
|
+
' muted: "text-muted-foreground text-sm",',
|
|
2597
|
+
" },",
|
|
2598
|
+
" },",
|
|
2599
|
+
" defaultVariants: {",
|
|
2600
|
+
' variant: "default",',
|
|
2601
|
+
" },",
|
|
2602
|
+
" }",
|
|
2603
|
+
");",
|
|
2539
2604
|
"",
|
|
2540
|
-
"
|
|
2541
|
-
' default: { backgroundColor: "#0f172a" },',
|
|
2542
|
-
' destructive: { backgroundColor: "#ef4444" },',
|
|
2543
|
-
' outline: { backgroundColor: "transparent", borderWidth: 1, borderColor: "#d4d4d8" },',
|
|
2544
|
-
' secondary: { backgroundColor: "#f4f4f5" },',
|
|
2545
|
-
' ghost: { backgroundColor: "transparent" },',
|
|
2546
|
-
' link: { backgroundColor: "transparent" },',
|
|
2547
|
-
"};",
|
|
2605
|
+
"type TextVariantProps = VariantProps<typeof textVariants>;",
|
|
2548
2606
|
"",
|
|
2549
|
-
|
|
2550
|
-
' default: { color: "#fafafa" },',
|
|
2551
|
-
' destructive: { color: "#fafafa" },',
|
|
2552
|
-
' outline: { color: "#18181b" },',
|
|
2553
|
-
' secondary: { color: "#18181b" },',
|
|
2554
|
-
' ghost: { color: "#18181b" },',
|
|
2555
|
-
' link: { color: "#2563eb", textDecorationLine: "underline" },',
|
|
2556
|
-
"};",
|
|
2607
|
+
'type TextVariant = NonNullable<TextVariantProps["variant"]>;',
|
|
2557
2608
|
"",
|
|
2558
|
-
"const
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2609
|
+
"const ROLE: Partial<Record<TextVariant, Role>> = {",
|
|
2610
|
+
' h1: "heading",',
|
|
2611
|
+
' h2: "heading",',
|
|
2612
|
+
' h3: "heading",',
|
|
2613
|
+
' h4: "heading",',
|
|
2614
|
+
' blockquote: Platform.select({ web: "blockquote" as Role }),',
|
|
2615
|
+
' code: Platform.select({ web: "code" as Role }),',
|
|
2563
2616
|
"};",
|
|
2564
2617
|
"",
|
|
2565
|
-
"const
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2618
|
+
"const ARIA_LEVEL: Partial<Record<TextVariant, string>> = {",
|
|
2619
|
+
' h1: "1",',
|
|
2620
|
+
' h2: "2",',
|
|
2621
|
+
' h3: "3",',
|
|
2622
|
+
' h4: "4",',
|
|
2570
2623
|
"};",
|
|
2571
2624
|
"",
|
|
2572
|
-
"
|
|
2573
|
-
|
|
2574
|
-
|
|
2625
|
+
"const TextClassContext = React.createContext<string | undefined>(undefined);",
|
|
2626
|
+
"",
|
|
2627
|
+
"function Text({",
|
|
2628
|
+
" className,",
|
|
2575
2629
|
" asChild = false,",
|
|
2576
|
-
|
|
2577
|
-
"
|
|
2578
|
-
"
|
|
2579
|
-
"
|
|
2580
|
-
"}
|
|
2581
|
-
" const
|
|
2582
|
-
"
|
|
2583
|
-
"
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
"
|
|
2588
|
-
"
|
|
2589
|
-
"
|
|
2590
|
-
"
|
|
2591
|
-
"
|
|
2630
|
+
' variant = "default",',
|
|
2631
|
+
" ...props",
|
|
2632
|
+
"}: React.ComponentProps<typeof RNText> &",
|
|
2633
|
+
" React.RefAttributes<typeof RNText> &",
|
|
2634
|
+
" TextVariantProps & { asChild?: boolean }) {",
|
|
2635
|
+
" const textClass = React.useContext(TextClassContext);",
|
|
2636
|
+
" const Component = asChild ? Slot : RNText;",
|
|
2637
|
+
" return (",
|
|
2638
|
+
" <Component",
|
|
2639
|
+
" className={cn(textVariants({ variant }), textClass, className)}",
|
|
2640
|
+
" role={variant ? ROLE[variant] : undefined}",
|
|
2641
|
+
" aria-level={variant ? ARIA_LEVEL[variant] : undefined}",
|
|
2642
|
+
" {...props}",
|
|
2643
|
+
" />",
|
|
2644
|
+
" );",
|
|
2645
|
+
"}",
|
|
2592
2646
|
"",
|
|
2593
|
-
"
|
|
2594
|
-
"
|
|
2595
|
-
|
|
2596
|
-
|
|
2647
|
+
"export { Text, TextClassContext };",
|
|
2648
|
+
""
|
|
2649
|
+
)
|
|
2650
|
+
},
|
|
2651
|
+
// ─── components/ui/button.tsx ────────────────────────────────────────
|
|
2652
|
+
{
|
|
2653
|
+
path: "src/components/ui/button.tsx",
|
|
2654
|
+
content: lines(
|
|
2655
|
+
'import { TextClassContext } from "@/src/components/ui/text";',
|
|
2656
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2657
|
+
'import { cva, type VariantProps } from "class-variance-authority";',
|
|
2658
|
+
'import { Platform, Pressable } from "react-native";',
|
|
2659
|
+
"",
|
|
2660
|
+
"const buttonVariants = cva(",
|
|
2661
|
+
" cn(",
|
|
2662
|
+
' "group shrink-0 flex-row items-center justify-center gap-2 rounded-md shadow-none",',
|
|
2663
|
+
" Platform.select({",
|
|
2664
|
+
` web: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",`,
|
|
2665
|
+
" })",
|
|
2666
|
+
" ),",
|
|
2667
|
+
" {",
|
|
2668
|
+
" variants: {",
|
|
2669
|
+
" variant: {",
|
|
2670
|
+
" default: cn(",
|
|
2671
|
+
' "bg-primary active:bg-primary/90 shadow-sm shadow-black/5",',
|
|
2672
|
+
' Platform.select({ web: "hover:bg-primary/90" })',
|
|
2673
|
+
" ),",
|
|
2674
|
+
" destructive: cn(",
|
|
2675
|
+
' "bg-destructive active:bg-destructive/90 dark:bg-destructive/60 shadow-sm shadow-black/5",',
|
|
2676
|
+
" Platform.select({",
|
|
2677
|
+
' web: "hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",',
|
|
2678
|
+
" })",
|
|
2679
|
+
" ),",
|
|
2680
|
+
" outline: cn(",
|
|
2681
|
+
' "border-border bg-background active:bg-accent dark:bg-input/30 dark:border-input dark:active:bg-input/50 border shadow-sm shadow-black/5",',
|
|
2682
|
+
" Platform.select({",
|
|
2683
|
+
' web: "hover:bg-accent dark:hover:bg-input/50",',
|
|
2684
|
+
" })",
|
|
2685
|
+
" ),",
|
|
2686
|
+
" secondary: cn(",
|
|
2687
|
+
' "bg-secondary active:bg-secondary/80 shadow-sm shadow-black/5",',
|
|
2688
|
+
' Platform.select({ web: "hover:bg-secondary/80" })',
|
|
2689
|
+
" ),",
|
|
2690
|
+
" ghost: cn(",
|
|
2691
|
+
' "active:bg-accent dark:active:bg-accent/50",',
|
|
2692
|
+
' Platform.select({ web: "hover:bg-accent dark:hover:bg-accent/50" })',
|
|
2693
|
+
" ),",
|
|
2694
|
+
' link: "",',
|
|
2695
|
+
" },",
|
|
2696
|
+
" size: {",
|
|
2697
|
+
" default: cn(",
|
|
2698
|
+
' "h-10 px-4 py-2 sm:h-9",',
|
|
2699
|
+
' Platform.select({ web: "has-[>svg]:px-3" })',
|
|
2700
|
+
" ),",
|
|
2701
|
+
" sm: cn(",
|
|
2702
|
+
' "h-9 gap-1.5 rounded-md px-3 sm:h-8",',
|
|
2703
|
+
' Platform.select({ web: "has-[>svg]:px-2.5" })',
|
|
2704
|
+
" ),",
|
|
2705
|
+
" lg: cn(",
|
|
2706
|
+
' "h-11 rounded-md px-6 sm:h-10",',
|
|
2707
|
+
' Platform.select({ web: "has-[>svg]:px-4" })',
|
|
2708
|
+
" ),",
|
|
2709
|
+
' icon: "h-10 w-10 sm:h-9 sm:w-9",',
|
|
2710
|
+
" },",
|
|
2711
|
+
" },",
|
|
2712
|
+
" defaultVariants: {",
|
|
2713
|
+
' variant: "default",',
|
|
2714
|
+
' size: "default",',
|
|
2597
2715
|
" },",
|
|
2598
|
-
"
|
|
2599
|
-
"
|
|
2600
|
-
" textStyle,",
|
|
2601
|
-
" ];",
|
|
2716
|
+
" }",
|
|
2717
|
+
");",
|
|
2602
2718
|
"",
|
|
2603
|
-
"
|
|
2604
|
-
"
|
|
2605
|
-
"
|
|
2606
|
-
"
|
|
2607
|
-
"
|
|
2608
|
-
"
|
|
2719
|
+
"const buttonTextVariants = cva(",
|
|
2720
|
+
" cn(",
|
|
2721
|
+
' "text-foreground text-sm font-medium",',
|
|
2722
|
+
' Platform.select({ web: "pointer-events-none transition-colors" })',
|
|
2723
|
+
" ),",
|
|
2724
|
+
" {",
|
|
2725
|
+
" variants: {",
|
|
2726
|
+
" variant: {",
|
|
2727
|
+
' default: "text-primary-foreground",',
|
|
2728
|
+
' destructive: "text-white",',
|
|
2729
|
+
" outline: cn(",
|
|
2730
|
+
' "group-active:text-accent-foreground",',
|
|
2731
|
+
' Platform.select({ web: "group-hover:text-accent-foreground" })',
|
|
2732
|
+
" ),",
|
|
2733
|
+
' secondary: "text-secondary-foreground",',
|
|
2734
|
+
' ghost: "group-active:text-accent-foreground",',
|
|
2735
|
+
" link: cn(",
|
|
2736
|
+
' "text-primary group-active:underline",',
|
|
2737
|
+
' Platform.select({ web: "underline-offset-4 hover:underline group-hover:underline" })',
|
|
2738
|
+
" ),",
|
|
2739
|
+
" },",
|
|
2740
|
+
" size: {",
|
|
2741
|
+
' default: "",',
|
|
2742
|
+
' sm: "",',
|
|
2743
|
+
' lg: "",',
|
|
2744
|
+
' icon: "",',
|
|
2745
|
+
" },",
|
|
2746
|
+
" },",
|
|
2747
|
+
" defaultVariants: {",
|
|
2748
|
+
' variant: "default",',
|
|
2749
|
+
' size: "default",',
|
|
2750
|
+
" },",
|
|
2609
2751
|
" }",
|
|
2752
|
+
");",
|
|
2753
|
+
"",
|
|
2754
|
+
"type ButtonProps = React.ComponentProps<typeof Pressable> &",
|
|
2755
|
+
" React.RefAttributes<typeof Pressable> &",
|
|
2756
|
+
" VariantProps<typeof buttonVariants>;",
|
|
2610
2757
|
"",
|
|
2758
|
+
"function Button({ className, variant, size, ...props }: ButtonProps) {",
|
|
2611
2759
|
" return (",
|
|
2612
|
-
" <
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
"
|
|
2616
|
-
"
|
|
2617
|
-
"
|
|
2618
|
-
" </
|
|
2760
|
+
" <TextClassContext.Provider value={buttonTextVariants({ variant, size })}>",
|
|
2761
|
+
" <Pressable",
|
|
2762
|
+
' className={cn(props.disabled && "opacity-50", buttonVariants({ variant, size }), className)}',
|
|
2763
|
+
' role="button"',
|
|
2764
|
+
" {...props}",
|
|
2765
|
+
" />",
|
|
2766
|
+
" </TextClassContext.Provider>",
|
|
2619
2767
|
" );",
|
|
2620
2768
|
"}",
|
|
2621
2769
|
"",
|
|
2622
|
-
"export
|
|
2770
|
+
"export { Button, buttonTextVariants, buttonVariants };",
|
|
2771
|
+
"export type { ButtonProps };",
|
|
2772
|
+
""
|
|
2623
2773
|
)
|
|
2624
2774
|
},
|
|
2775
|
+
// ─── components/ui/input.tsx ──────────────────────────────────────────
|
|
2625
2776
|
{
|
|
2626
2777
|
path: "src/components/ui/input.tsx",
|
|
2627
2778
|
content: lines(
|
|
2628
|
-
'import
|
|
2629
|
-
|
|
2630
|
-
" TextInput,",
|
|
2631
|
-
" type TextInputProps,",
|
|
2632
|
-
" type StyleProp,",
|
|
2633
|
-
" type ViewStyle,",
|
|
2634
|
-
" type TextStyle,",
|
|
2635
|
-
" View,",
|
|
2636
|
-
" Text,",
|
|
2637
|
-
'} from "react-native";',
|
|
2638
|
-
"",
|
|
2639
|
-
"interface InputProps extends TextInputProps {",
|
|
2640
|
-
" label?: string;",
|
|
2641
|
-
" error?: string;",
|
|
2642
|
-
" containerStyle?: StyleProp<ViewStyle>;",
|
|
2643
|
-
" inputStyle?: StyleProp<TextStyle>;",
|
|
2644
|
-
"}",
|
|
2645
|
-
"",
|
|
2646
|
-
"export const Input = forwardRef<TextInput, InputProps>(",
|
|
2647
|
-
" ({ label, error, containerStyle, inputStyle, ...rest }, ref) => {",
|
|
2648
|
-
" return (",
|
|
2649
|
-
" <View style={containerStyle}>",
|
|
2650
|
-
" {label && <Text style={styles.label}>{label}</Text>}",
|
|
2651
|
-
" <TextInput",
|
|
2652
|
-
" ref={ref}",
|
|
2653
|
-
" style={[",
|
|
2654
|
-
" styles.input,",
|
|
2655
|
-
" error && styles.inputError,",
|
|
2656
|
-
" inputStyle,",
|
|
2657
|
-
" ]}",
|
|
2658
|
-
' placeholderTextColor="#a1a1aa"',
|
|
2659
|
-
" {...rest}",
|
|
2660
|
-
" />",
|
|
2661
|
-
" {error && <Text style={styles.errorText}>{error}</Text>}",
|
|
2662
|
-
" </View>",
|
|
2663
|
-
" );",
|
|
2664
|
-
" }",
|
|
2665
|
-
");",
|
|
2779
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2780
|
+
'import { Platform, TextInput } from "react-native";',
|
|
2666
2781
|
"",
|
|
2667
|
-
|
|
2782
|
+
"function Input({",
|
|
2783
|
+
" className,",
|
|
2784
|
+
" ...props",
|
|
2785
|
+
"}: React.ComponentProps<typeof TextInput> & React.RefAttributes<TextInput>) {",
|
|
2786
|
+
" return (",
|
|
2787
|
+
" <TextInput",
|
|
2788
|
+
" className={cn(",
|
|
2789
|
+
' "dark:bg-input/30 border-input bg-background text-foreground flex h-10 w-full min-w-0 flex-row items-center rounded-md border px-3 py-1 text-base leading-5 shadow-sm shadow-black/5 sm:h-9",',
|
|
2790
|
+
" props.editable === false &&",
|
|
2791
|
+
" cn(",
|
|
2792
|
+
' "opacity-50",',
|
|
2793
|
+
' Platform.select({ web: "disabled:pointer-events-none disabled:cursor-not-allowed" })',
|
|
2794
|
+
" ),",
|
|
2795
|
+
" Platform.select({",
|
|
2796
|
+
" web: cn(",
|
|
2797
|
+
' "placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground outline-none transition-[color,box-shadow] md:text-sm",',
|
|
2798
|
+
' "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",',
|
|
2799
|
+
' "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"',
|
|
2800
|
+
" ),",
|
|
2801
|
+
' native: "placeholder:text-muted-foreground/50",',
|
|
2802
|
+
" }),",
|
|
2803
|
+
" className",
|
|
2804
|
+
" )}",
|
|
2805
|
+
" {...props}",
|
|
2806
|
+
" />",
|
|
2807
|
+
" );",
|
|
2808
|
+
"}",
|
|
2668
2809
|
"",
|
|
2669
|
-
"
|
|
2670
|
-
"
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
'
|
|
2680
|
-
"
|
|
2681
|
-
"
|
|
2682
|
-
"
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
"
|
|
2686
|
-
"
|
|
2687
|
-
|
|
2688
|
-
"
|
|
2689
|
-
"
|
|
2690
|
-
"
|
|
2691
|
-
|
|
2692
|
-
"
|
|
2693
|
-
"
|
|
2694
|
-
"
|
|
2810
|
+
"export { Input };",
|
|
2811
|
+
""
|
|
2812
|
+
)
|
|
2813
|
+
},
|
|
2814
|
+
// ─── components/ui/label.tsx ─────────────────────────────────────────
|
|
2815
|
+
{
|
|
2816
|
+
path: "src/components/ui/label.tsx",
|
|
2817
|
+
content: lines(
|
|
2818
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2819
|
+
'import * as LabelPrimitive from "@rn-primitives/label";',
|
|
2820
|
+
'import { Platform } from "react-native";',
|
|
2821
|
+
"",
|
|
2822
|
+
"function Label({",
|
|
2823
|
+
" className,",
|
|
2824
|
+
" onPress,",
|
|
2825
|
+
" onLongPress,",
|
|
2826
|
+
" onPressIn,",
|
|
2827
|
+
" onPressOut,",
|
|
2828
|
+
" disabled,",
|
|
2829
|
+
" ...props",
|
|
2830
|
+
"}: React.ComponentProps<typeof LabelPrimitive.Text>) {",
|
|
2831
|
+
" return (",
|
|
2832
|
+
" <LabelPrimitive.Root",
|
|
2833
|
+
" className={cn(",
|
|
2834
|
+
' "flex select-none flex-row items-center gap-2",',
|
|
2835
|
+
" Platform.select({",
|
|
2836
|
+
' web: "cursor-default leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",',
|
|
2837
|
+
" }),",
|
|
2838
|
+
' disabled && "opacity-50"',
|
|
2839
|
+
" )}",
|
|
2840
|
+
" onPress={onPress}",
|
|
2841
|
+
" onLongPress={onLongPress}",
|
|
2842
|
+
" onPressIn={onPressIn}",
|
|
2843
|
+
" onPressOut={onPressOut}",
|
|
2844
|
+
" disabled={disabled}",
|
|
2845
|
+
" >",
|
|
2846
|
+
" <LabelPrimitive.Text",
|
|
2847
|
+
" className={cn(",
|
|
2848
|
+
' "text-foreground text-sm font-medium",',
|
|
2849
|
+
' Platform.select({ web: "leading-none" }),',
|
|
2850
|
+
" className",
|
|
2851
|
+
" )}",
|
|
2852
|
+
" {...props}",
|
|
2853
|
+
" />",
|
|
2854
|
+
" </LabelPrimitive.Root>",
|
|
2855
|
+
" );",
|
|
2856
|
+
"}",
|
|
2695
2857
|
"",
|
|
2696
|
-
"export
|
|
2858
|
+
"export { Label };",
|
|
2859
|
+
""
|
|
2697
2860
|
)
|
|
2698
2861
|
},
|
|
2862
|
+
// ─── components/ui/card.tsx ──────────────────────────────────────────
|
|
2699
2863
|
{
|
|
2700
2864
|
path: "src/components/ui/card.tsx",
|
|
2701
2865
|
content: lines(
|
|
2702
|
-
'import
|
|
2703
|
-
'import {
|
|
2866
|
+
'import { Text, TextClassContext } from "@/src/components/ui/text";',
|
|
2867
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2868
|
+
'import { View } from "react-native";',
|
|
2869
|
+
"",
|
|
2870
|
+
"function Card({",
|
|
2871
|
+
" className,",
|
|
2872
|
+
" ...props",
|
|
2873
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2874
|
+
" return (",
|
|
2875
|
+
' <TextClassContext.Provider value="text-card-foreground">',
|
|
2876
|
+
" <View",
|
|
2877
|
+
" className={cn(",
|
|
2878
|
+
' "bg-card border-border flex flex-col gap-6 rounded-xl border py-6 shadow-sm shadow-black/5",',
|
|
2879
|
+
" className",
|
|
2880
|
+
" )}",
|
|
2881
|
+
" {...props}",
|
|
2882
|
+
" />",
|
|
2883
|
+
" </TextClassContext.Provider>",
|
|
2884
|
+
" );",
|
|
2885
|
+
"}",
|
|
2704
2886
|
"",
|
|
2705
|
-
"
|
|
2706
|
-
"
|
|
2707
|
-
"
|
|
2708
|
-
"
|
|
2709
|
-
"
|
|
2710
|
-
"
|
|
2711
|
-
"
|
|
2712
|
-
' padding?: "none" | "sm" | "md" | "lg";',
|
|
2887
|
+
"function CardHeader({",
|
|
2888
|
+
" className,",
|
|
2889
|
+
" ...props",
|
|
2890
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2891
|
+
" return (",
|
|
2892
|
+
' <View className={cn("flex flex-col gap-1.5 px-6", className)} {...props} />',
|
|
2893
|
+
" );",
|
|
2713
2894
|
"}",
|
|
2714
2895
|
"",
|
|
2715
|
-
|
|
2716
|
-
"
|
|
2717
|
-
"
|
|
2718
|
-
"
|
|
2719
|
-
"
|
|
2720
|
-
"
|
|
2896
|
+
"function CardTitle({",
|
|
2897
|
+
" className,",
|
|
2898
|
+
" ref,",
|
|
2899
|
+
" ...props",
|
|
2900
|
+
"}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {",
|
|
2901
|
+
" return (",
|
|
2902
|
+
" <Text",
|
|
2903
|
+
" ref={ref}",
|
|
2904
|
+
' role="heading"',
|
|
2905
|
+
" aria-level={3}",
|
|
2906
|
+
' className={cn("font-semibold leading-none", className)}',
|
|
2907
|
+
" {...props}",
|
|
2908
|
+
" />",
|
|
2909
|
+
" );",
|
|
2910
|
+
"}",
|
|
2721
2911
|
"",
|
|
2722
|
-
"
|
|
2723
|
-
"
|
|
2724
|
-
"
|
|
2725
|
-
"
|
|
2726
|
-
" style,",
|
|
2727
|
-
" titleStyle,",
|
|
2728
|
-
" descriptionStyle,",
|
|
2729
|
-
' padding = "md",',
|
|
2730
|
-
"}: CardProps) {",
|
|
2912
|
+
"function CardDescription({",
|
|
2913
|
+
" className,",
|
|
2914
|
+
" ...props",
|
|
2915
|
+
"}: React.ComponentProps<typeof Text> & React.RefAttributes<typeof Text>) {",
|
|
2731
2916
|
" return (",
|
|
2732
|
-
|
|
2733
|
-
"
|
|
2734
|
-
"
|
|
2735
|
-
|
|
2736
|
-
"
|
|
2737
|
-
"
|
|
2738
|
-
|
|
2739
|
-
"
|
|
2740
|
-
"
|
|
2741
|
-
"
|
|
2742
|
-
"
|
|
2743
|
-
"
|
|
2744
|
-
"
|
|
2745
|
-
"
|
|
2917
|
+
' <Text className={cn("text-muted-foreground text-sm", className)} {...props} />',
|
|
2918
|
+
" );",
|
|
2919
|
+
"}",
|
|
2920
|
+
"",
|
|
2921
|
+
"function CardContent({",
|
|
2922
|
+
" className,",
|
|
2923
|
+
" ...props",
|
|
2924
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2925
|
+
" return (",
|
|
2926
|
+
' <View className={cn("px-6", className)} {...props} />',
|
|
2927
|
+
" );",
|
|
2928
|
+
"}",
|
|
2929
|
+
"",
|
|
2930
|
+
"function CardFooter({",
|
|
2931
|
+
" className,",
|
|
2932
|
+
" ...props",
|
|
2933
|
+
"}: React.ComponentProps<typeof View> & React.RefAttributes<View>) {",
|
|
2934
|
+
" return (",
|
|
2935
|
+
' <View className={cn("flex flex-row items-center px-6", className)} {...props} />',
|
|
2936
|
+
" );",
|
|
2937
|
+
"}",
|
|
2938
|
+
"",
|
|
2939
|
+
"export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };",
|
|
2940
|
+
""
|
|
2941
|
+
)
|
|
2942
|
+
},
|
|
2943
|
+
// ─── components/ui/separator.tsx ──────────────────────────────────────
|
|
2944
|
+
{
|
|
2945
|
+
path: "src/components/ui/separator.tsx",
|
|
2946
|
+
content: lines(
|
|
2947
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2948
|
+
'import * as SeparatorPrimitive from "@rn-primitives/separator";',
|
|
2949
|
+
"",
|
|
2950
|
+
"function Separator({",
|
|
2951
|
+
" className,",
|
|
2952
|
+
' orientation = "horizontal",',
|
|
2953
|
+
" decorative = true,",
|
|
2954
|
+
" ...props",
|
|
2955
|
+
"}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {",
|
|
2956
|
+
" return (",
|
|
2957
|
+
" <SeparatorPrimitive.Root",
|
|
2958
|
+
" decorative={decorative}",
|
|
2959
|
+
" orientation={orientation}",
|
|
2960
|
+
" className={cn(",
|
|
2961
|
+
' "bg-border shrink-0",',
|
|
2962
|
+
' orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",',
|
|
2963
|
+
" className",
|
|
2746
2964
|
" )}",
|
|
2747
|
-
" {
|
|
2748
|
-
"
|
|
2749
|
-
"
|
|
2750
|
-
"
|
|
2965
|
+
" {...props}",
|
|
2966
|
+
" />",
|
|
2967
|
+
" );",
|
|
2968
|
+
"}",
|
|
2969
|
+
"",
|
|
2970
|
+
"export { Separator };",
|
|
2971
|
+
""
|
|
2972
|
+
)
|
|
2973
|
+
},
|
|
2974
|
+
// ─── components/ui/alert-dialog.tsx ──────────────────────────────────
|
|
2975
|
+
{
|
|
2976
|
+
path: "src/components/ui/alert-dialog.tsx",
|
|
2977
|
+
content: lines(
|
|
2978
|
+
'import { buttonTextVariants, buttonVariants } from "@/src/components/ui/button";',
|
|
2979
|
+
'import { TextClassContext } from "@/src/components/ui/text";',
|
|
2980
|
+
'import { cn } from "@/src/lib/utils";',
|
|
2981
|
+
'import * as AlertDialogPrimitive from "@rn-primitives/alert-dialog";',
|
|
2982
|
+
'import * as React from "react";',
|
|
2983
|
+
'import { Platform, View, type ViewProps } from "react-native";',
|
|
2984
|
+
"",
|
|
2985
|
+
"const AlertDialog = AlertDialogPrimitive.Root;",
|
|
2986
|
+
"",
|
|
2987
|
+
"const AlertDialogTrigger = AlertDialogPrimitive.Trigger;",
|
|
2988
|
+
"",
|
|
2989
|
+
"const AlertDialogPortal = AlertDialogPrimitive.Portal;",
|
|
2990
|
+
"",
|
|
2991
|
+
"function AlertDialogOverlay({",
|
|
2992
|
+
" className,",
|
|
2993
|
+
" ...props",
|
|
2994
|
+
'}: Omit<React.ComponentProps<typeof AlertDialogPrimitive.Overlay>, "asChild"> & {',
|
|
2995
|
+
" children?: React.ReactNode;",
|
|
2996
|
+
"}) {",
|
|
2997
|
+
" return (",
|
|
2998
|
+
" <AlertDialogPrimitive.Overlay",
|
|
2999
|
+
" className={cn(",
|
|
3000
|
+
' "absolute bottom-0 left-0 right-0 top-0 z-50 flex items-center justify-center bg-black/50 p-2",',
|
|
3001
|
+
" Platform.select({",
|
|
3002
|
+
' web: "animate-in fade-in-0 fixed",',
|
|
3003
|
+
" }),",
|
|
3004
|
+
" className",
|
|
2751
3005
|
" )}",
|
|
2752
|
-
" {
|
|
2753
|
-
"
|
|
3006
|
+
" {...props}",
|
|
3007
|
+
" />",
|
|
2754
3008
|
" );",
|
|
2755
3009
|
"}",
|
|
2756
3010
|
"",
|
|
2757
|
-
"
|
|
2758
|
-
"
|
|
2759
|
-
"
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
"
|
|
2763
|
-
"
|
|
2764
|
-
"
|
|
2765
|
-
"
|
|
2766
|
-
|
|
2767
|
-
"
|
|
2768
|
-
"
|
|
2769
|
-
"
|
|
3011
|
+
"function AlertDialogContent({",
|
|
3012
|
+
" className,",
|
|
3013
|
+
" portalHost,",
|
|
3014
|
+
" ...props",
|
|
3015
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {",
|
|
3016
|
+
" portalHost?: string;",
|
|
3017
|
+
"}) {",
|
|
3018
|
+
" return (",
|
|
3019
|
+
" <AlertDialogPortal hostName={portalHost}>",
|
|
3020
|
+
" <AlertDialogOverlay>",
|
|
3021
|
+
" <AlertDialogPrimitive.Content",
|
|
3022
|
+
" className={cn(",
|
|
3023
|
+
' "bg-background border-border z-50 flex w-full max-w-[calc(100%-2rem)] flex-col gap-4 rounded-lg border p-6 shadow-lg shadow-black/5 sm:max-w-lg",',
|
|
3024
|
+
" Platform.select({",
|
|
3025
|
+
' web: "animate-in fade-in-0 zoom-in-95 duration-200",',
|
|
3026
|
+
" }),",
|
|
3027
|
+
" className",
|
|
3028
|
+
" )}",
|
|
3029
|
+
" {...props}",
|
|
3030
|
+
" />",
|
|
3031
|
+
" </AlertDialogOverlay>",
|
|
3032
|
+
" </AlertDialogPortal>",
|
|
3033
|
+
" );",
|
|
3034
|
+
"}",
|
|
3035
|
+
"",
|
|
3036
|
+
"function AlertDialogHeader({ className, ...props }: ViewProps) {",
|
|
3037
|
+
" return (",
|
|
3038
|
+
' <TextClassContext.Provider value="text-center sm:text-left">',
|
|
3039
|
+
' <View className={cn("flex flex-col gap-2", className)} {...props} />',
|
|
3040
|
+
" </TextClassContext.Provider>",
|
|
3041
|
+
" );",
|
|
3042
|
+
"}",
|
|
3043
|
+
"",
|
|
3044
|
+
"function AlertDialogFooter({ className, ...props }: ViewProps) {",
|
|
3045
|
+
" return (",
|
|
3046
|
+
' <View className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)} {...props} />',
|
|
3047
|
+
" );",
|
|
3048
|
+
"}",
|
|
3049
|
+
"",
|
|
3050
|
+
"function AlertDialogTitle({",
|
|
3051
|
+
" className,",
|
|
3052
|
+
" ...props",
|
|
3053
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {",
|
|
3054
|
+
" return (",
|
|
3055
|
+
" <AlertDialogPrimitive.Title",
|
|
3056
|
+
' className={cn("text-foreground text-lg font-semibold", className)}',
|
|
3057
|
+
" {...props}",
|
|
3058
|
+
" />",
|
|
3059
|
+
" );",
|
|
3060
|
+
"}",
|
|
3061
|
+
"",
|
|
3062
|
+
"function AlertDialogDescription({",
|
|
3063
|
+
" className,",
|
|
3064
|
+
" ...props",
|
|
3065
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {",
|
|
3066
|
+
" return (",
|
|
3067
|
+
" <AlertDialogPrimitive.Description",
|
|
3068
|
+
' className={cn("text-muted-foreground text-sm", className)}',
|
|
3069
|
+
" {...props}",
|
|
3070
|
+
" />",
|
|
3071
|
+
" );",
|
|
3072
|
+
"}",
|
|
3073
|
+
"",
|
|
3074
|
+
"function AlertDialogAction({",
|
|
3075
|
+
" className,",
|
|
3076
|
+
" ...props",
|
|
3077
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Action>) {",
|
|
3078
|
+
" return (",
|
|
3079
|
+
" <TextClassContext.Provider value={buttonTextVariants({ className })}>",
|
|
3080
|
+
" <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...props} />",
|
|
3081
|
+
" </TextClassContext.Provider>",
|
|
3082
|
+
" );",
|
|
3083
|
+
"}",
|
|
2770
3084
|
"",
|
|
2771
|
-
"
|
|
3085
|
+
"function AlertDialogCancel({",
|
|
3086
|
+
" className,",
|
|
3087
|
+
" ...props",
|
|
3088
|
+
"}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {",
|
|
3089
|
+
" return (",
|
|
3090
|
+
' <TextClassContext.Provider value={buttonTextVariants({ className, variant: "outline" })}>',
|
|
3091
|
+
" <AlertDialogPrimitive.Cancel",
|
|
3092
|
+
' className={cn(buttonVariants({ variant: "outline" }), className)} {...props} />',
|
|
3093
|
+
" </TextClassContext.Provider>",
|
|
3094
|
+
" );",
|
|
3095
|
+
"}",
|
|
3096
|
+
"",
|
|
3097
|
+
"export {",
|
|
3098
|
+
" AlertDialog,",
|
|
3099
|
+
" AlertDialogAction,",
|
|
3100
|
+
" AlertDialogCancel,",
|
|
3101
|
+
" AlertDialogContent,",
|
|
3102
|
+
" AlertDialogDescription,",
|
|
3103
|
+
" AlertDialogFooter,",
|
|
3104
|
+
" AlertDialogHeader,",
|
|
3105
|
+
" AlertDialogOverlay,",
|
|
3106
|
+
" AlertDialogPortal,",
|
|
3107
|
+
" AlertDialogTitle,",
|
|
3108
|
+
" AlertDialogTrigger,",
|
|
3109
|
+
"};",
|
|
3110
|
+
""
|
|
2772
3111
|
)
|
|
2773
3112
|
}
|
|
2774
3113
|
]
|
|
@@ -2814,7 +3153,8 @@ function generateBaseTemplates(projectName) {
|
|
|
2814
3153
|
// ─── app/_layout.tsx ────────────────────────────────────────────────
|
|
2815
3154
|
{
|
|
2816
3155
|
path: "app/_layout.tsx",
|
|
2817
|
-
content: `import
|
|
3156
|
+
content: `import "../global.css";
|
|
3157
|
+
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
|
|
2818
3158
|
import { useFonts } from "expo-font";
|
|
2819
3159
|
import { Stack } from "expo-router";
|
|
2820
3160
|
import * as SplashScreen from "expo-splash-screen";
|
|
@@ -3239,8 +3579,45 @@ module.exports = {
|
|
|
3239
3579
|
"./app/**/*.{js,jsx,ts,tsx}",
|
|
3240
3580
|
"./src/**/*.{js,jsx,ts,tsx}",
|
|
3241
3581
|
],
|
|
3582
|
+
presets: [require("nativewind/preset")],
|
|
3242
3583
|
theme: {
|
|
3243
|
-
extend: {
|
|
3584
|
+
extend: {
|
|
3585
|
+
colors: {
|
|
3586
|
+
border: "hsl(var(--border))",
|
|
3587
|
+
input: "hsl(var(--input))",
|
|
3588
|
+
ring: "hsl(var(--ring))",
|
|
3589
|
+
background: "hsl(var(--background))",
|
|
3590
|
+
foreground: "hsl(var(--foreground))",
|
|
3591
|
+
primary: {
|
|
3592
|
+
DEFAULT: "hsl(var(--primary))",
|
|
3593
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
3594
|
+
},
|
|
3595
|
+
secondary: {
|
|
3596
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
3597
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
3598
|
+
},
|
|
3599
|
+
destructive: {
|
|
3600
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
3601
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
3602
|
+
},
|
|
3603
|
+
muted: {
|
|
3604
|
+
DEFAULT: "hsl(var(--muted))",
|
|
3605
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
3606
|
+
},
|
|
3607
|
+
accent: {
|
|
3608
|
+
DEFAULT: "hsl(var(--accent))",
|
|
3609
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
3610
|
+
},
|
|
3611
|
+
popover: {
|
|
3612
|
+
DEFAULT: "hsl(var(--popover))",
|
|
3613
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
3614
|
+
},
|
|
3615
|
+
card: {
|
|
3616
|
+
DEFAULT: "hsl(var(--card))",
|
|
3617
|
+
foreground: "hsl(var(--card-foreground))",
|
|
3618
|
+
},
|
|
3619
|
+
},
|
|
3620
|
+
},
|
|
3244
3621
|
},
|
|
3245
3622
|
plugins: [],
|
|
3246
3623
|
};
|
|
@@ -3250,10 +3627,11 @@ module.exports = {
|
|
|
3250
3627
|
{
|
|
3251
3628
|
path: "metro.config.js",
|
|
3252
3629
|
content: `const { getDefaultConfig } = require("expo/metro-config");
|
|
3630
|
+
const { withNativeWind } = require("nativewind/metro");
|
|
3253
3631
|
|
|
3254
3632
|
const config = getDefaultConfig(__dirname);
|
|
3255
3633
|
|
|
3256
|
-
module.exports = config;
|
|
3634
|
+
module.exports = withNativeWind(config, { input: "./global.css" });
|
|
3257
3635
|
`
|
|
3258
3636
|
},
|
|
3259
3637
|
// ─── babel.config.js ─────────────────────────────────────────────────
|
|
@@ -3262,10 +3640,67 @@ module.exports = config;
|
|
|
3262
3640
|
content: `module.exports = function (api) {
|
|
3263
3641
|
api.cache(true);
|
|
3264
3642
|
return {
|
|
3265
|
-
presets: [
|
|
3266
|
-
|
|
3643
|
+
presets: [
|
|
3644
|
+
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
|
3645
|
+
"nativewind/babel",
|
|
3646
|
+
],
|
|
3647
|
+
plugins: ["react-native-reanimated/plugin"],
|
|
3267
3648
|
};
|
|
3268
3649
|
};
|
|
3650
|
+
`
|
|
3651
|
+
},
|
|
3652
|
+
// ─── global.css (NativeWind CSS variables for rnr components) ─────────
|
|
3653
|
+
{
|
|
3654
|
+
path: "global.css",
|
|
3655
|
+
content: `@tailwind base;
|
|
3656
|
+
@tailwind components;
|
|
3657
|
+
@tailwind utilities;
|
|
3658
|
+
|
|
3659
|
+
@layer base {
|
|
3660
|
+
:root {
|
|
3661
|
+
--background: 0 0% 100%;
|
|
3662
|
+
--foreground: 240 10% 3.9%;
|
|
3663
|
+
--card: 0 0% 100%;
|
|
3664
|
+
--card-foreground: 240 10% 3.9%;
|
|
3665
|
+
--popover: 0 0% 100%;
|
|
3666
|
+
--popover-foreground: 240 10% 3.9%;
|
|
3667
|
+
--primary: 240 5.9% 10%;
|
|
3668
|
+
--primary-foreground: 0 0% 98%;
|
|
3669
|
+
--secondary: 240 4.8% 95.9%;
|
|
3670
|
+
--secondary-foreground: 240 5.9% 10%;
|
|
3671
|
+
--muted: 240 4.8% 95.9%;
|
|
3672
|
+
--muted-foreground: 240 3.8% 46.1%;
|
|
3673
|
+
--accent: 240 4.8% 95.9%;
|
|
3674
|
+
--accent-foreground: 240 5.9% 10%;
|
|
3675
|
+
--destructive: 0 84.2% 60.2%;
|
|
3676
|
+
--destructive-foreground: 0 0% 98%;
|
|
3677
|
+
--border: 240 5.9% 90%;
|
|
3678
|
+
--input: 240 5.9% 90%;
|
|
3679
|
+
--ring: 240 5.9% 10%;
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
.dark {
|
|
3683
|
+
--background: 240 10% 3.9%;
|
|
3684
|
+
--foreground: 0 0% 98%;
|
|
3685
|
+
--card: 240 10% 3.9%;
|
|
3686
|
+
--card-foreground: 0 0% 98%;
|
|
3687
|
+
--popover: 240 10% 3.9%;
|
|
3688
|
+
--popover-foreground: 0 0% 98%;
|
|
3689
|
+
--primary: 0 0% 98%;
|
|
3690
|
+
--primary-foreground: 240 5.9% 10%;
|
|
3691
|
+
--secondary: 240 3.7% 15.9%;
|
|
3692
|
+
--secondary-foreground: 0 0% 98%;
|
|
3693
|
+
--muted: 240 3.7% 15.9%;
|
|
3694
|
+
--muted-foreground: 240 5% 64.9%;
|
|
3695
|
+
--accent: 240 3.7% 15.9%;
|
|
3696
|
+
--accent-foreground: 0 0% 98%;
|
|
3697
|
+
--destructive: 0 62.8% 30.6%;
|
|
3698
|
+
--destructive-foreground: 0 0% 98%;
|
|
3699
|
+
--border: 240 3.7% 15.9%;
|
|
3700
|
+
--input: 240 3.7% 15.9%;
|
|
3701
|
+
--ring: 240 4.9% 83.9%;
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3269
3704
|
`
|
|
3270
3705
|
},
|
|
3271
3706
|
// ─── .gitignore ─────────────────────────────────────────────────────
|
|
@@ -3314,6 +3749,392 @@ expo-env.d.ts
|
|
|
3314
3749
|
];
|
|
3315
3750
|
}
|
|
3316
3751
|
|
|
3752
|
+
// src/templates/login-tabs.ts
|
|
3753
|
+
function generateLoginTabsTemplates(projectName) {
|
|
3754
|
+
return [
|
|
3755
|
+
// ─── app/_layout.tsx (overwrite base) ────────────────────────────────
|
|
3756
|
+
{
|
|
3757
|
+
path: "app/_layout.tsx",
|
|
3758
|
+
content: lines(
|
|
3759
|
+
'import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";',
|
|
3760
|
+
'import { useFonts } from "expo-font";',
|
|
3761
|
+
'import { Stack } from "expo-router";',
|
|
3762
|
+
'import * as SplashScreen from "expo-splash-screen";',
|
|
3763
|
+
'import { useEffect } from "react";',
|
|
3764
|
+
'import { useColorScheme } from "react-native";',
|
|
3765
|
+
"",
|
|
3766
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3767
|
+
"",
|
|
3768
|
+
"SplashScreen.preventAutoHideAsync();",
|
|
3769
|
+
"",
|
|
3770
|
+
"export default function RootLayout() {",
|
|
3771
|
+
" const colorScheme = useColorScheme();",
|
|
3772
|
+
" const [loaded] = useFonts({",
|
|
3773
|
+
' SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),',
|
|
3774
|
+
" });",
|
|
3775
|
+
"",
|
|
3776
|
+
" useEffect(() => {",
|
|
3777
|
+
" if (loaded) {",
|
|
3778
|
+
" SplashScreen.hideAsync();",
|
|
3779
|
+
" }",
|
|
3780
|
+
" }, [loaded]);",
|
|
3781
|
+
"",
|
|
3782
|
+
" if (!loaded) {",
|
|
3783
|
+
" return null;",
|
|
3784
|
+
" }",
|
|
3785
|
+
"",
|
|
3786
|
+
" return (",
|
|
3787
|
+
' <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>',
|
|
3788
|
+
" <Stack>",
|
|
3789
|
+
' <Stack.Screen name="login" options={{ headerShown: false }} />',
|
|
3790
|
+
' <Stack.Screen name="(tabs)" options={{ headerShown: false }} />',
|
|
3791
|
+
' <Stack.Screen name="+not-found" />',
|
|
3792
|
+
" </Stack>",
|
|
3793
|
+
" </ThemeProvider>",
|
|
3794
|
+
" );",
|
|
3795
|
+
"}",
|
|
3796
|
+
""
|
|
3797
|
+
)
|
|
3798
|
+
},
|
|
3799
|
+
// ─── app/login.tsx ───────────────────────────────────────────────────
|
|
3800
|
+
{
|
|
3801
|
+
path: "app/login.tsx",
|
|
3802
|
+
content: lines(
|
|
3803
|
+
'import { SignInForm } from "@/src/components/SignInForm";',
|
|
3804
|
+
'import { View } from "react-native";',
|
|
3805
|
+
"",
|
|
3806
|
+
"export default function LoginScreen() {",
|
|
3807
|
+
" return (",
|
|
3808
|
+
' <View className="flex-1 items-center justify-center p-4">',
|
|
3809
|
+
" <SignInForm />",
|
|
3810
|
+
" </View>",
|
|
3811
|
+
" );",
|
|
3812
|
+
"}",
|
|
3813
|
+
""
|
|
3814
|
+
)
|
|
3815
|
+
},
|
|
3816
|
+
// ─── app/(tabs)/_layout.tsx (overwrite base) ───────────────────────
|
|
3817
|
+
{
|
|
3818
|
+
path: "app/(tabs)/_layout.tsx",
|
|
3819
|
+
content: lines(
|
|
3820
|
+
'import { Tabs } from "expo-router";',
|
|
3821
|
+
'import { Platform } from "react-native";',
|
|
3822
|
+
"",
|
|
3823
|
+
'import { Colors } from "@/src/constants/Colors";',
|
|
3824
|
+
'import { useColorScheme } from "@/src/hooks/useColorScheme";',
|
|
3825
|
+
"",
|
|
3826
|
+
"export default function TabLayout() {",
|
|
3827
|
+
" const colorScheme = useColorScheme();",
|
|
3828
|
+
"",
|
|
3829
|
+
" return (",
|
|
3830
|
+
" <Tabs",
|
|
3831
|
+
" screenOptions={{",
|
|
3832
|
+
' tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,',
|
|
3833
|
+
" headerStyle: {",
|
|
3834
|
+
' backgroundColor: Colors[colorScheme ?? "light"].background,',
|
|
3835
|
+
" },",
|
|
3836
|
+
" headerShadowVisible: false,",
|
|
3837
|
+
" tabBarStyle: Platform.select({",
|
|
3838
|
+
" ios: {",
|
|
3839
|
+
' position: "absolute",',
|
|
3840
|
+
" },",
|
|
3841
|
+
" default: {},",
|
|
3842
|
+
" }),",
|
|
3843
|
+
" }}",
|
|
3844
|
+
" >",
|
|
3845
|
+
" <Tabs.Screen",
|
|
3846
|
+
' name="home"',
|
|
3847
|
+
" options={{",
|
|
3848
|
+
' title: "Home",',
|
|
3849
|
+
" tabBarIcon: () => null,",
|
|
3850
|
+
" }}",
|
|
3851
|
+
" />",
|
|
3852
|
+
" <Tabs.Screen",
|
|
3853
|
+
' name="list"',
|
|
3854
|
+
" options={{",
|
|
3855
|
+
' title: "List",',
|
|
3856
|
+
" tabBarIcon: () => null,",
|
|
3857
|
+
" }}",
|
|
3858
|
+
" />",
|
|
3859
|
+
" <Tabs.Screen",
|
|
3860
|
+
' name="mine"',
|
|
3861
|
+
" options={{",
|
|
3862
|
+
' title: "Mine",',
|
|
3863
|
+
" tabBarIcon: () => null,",
|
|
3864
|
+
" }}",
|
|
3865
|
+
" />",
|
|
3866
|
+
" </Tabs>",
|
|
3867
|
+
" );",
|
|
3868
|
+
"}",
|
|
3869
|
+
""
|
|
3870
|
+
)
|
|
3871
|
+
},
|
|
3872
|
+
// ─── app/(tabs)/home.tsx ────────────────────────────────────────────
|
|
3873
|
+
{
|
|
3874
|
+
path: "app/(tabs)/home.tsx",
|
|
3875
|
+
content: lines(
|
|
3876
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3877
|
+
"import {",
|
|
3878
|
+
" AlertDialog,",
|
|
3879
|
+
" AlertDialogAction,",
|
|
3880
|
+
" AlertDialogCancel,",
|
|
3881
|
+
" AlertDialogContent,",
|
|
3882
|
+
" AlertDialogDescription,",
|
|
3883
|
+
" AlertDialogFooter,",
|
|
3884
|
+
" AlertDialogHeader,",
|
|
3885
|
+
" AlertDialogTitle,",
|
|
3886
|
+
" AlertDialogTrigger,",
|
|
3887
|
+
'} from "@/src/components/ui/alert-dialog";',
|
|
3888
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3889
|
+
'import { View } from "react-native";',
|
|
3890
|
+
"",
|
|
3891
|
+
"export default function HomeScreen() {",
|
|
3892
|
+
" return (",
|
|
3893
|
+
' <View className="flex-1 gap-6 p-6">',
|
|
3894
|
+
' <Text variant="h3">UI Components</Text>',
|
|
3895
|
+
' <Text variant="muted">React Native Reusables components showcase</Text>',
|
|
3896
|
+
"",
|
|
3897
|
+
" {/* Button variants */}",
|
|
3898
|
+
' <View className="gap-3">',
|
|
3899
|
+
' <Text variant="large">Buttons</Text>',
|
|
3900
|
+
' <View className="flex-row flex-wrap gap-2">',
|
|
3901
|
+
" <Button onPress={() => {}}>",
|
|
3902
|
+
" <Text>Default</Text>",
|
|
3903
|
+
" </Button>",
|
|
3904
|
+
' <Button variant="secondary" onPress={() => {}}>',
|
|
3905
|
+
" <Text>Secondary</Text>",
|
|
3906
|
+
" </Button>",
|
|
3907
|
+
' <Button variant="destructive" onPress={() => {}}>',
|
|
3908
|
+
" <Text>Destructive</Text>",
|
|
3909
|
+
" </Button>",
|
|
3910
|
+
' <Button variant="outline" onPress={() => {}}>',
|
|
3911
|
+
" <Text>Outline</Text>",
|
|
3912
|
+
" </Button>",
|
|
3913
|
+
' <Button variant="ghost" onPress={() => {}}>',
|
|
3914
|
+
" <Text>Ghost</Text>",
|
|
3915
|
+
" </Button>",
|
|
3916
|
+
' <Button variant="link" onPress={() => {}}>',
|
|
3917
|
+
" <Text>Link</Text>",
|
|
3918
|
+
" </Button>",
|
|
3919
|
+
" </View>",
|
|
3920
|
+
" </View>",
|
|
3921
|
+
"",
|
|
3922
|
+
" {/* AlertDialog */}",
|
|
3923
|
+
' <View className="gap-3">',
|
|
3924
|
+
' <Text variant="large">Alert Dialog</Text>',
|
|
3925
|
+
" <AlertDialog>",
|
|
3926
|
+
" <AlertDialogTrigger asChild>",
|
|
3927
|
+
' <Button variant="outline">',
|
|
3928
|
+
" <Text>Show Alert</Text>",
|
|
3929
|
+
" </Button>",
|
|
3930
|
+
" </AlertDialogTrigger>",
|
|
3931
|
+
" <AlertDialogContent>",
|
|
3932
|
+
" <AlertDialogHeader>",
|
|
3933
|
+
" <AlertDialogTitle>Are you sure?</AlertDialogTitle>",
|
|
3934
|
+
" <AlertDialogDescription>",
|
|
3935
|
+
" This action cannot be undone. This will permanently delete your account and remove your data from our servers.",
|
|
3936
|
+
" </AlertDialogDescription>",
|
|
3937
|
+
" </AlertDialogHeader>",
|
|
3938
|
+
" <AlertDialogFooter>",
|
|
3939
|
+
" <AlertDialogCancel>",
|
|
3940
|
+
" <Text>Cancel</Text>",
|
|
3941
|
+
" </AlertDialogCancel>",
|
|
3942
|
+
" <AlertDialogAction>",
|
|
3943
|
+
" <Text>Continue</Text>",
|
|
3944
|
+
" </AlertDialogAction>",
|
|
3945
|
+
" </AlertDialogFooter>",
|
|
3946
|
+
" </AlertDialogContent>",
|
|
3947
|
+
" </AlertDialog>",
|
|
3948
|
+
" </View>",
|
|
3949
|
+
" </View>",
|
|
3950
|
+
" );",
|
|
3951
|
+
"}",
|
|
3952
|
+
""
|
|
3953
|
+
)
|
|
3954
|
+
},
|
|
3955
|
+
// ─── app/(tabs)/list.tsx ────────────────────────────────────────────
|
|
3956
|
+
{
|
|
3957
|
+
path: "app/(tabs)/list.tsx",
|
|
3958
|
+
content: lines(
|
|
3959
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3960
|
+
'import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3961
|
+
'import { View, ScrollView } from "react-native";',
|
|
3962
|
+
"",
|
|
3963
|
+
"const ITEMS = Array.from({ length: 20 }, (_, i) => ({",
|
|
3964
|
+
" id: String(i + 1),",
|
|
3965
|
+
" title: `Item ${i + 1}`,",
|
|
3966
|
+
" description: `Description for item ${i + 1}`,",
|
|
3967
|
+
"}));",
|
|
3968
|
+
"",
|
|
3969
|
+
"export default function ListScreen() {",
|
|
3970
|
+
" return (",
|
|
3971
|
+
' <ScrollView className="flex-1 p-4">',
|
|
3972
|
+
' <Text variant="h3" className="mb-4">List</Text>',
|
|
3973
|
+
' <View className="gap-3">',
|
|
3974
|
+
" {ITEMS.map((item) => (",
|
|
3975
|
+
" <Card key={item.id}>",
|
|
3976
|
+
" <CardHeader>",
|
|
3977
|
+
" <CardTitle>{item.title}</CardTitle>",
|
|
3978
|
+
" <CardDescription>{item.description}</CardDescription>",
|
|
3979
|
+
" </CardHeader>",
|
|
3980
|
+
" <CardContent />",
|
|
3981
|
+
" </Card>",
|
|
3982
|
+
" ))}",
|
|
3983
|
+
" </View>",
|
|
3984
|
+
" </ScrollView>",
|
|
3985
|
+
" );",
|
|
3986
|
+
"}",
|
|
3987
|
+
""
|
|
3988
|
+
)
|
|
3989
|
+
},
|
|
3990
|
+
// ─── app/(tabs)/mine.tsx ────────────────────────────────────────────
|
|
3991
|
+
{
|
|
3992
|
+
path: "app/(tabs)/mine.tsx",
|
|
3993
|
+
content: lines(
|
|
3994
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
3995
|
+
'import { Card, CardContent, CardHeader, CardTitle } from "@/src/components/ui/card";',
|
|
3996
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
3997
|
+
'import { View } from "react-native";',
|
|
3998
|
+
'import { router } from "expo-router";',
|
|
3999
|
+
"",
|
|
4000
|
+
"export default function MineScreen() {",
|
|
4001
|
+
" return (",
|
|
4002
|
+
' <View className="flex-1 p-6">',
|
|
4003
|
+
' <Text variant="h3" className="mb-6">Mine</Text>',
|
|
4004
|
+
"",
|
|
4005
|
+
' <Card className="mb-6">',
|
|
4006
|
+
" <CardHeader>",
|
|
4007
|
+
" <CardTitle>Profile</CardTitle>",
|
|
4008
|
+
" </CardHeader>",
|
|
4009
|
+
" <CardContent>",
|
|
4010
|
+
' <View className="gap-2">',
|
|
4011
|
+
' <Text variant="muted">User Name</Text>',
|
|
4012
|
+
" <Text>user@example.com</Text>",
|
|
4013
|
+
" </View>",
|
|
4014
|
+
" </CardContent>",
|
|
4015
|
+
" </Card>",
|
|
4016
|
+
"",
|
|
4017
|
+
" <Button",
|
|
4018
|
+
' variant="destructive"',
|
|
4019
|
+
' className="w-full"',
|
|
4020
|
+
" onPress={() => {",
|
|
4021
|
+
' router.replace("/login");',
|
|
4022
|
+
" }}",
|
|
4023
|
+
" >",
|
|
4024
|
+
" <Text>Sign Out</Text>",
|
|
4025
|
+
" </Button>",
|
|
4026
|
+
" </View>",
|
|
4027
|
+
" );",
|
|
4028
|
+
"}",
|
|
4029
|
+
""
|
|
4030
|
+
)
|
|
4031
|
+
},
|
|
4032
|
+
// ─── src/components/SignInForm.tsx ───────────────────────────────────
|
|
4033
|
+
{
|
|
4034
|
+
path: "src/components/SignInForm.tsx",
|
|
4035
|
+
content: lines(
|
|
4036
|
+
'import { Button } from "@/src/components/ui/button";',
|
|
4037
|
+
"import {",
|
|
4038
|
+
" Card,",
|
|
4039
|
+
" CardContent,",
|
|
4040
|
+
" CardDescription,",
|
|
4041
|
+
" CardHeader,",
|
|
4042
|
+
" CardTitle,",
|
|
4043
|
+
'} from "@/src/components/ui/card";',
|
|
4044
|
+
'import { Input } from "@/src/components/ui/input";',
|
|
4045
|
+
'import { Label } from "@/src/components/ui/label";',
|
|
4046
|
+
'import { Separator } from "@/src/components/ui/separator";',
|
|
4047
|
+
'import { Text } from "@/src/components/ui/text";',
|
|
4048
|
+
'import * as React from "react";',
|
|
4049
|
+
'import { Pressable, type TextInput, View } from "react-native";',
|
|
4050
|
+
'import { router } from "expo-router";',
|
|
4051
|
+
"",
|
|
4052
|
+
"export function SignInForm() {",
|
|
4053
|
+
" const passwordInputRef = React.useRef<TextInput>(null);",
|
|
4054
|
+
"",
|
|
4055
|
+
" function onEmailSubmitEditing() {",
|
|
4056
|
+
" passwordInputRef.current?.focus();",
|
|
4057
|
+
" }",
|
|
4058
|
+
"",
|
|
4059
|
+
" function onSubmit() {",
|
|
4060
|
+
" // TODO: Submit form and navigate to protected screen if successful",
|
|
4061
|
+
' router.replace("/(tabs)/home");',
|
|
4062
|
+
" }",
|
|
4063
|
+
"",
|
|
4064
|
+
" return (",
|
|
4065
|
+
' <View className="gap-6 w-full max-w-sm">',
|
|
4066
|
+
' <Card className="shadow-none sm:shadow-sm sm:shadow-black/5">',
|
|
4067
|
+
" <CardHeader>",
|
|
4068
|
+
' <CardTitle className="text-center text-xl sm:text-left">Sign in to your app</CardTitle>',
|
|
4069
|
+
' <CardDescription className="text-center sm:text-left">',
|
|
4070
|
+
" Welcome back! Please sign in to continue",
|
|
4071
|
+
" </CardDescription>",
|
|
4072
|
+
" </CardHeader>",
|
|
4073
|
+
' <CardContent className="gap-6">',
|
|
4074
|
+
' <View className="gap-6">',
|
|
4075
|
+
' <View className="gap-1.5">',
|
|
4076
|
+
' <Label nativeID="email">Email</Label>',
|
|
4077
|
+
" <Input",
|
|
4078
|
+
' placeholder="m@example.com"',
|
|
4079
|
+
' keyboardType="email-address"',
|
|
4080
|
+
' autoComplete="email"',
|
|
4081
|
+
' autoCapitalize="none"',
|
|
4082
|
+
" onSubmitEditing={onEmailSubmitEditing}",
|
|
4083
|
+
' returnKeyType="next"',
|
|
4084
|
+
' submitBehavior="submit"',
|
|
4085
|
+
" />",
|
|
4086
|
+
" </View>",
|
|
4087
|
+
' <View className="gap-1.5">',
|
|
4088
|
+
' <View className="flex-row items-center">',
|
|
4089
|
+
' <Label nativeID="password">Password</Label>',
|
|
4090
|
+
" <Button",
|
|
4091
|
+
' variant="link"',
|
|
4092
|
+
' size="sm"',
|
|
4093
|
+
' className="ml-auto h-4 px-1 py-0 sm:h-4"',
|
|
4094
|
+
" onPress={() => {",
|
|
4095
|
+
" // TODO: Navigate to forgot password screen",
|
|
4096
|
+
" }}",
|
|
4097
|
+
" >",
|
|
4098
|
+
' <Text className="font-normal leading-4">Forgot your password?</Text>',
|
|
4099
|
+
" </Button>",
|
|
4100
|
+
" </View>",
|
|
4101
|
+
" <Input",
|
|
4102
|
+
" ref={passwordInputRef}",
|
|
4103
|
+
" secureTextEntry",
|
|
4104
|
+
' returnKeyType="send"',
|
|
4105
|
+
" onSubmitEditing={onSubmit}",
|
|
4106
|
+
" />",
|
|
4107
|
+
" </View>",
|
|
4108
|
+
' <Button className="w-full" onPress={onSubmit}>',
|
|
4109
|
+
" <Text>Continue</Text>",
|
|
4110
|
+
" </Button>",
|
|
4111
|
+
" </View>",
|
|
4112
|
+
' <Text className="text-center text-sm">',
|
|
4113
|
+
' Don't have an account?{" "}',
|
|
4114
|
+
" <Pressable",
|
|
4115
|
+
" onPress={() => {",
|
|
4116
|
+
" // TODO: Navigate to sign up screen",
|
|
4117
|
+
" }}",
|
|
4118
|
+
" >",
|
|
4119
|
+
' <Text className="text-sm underline underline-offset-4">Sign up</Text>',
|
|
4120
|
+
" </Pressable>",
|
|
4121
|
+
" </Text>",
|
|
4122
|
+
' <View className="flex-row items-center">',
|
|
4123
|
+
' <Separator className="flex-1" />',
|
|
4124
|
+
' <Text className="text-muted-foreground px-4 text-sm">or</Text>',
|
|
4125
|
+
' <Separator className="flex-1" />',
|
|
4126
|
+
" </View>",
|
|
4127
|
+
" </CardContent>",
|
|
4128
|
+
" </Card>",
|
|
4129
|
+
" </View>",
|
|
4130
|
+
" );",
|
|
4131
|
+
"}",
|
|
4132
|
+
""
|
|
4133
|
+
)
|
|
4134
|
+
}
|
|
4135
|
+
];
|
|
4136
|
+
}
|
|
4137
|
+
|
|
3317
4138
|
// src/utils/file.ts
|
|
3318
4139
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
3319
4140
|
var import_path = __toESM(require("path"));
|
|
@@ -3322,6 +4143,9 @@ async function writeFile(filePath, content) {
|
|
|
3322
4143
|
await import_fs_extra.default.ensureDir(dir);
|
|
3323
4144
|
await import_fs_extra.default.writeFile(filePath, content, "utf-8");
|
|
3324
4145
|
}
|
|
4146
|
+
async function readJson(filePath) {
|
|
4147
|
+
return import_fs_extra.default.readJson(filePath);
|
|
4148
|
+
}
|
|
3325
4149
|
async function writeJson(filePath, data, spaces = 2) {
|
|
3326
4150
|
const dir = import_path.default.dirname(filePath);
|
|
3327
4151
|
await import_fs_extra.default.ensureDir(dir);
|
|
@@ -3337,6 +4161,18 @@ function replaceTemplateVars(content, vars) {
|
|
|
3337
4161
|
}
|
|
3338
4162
|
|
|
3339
4163
|
// src/utils/package.ts
|
|
4164
|
+
async function mergeDependencies(pkgPath, deps, devDeps) {
|
|
4165
|
+
const pkg = await readJson(pkgPath);
|
|
4166
|
+
pkg.dependencies = {
|
|
4167
|
+
...pkg.dependencies || {},
|
|
4168
|
+
...deps
|
|
4169
|
+
};
|
|
4170
|
+
pkg.devDependencies = {
|
|
4171
|
+
...pkg.devDependencies || {},
|
|
4172
|
+
...devDeps
|
|
4173
|
+
};
|
|
4174
|
+
await writeJson(pkgPath, pkg);
|
|
4175
|
+
}
|
|
3340
4176
|
function generateBasePackageJson(projectName) {
|
|
3341
4177
|
return {
|
|
3342
4178
|
name: projectName,
|
|
@@ -3361,7 +4197,8 @@ function generateBasePackageJson(projectName) {
|
|
|
3361
4197
|
"react-native-safe-area-context": "~5.6.0",
|
|
3362
4198
|
"react-native-screens": "~4.16.0",
|
|
3363
4199
|
nativewind: "^4.1.0",
|
|
3364
|
-
tailwindcss: "^3.4.0"
|
|
4200
|
+
tailwindcss: "^3.4.0",
|
|
4201
|
+
"react-native-svg": "^15.8.0"
|
|
3365
4202
|
},
|
|
3366
4203
|
devDependencies: {
|
|
3367
4204
|
"@types/react": "~19.1.0",
|
|
@@ -3372,9 +4209,11 @@ function generateBasePackageJson(projectName) {
|
|
|
3372
4209
|
|
|
3373
4210
|
// src/index.ts
|
|
3374
4211
|
var import_execa = require("execa");
|
|
4212
|
+
var CLI_VERSION = "1.3.0";
|
|
4213
|
+
var CONFIG_FILE = ".expo-bbase.json";
|
|
3375
4214
|
async function run() {
|
|
3376
4215
|
const program = new import_commander.Command();
|
|
3377
|
-
program.name("expo-bbase").description("Expo SDK 54+
|
|
4216
|
+
program.name("expo-bbase").description("Expo SDK 54+ scaffolding CLI tool").version(CLI_VERSION);
|
|
3378
4217
|
program.argument("[project-name]", "Name of the project to create").action(async (projectName) => {
|
|
3379
4218
|
if (!projectName) {
|
|
3380
4219
|
console.error(import_chalk.default.red("Error: Please provide a project name."));
|
|
@@ -3384,71 +4223,120 @@ async function run() {
|
|
|
3384
4223
|
await createProject(projectName);
|
|
3385
4224
|
});
|
|
3386
4225
|
registerCreateCommand(program);
|
|
4226
|
+
registerUpgradeCommand(program);
|
|
4227
|
+
registerAddCommand(program);
|
|
3387
4228
|
await program.parseAsync(process.argv);
|
|
3388
4229
|
}
|
|
4230
|
+
async function readProjectConfig(targetDir) {
|
|
4231
|
+
const configPath = import_path2.default.join(targetDir, CONFIG_FILE);
|
|
4232
|
+
if (!await import_fs_extra2.default.pathExists(configPath)) {
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
return import_fs_extra2.default.readJson(configPath);
|
|
4236
|
+
}
|
|
4237
|
+
async function writeProjectConfig(targetDir, config) {
|
|
4238
|
+
const configPath = import_path2.default.join(targetDir, CONFIG_FILE);
|
|
4239
|
+
await writeJson(configPath, config);
|
|
4240
|
+
}
|
|
3389
4241
|
async function createProject(projectName) {
|
|
3390
4242
|
console.log();
|
|
3391
4243
|
console.log(
|
|
3392
4244
|
import_chalk.default.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")
|
|
3393
4245
|
);
|
|
3394
4246
|
console.log(
|
|
3395
|
-
import_chalk.default.bold.cyan(" \u2551 expo-bbase \u2014 Expo \
|
|
4247
|
+
import_chalk.default.bold.cyan(" \u2551 expo-bbase \u2014 Expo Scaffolding \u2551")
|
|
3396
4248
|
);
|
|
3397
4249
|
console.log(
|
|
3398
4250
|
import_chalk.default.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
|
|
3399
4251
|
);
|
|
3400
4252
|
console.log();
|
|
4253
|
+
const { uiTemplate } = await (0, import_prompts.default)({
|
|
4254
|
+
type: "select",
|
|
4255
|
+
name: "uiTemplate",
|
|
4256
|
+
message: "Choose a UI template",
|
|
4257
|
+
choices: [
|
|
4258
|
+
{
|
|
4259
|
+
title: `${import_chalk.default.bold("Login + Tabs")} \u2014 Login page, Home/List/Mine tabs with rnr components`,
|
|
4260
|
+
value: "login-tabs",
|
|
4261
|
+
description: "Pre-built login form, 3-tab layout with Button & AlertDialog demos"
|
|
4262
|
+
},
|
|
4263
|
+
{
|
|
4264
|
+
title: `${import_chalk.default.bold("Default")} \u2014 Blank tabs (Home + Explore)`,
|
|
4265
|
+
value: "default",
|
|
4266
|
+
description: "Minimal starter with basic tab navigation"
|
|
4267
|
+
}
|
|
4268
|
+
],
|
|
4269
|
+
initial: 0
|
|
4270
|
+
});
|
|
4271
|
+
if (uiTemplate === void 0) {
|
|
4272
|
+
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
4273
|
+
process.exit(0);
|
|
4274
|
+
}
|
|
4275
|
+
const isLoginTabs = uiTemplate === "login-tabs";
|
|
3401
4276
|
const choices = modules.map((m) => ({
|
|
3402
4277
|
title: `${import_chalk.default.bold(m.name)} \u2014 ${import_chalk.default.gray(m.description)}`,
|
|
3403
4278
|
value: m.id,
|
|
3404
|
-
|
|
4279
|
+
// Auto-select ui-reusables when login-tabs template is chosen
|
|
4280
|
+
selected: isLoginTabs && m.id === "ui-reusables" ? true : m.defaultChecked
|
|
3405
4281
|
}));
|
|
3406
4282
|
const { selectedModules } = await (0, import_prompts.default)({
|
|
3407
4283
|
type: "multiselect",
|
|
3408
4284
|
name: "selectedModules",
|
|
3409
|
-
message: "
|
|
4285
|
+
message: "Select modules (Space to toggle, Enter to confirm)",
|
|
3410
4286
|
choices,
|
|
3411
|
-
hint: "-
|
|
4287
|
+
hint: "- Space toggle \xB7 a select all/none \xB7 Enter confirm",
|
|
3412
4288
|
instructions: false
|
|
3413
4289
|
});
|
|
3414
4290
|
if (selectedModules === void 0) {
|
|
3415
|
-
console.log(import_chalk.default.yellow("\
|
|
4291
|
+
console.log(import_chalk.default.yellow("\nCancelled."));
|
|
3416
4292
|
process.exit(0);
|
|
3417
4293
|
}
|
|
3418
|
-
|
|
4294
|
+
let finalModuleIds = selectedModules;
|
|
4295
|
+
if (isLoginTabs && !finalModuleIds.includes("ui-reusables")) {
|
|
4296
|
+
finalModuleIds = ["ui-reusables", ...finalModuleIds];
|
|
4297
|
+
}
|
|
4298
|
+
const selectedModuleDefs = getModulesByIds(finalModuleIds);
|
|
3419
4299
|
const targetDir = import_path2.default.resolve(process.cwd(), projectName);
|
|
3420
4300
|
console.log();
|
|
3421
|
-
console.log(import_chalk.default.white(` \u{1F4E6}
|
|
4301
|
+
console.log(import_chalk.default.white(` \u{1F4E6} Project: ${import_chalk.default.bold(projectName)}`));
|
|
4302
|
+
console.log(import_chalk.default.white(` \u{1F4C2} Path: ${import_chalk.default.gray(targetDir)}`));
|
|
3422
4303
|
console.log(
|
|
3423
|
-
import_chalk.default.white(
|
|
4304
|
+
import_chalk.default.white(
|
|
4305
|
+
` \u{1F3A8} Template: ${import_chalk.default.green(isLoginTabs ? "Login + Tabs" : "Default")}`
|
|
4306
|
+
)
|
|
3424
4307
|
);
|
|
3425
4308
|
console.log(
|
|
3426
4309
|
import_chalk.default.white(
|
|
3427
|
-
` \u{1F9E9}
|
|
4310
|
+
` \u{1F9E9} Modules: ${import_chalk.default.green(selectedModuleDefs.map((m) => m.name).join(", ") || "none")}`
|
|
3428
4311
|
)
|
|
3429
4312
|
);
|
|
3430
4313
|
console.log();
|
|
3431
|
-
const spinner = (0, import_ora.default)("
|
|
4314
|
+
const spinner = (0, import_ora.default)("Creating project...").start();
|
|
3432
4315
|
try {
|
|
3433
4316
|
const baseTemplates = generateBaseTemplates(projectName);
|
|
3434
4317
|
for (const file of baseTemplates) {
|
|
3435
4318
|
const filePath = import_path2.default.join(targetDir, file.path);
|
|
3436
|
-
const content = replaceTemplateVars(file.content, {
|
|
3437
|
-
projectName
|
|
3438
|
-
});
|
|
4319
|
+
const content = replaceTemplateVars(file.content, { projectName });
|
|
3439
4320
|
await writeFile(filePath, content);
|
|
3440
4321
|
}
|
|
3441
|
-
spinner.text = "
|
|
4322
|
+
spinner.text = "Writing module files...";
|
|
3442
4323
|
for (const mod of selectedModuleDefs) {
|
|
3443
4324
|
for (const file of mod.files) {
|
|
3444
4325
|
const filePath = import_path2.default.join(targetDir, file.path);
|
|
3445
|
-
const content = replaceTemplateVars(file.content, {
|
|
3446
|
-
projectName
|
|
3447
|
-
});
|
|
4326
|
+
const content = replaceTemplateVars(file.content, { projectName });
|
|
3448
4327
|
await writeFile(filePath, content);
|
|
3449
4328
|
}
|
|
3450
4329
|
}
|
|
3451
|
-
|
|
4330
|
+
if (isLoginTabs) {
|
|
4331
|
+
spinner.text = "Writing UI template files...";
|
|
4332
|
+
const loginTabsTemplates = generateLoginTabsTemplates(projectName);
|
|
4333
|
+
for (const file of loginTabsTemplates) {
|
|
4334
|
+
const filePath = import_path2.default.join(targetDir, file.path);
|
|
4335
|
+
const content = replaceTemplateVars(file.content, { projectName });
|
|
4336
|
+
await writeFile(filePath, content);
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
spinner.text = "Generating package.json...";
|
|
3452
4340
|
const pkgJson = generateBasePackageJson(projectName);
|
|
3453
4341
|
const allDeps = {};
|
|
3454
4342
|
const allDevDeps = {};
|
|
@@ -3463,48 +4351,302 @@ async function createProject(projectName) {
|
|
|
3463
4351
|
);
|
|
3464
4352
|
const pkgPath = import_path2.default.join(targetDir, "package.json");
|
|
3465
4353
|
await writeJson(pkgPath, pkgJson);
|
|
3466
|
-
spinner.text = "
|
|
4354
|
+
spinner.text = "Configuring app.json...";
|
|
3467
4355
|
await updateAppJson(targetDir, selectedModuleDefs, projectName);
|
|
3468
|
-
spinner.text = "
|
|
4356
|
+
spinner.text = "Configuring Babel...";
|
|
3469
4357
|
await updateBabelConfig(targetDir, selectedModuleDefs);
|
|
3470
|
-
spinner.text = "
|
|
4358
|
+
spinner.text = "Configuring layout...";
|
|
3471
4359
|
await updateLayoutFile(targetDir, selectedModuleDefs);
|
|
3472
|
-
|
|
4360
|
+
await writeProjectConfig(targetDir, {
|
|
4361
|
+
projectName,
|
|
4362
|
+
selectedModules: finalModuleIds,
|
|
4363
|
+
cliVersion: CLI_VERSION,
|
|
4364
|
+
uiTemplate: isLoginTabs ? "login-tabs" : "default"
|
|
4365
|
+
});
|
|
4366
|
+
spinner.text = "Installing dependencies (yarn install)...";
|
|
3473
4367
|
try {
|
|
3474
4368
|
await (0, import_execa.execa)("yarn", ["install"], {
|
|
3475
4369
|
cwd: targetDir,
|
|
3476
4370
|
timeout: 3e5
|
|
3477
|
-
// 5 minute timeout
|
|
3478
4371
|
});
|
|
3479
4372
|
} catch (installError) {
|
|
3480
4373
|
const errMsg = installError instanceof Error ? installError.message : String(installError);
|
|
3481
|
-
spinner.warn("yarn install
|
|
3482
|
-
console.log(import_chalk.default.red(`
|
|
3483
|
-
console.log(
|
|
3484
|
-
import_chalk.default.gray(` cd ${projectName} && yarn install`)
|
|
3485
|
-
);
|
|
4374
|
+
spinner.warn("yarn install failed, please install manually");
|
|
4375
|
+
console.log(import_chalk.default.red(` Error: ${errMsg}`));
|
|
4376
|
+
console.log(import_chalk.default.gray(` cd ${projectName} && yarn install`));
|
|
3486
4377
|
}
|
|
3487
|
-
spinner.succeed(import_chalk.default.green("
|
|
4378
|
+
spinner.succeed(import_chalk.default.green("Project created!"));
|
|
3488
4379
|
console.log();
|
|
3489
|
-
console.log(import_chalk.default.bold(" \u{1F389}
|
|
4380
|
+
console.log(import_chalk.default.bold(" \u{1F389} Next steps:"));
|
|
3490
4381
|
console.log(import_chalk.default.white(` cd ${projectName}`));
|
|
3491
4382
|
console.log(import_chalk.default.white(" npx expo start"));
|
|
4383
|
+
if (isLoginTabs) {
|
|
4384
|
+
console.log(import_chalk.default.gray(" \u2192 App starts at login page, sign in to see tabs"));
|
|
4385
|
+
}
|
|
3492
4386
|
console.log();
|
|
3493
4387
|
if (selectedModuleDefs.length > 0) {
|
|
3494
|
-
console.log(import_chalk.default.bold(" \u{1F4CB}
|
|
4388
|
+
console.log(import_chalk.default.bold(" \u{1F4CB} Selected modules:"));
|
|
3495
4389
|
for (const mod of selectedModuleDefs) {
|
|
3496
4390
|
console.log(import_chalk.default.white(` \u2713 ${mod.name}`));
|
|
3497
4391
|
}
|
|
3498
4392
|
console.log();
|
|
3499
4393
|
}
|
|
3500
4394
|
} catch (error) {
|
|
3501
|
-
spinner.fail(import_chalk.default.red("
|
|
4395
|
+
spinner.fail(import_chalk.default.red("Project creation failed"));
|
|
3502
4396
|
console.error(error);
|
|
3503
4397
|
process.exit(1);
|
|
3504
4398
|
}
|
|
3505
4399
|
}
|
|
3506
|
-
async function
|
|
4400
|
+
async function upgradeProject(targetDir) {
|
|
4401
|
+
console.log();
|
|
4402
|
+
console.log(
|
|
4403
|
+
import_chalk.default.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")
|
|
4404
|
+
);
|
|
4405
|
+
console.log(
|
|
4406
|
+
import_chalk.default.bold.cyan(" \u2551 expo-bbase \u2014 Upgrade \u2551")
|
|
4407
|
+
);
|
|
4408
|
+
console.log(
|
|
4409
|
+
import_chalk.default.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")
|
|
4410
|
+
);
|
|
4411
|
+
console.log();
|
|
4412
|
+
const absDir = import_path2.default.resolve(targetDir);
|
|
4413
|
+
const config = await readProjectConfig(absDir);
|
|
4414
|
+
if (!config) {
|
|
4415
|
+
console.error(
|
|
4416
|
+
import_chalk.default.red(
|
|
4417
|
+
` \u2716 No ${CONFIG_FILE} found in ${absDir}
|
|
4418
|
+
This directory doesn't appear to be an expo-bbase project.
|
|
4419
|
+
If it is, run "expo-bbase add" to register modules.`
|
|
4420
|
+
)
|
|
4421
|
+
);
|
|
4422
|
+
process.exit(1);
|
|
4423
|
+
}
|
|
4424
|
+
console.log(
|
|
4425
|
+
import_chalk.default.white(` \u{1F4C2} Project: ${import_chalk.default.bold(config.projectName)}`)
|
|
4426
|
+
);
|
|
4427
|
+
console.log(
|
|
4428
|
+
import_chalk.default.white(` \u{1F4CB} CLI version: ${import_chalk.default.gray(config.cliVersion || "unknown")} \u2192 ${import_chalk.default.green(CLI_VERSION)}`)
|
|
4429
|
+
);
|
|
4430
|
+
console.log(
|
|
4431
|
+
import_chalk.default.white(
|
|
4432
|
+
` \u{1F9E9} Modules: ${import_chalk.default.green(config.selectedModules.join(", ") || "none")}`
|
|
4433
|
+
)
|
|
4434
|
+
);
|
|
4435
|
+
console.log();
|
|
4436
|
+
const spinner = (0, import_ora.default)("Upgrading project...").start();
|
|
4437
|
+
try {
|
|
4438
|
+
const selectedModuleDefs = getModulesByIds(config.selectedModules);
|
|
4439
|
+
spinner.text = "Updating module files...";
|
|
4440
|
+
for (const mod of selectedModuleDefs) {
|
|
4441
|
+
for (const file of mod.files) {
|
|
4442
|
+
const filePath = import_path2.default.join(absDir, file.path);
|
|
4443
|
+
const content = replaceTemplateVars(file.content, {
|
|
4444
|
+
projectName: config.projectName
|
|
4445
|
+
});
|
|
4446
|
+
await writeFile(filePath, content);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
spinner.text = "Updating dependencies...";
|
|
4450
|
+
const allDeps = {};
|
|
4451
|
+
const allDevDeps = {};
|
|
4452
|
+
for (const mod of selectedModuleDefs) {
|
|
4453
|
+
Object.assign(allDeps, mod.dependencies);
|
|
4454
|
+
Object.assign(allDevDeps, mod.devDependencies);
|
|
4455
|
+
}
|
|
4456
|
+
const basePkg = generateBasePackageJson(config.projectName);
|
|
4457
|
+
Object.assign(allDeps, basePkg.dependencies);
|
|
4458
|
+
Object.assign(
|
|
4459
|
+
allDevDeps,
|
|
4460
|
+
basePkg.devDependencies
|
|
4461
|
+
);
|
|
4462
|
+
const pkgPath = import_path2.default.join(absDir, "package.json");
|
|
4463
|
+
await mergeDependencies(pkgPath, allDeps, allDevDeps);
|
|
4464
|
+
spinner.text = "Updating app.json...";
|
|
4465
|
+
await updateAppJson(absDir, selectedModuleDefs, config.projectName);
|
|
4466
|
+
spinner.text = "Updating Babel config...";
|
|
4467
|
+
await updateBabelConfig(absDir, selectedModuleDefs);
|
|
4468
|
+
spinner.text = "Updating layout...";
|
|
4469
|
+
await updateLayoutFile(absDir, selectedModuleDefs);
|
|
4470
|
+
config.cliVersion = CLI_VERSION;
|
|
4471
|
+
await writeProjectConfig(absDir, config);
|
|
4472
|
+
spinner.text = "Installing updated dependencies (yarn install)...";
|
|
4473
|
+
try {
|
|
4474
|
+
await (0, import_execa.execa)("yarn", ["install"], {
|
|
4475
|
+
cwd: absDir,
|
|
4476
|
+
timeout: 3e5
|
|
4477
|
+
});
|
|
4478
|
+
} catch (installError) {
|
|
4479
|
+
const errMsg = installError instanceof Error ? installError.message : String(installError);
|
|
4480
|
+
spinner.warn("yarn install failed, please install manually");
|
|
4481
|
+
console.log(import_chalk.default.red(` Error: ${errMsg}`));
|
|
4482
|
+
}
|
|
4483
|
+
spinner.succeed(import_chalk.default.green("Project upgraded!"));
|
|
4484
|
+
console.log();
|
|
4485
|
+
console.log(import_chalk.default.bold(" \u{1F4CB} Upgrade summary:"));
|
|
4486
|
+
console.log(import_chalk.default.white(` CLI: ${import_chalk.default.gray(config.cliVersion)} (before)`));
|
|
4487
|
+
console.log(import_chalk.default.white(` Modules: ${import_chalk.default.green(config.selectedModules.join(", "))}`));
|
|
4488
|
+
console.log();
|
|
4489
|
+
console.log(import_chalk.default.bold(" \u{1F389} Run your project:"));
|
|
4490
|
+
console.log(import_chalk.default.white(" npx expo start"));
|
|
4491
|
+
console.log();
|
|
4492
|
+
} catch (error) {
|
|
4493
|
+
spinner.fail(import_chalk.default.red("Upgrade failed"));
|
|
4494
|
+
console.error(error);
|
|
4495
|
+
process.exit(1);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
async function addModule(moduleIds, targetDir) {
|
|
4499
|
+
console.log();
|
|
4500
|
+
const absDir = import_path2.default.resolve(targetDir);
|
|
4501
|
+
let config = await readProjectConfig(absDir);
|
|
4502
|
+
if (!config) {
|
|
4503
|
+
const pkgPath = import_path2.default.join(absDir, "package.json");
|
|
4504
|
+
if (!await import_fs_extra2.default.pathExists(pkgPath)) {
|
|
4505
|
+
console.error(
|
|
4506
|
+
import_chalk.default.red(` \u2716 No package.json found in ${absDir}`)
|
|
4507
|
+
);
|
|
4508
|
+
process.exit(1);
|
|
4509
|
+
}
|
|
4510
|
+
const pkg = await import_fs_extra2.default.readJson(pkgPath);
|
|
4511
|
+
config = {
|
|
4512
|
+
projectName: pkg.name || import_path2.default.basename(absDir),
|
|
4513
|
+
selectedModules: [],
|
|
4514
|
+
cliVersion: CLI_VERSION
|
|
4515
|
+
};
|
|
4516
|
+
console.log(
|
|
4517
|
+
import_chalk.default.yellow(
|
|
4518
|
+
` \u26A0 No ${CONFIG_FILE} found. Creating one for project "${config.projectName}".`
|
|
4519
|
+
)
|
|
4520
|
+
);
|
|
4521
|
+
}
|
|
4522
|
+
if (moduleIds.length === 0) {
|
|
4523
|
+
const availableModules = modules.filter(
|
|
4524
|
+
(m) => !config.selectedModules.includes(m.id)
|
|
4525
|
+
);
|
|
4526
|
+
if (availableModules.length === 0) {
|
|
4527
|
+
console.log(import_chalk.default.green(" \u2713 All modules are already installed!"));
|
|
4528
|
+
process.exit(0);
|
|
4529
|
+
}
|
|
4530
|
+
const choices = availableModules.map((m) => ({
|
|
4531
|
+
title: `${import_chalk.default.bold(m.name)} \u2014 ${import_chalk.default.gray(m.description)}`,
|
|
4532
|
+
value: m.id,
|
|
4533
|
+
selected: false
|
|
4534
|
+
}));
|
|
4535
|
+
const { selected } = await (0, import_prompts.default)({
|
|
4536
|
+
type: "multiselect",
|
|
4537
|
+
name: "selected",
|
|
4538
|
+
message: "Select modules to add (Space to toggle, Enter to confirm)",
|
|
4539
|
+
choices,
|
|
4540
|
+
hint: "- Space toggle \xB7 a select all/none \xB7 Enter confirm",
|
|
4541
|
+
instructions: false
|
|
4542
|
+
});
|
|
4543
|
+
if (selected === void 0 || selected.length === 0) {
|
|
4544
|
+
console.log(import_chalk.default.yellow(" No modules selected."));
|
|
4545
|
+
process.exit(0);
|
|
4546
|
+
}
|
|
4547
|
+
moduleIds = selected;
|
|
4548
|
+
}
|
|
4549
|
+
const invalidIds = moduleIds.filter((id) => !getModuleById(id));
|
|
4550
|
+
if (invalidIds.length > 0) {
|
|
4551
|
+
console.error(
|
|
4552
|
+
import_chalk.default.red(` \u2716 Unknown module(s): ${invalidIds.join(", ")}`)
|
|
4553
|
+
);
|
|
4554
|
+
console.log(
|
|
4555
|
+
import_chalk.default.gray(
|
|
4556
|
+
` Available: ${modules.map((m) => m.id).join(", ")}`
|
|
4557
|
+
)
|
|
4558
|
+
);
|
|
4559
|
+
process.exit(1);
|
|
4560
|
+
}
|
|
4561
|
+
const newModuleIds = moduleIds.filter(
|
|
4562
|
+
(id) => !config.selectedModules.includes(id)
|
|
4563
|
+
);
|
|
4564
|
+
if (newModuleIds.length === 0) {
|
|
4565
|
+
console.log(
|
|
4566
|
+
import_chalk.default.yellow(" All specified modules are already installed.")
|
|
4567
|
+
);
|
|
4568
|
+
process.exit(0);
|
|
4569
|
+
}
|
|
4570
|
+
const newModuleDefs = getModulesByIds(newModuleIds);
|
|
4571
|
+
console.log(
|
|
4572
|
+
import_chalk.default.white(` \u{1F4C2} Project: ${import_chalk.default.bold(config.projectName)}`)
|
|
4573
|
+
);
|
|
4574
|
+
console.log(
|
|
4575
|
+
import_chalk.default.white(
|
|
4576
|
+
` \u2795 Adding: ${import_chalk.default.green(newModuleDefs.map((m) => m.name).join(", "))}`
|
|
4577
|
+
)
|
|
4578
|
+
);
|
|
4579
|
+
console.log();
|
|
4580
|
+
const spinner = (0, import_ora.default)("Adding modules...").start();
|
|
4581
|
+
try {
|
|
4582
|
+
spinner.text = "Writing module files...";
|
|
4583
|
+
for (const mod of newModuleDefs) {
|
|
4584
|
+
for (const file of mod.files) {
|
|
4585
|
+
const filePath = import_path2.default.join(absDir, file.path);
|
|
4586
|
+
const content = replaceTemplateVars(file.content, {
|
|
4587
|
+
projectName: config.projectName
|
|
4588
|
+
});
|
|
4589
|
+
await writeFile(filePath, content);
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
spinner.text = "Updating dependencies...";
|
|
4593
|
+
const allDeps = {};
|
|
4594
|
+
const allDevDeps = {};
|
|
4595
|
+
for (const mod of newModuleDefs) {
|
|
4596
|
+
Object.assign(allDeps, mod.dependencies);
|
|
4597
|
+
Object.assign(allDevDeps, mod.devDependencies);
|
|
4598
|
+
}
|
|
4599
|
+
const pkgPath = import_path2.default.join(absDir, "package.json");
|
|
4600
|
+
await mergeDependencies(pkgPath, allDeps, allDevDeps);
|
|
4601
|
+
spinner.text = "Updating app.json...";
|
|
4602
|
+
const existingModules = getModulesByIds(config.selectedModules);
|
|
4603
|
+
await updateAppJson(
|
|
4604
|
+
absDir,
|
|
4605
|
+
[...existingModules, ...newModuleDefs],
|
|
4606
|
+
config.projectName
|
|
4607
|
+
);
|
|
4608
|
+
spinner.text = "Updating Babel config...";
|
|
4609
|
+
await updateBabelConfig(absDir, newModuleDefs);
|
|
4610
|
+
spinner.text = "Updating layout...";
|
|
4611
|
+
await updateLayoutFile(absDir, newModuleDefs);
|
|
4612
|
+
config.selectedModules.push(...newModuleIds);
|
|
4613
|
+
config.cliVersion = CLI_VERSION;
|
|
4614
|
+
await writeProjectConfig(absDir, config);
|
|
4615
|
+
spinner.text = "Installing dependencies (yarn install)...";
|
|
4616
|
+
try {
|
|
4617
|
+
await (0, import_execa.execa)("yarn", ["install"], {
|
|
4618
|
+
cwd: absDir,
|
|
4619
|
+
timeout: 3e5
|
|
4620
|
+
});
|
|
4621
|
+
} catch (installError) {
|
|
4622
|
+
const errMsg = installError instanceof Error ? installError.message : String(installError);
|
|
4623
|
+
spinner.warn("yarn install failed, please install manually");
|
|
4624
|
+
console.log(import_chalk.default.red(` Error: ${errMsg}`));
|
|
4625
|
+
}
|
|
4626
|
+
spinner.succeed(import_chalk.default.green("Modules added!"));
|
|
4627
|
+
console.log();
|
|
4628
|
+
console.log(import_chalk.default.bold(" \u{1F4CB} Added modules:"));
|
|
4629
|
+
for (const mod of newModuleDefs) {
|
|
4630
|
+
console.log(import_chalk.default.white(` \u2713 ${mod.name} (${mod.id})`));
|
|
4631
|
+
}
|
|
4632
|
+
console.log();
|
|
4633
|
+
console.log(import_chalk.default.bold(" \u{1F9E9} All installed modules:"));
|
|
4634
|
+
for (const id of config.selectedModules) {
|
|
4635
|
+
const m = getModuleById(id);
|
|
4636
|
+
console.log(import_chalk.default.white(` \u2022 ${m?.name || id}`));
|
|
4637
|
+
}
|
|
4638
|
+
console.log();
|
|
4639
|
+
} catch (error) {
|
|
4640
|
+
spinner.fail(import_chalk.default.red("Failed to add modules"));
|
|
4641
|
+
console.error(error);
|
|
4642
|
+
process.exit(1);
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
async function updateAppJson(targetDir, selectedModules, _projectName) {
|
|
3507
4646
|
const appJsonPath = import_path2.default.join(targetDir, "app.json");
|
|
4647
|
+
if (!await import_fs_extra2.default.pathExists(appJsonPath)) {
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
3508
4650
|
const appJson = await import_fs_extra2.default.readJson(appJsonPath);
|
|
3509
4651
|
const existingPlugins = appJson.expo?.plugins || [];
|
|
3510
4652
|
for (const mod of selectedModules) {
|
|
@@ -3526,41 +4668,55 @@ async function updateAppJson(targetDir, selectedModules, projectName) {
|
|
|
3526
4668
|
}
|
|
3527
4669
|
async function updateBabelConfig(targetDir, selectedModules) {
|
|
3528
4670
|
const babelPath = import_path2.default.join(targetDir, "babel.config.js");
|
|
4671
|
+
if (!await import_fs_extra2.default.pathExists(babelPath)) {
|
|
4672
|
+
return;
|
|
4673
|
+
}
|
|
3529
4674
|
let content = await import_fs_extra2.default.readFile(babelPath, "utf-8");
|
|
3530
4675
|
const extraPlugins = [];
|
|
3531
4676
|
for (const mod of selectedModules) {
|
|
3532
|
-
if (mod.babelPlugins) {
|
|
4677
|
+
if (mod.babelPlugins && mod.babelPlugins.length > 0) {
|
|
3533
4678
|
extraPlugins.push(...mod.babelPlugins);
|
|
3534
4679
|
}
|
|
3535
4680
|
}
|
|
3536
4681
|
if (extraPlugins.length > 0) {
|
|
3537
|
-
const
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
4682
|
+
const pluginsToAdd = extraPlugins.filter((p) => !content.includes(p));
|
|
4683
|
+
if (pluginsToAdd.length > 0) {
|
|
4684
|
+
const pluginStrings = pluginsToAdd.map((p) => ` "${p}"`).join(",\n");
|
|
4685
|
+
content = content.replace(
|
|
4686
|
+
/plugins:\s*\[([^\]]*)\]/,
|
|
4687
|
+
`plugins: [$1${pluginStrings ? ",\n" + pluginStrings : ""}]`
|
|
4688
|
+
);
|
|
4689
|
+
await import_fs_extra2.default.writeFile(babelPath, content, "utf-8");
|
|
4690
|
+
}
|
|
3543
4691
|
}
|
|
3544
4692
|
}
|
|
3545
4693
|
async function updateLayoutFile(targetDir, selectedModules) {
|
|
3546
4694
|
const layoutPath = import_path2.default.join(targetDir, "app/_layout.tsx");
|
|
3547
|
-
|
|
3548
|
-
|
|
4695
|
+
if (!await import_fs_extra2.default.pathExists(layoutPath)) {
|
|
4696
|
+
return;
|
|
4697
|
+
}
|
|
4698
|
+
let content = await import_fs_extra2.default.readFile(layoutPath, "utf-8");
|
|
3549
4699
|
const extraImports = [];
|
|
3550
4700
|
const extraProviderPairs = [];
|
|
3551
4701
|
for (const mod of selectedModules) {
|
|
3552
4702
|
if (mod.layoutImports) {
|
|
3553
|
-
|
|
4703
|
+
for (const imp of mod.layoutImports) {
|
|
4704
|
+
if (!content.includes(imp)) {
|
|
4705
|
+
extraImports.push(imp);
|
|
4706
|
+
}
|
|
4707
|
+
}
|
|
3554
4708
|
}
|
|
3555
4709
|
if (mod.layoutProviders) {
|
|
3556
4710
|
for (const provider of mod.layoutProviders) {
|
|
3557
4711
|
const match = provider.match(/^<(\w+)/);
|
|
3558
4712
|
if (match) {
|
|
3559
4713
|
const tagName = match[1];
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
4714
|
+
if (!content.includes(`<${tagName}`)) {
|
|
4715
|
+
extraProviderPairs.push({
|
|
4716
|
+
open: ` ${provider}`,
|
|
4717
|
+
close: ` </${tagName}>`
|
|
4718
|
+
});
|
|
4719
|
+
}
|
|
3564
4720
|
}
|
|
3565
4721
|
}
|
|
3566
4722
|
}
|
|
@@ -3589,7 +4745,7 @@ async function updateLayoutFile(targetDir, selectedModules) {
|
|
|
3589
4745
|
}
|
|
3590
4746
|
}
|
|
3591
4747
|
}
|
|
3592
|
-
await
|
|
4748
|
+
await import_fs_extra2.default.writeFile(layoutPath, content, "utf-8");
|
|
3593
4749
|
}
|
|
3594
4750
|
run().catch((error) => {
|
|
3595
4751
|
console.error(import_chalk.default.red("Fatal error:"), error);
|
|
@@ -3597,7 +4753,9 @@ run().catch((error) => {
|
|
|
3597
4753
|
});
|
|
3598
4754
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3599
4755
|
0 && (module.exports = {
|
|
4756
|
+
addModule,
|
|
3600
4757
|
createProject,
|
|
3601
|
-
run
|
|
4758
|
+
run,
|
|
4759
|
+
upgradeProject
|
|
3602
4760
|
});
|
|
3603
4761
|
//# sourceMappingURL=index.js.map
|