npm package: @statsbygg/generators
Description: Boilerplate generation scripts for Statsbygg projects
Latest version: 0.1.16

# @statsbygg/generators

> Interactive code generators for scaffolding components, forms, API services, state management, and routes in Statsbygg applications.

## Overview

`@statsbygg/generators` is a collection of CLI tools that automate the creation of boilerplate code. Instead of manually creating files, importing dependencies, and writing repetitive code, these generators handle it for you while enforcing Statsbygg's best practices.

**Philosophy:**

- **Golden Path**: Generators produce opinionated, production-ready code
- **Consistency**: All generated code follows the same patterns and structure
- **Speed**: Scaffold complex features in seconds, not hours
- **Flexibility**: Customize generated code through interactive prompts

## Installation

This package is automatically included in projects created with `@statsbygg/create-app`. If you need to add it manually:

```bash
npm install --save-dev @statsbygg/generators
```

## Usage

All generators are available as npm scripts in your `package.json`:

```bash
npm run gen:<generator-name>
```

Each generator provides an interactive CLI that guides you through the creation process.

---

## Available Generators

### 1. `gen:component` - Component Generator

Scaffolds a new React component with TypeScript, CSS modules, tests, and Storybook stories.

#### Command

```bash
npm run gen:component
```

Or with a specific name:

```bash
npm run gen:component MyButton
```

#### Interactive Prompts

1. **Component name**: PascalCase (e.g., `UserCard`, `SearchBar`)
2. **Component type**:
   - `ui` - Reusable UI component (e.g., buttons, inputs)
   - `feature` - Feature-specific component
3. **Feature name** (if type is `feature`): e.g., `userManagement`, `dashboard`
4. **Nested path** (optional): Subdirectory within the feature (e.g., `forms`, `modals`)
5. **Files to create**:
   - Component file (.tsx) - Always included
   - Styles (.module.css)
   - Types (.types.ts)
   - Tests (.test.tsx)
   - Stories (.stories.tsx)
6. **Next.js integration options**:
   - State management (Zustand)
   - API integration (TanStack Query)
   - Client component

#### Generated Structure

**For UI Component:**

```
src/components/ui/MyButton/
├── MyButton.tsx              # Main component
├── MyButton.module.css       # CSS module
├── MyButton.types.ts         # TypeScript types
├── MyButton.test.tsx         # Vitest tests
├── MyButton.stories.tsx      # Storybook stories
└── index.ts                  # Barrel export
```

**For Feature Component:**

```
src/components/features/userManagement/forms/UserForm/
├── UserForm.tsx
├── UserForm.module.css
├── UserForm.types.ts
├── UserForm.test.tsx
├── UserForm.stories.tsx
└── index.ts
```

#### Generated Code Example

```tsx
// MyButton.tsx
import { MyButtonProps } from './MyButton.types';
import styles from './MyButton.module.css';

export function MyButton(props: MyButtonProps) {
  return (
    <div className={styles.container}>
      MyButton
    </div>
  );
}
```

#### Index File Updates

The generator automatically updates:
- `/src/components/ui/index.ts` (for UI components)
- `/src/components/features/{featureName}/index.ts` (for feature components)

---

### 2. `gen:route` - App Router Route Generator

Creates Next.js App Router pages with special files (layout, loading, error, not-found).

#### Command

```bash
npm run gen:route
```

Or specify the route path directly:

```bash
npm run gen:route dashboard
npm run gen:route admin/users
npm run gen:route api/users
```

#### Interactive Prompts

1. **Route path**: e.g., `about`, `dashboard`, `api/users`
2. **Files to create**:
   - Page (page.tsx or route.ts for API routes)
   - Styles (page.module.css)
   - Loading (loading.tsx)
   - Error (error.tsx)
   - Not Found (not-found.tsx)
   - Layout (layout.tsx)
   - Template (template.tsx)
3. **Client component?** (for page routes only)

#### Generated Structure

**For Standard Page:**

