@androbinco/library-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # @androbinco/library-cli
2
+
3
+ Interactive CLI tool to add Robin library components to your React project. This tool provides a beautiful, user-friendly interface for selecting and copying components into your project structure.
4
+
5
+ ## Description
6
+
7
+ `@androbinco/library-cli` is an interactive command-line tool that helps you quickly add pre-built React components to your project. With a modern, Astro-like UI powered by `@clack/prompts`, you can easily browse and install components with just a few keystrokes.
8
+
9
+ ## Installation
10
+
11
+ ### Global Installation
12
+
13
+ ```bash
14
+ npm install -g @androbinco/library-cli
15
+ ```
16
+
17
+ ### Using npx (without installation)
18
+
19
+ ```bash
20
+ npx @androbinco/library-cli
21
+ ```
22
+
23
+ ### Using pnpx (for pnpm users)
24
+
25
+ ```bash
26
+ pnpx @androbinco/library-cli
27
+ ```
28
+
29
+ ## Prerequisites
30
+
31
+ - Node.js (v14 or higher)
32
+ - A React project (components require React to function)
33
+ - `package.json` in your project root (recommended for dependency detection)
34
+
35
+ ## Quick Start
36
+
37
+ 1. Navigate to your React project directory:
38
+ ```bash
39
+ cd my-react-project
40
+ ```
41
+
42
+ 2. Run the CLI:
43
+ ```bash
44
+ npx @androbinco/library-cli
45
+ ```
46
+
47
+ 3. Select a component from the interactive menu
48
+
49
+ 4. The component will be copied to `src/components/{component-name}/`
50
+
51
+ ## Usage
52
+
53
+ ### Basic Usage
54
+
55
+ Simply run the CLI from your project root:
56
+
57
+ ```bash
58
+ robin-library-cli
59
+ ```
60
+
61
+ The CLI will:
62
+ 1. Display a welcome message
63
+ 2. Check for React dependency in your `package.json` (warns if not found)
64
+ 3. Show an interactive menu of available components
65
+ 4. Copy the selected component to `src/components/{component-name}/`
66
+
67
+ ### Component Structure & Flow
68
+
69
+ Flow:
70
+ 1. Main menu (for now: Components or Exit)
71
+ 2. Components: multiselect prompt — “Please select one or more components”
72
+ 3. Copy selected components (one run can copy many)
73
+ 4. Post-action prompt: “Do something else?” (loop back to menu) or Exit
74
+
75
+ Components are copied to the following structure:
76
+
77
+ ```
78
+ your-project/
79
+ ├── src/
80
+ │ └── components/
81
+ │ └── {component-name}/
82
+ │ └── {component-files}
83
+ └── package.json
84
+ ```
85
+
86
+ ### Overwrite & Safety Behavior
87
+
88
+ If a component directory already exists:
89
+ - **Empty directory**: The component will be copied without prompting
90
+ - **Non-empty directory**: You'll be prompted to confirm overwrite
91
+ - Select `Yes` to overwrite existing files
92
+ - Select `No` to cancel the operation
93
+
94
+ Additional safety:
95
+ - Destination paths are validated to stay inside your current project
96
+ - Templates are resolved via ESM-safe `import.meta.url`
97
+
98
+ ## Available Components
99
+
100
+ ### Example Component
101
+
102
+ - **Name**: `example`
103
+ - **Description**: A simple example component with Hello World
104
+ - **Files**: `example.tsx`
105
+ - **Location**: `src/components/example/example.tsx`
106
+
107
+ ### Button Component
108
+
109
+ - **Name**: `button`
110
+ - **Description**: Minimal button component
111
+ - **Files**: `button.tsx`
112
+ - **Location**: `src/components/button/button.tsx`
113
+
114
+ ### Card Component
115
+
116
+ - **Name**: `card`
117
+ - **Description**: Minimal card wrapper
118
+ - **Files**: `card.tsx`
119
+ - **Location**: `src/components/card/card.tsx`
120
+
121
+ ### Hero Component
122
+
123
+ - **Name**: `hero`
124
+ - **Description**: Minimal hero section
125
+ - **Files**: `hero.tsx`
126
+ - **Location**: `src/components/hero/hero.tsx`
127
+
128
+ More components will be added in future updates.
129
+
130
+ ## Features
131
+
132
+ - ✨ Beautiful, modern UI with Astro-like aesthetics
133
+ - 🎯 Interactive component selection
134
+ - 🔍 Automatic React dependency detection
135
+ - 🛡️ Safe overwrite handling (checks for empty directories)
136
+ - 📁 Automatic directory creation
137
+ - ⚠️ Helpful warnings and error messages
138
+ - 🚀 Zero configuration required
139
+
140
+ ## How It Works
141
+
142
+ 1. **Dependency Check**: The CLI checks your `package.json` for React. If not found, it displays a warning but still allows you to proceed (you might add React later).
143
+
144
+ 2. **Component Selection**: An interactive menu displays all available components with descriptions.
145
+
146
+ 3. **Directory Check**: Before copying, the CLI checks if the destination directory exists and whether it's empty.
147
+
148
+ 4. **Copy Operation**: All files from the component template are copied to `src/components/{component-name}/`, creating directories as needed.
149
+
150
+ 5. **Completion**: A success message confirms the component has been added to your project.
151
+
152
+ ## Common Use Cases
153
+
154
+ ### Adding a Component to a New Project
155
+
156
+ ```bash
157
+ mkdir my-app && cd my-app
158
+ npm init -y
159
+ npm install react react-dom
160
+ npx @androbinco/library-cli
161
+ ```
162
+
163
+ ### Adding Multiple Components
164
+
165
+ Run once and select multiple components in the multiselect:
166
+
167
+ ```bash
168
+ npx @androbinco/library-cli # Pick multiple components in one go
169
+ ```
170
+
171
+ ### Working with Existing Components
172
+
173
+ If you've modified a component and want to update it:
174
+
175
+ 1. Run the CLI
176
+ 2. Select the component
177
+ 3. When prompted about overwriting, choose `Yes`
178
+
179
+ **Note**: This will overwrite your custom changes. Consider backing up your modifications first.
180
+
181
+ ## Troubleshooting
182
+
183
+ ### "React dependency not found" Warning
184
+
185
+ This warning appears if React is not listed in your `package.json`. The CLI will still copy the component, but you'll need to install React for it to work:
186
+
187
+ ```bash
188
+ npm install react react-dom
189
+ # or
190
+ pnpm add react react-dom
191
+ # or
192
+ yarn add react react-dom
193
+ ```
194
+
195
+ ### "No package.json found" Warning
196
+
197
+ If you're not in a project directory with a `package.json`, the CLI will warn you but still allow component installation. Make sure you're in the correct directory.
198
+
199
+ ### Permission Errors
200
+
201
+ If you encounter permission errors, ensure:
202
+ - You have write permissions in the current directory
203
+ - The `src/components/` directory (or its parent) is not read-only
204
+
205
+ ### Component Not Appearing
206
+
207
+ If a component doesn't appear in the selection menu:
208
+ - Make sure you're using the latest version of the CLI
209
+ - Check that the component template exists in the CLI's templates directory
210
+
211
+ ## Development
212
+
213
+ For contributing to this CLI tool, see the main Robin CLI Tools monorepo.
214
+
215
+ ### Local Development
216
+
217
+ 1. Clone the monorepo
218
+ 2. Install dependencies:
219
+ ```bash
220
+ pnpm install
221
+ ```
222
+ 3. Link the package:
223
+ ```bash
224
+ pnpm --filter @androbinco/library-cli dev
225
+ ```
226
+
227
+ ## Technical Details
228
+
229
+ ### Template Resolution
230
+
231
+ The CLI uses ESM-compatible path resolution (`import.meta.url`) to locate templates, ensuring it works correctly when distributed via NPM regardless of where it's installed.
232
+
233
+ ### File Operations
234
+
235
+ - Uses `fs-extra` for reliable file operations
236
+ - Automatically creates nested directories as needed
237
+ - Handles file overwrites safely
238
+
239
+ ### Error Handling
240
+
241
+ - Graceful error handling with user-friendly messages
242
+ - Non-blocking warnings (dependency checks don't prevent installation)
243
+ - Clear cancellation paths
244
+
245
+ ## Updating
246
+
247
+ To update to the latest version:
248
+
249
+ ```bash
250
+ npm update -g @androbinco/library-cli
251
+ ```
252
+
253
+ Or use npx/pnpx to always get the latest version without global installation.
254
+
255
+ ## License
256
+
257
+ MIT
258
+
259
+ ## Support
260
+
261
+ For issues, questions, or contributions, please refer to the main Robin CLI Tools monorepo.
262
+
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@androbinco/library-cli",
3
+ "version": "0.1.0",
4
+ "description": "Interactive CLI tool to add Robin library components to your project",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "bin": {
8
+ "robin-library-cli": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src/",
12
+ "README.md"
13
+ ],
14
+ "keywords": [
15
+ "cli",
16
+ "robin",
17
+ "library",
18
+ "components",
19
+ "react"
20
+ ],
21
+ "author": "androbinco",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@clack/prompts": "^0.7.0",
25
+ "fs-extra": "^11.1.1"
26
+ },
27
+ "devDependencies": {
28
+ "nodemon": "^3.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "start": "node ./src/index.js",
35
+ "dev": "node ./src/index.js",
36
+ "test": "echo \"Error: no test specified for robin-library-cli\" && exit 1",
37
+ "build": "echo \"robin-library-cli: No build step required.\"",
38
+ "lint": "echo \"robin-library-cli: Lint not configured.\""
39
+ }
40
+ }
package/src/index.js ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as p from '@clack/prompts';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { checkReactDependency } from './utils/dependencies.js';
7
+ import { handleComponentsFlow } from './utils/components.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const TEMPLATES_DIR = path.join(__dirname, 'templates');
12
+
13
+ // Component registry
14
+ const COMPONENTS = [
15
+ { name: 'example', description: 'Hello World example', sourceDir: 'example', dependencies: [] },
16
+ { name: 'button', description: 'Minimal button component', sourceDir: 'button', dependencies: [] },
17
+ { name: 'card', description: 'Minimal card wrapper', sourceDir: 'card', dependencies: [] },
18
+ { name: 'hero', description: 'Minimal hero section', sourceDir: 'hero', dependencies: [] },
19
+ {
20
+ name: 'carousel',
21
+ description: 'Carousel component with navigation and pagination',
22
+ sourceDir: 'carousel',
23
+ dependencies: ['embla-carousel-react', 'embla-carousel', 'class-variance-authority']
24
+ }
25
+ ];
26
+
27
+ async function mainMenu() {
28
+ const action = await p.select({
29
+ message: 'What would you like to do?',
30
+ options: [
31
+ { value: 'components', label: 'Components', hint: 'Browse and copy components' },
32
+ { value: 'exit', label: 'Exit' }
33
+ ]
34
+ });
35
+
36
+ return action;
37
+ }
38
+
39
+ async function main() {
40
+ // Beautiful intro
41
+ p.intro('✨ Welcome to Robin Library CLI');
42
+
43
+ // Check React dependency
44
+ await checkReactDependency();
45
+
46
+ let continueLoop = true;
47
+ while (continueLoop) {
48
+ const action = await mainMenu();
49
+
50
+ if (p.isCancel(action) || action === 'exit') {
51
+ break;
52
+ }
53
+
54
+ if (action === 'components') {
55
+ await handleComponentsFlow(COMPONENTS, TEMPLATES_DIR);
56
+ }
57
+
58
+ const doMore = await p.select({
59
+ message: 'Would you like to do something else?',
60
+ options: [
61
+ { value: 'continue', label: 'Yes, go back to the menu' },
62
+ { value: 'exit', label: 'No, exit' }
63
+ ]
64
+ });
65
+
66
+ if (p.isCancel(doMore) || doMore === 'exit') {
67
+ continueLoop = false;
68
+ }
69
+ }
70
+
71
+ // Beautiful outro
72
+ p.outro('🎉 Done! Happy coding!');
73
+ }
74
+
75
+ main().catch((error) => {
76
+ p.log.error(`Unexpected error: ${error.message}`);
77
+ process.exit(1);
78
+ });
79
+
@@ -0,0 +1,5 @@
1
+ // @ts-nocheck
2
+
3
+ export default function Button() {
4
+ return <button>Click me</button>;
5
+ }
@@ -0,0 +1,5 @@
1
+ // @ts-nocheck
2
+
3
+ export default function Card() {
4
+ return <div>Card content</div>;
5
+ }
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { DetailedHTMLProps, HTMLAttributes, useEffect } from "react";
3
+
4
+ import "./styles/embla.css";
5
+ import { EmblaOptionsType } from "embla-carousel";
6
+ import useEmblaCarousel from "embla-carousel-react";
7
+
8
+ import { cn } from "@/common/utils/classname-builder";
9
+
10
+ import {
11
+ NavigationButtons,
12
+ NextButton,
13
+ PrevButton,
14
+ } from "./components/navigation-buttons";
15
+ import { Pagination } from "./components/pagination";
16
+ import {
17
+ CarouselProvider,
18
+ useCarouselContext,
19
+ } from "./components/provider/carousel.provider";
20
+
21
+ /*
22
+ This is a carousel component that is used to display a list of items in a carousel.
23
+ It is a wrapper around the embla-carousel library and uses the useEmblaCarousel hook to create the carousel.
24
+ It is a client component that is used to display a list of items in a carousel.
25
+
26
+ You have some examples in the examples folder. To test them you can just use them like this in any page:
27
+
28
+ const slides = Array.from({ length: 5 }, (_, i) => i);
29
+ <>
30
+ <LoopCenterCarousel slides={slides} />
31
+ <CarouselWithNumberPagination slides={slides} />
32
+ </>
33
+ */
34
+
35
+ const BodyCarousel = ({
36
+ children,
37
+ className,
38
+ containerClassName,
39
+ ...props
40
+ }: {
41
+ children: React.ReactNode;
42
+ className?: string;
43
+ containerClassName?: string;
44
+ } & DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
45
+ const { options, setEmblaApi } = useCarouselContext();
46
+ const [emblaRef, emblaApi] = useEmblaCarousel({
47
+ direction: "ltr",
48
+ ...options,
49
+ });
50
+
51
+ useEffect(() => {
52
+ setEmblaApi(emblaApi);
53
+ }, [emblaApi, setEmblaApi]);
54
+
55
+ return (
56
+ <div
57
+ ref={emblaRef}
58
+ className={cn("embla w-full overflow-hidden", containerClassName)}
59
+ {...props}
60
+ >
61
+ <div className={cn("flex w-full gap-2 px-2", className)}>{children}</div>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ const RootCarousel = ({
67
+ children,
68
+ options,
69
+ className,
70
+ ...props
71
+ }: {
72
+ children: React.ReactNode;
73
+ options: EmblaOptionsType;
74
+ className?: string;
75
+ } & React.HTMLAttributes<HTMLDivElement>) => {
76
+ return (
77
+ <CarouselProvider options={options}>
78
+ <div className={cn("relative", className)} {...props}>
79
+ {children}
80
+ </div>
81
+ </CarouselProvider>
82
+ );
83
+ };
84
+
85
+ const SlideCarousel = ({
86
+ className,
87
+ children,
88
+ ...props
89
+ }: {
90
+ className?: string;
91
+ children: React.ReactNode;
92
+ } & React.HTMLAttributes<HTMLDivElement>) => {
93
+ return (
94
+ <div className={cn("embla-slot min-h-max", className)} {...props}>
95
+ {children}
96
+ </div>
97
+ );
98
+ };
99
+
100
+ export const Carousel = {
101
+ Root: RootCarousel,
102
+ Slides: BodyCarousel,
103
+ Slide: SlideCarousel,
104
+ Pagination: Pagination,
105
+ Navigation: NavigationButtons,
106
+ NextButton: NextButton,
107
+ PrevButton: PrevButton,
108
+ };
@@ -0,0 +1,100 @@
1
+ import React, { ComponentPropsWithRef } from "react";
2
+
3
+ import { cva, VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/common/utils/classname-builder";
6
+
7
+ import { useCarouselContext } from "./provider/carousel.provider";
8
+
9
+ const buttonVariants = cva(
10
+ "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
11
+ {
12
+ variants: {
13
+ variant: {
14
+ primary: "bg-blue-600 text-white hover:bg-blue-700",
15
+ secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
16
+ outline: "border border-gray-300 bg-transparent hover:bg-gray-100",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "primary",
21
+ },
22
+ }
23
+ );
24
+
25
+ export const PrevButton: React.FC<
26
+ {
27
+ variant?: VariantProps<typeof buttonVariants>["variant"];
28
+ } & ComponentPropsWithRef<"button">
29
+ > = ({ variant = "primary", children, ...props }) => {
30
+ const { prevBtnDisabled, onPrevButtonClick } = useCarouselContext();
31
+
32
+ return (
33
+ <button
34
+ className={cn(buttonVariants({ variant }))}
35
+ disabled={prevBtnDisabled}
36
+ onClick={onPrevButtonClick}
37
+ {...props}
38
+ >
39
+ <svg className="size-4" viewBox="0 0 532 532">
40
+ <path
41
+ d="M355.66 11.354c13.793-13.805 36.208-13.805 50.001 0 13.785 13.804 13.785 36.238 0 50.034L201.22 266l204.442 204.61c13.785 13.805 13.785 36.239 0 50.044-13.793 13.796-36.208 13.796-50.002 0a5994246.277 5994246.277 0 0 0-229.332-229.454 35.065 35.065 0 0 1-10.326-25.126c0-9.2 3.393-18.26 10.326-25.2C172.192 194.973 332.731 34.31 355.66 11.354Z"
42
+ fill="currentColor"
43
+ />
44
+ </svg>
45
+ {children}
46
+ </button>
47
+ );
48
+ };
49
+
50
+ export const NextButton: React.FC<
51
+ {
52
+ variant?: VariantProps<typeof buttonVariants>["variant"];
53
+ } & ComponentPropsWithRef<"button">
54
+ > = ({ variant = "primary", children, ...props }) => {
55
+ const { nextBtnDisabled, onNextButtonClick } = useCarouselContext();
56
+
57
+ return (
58
+ <button
59
+ className={cn(buttonVariants({ variant }))}
60
+ disabled={nextBtnDisabled}
61
+ onClick={onNextButtonClick}
62
+ {...props}
63
+ >
64
+ <svg className="size-4" viewBox="0 0 532 532">
65
+ <path
66
+ d="M176.34 520.646c-13.793 13.805-36.208 13.805-50.001 0-13.785-13.804-13.785-36.238 0-50.034L330.78 266 126.34 61.391c-13.785-13.805-13.785-36.239 0-50.044 13.793-13.796 36.208-13.796 50.002 0 22.928 22.947 206.395 206.507 229.332 229.454a35.065 35.065 0 0 1 10.326 25.126c0 9.2-3.393 18.26-10.326 25.2-45.865 45.901-206.404 206.564-229.332 229.52Z"
67
+ fill="currentColor"
68
+ />
69
+ </svg>
70
+ {children}
71
+ </button>
72
+ );
73
+ };
74
+
75
+ const containerVariants = cva("", {
76
+ variants: {
77
+ variant: {
78
+ "absolute-center":
79
+ "absolute top-1/2 flex w-full -translate-y-1/2 justify-between",
80
+ },
81
+ },
82
+ });
83
+
84
+ export const NavigationButtons = ({
85
+ className,
86
+ variant = "absolute-center",
87
+ buttonVariant = "primary",
88
+ ...props
89
+ }: {
90
+ className?: string;
91
+ variant?: VariantProps<typeof containerVariants>["variant"];
92
+ buttonVariant?: VariantProps<typeof buttonVariants>["variant"];
93
+ } & React.HTMLAttributes<HTMLDivElement>) => {
94
+ return (
95
+ <div className={cn(containerVariants({ variant }), className)} {...props}>
96
+ <PrevButton variant={buttonVariant} />
97
+ <NextButton variant={buttonVariant} />
98
+ </div>
99
+ );
100
+ };
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+
3
+ import { cva, VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '@/common/utils/classname-builder';
6
+
7
+ import { useCarouselContext } from './provider/carousel.provider';
8
+
9
+ export const Pagination: React.FC<
10
+ React.HTMLAttributes<HTMLDivElement> & { type?: 'dot' | 'number' | 'progress' }
11
+ > = ({ className, type = 'dot', ...props }) => {
12
+ const { scrollSnaps, selectedIndex, onDotButtonClick } = useCarouselContext();
13
+
14
+ if (type === 'number')
15
+ return (
16
+ <div className={cn('mx-auto flex w-max gap-2', className)} {...props}>
17
+ <p className="text-body-2 text-grey-50-15">
18
+ {selectedIndex + 1} / {scrollSnaps.length}
19
+ </p>
20
+ </div>
21
+ );
22
+ if (type === 'progress')
23
+ return (
24
+ <div
25
+ className={cn(
26
+ 'relative mx-auto flex h-2 w-200 max-w-full gap-2 overflow-hidden rounded-full bg-bg-primary',
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ <div
32
+ className="pointer-events-none absolute bottom-0 left-0 h-2 w-full bg-fill-brand-primary transition-all duration-300 ease-out"
33
+ style={{
34
+ transformOrigin: 'left',
35
+ transform: `scaleX(${(selectedIndex + 1) / scrollSnaps.length})`,
36
+ }}
37
+ />
38
+ {scrollSnaps.map((_, index) => (
39
+ <div
40
+ key={index}
41
+ className="h-2 w-full cursor-pointer"
42
+ onClick={() => onDotButtonClick(index)}
43
+ />
44
+ ))}
45
+ </div>
46
+ );
47
+
48
+ if (type === 'dot')
49
+ return (
50
+ <div className={cn('mx-auto flex w-max gap-2', className)} {...props}>
51
+ {scrollSnaps.map((_, index) => (
52
+ <DotButton key={index} index={index} variant="primary" />
53
+ ))}
54
+ </div>
55
+ );
56
+ };
57
+
58
+ type DotButtonVariants = VariantProps<typeof dotButtonVariants>['variant'];
59
+ const dotButtonVariants = cva(
60
+ 'size-3 cursor-pointer rounded-full transition-all duration-300 ease-out focus:outline-2',
61
+ {
62
+ variants: {
63
+ variant: {
64
+ primary: 'bg-fill-brand-primary',
65
+ },
66
+ selected: {
67
+ true: 'w-9 !bg-amber-950',
68
+ false: '',
69
+ },
70
+ },
71
+ },
72
+ );
73
+
74
+ export const DotButton: React.FC<{ index: number; variant: DotButtonVariants }> = ({
75
+ index,
76
+ variant = 'primary',
77
+ ...restProps
78
+ }) => {
79
+ const { selectedIndex, onDotButtonClick } = useCarouselContext();
80
+
81
+ return (
82
+ <button
83
+ className={dotButtonVariants({ variant, selected: selectedIndex === index })}
84
+ disabled={selectedIndex === index}
85
+ onClick={() => onDotButtonClick(index)}
86
+ {...restProps}
87
+ />
88
+ );
89
+ };