```
src/app/dashboard/
├── page.tsx                  # Main page component
├── page.module.css           # Page-specific styles
├── loading.tsx               # Loading UI (Suspense fallback)
├── error.tsx                 # Error boundary
├── not-found.tsx             # 404 page
├── layout.tsx                # Layout wrapper
└── template.tsx              # Template with animations
```

**For API Route:**

```
src/app/api/users/
└── route.ts                  # API endpoint handlers
```

#### Generated Code Example

```tsx
// page.tsx (Server Component)
import { Heading } from '@digdir/designsystemet-react';
import styles from './page.module.css';

export default function DashboardPage() {
  return (
    <div className={styles.container}>
      <Heading level={1}>Dashboard</Heading>
      <p>This is the dashboard page.</p>
    </div>
  );
}
```

```tsx
// error.tsx (Client Component)
'use client';

import { Button, Heading } from '@digdir/designsystemet-react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import styles from './error.module.css';

interface ErrorProps {
  error: Error & { digest?: string };
  reset: () => void;
}

export default function Error({ error, reset }: ErrorProps) {
  return (
    <div className={styles.container}>
      <AlertTriangle className={styles.icon} />
      <Heading level={1}>Something went wrong</Heading>
      <Button onClick={reset}>
        <RefreshCw /> Try again
      </Button>
    </div>
  );
}
```

```typescript
// route.ts (API Route)
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  try {
    return NextResponse.json({ message: 'GET users' });
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    return NextResponse.json({ message: 'POST users', data: body });
  } catch (error) {
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}
```

---

### 3. `gen:api` - API Service Generator

Creates type-safe API request functions and TanStack Query hooks for data fetching.

#### Command

```bash
npm run gen:api
```

Or specify the API name:

```bash
npm run gen:api users
```

#### Interactive Prompts

1. **API service name**: camelCase (e.g., `users`, `products`, `newsArticles`)
2. **API endpoint path**: e.g., `/users`, `/api/products` (defaults to kebab-case of name)
3. **Select endpoints**:
   - Get List (GET collection)
   - Get Single (GET by ID)
   - Create (POST)
   - Update (PUT)
   - Delete (DELETE)
4. **Requires authentication?**: Yes/No

#### Generated Structure

```
src/lib/api/
├── requests/
│   └── users.ts              # Request functions (get, post, put, delete)
├── hooks/
│   └── users.ts              # TanStack Query hooks
└── index.ts                  # Updated with exports

src/types/api/
└── users.ts                  # TypeScript interfaces
```

#### Generated Code Example

**Request Functions (`requests/users.ts`):**

```typescript
import { getRequest, postRequest, putRequest, deleteRequest } from '../requests';
import {
  GetUsersListResponse,
  GetUsersByIdResponse,
  CreateUsersRequest,
  CreateUsersResponse,
} from '@/types/api/users';

const ENDPOINT = '/users';

async function getUsersList(): Promise<GetUsersListResponse> {
  const response = await getRequest(ENDPOINT);
  return response.json();
}

async function getUsersById(id: string): Promise<GetUsersByIdResponse> {
  const response = await getRequest(`${ENDPOINT}/${id}`);
  return response.json();
}

async function createUsers(data: CreateUsersRequest): Promise<CreateUsersResponse> {
  const response = await postRequest(ENDPOINT, data);
  return response.json();
}

export { getUsersList, getUsersById, createUsers };
```

**TanStack Query Hooks (`hooks/users.ts`):**

```typescript
import { useQuery, useMutation } from '@tanstack/react-query';
import { getUsersList, getUsersById, createUsers } from '../requests/users';
import {
  GetUsersListResponse,
  GetUsersByIdResponse,
  CreateUsersRequest,
  CreateUsersResponse,
} from '@/types/api/users';

export function useGetUsersList(options?: any) {
  return useQuery<GetUsersListResponse>({
    queryKey: ['users'],
    queryFn: () => getUsersList(),
    ...options,
  });
}

export function useGetUsersById(id: string, options?: any) {
  return useQuery<GetUsersByIdResponse>({
    queryKey: ['users', id],
    queryFn: () => getUsersById(id),
    enabled: !!id,
    ...options,
  });
}

export function useCreateUsers(options?: any) {
  return useMutation<CreateUsersResponse, Error, CreateUsersRequest>({
    mutationFn: (data: CreateUsersRequest) => createUsers(data),
    ...options,
  });
}
```

**TypeScript Types (`types/api/users.ts`):**

```typescript
export interface User {
  id: string;
  // TODO: Define your User entity type
}

export interface GetUsersListResponse {
  data: User[];
  total: number;
  page?: number;
  limit?: number;
}

export interface GetUsersByIdResponse extends User {
  // TODO: Add any additional fields
}

export interface CreateUsersRequest {
  // TODO: Define request type
}

export interface CreateUsersResponse extends User {
  // TODO: Add any additional fields
}
```

#### Usage Example

```tsx
'use client';

import { useGetUsersList, useCreateUsers } from '@/lib/api';

export function UserList() {
  const { data, isLoading, error } = useGetUsersList();
  const createUser = useCreateUsers();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <ul>
        {data?.data.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      
      <button
        onClick={() => createUser.mutate({ name: 'John Doe' })}
      >
        Add User
      </button>
    </div>
  );
}
```

---

### 4. `gen:state` - State Management Generator

Creates Zustand stores with different patterns (list management, UI state, API integration).

#### Command

```bash
npm run gen:state
```

#### Interactive Prompts

1. **State name**: camelCase (e.g., `userList`, `appSettings`, `searchFilters`)
2. **Generation mode**:
   - Guided Setup (interactive pattern selection)
   - Schema-based (use a state schema file)
3. **State pattern** (for guided setup):
   - List Management (arrays with filtering, sorting, CRUD)
   - UI State (simple toggles, forms, modals)
   - API Integration (loading/error/data states)
4. **Features** (based on pattern):
   - **List**: Filtering, Sorting, Pagination, Persistence
   - **UI**: Persistence, Reset Actions
   - **API**: Caching, Optimistic Updates

#### Generated Structure

```
src/lib/store/
├── useUserListStore.ts       # Zustand store
└── useUserList.ts            # Optional: API integration hook
```

#### Generated Code Example

**List Management Store:**

```typescript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface UserListState {
  // Data
  items: unknown[];
  filters: Record<string, unknown>;
  filteredItems: unknown[];
  sortBy: string;
  sortOrder: 'asc' | 'desc';

  // Actions
  setItems: (items: unknown[]) => void;
  addItem: (item: unknown) => void;
  updateItem: (id: string, updates: Partial<unknown>) => void;
  removeItem: (id: string) => void;
  clearItems: () => void;
  setFilter: (key: string, value: unknown) => void;
  clearFilter: (key?: string) => void;
  setSortBy: (field: string, order?: 'asc' | 'desc') => void;
}

const initialState = {
  items: [],
  filters: {},
  filteredItems: [],
  sortBy: '',
  sortOrder: 'asc' as const,
};

export const useUserListStore = create<UserListState>()(
  immer((set, get) => ({
    ...initialState,

    setItems: (items) => set((state) => {
      state.items = items;
      state.filteredItems = filterItems(items, state.filters);
    }),

    addItem: (item) => set((state) => {
      state.items.push(item);
      state.filteredItems = filterItems(state.items, state.filters);
    }),

    setFilter: (key, value) => set((state) => {
      if (value === null || value === undefined || value === '') {
        delete state.filters[key];
      } else {
        state.filters[key] = value;
      }
      state.filteredItems = filterItems(state.items, state.filters);
    }),

    // ... other actions
  }))
);

// Selectors
export const useUserListItems = () => useUserListStore((state) => state.items);
export const useUserListFilteredItems = () => useUserListStore((state) => state.filteredItems);

// Helper function
function filterItems(items: unknown[], filters: Record<string, unknown>): unknown[] {
  if (Object.keys(filters).length === 0) return items;
  
  return items.filter((item: any) => {
    return Object.entries(filters).every(([key, value]) => {
      if (!value) return true;
      const itemValue = item[key];
      if (typeof itemValue === 'string' && typeof value === 'string') {
        return itemValue.toLowerCase().includes(value.toLowerCase());
      }
      return itemValue === value;
    });
  });
}
```

**UI State Store:**

```typescript
interface AppSettingsState {
  value: unknown;
  setValue: (value: unknown) => void;
  reset: () => void;
}

const initialState = { value: null };

export const useAppSettingsStore = create<AppSettingsState>()(
  immer((set) => ({
    ...initialState,
    
    setValue: (value) => set((state) => {
      state.value = value;
    }),
    
    reset: () => set(initialState),
  }))
);
```

#### Usage Example

```tsx
'use client';

import { useUserListStore, useUserListFilteredItems } from '@/lib/store/useUserListStore';

export function UserList() {
  const filteredUsers = useUserListFilteredItems();
  const { setFilter, addItem } = useUserListStore();

  return (
    <div>
      <input
        type="text"
        placeholder="Search users..."
        onChange={(e) => setFilter('name', e.target.value)}
      />
      
      <ul>
        {filteredUsers.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      
      <button onClick={() => addItem({ id: '123', name: 'New User' })}>
        Add User
      </button>
    </div>
  );
}
```

---

### 5. `gen:schema` - Form Schema Generator

Creates form schemas using a fluent DSL. These schemas are used by the `gen:form` generator.

#### Command

```bash
npm run gen:schema
```

#### Interactive Prompts

1. **Form name**: PascalCase (e.g., `ContactForm`, `UserRegistrationForm`)
2. **Features**:
   - Validation (Zod)
   - Auto-save drafts
   - Advanced state management
   - File upload
   - Storybook stories
   - Testing
3. **Number of sections**: How many groups of fields (1-5)
4. **For each section**:
   - Section name (camelCase)
   - Number of fields (1-10)
5. **For each field**:
   - Field name (camelCase)
   - Field type (string, email, number, textarea, select, checkbox, boolean, files, any)
   - Configuration (required, validation rules, options, etc.)

#### Generated Structure

```
schemas/
└── contact-form.schema.js    # Form schema definition
```

#### Generated Schema Example

```javascript
/**
 * Form Schema: ContactForm
 * This schema is designed to be loaded by the @statsbygg/generators tool.
 */

module.exports = defineFormSchema({
  name: 'ContactForm',
  features: ['validation', 'testing'],
  translationNamespace: 'contactForm',
  
  fields: {
    firstName: field.string()
      .min(2, 'validation.string.min')
      .max(50, 'validation.string.max')
      .required(),
      
    lastName: field.string()
      .min(2, 'validation.string.min')
      .max(50, 'validation.string.max')
      .required(),
      
    email: field.email()
      .required(),
      
    message: field.textarea()
      .rows(6)
      .min(10, 'validation.string.min')
      .required(),
      
    contactMethod: field.select({
      options: [
        { value: 'email', label: 'Email' },
        { value: 'phone', label: 'Phone' },
        { value: 'mail', label: 'Mail' },
      ]
    }).required(),
      
    agreeToTerms: field.boolean()
      .required(),
  },
  
  sections: [
    {
      name: 'personalInfo',
      title: 'contactForm.sections.personalInfo',
      fields: ['firstName', 'lastName', 'email']
    },
    {
      name: 'messageDetails',
      title: 'contactForm.sections.messageDetails',
      fields: ['message', 'contactMethod', 'agreeToTerms']
    }
  ],
});
```

#### Field API Reference

**String Fields:**
```javascript
field.string()
  .min(2, 'Custom error message')
  .max(100)
  .pattern(/^[A-Z]/, 'Must start with capital letter')
  .required()
```

**Email Fields:**
```javascript
field.email()
  .required()
```

**Number Fields:**
```javascript
field.number()
  .min(0)
  .max(100)
  .step(0.1)
  .integer('Must be a whole number')
  .required()
```

**Textarea Fields:**
```javascript
field.textarea()
  .rows(6)
  .min(10)
  .max(500)
  .required()
```

**Select Fields:**
```javascript
field.select({
  options: [
    { value: 'option1', label: 'Option 1' },
    { value: 'option2', label: 'Option 2' },
  ]
})
  .required()
  .searchable()  // Enable search/filter
  .multiple()    // Allow multiple selection
```

**File Upload Fields:**
```javascript
field.files()
  .accept(['.pdf', '.doc', '.docx'])
  .maxFiles(3)
  .maxSize('10MB')
  .multiple()
  .required()
```

**Boolean/Checkbox Fields:**
```javascript
field.boolean()
  .required()
```

---

### 6. `gen:form` - Form Component Generator

Generates production-ready form components with React Hook Form, Zod validation, and DDDS components.

#### Command

```bash
npm run gen:form
```

Or generate directly from a schema file:

```bash
npm run gen:form schemas/contact-form.schema.js
```

#### Interactive Prompts

1. **Select schema file**: Choose from available `.schema.js` files in `/schemas`
2. **Select feature**: Choose which feature this form belongs to
3. **Nested path** (optional): Subdirectory within the feature (e.g., `forms`)

#### Generated Structure

```
src/components/features/contact/forms/ContactForm/
├── ContactForm.tsx               # Main form component
├── ContactForm.module.css        # Styles
├── ContactForm.types.ts          # TypeScript types
├── contactFormSchema.ts          # Zod schema
├── ContactForm.test.tsx          # Vitest tests
├── ContactForm.stories.tsx       # Storybook stories
├── useContactForm.ts             # Custom hook (if features include autoSave)
├── contactFormStore.ts           # Zustand store (if features include persistence)
└── index.ts                      # Barrel export

src/lib/locales/no/forms/
└── contactForm.json              # Norwegian translations

src/lib/locales/en/forms/
└── contactForm.json              # English translations
```

#### Generated Form Component Example

```tsx
'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button, Card, Spinner, Textfield, Select } from '@digdir/designsystemet-react';
import { useTranslation } from '@/lib/i18n';
import clsx from 'clsx';
import { contactFormSchema } from './contactFormSchema';
import { ContactFormData } from './ContactForm.types';
import styles from './ContactForm.module.css';

interface ContactFormProps {
  onSubmit: (data: ContactFormData) => void;
  defaultValues?: Partial<ContactFormData>;
  className?: string;
  isLoading?: boolean;
}

export function ContactForm({ 
  onSubmit, 
  defaultValues, 
  className,
  isLoading = false
}: ContactFormProps) {
  const { t } = useTranslation('contactForm');
  
  const form = useForm<ContactFormData>({
    resolver: zodResolver(contactFormSchema),
    defaultValues,
  });

  const onFormSubmit = async (data: ContactFormData) => {
    try {
      await onSubmit(data);
    } catch (error) {
      console.error('Form submission failed:', error);
    }
  };

  if (isLoading || form.formState.isSubmitting) {
    return (
      <Card className={clsx(styles.form, className)}>
        <div className={styles.loadingContainer}>
          <Spinner size="large" />
          <p>{t('actions.loading')}</p>
        </div>
      </Card>
    );
  }

  return (
    <Card className={clsx(styles.form, className)}>
      <form onSubmit={form.handleSubmit(onFormSubmit)} className={styles.formContainer}>
        {/* Personal Info Section */}
        <section className={styles.section}>
          <h2 className={styles.sectionTitle}>{t('sections.personalInfo')}</h2>
          
          <div className={styles.fieldGrid}>
            <div className={styles.field}>
              <Textfield
                type="text"
                label={t('fields.firstName.label')}
                placeholder={t('fields.firstName.placeholder')}
                error={form.formState.errors.firstName?.message}
                required={true}
                {...form.register('firstName')}
              />
            </div>
            
            <div className={styles.field}>
              <Textfield
                type="text"
                label={t('fields.lastName.label')}
                placeholder={t('fields.lastName.placeholder')}
                error={form.formState.errors.lastName?.message}
                required={true}
                {...form.register('lastName')}
              />
            </div>
            
            <div className={styles.field}>
              <Textfield
                type="email"
                label={t('fields.email.label')}
                placeholder={t('fields.email.placeholder')}
                error={form.formState.errors.email?.message}
                required={true}
                {...form.register('email')}
              />
            </div>
          </div>
        </section>

        {/* Message Details Section */}
        <section className={styles.section}>
          <h2 className={styles.sectionTitle}>{t('sections.messageDetails')}</h2>
          
          <div className={styles.fieldGrid}>
            <div className={styles.field}>
              <Textfield
                as="textarea"
                label={t('fields.message.label')}
                placeholder={t('fields.message.placeholder')}
                rows={6}
                error={form.formState.errors.message?.message}
                required={true}
                {...form.register('message')}
              />
            </div>
            
            <div className={styles.field}>
              <Select
                label={t('fields.contactMethod.label')}
                error={form.formState.errors.contactMethod?.message}
                required={true}
                {...form.register('contactMethod')}
              >
                <option value="">{t('common.select_option')}</option>
                <option value="email">{t('contactMethodOptions.email')}</option>
                <option value="phone">{t('contactMethodOptions.phone')}</option>
                <option value="mail">{t('contactMethodOptions.mail')}</option>
              </Select>
            </div>
            
            <div className={styles.field}>
              <label className={styles.checkboxLabel}>
                <input
                  type="checkbox"
                  className={styles.checkbox}
                  {...form.register('agreeToTerms')}
                />
                {t('fields.agreeToTerms.label')}
              </label>
            </div>
          </div>
        </section>
        
        {/* Submit Actions */}
        <div className={styles.actions}>
          <Button
            type="submit"
            size="large"
            disabled={form.formState.isSubmitting}
          >
            {form.formState.isSubmitting ? (
              <>
                <Spinner size="small" />
                {t('actions.submitting')}
              </>
            ) : (
              t('actions.submit')
            )}
          </Button>
          
          <Button
            type="button"
            variant="secondary"
            size="large"
            onClick={() => window.history.back()}
          >
            {t('actions.cancel')}
          </Button>
        </div>
      </form>
    </Card>
  );
}
```

#### Generated Zod Schema Example

```typescript
import { z } from 'zod';

export const contactFormSchema = z.object({
  firstName: z.string().min(2, 'validation.string.min').max(50, 'validation.string.max'),
  lastName: z.string().min(2, 'validation.string.min').max(50, 'validation.string.max'),
  email: z.string().email(),
  message: z.string().min(10, 'validation.string.min'),
  contactMethod: z.enum(['email', 'phone', 'mail']),
  agreeToTerms: z.boolean(),
});

export type ContactFormData = z.infer<typeof contactFormSchema>;
```

#### Generated Translation Files

**Norwegian (`no/forms/contactForm.json`):**

```json
{
  "sections": {
    "personalInfo": "Personlig informasjon",
    "messageDetails": "Melding"
  },
  "fields": {
    "firstName": {
      "label": "Fornavn",
      "placeholder": "Skriv inn fornavn"
    },
    "lastName": {
      "label": "Etternavn",
      "placeholder": "Skriv inn etternavn"
    },
    "email": {
      "label": "E-post",
      "placeholder": "din@epost.no"
    },
    "message": {
      "label": "Melding",
      "placeholder": "Skriv din melding her..."
    },
    "contactMethod": {
      "label": "Foretrukket kontaktmetode"
    },
    "agreeToTerms": {
      "label": "Jeg godtar vilkårene"
    }
  },
  "contactMethodOptions": {
    "email": "E-post",
    "phone": "Telefon",
    "mail": "Post"
  },
  "validation": {
    "string": {
      "required": "Dette feltet er påkrevd",
      "min": "For kort",
      "max": "For langt"
    }
  },
  "actions": {
    "submit": "Send inn",
    "submitting": "Sender...",
    "cancel": "Avbryt",
    "loading": "Laster..."
  },
  "common": {
    "select_option": "Velg et alternativ..."
  }
}
```

#### Usage Example

```tsx
// In a page or parent component
import { ContactForm } from '@/components/features/contact';

export default function ContactPage() {
  const handleSubmit = async (data) => {
    console.log('Form data:', data);
    // Send to API
  };

  return (
    <div>
      <h1>Contact Us</h1>
      <ContactForm onSubmit={handleSubmit} />
    </div>
  );
}
```

---

### 7. `i18n:update` - i18n Configuration Updater

Automatically scans translation files and updates the i18n configuration.

#### Command

```bash
npm run i18n:update
```

#### What It Does

1. Scans `/src/lib/locales/` for all JSON translation files
2. Generates import statements for each file
3. Builds the resource structure for i18next
4. Updates `/src/lib/i18n/config.ts` with all translations

#### When to Use

Run this command whenever you:
- Add a new translation file
- Add a new locale directory
- Reorganize translation files

#### Before Running

```
src/lib/locales/
├── no/
│   ├── common.json
│   └── forms/
│       └── contactForm.json
└── en/
    ├── common.json
    └── forms/
        └── contactForm.json
```

#### After Running

```typescript
// config.ts (auto-generated)
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';

import commonNo from '../locales/no/common.json';
import formsContactFormNo from '../locales/no/forms/contactForm.json';
import commonEn from '../locales/en/common.json';
import formsContactFormEn from '../locales/en/forms/contactForm.json';

const resources = {
  no: {
    common: commonNo,
    forms: {
      contactForm: formsContactFormNo
    }
  },
  en: {
    common: commonEn,
    forms: {
      contactForm: formsContactFormEn
    }
  },
};

i18n.use(initReactI18next).init({
  resources,
  lng: 'no',
  fallbackLng: 'no',
  defaultNS: 'common',
  ns: ['common', 'forms.contactForm'],
  interpolation: { escapeValue: false },
  react: { useSuspense: false },
});

export default i18n;
```

---

## Advanced Usage

### Generator Options via CLI

Most generators support passing options directly:

```bash
# Component generator
npm run gen:component MyButton -- --type=ui --no-tests

# API generator
npm run gen:api users -- --auth=true --endpoints=list,single,create
```

### Custom Templates

You can override default templates by creating a `.generators/` directory in your project root:

```
.generators/
└── templates/
    └── component/
        └── component.tsx.hbs
```

### Generator Hooks

Add pre/post generation hooks in `.generators/hooks.js`:

```javascript
module.exports = {
  beforeGenerate: async (type, options) => {
    console.log(`Generating ${type}...`);
  },
  
  afterGenerate: async (type, files) => {
    console.log(`Created ${files.length} files`);
  },
};
```

## Troubleshooting

### Generator Fails with "Permission Denied"

Run with elevated permissions:
```bash
sudo npm run gen:component
```

Or fix permissions on your node_modules:
```bash
sudo chown -R $(whoami) node_modules
```

### Generated Files Not Updating in VS Code

Reload the TypeScript server: `Cmd+Shift+P` → "TypeScript: Restart TS Server"

### Import Paths Not Resolving

Ensure your `tsconfig.json` has the correct paths:

```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

### Schema Parser Errors

If the schema generator fails:

1. Check that you're using valid JavaScript in the schema file
2. Don't use `require()` or `import` in schema files
3. Avoid arrow functions - use `function() {}` syntax

### Translation Keys Not Found

After adding translation files, run:

```bash
npm run i18n:update
```

Then restart your dev server.

## Learn More

- [React Hook Form Documentation](https://react-hook-form.com/)
- [Zod Documentation](https://zod.dev/)
- [TanStack Query Documentation](https://tanstack.com/query/latest)
- [Zustand Documentation](https://zustand-demo.pmnd.rs/)
- [i18next Documentation](https://www.i18next.com/)

---

**Created with ❤️ as Part of the Statsbygg Component System**