npm package: @sfdc-webapps/cli
Description: CLI tool for applying feature patches to base apps
Latest version: 1.0.6

# Feature Patch CLI

CLI tool for applying feature patches to apps in the webapps-templates monorepo.

## Installation

Build the CLI from the monorepo root:

```bash
yarn build --workspace=@sfdc-webapps/cli
```

## Usage

### Apply patches

```bash
# Apply patches to a target directory (base app is used as reference only)
yarn apply-patches <feature-path> <app-path> <target-dir>

# Examples:
# Apply navigation-menu patches to my-app (using base-react-app as reference)
yarn apply-patches packages/feature-navigation-menu packages/base-react-app my-app

# Skip dependency installation
yarn apply-patches packages/feature-navigation-menu packages/base-react-app my-app -- --skip-dependency-changes

# Reset target directory to base app state before applying (preserves node_modules)
yarn apply-patches packages/feature-navigation-menu packages/base-react-app my-app -- --reset
```

### Direct usage

```bash
# From monorepo root
node packages/cli/dist/index.js <feature-path> <app-path> <target-dir>

# With flags
node packages/cli/dist/index.js <feature-path> <app-path> my-app --skip-dependency-changes --reset
```

### Create a new feature

Create a new feature package from the base-feature template:

```bash
# Create a new feature (will be prefixed with "feature-")
yarn new-feature <feature-name>

# Examples:
yarn new-feature navigation       # Creates packages/feature-navigation
yarn new-feature user-dashboard   # Creates packages/feature-user-dashboard
yarn new-feature api-client       # Creates packages/feature-api-client
```

#### Direct usage

```bash
# From monorepo root
node packages/cli/dist/index.js new-feature <feature-name>
```

#### Feature Name Requirements

- **Format**: Must be in kebab-case (lowercase with hyphens)
- **Characters**: Only alphanumeric characters and hyphens allowed
- **Cannot**: Start or end with hyphens, have consecutive hyphens
- **Reserved**: Cannot be "base" or "cli"
- **Auto-prefix**: Automatically prefixed with "feature-" (e.g., `nav-menu` → `feature-nav-menu`)

Examples:
- ✅ `navigation` → `feature-navigation`
- ✅ `user-auth` → `feature-user-auth`
- ✅ `dashboard-v2` → `feature-dashboard-v2`
- ❌ `Navigation` (uppercase)
- ❌ `user_auth` (underscore)
- ❌ `--menu` (starts with hyphen)
- ❌ `nav--menu` (consecutive hyphens)

#### What the command does

The `new-feature` command:

1. **Validates** the feature name format
2. **Checks** that the feature doesn't already exist
3. **Copies** the base-feature template to `packages/feature-{name}/`
4. **Renames** the template directory from `base-feature` to `{name}`
5. **Updates** `package.json` with the new feature name
6. **Updates** `tsconfig.app.json` with the new directory path
7. **Creates** a ready-to-use feature package

The created feature will have:
- ✅ Correct `package.json` configuration
- ✅ TypeScript configuration
- ✅ Feature structure following conventions
- ✅ Ready for customization and development

#### Next steps after creation

```bash
cd packages/feature-{name}
# 1. Customize template files in template/ directory
# 2. Update feature.ts with feature configuration
# 3. Test with: yarn apply-patches packages/feature-{name} packages/base-react-app test-app
```

### Options

- `<feature-path>`: Path to the feature directory (can be relative to monorepo root or absolute). The feature must contain a feature.ts file.
- `<app-path>`: Path to the base app directory (can be relative to monorepo root or absolute). Used as a reference for file inheritance and validation. The base app remains unchanged.
- `<target-dir>`: **Required**. Target directory where the feature will be applied. Can be a relative or absolute path. The CLI will create this directory and apply all features to it.
- `--skip-dependency-changes`: Skip installing dependencies from package.json. Only file changes will be applied.
- `--reset`: Reset target directory to base app state before applying patches. Syncs the target with the base app by removing extra files, updating changed files, and adding new files (preserves node_modules).

## What it does

The CLI tool:

1. **Validates** that the specified feature path exists and contains a feature.ts file, and the base app path is valid
2. **Prepares target directory**: Creates the target directory (or resets it to base app state if `--reset` flag is used, preserving node_modules)
3. **Resolves dependencies**: Recursively resolves all feature dependencies, detecting circular dependencies and building an ordered list where dependencies are applied before dependent features
4. **Loads** the feature definitions from each `feature.ts` file in dependency order
5. **Discovers files** from each feature's template directory (defaults to `template`, configurable via `templateDir` in feature.ts)
6. **Validates paths**: For each feature, ensures:
   - No conflicting paths exist (e.g., both `routes.tsx` and `__delete__routes.tsx`, or `__prepend__global.css` and `__append__global.css`)
   - Files marked with `__delete__`, `__inherit__`, `__prepend__`, or `__append__` exist in the base app
7. **Applies file changes** from each feature's template directory in dependency order:
   - **Delete operations**: Removes files/directories marked with `__delete__` prefix from the target app
   - **Inherit operations**: Skips files marked with `__inherit__` (inherited from base app)
   - **Prepend operations**: Adds content from feature file before the base file's content (for files marked with `__prepend__`)
   - **Append operations**: Adds content from feature file after the base file's content (for files marked with `__append__`)
   - **Import path fixing**: Automatically removes `__inherit__` prefix from import statements in JS/TS files
   - **File changes**: Copies each file from the template directory to the target
   - **Route merging**: Intelligently merges route files (`routes.tsx`), accumulating routes from all features
8. **Aggregates and installs dependencies**: Collects all NPM dependencies from all features and installs them in a single `yarn` command (unless `--skip-dependency-changes` is used). Detects and errors on version conflicts.

## Examples

### Example: Apply feature patches to create a new app

```bash
$ yarn apply-patches packages/feature-navigation-menu packages/base-react-app my-app

Applying patches: packages/feature-navigation-menu → my-app
ℹ Validating paths...
✓ Validation passed
ℹ Creating target directory my-app...
✓ Target directory created

Resolving Dependencies
ℹ No dependencies to resolve


Applying: packages/feature-navigation-menu
ℹ Discovering files...
ℹ Found 6 file(s)
ℹ Validating paths...
✓ Paths validated
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/navigationMenu.tsx
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/appLayout.tsx
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/routes.tsx
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/router-utils.tsx
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/about.tsx
✓ Added digitalExperiences/webApplications/feature-navigation-menu/src/new.tsx

Installing dependencies
ℹ Installing dependencies...
[yarn output]
✓ Dependencies installed

✓ Success
✓ Created: /path/to/monorepo/my-app
```

## Creating Features

**Quick Start:** Use the CLI to create a new feature from the template:

```bash
yarn new-feature your-feature-name
```

This creates a new feature at `packages/feature-your-feature-name/` with all the necessary configuration files. Then customize the template files in the `template/` directory.

For manual setup or advanced configuration, see below...

---

Features are defined in your feature's `feature.ts` file and must be exported as a **default export**. The default export can be either a single feature object or an array of features. Import types from `packages/cli/src/types.js`.

### Feature Structure

```
packages/feature-my-feature/
├── feature.ts           # Feature configuration
├── package.json         # NPM dependencies for development
└── template/            # Template files (default directory name)
    ├── webApp/          # Web application files (mapped to digitalExperiences/webApplications/<feature-name>/)
    │   └── src/
    │       ├── routes.tsx
    │       ├── component1.tsx
    │       └── component2.tsx
    └── classes/         # SFDX metadata (placed at root level in dist)
        └── MyClass.cls
```

**Note**: The CLI handles two types of files differently:
- **Web Application files** (under `webApp/`): Automatically mapped to `digitalExperiences/webApplications/<feature-name>/`
- **SFDX metadata files** (like `classes/`, `triggers/`, `objects/`, etc.): Placed at root level in the output directory

This structure ensures proper organization for both digital experience applications and Salesforce metadata.

### Feature Configuration

The feature configuration file specifies:
- `templateDir`: Directory containing template files (defaults to `template`)
- `webAppName`: Name of the web application (defaults to the feature name extracted from the directory). This is used for constructing the default route path.
- `routeFilePath`: Path to the routes file for merging (defaults to `digitalExperiences/webApplications/<webAppName>/src/routes.tsx`)
- `packageJson`: NPM dependencies to install in the target app
- `dependencies`: Array of other features this feature depends on (applied first)

### Basic Feature Example:

```typescript
import type { Feature } from '../cli/src/types.js';

const feature: Feature = {
  // All fields are optional with sensible defaults
  templateDir: 'template', // Optional, defaults to 'template'
  webAppName: 'my-feature', // Optional, defaults to feature directory name
  // routeFilePath defaults to 'digitalExperiences/webApplications/<webAppName>/src/routes.tsx'
  packageJson: {
    dependencies: {
      'react-router': '^7.10.1',
    },
  },
};

export default feature;
```

### Feature with Dependencies:

```typescript
import type { Feature } from '../cli/src/types.js';

const feature: Feature = {
  // This feature depends on navigation-menu feature
  // navigation-menu will be applied first, then this feature
  dependencies: ['packages/feature-navigation-menu'],
  packageJson: {
    dependencies: {
      'some-package': '^1.0.0',
    },
  },
};

export default feature;
```

### Custom Configuration:

```typescript
import type { Feature } from '../cli/src/types.js';

const feature: Feature = {
  templateDir: 'src', // Use 'src' instead of 'template'
  webAppName: 'custom-app-name', // Override default app name
  routeFilePath: 'custom/path/to/routes.tsx', // Custom route file path
  packageJson: {
    dependencies: {
      'react-router': '^7.10.1',
    },
  },
};

export default feature;
```

**Notes:**
- `templateDir`: All files in this directory will be discovered and applied to the target app
- `webAppName`: Used to construct the default route path and organize files. Defaults to the feature directory name (e.g., `feature-navigation-menu` → `feature-navigation-menu`)
- `routeFilePath`: Must be a path relative to `templateDir`. If not specified, defaults to `digitalExperiences/webApplications/<webAppName>/src/routes.tsx`

## Path Mappings

Path mappings allow features to use simplified directory structures that are automatically transformed to the full Salesforce Digital Experience structure. This makes feature templates easier to create and maintain by removing repetitive nested directory paths.

### Default Behavior (Enabled by Default)

By default, all features automatically get the `webApp` mapping, which transforms web application files into the proper nested structure. For example, in `feature-navigation-menu`:

```
template/webApp/src/app.tsx → dist/digitalExperiences/webApplications/feature-navigation-menu/src/app.tsx
```

This simplifies feature templates by removing the repetitive nested directory structure.

**Important**: Only files under `webApp/` get the nested structure. SFDX metadata types (like `classes/`, `triggers/`, `objects/`, `lwc/`, etc.) are placed at root level:

```
feature-navigation-menu/template/
├── webApp/
│   └── src/
│       └── app.tsx          → dist/digitalExperiences/webApplications/feature-navigation-menu/src/app.tsx
└── classes/
    └── NavMenu.cls          → dist/classes/NavMenu.cls (root level)
```

### Using the Default Mapping

Simply organize your template files under `webApp/` for web application code, and at the root level for SFDX metadata:

```typescript
// feature.ts - No configuration needed
export default {};
```

```
template/
├── webApp/           # Web application files (automatically mapped)
│   └── src/
│       ├── routes.tsx
│       ├── app.tsx
│       └── components/
│           └── Header.tsx
└── classes/          # SFDX metadata (placed at root level)
    └── MyClass.cls
```

**Result**:
- Web app files go to `dist/digitalExperiences/webApplications/feature-name/src/`
- SFDX metadata stays at root: `dist/classes/MyClass.cls`

### Disabling Path Mappings (Opt-Out)

Use full paths when you need precise control or for backwards compatibility:

```typescript
// feature.ts
export default {
  pathMappings: {
    enabled: false  // Disable automatic mapping
  }
};
```

```
template/
└── digitalExperiences/    # Use full structure
    └── webApplications/
        └── <feature-name>/
            └── src/
                └── routes.tsx
```

### Custom Path Mappings

Define custom mappings for non-standard structures:

```typescript
// feature.ts
export default {
  pathMappings: {
    mappings: [
      {
        from: 'web',
        to: 'digitalExperiences/webApplications/custom-app-name'
      }
    ]
  }
};
```

```
template/
└── web/              # Custom prefix
    └── src/
        └── app.tsx
```

**Result**: Maps `web/src/app.tsx` → `digitalExperiences/webApplications/custom-app-name/src/app.tsx`

### Multiple Mappings

You can define multiple mappings in one feature:

```typescript
export default {
  pathMappings: {
    mappings: [
      { from: 'webApp', to: 'digitalExperiences/webApplications/my-app' },
      { from: 'shared', to: 'digitalExperiences/shared-resources' }
    ]
  }
};
```

### Mixed Path Formats

You can mix web application files, SFDX metadata, and full paths in the same feature:

```
template/
├── webApp/                    # Mapped to digitalExperiences/webApplications/<feature-name>/
│   └── src/
│       └── app.tsx
├── classes/                   # SFDX metadata (placed at root)
│   └── MyClass.cls
├── triggers/                  # SFDX metadata (placed at root)
│   └── MyTrigger.trigger
└── digitalExperiences/        # Full paths (passed through)
    └── siteAssets/
        └── logo.png
```

All formats work together seamlessly:
- `webApp/` files → `digitalExperiences/webApplications/<feature-name>/`
- SFDX metadata (`classes/`, `triggers/`, `objects/`, `lwc/`, etc.) → Root level
- Full paths (already containing `digitalExperiences/`) → Used as-is

### Path Mapping Examples

**Example 1: Default mapping (recommended)**
```typescript
export default {};  // That's it!
```

**Example 2: Opt-out for backwards compatibility**
```typescript
export default {
  pathMappings: { enabled: false }
};
```

**Example 3: Custom app name**
```typescript
export default {
  pathMappings: {
    mappings: [
      { from: 'webApp', to: 'digitalExperiences/webApplications/custom-name' }
    ]
  }
};
```

**Example 4: Completely custom structure**
```typescript
export default {
  pathMappings: {
    mappings: [
      { from: 'src', to: 'app/sources' },
      { from: 'assets', to: 'public/static' }
    ]
  }
};
```

### How Path Mappings Work

1. **Discovery**: CLI discovers all files in your template directory
2. **Mapping**: Each file path is checked against mapping rules (first match wins)
3. **Transformation**: Matching prefix is replaced with target prefix
4. **Pass-Through**: Paths that don't match any mapping are used as-is
5. **Application**: Transformed paths are used for file operations

This ensures:
- ✅ Backwards compatibility (old features still work)
- ✅ Simplified templates (new features are easier to create)
- ✅ Flexibility (custom mappings for special cases)
- ✅ No breaking changes (opt-in for existing features, opt-out available)

## Feature Dependencies

Features can depend on other features. Dependencies are automatically resolved and applied in the correct order.

### How Dependencies Work

1. **Declaration**: Specify dependencies in your `feature.ts` file
2. **Resolution**: CLI recursively resolves all dependencies (including nested dependencies)
3. **Ordering**: Dependencies are always applied before the feature that depends on them
4. **Circular Detection**: CLI detects and prevents circular dependencies
5. **File Layering**: Files from dependencies can be overridden by dependent features (main feature wins)
6. **Route Accumulation**: Routes from all features are merged together

### Dependency Resolution Order

When you apply a feature with dependencies:

```
Feature A depends on Feature B
Feature B depends on Feature C

Application order: C → B → A (dependencies first)
```

The CLI builds a complete dependency graph and applies features in topological order.

### Example: Building on Navigation Features

```typescript
// packages/feature-navigation-menu/feature.ts
import type { Feature } from '../cli/src/types.js';

const feature: Feature = {
  // Navigation menu has no dependencies
};

export default feature;
```

```typescript
// packages/feature-admin-dashboard/feature.ts
import type { Feature } from '../cli/src/types.js';

const feature: Feature = {
  // Admin dashboard builds on top of navigation menu
  dependencies: ['packages/feature-navigation-menu'],
};

export default feature;
```

When you apply `feature-admin-dashboard`:
1. CLI resolves `feature-navigation-menu` as a dependency
2. Applies `feature-navigation-menu` first (navigation menu files and routes)
3. Applies `feature-admin-dashboard` second (dashboard files and routes)
4. Result: App has both navigation menu and admin dashboard

### Nested Dependencies

Dependencies can have their own dependencies. The CLI resolves them recursively:

```
Feature App depends on Feature Dashboard
Feature Dashboard depends on Feature Navigation
Feature Navigation depends on Feature Auth

Application order: Auth → Navigation → Dashboard → App
```

### Circular Dependency Detection

The CLI detects and prevents circular dependencies:

```typescript
// Feature A depends on B
// Feature B depends on C
// Feature C depends on A

// CLI will error:
// "Circular dependency detected:
//  packages/feature-a → packages/feature-b → packages/feature-c → packages/feature-a"
```

**Fix**: Remove one of the dependencies to break the cycle.

### Main Feature Wins

When multiple features modify the same file:
- **Dependencies applied first**: Their files are written to the target
- **Main feature applied last**: Its files overwrite dependency files
- **Result**: Main feature can customize/override dependency behavior

Example:
```
feature-navigation-menu provides: src/appLayout.tsx
feature-custom-app also provides: src/appLayout.tsx

When applying feature-custom-app:
1. navigation-menu's appLayout.tsx is applied
2. custom-app's appLayout.tsx overwrites it
3. Final result: custom-app's version is used
```

### Route Merging with Dependencies

Routes from all features are merged together:

**Base App Routes:**
```typescript
export const routes = [
  {
    path: '/',
    children: [
      { index: true, element: <Home /> }
    ]
  }
];
```

**navigation-menu Routes:**
```typescript
export const routes = [
  {
    path: '/',
    children: [
      { path: 'about', element: <About /> },
      { path: 'contact', element: <Contact /> }
    ]
  }
];
```

**Final Merged Routes:**
```typescript
export const routes = [
  {
    path: '/',
    children: [
      { index: true, element: <Home /> },      // From base
      { path: 'about', element: <About /> },   // From navigation-menu
      { path: 'contact', element: <Contact /> } // From navigation-menu
    ]
  }
];
```

Routes accumulate across all features, preserving routes from base app and all dependencies.

### Dependency Paths

Dependency paths can be:
- **Relative to monorepo root**: `'packages/feature-navigation-menu'`
- **Absolute paths**: `'/absolute/path/to/feature'`

The CLI normalizes and resolves all paths consistently.

### Diamond Dependencies

When multiple features depend on the same feature:

```
Feature A depends on Feature C
Feature B depends on Feature C
Feature Main depends on A and B

Dependency graph:
    Main
    /  \
   A    B
    \  /
     C
```

**Resolution**: Feature C is applied once (not duplicated).

**Application order**: `C → A → B → Main`

### Watch Mode with Dependencies

When using watch mode, dependencies are:
- Applied once on initial startup with reset to ensure clean state
- Re-applied when template files change (always resets to base app state, preserving node_modules)
- Not watched for changes (only the main feature is watched)

This is efficient for development: edit your main feature while keeping dependencies stable. The reset behavior ensures stale files are removed while preserving built artifacts in node_modules.

## File Application

The CLI discovers all files in your feature's template directory and applies them to the target app:

1. **Standard Files**: Copied directly to the target, creating directories as needed
2. **Delete Markers**: Files/directories prefixed with `__delete__` mark files for deletion from the target app
3. **Inherit Markers**: Files prefixed with `__inherit__` are kept in the feature for type safety but not copied (inherited from base app)
4. **Prepend Markers**: Files prefixed with `__prepend__` add their content before the base file's content
5. **Append Markers**: Files prefixed with `__append__` add their content after the base file's content
6. **Route Files** (`routes.tsx`): Intelligently merged with existing routes to preserve base app routes

### Deleting Files and Directories

Features can delete files or directories from the target app by using the `__delete__` prefix in the template directory.

#### How It Works

1. Create a file with the `__delete__` prefix in your feature's template directory
2. The prefix can appear anywhere in the path
3. When the feature is applied, the corresponding file/directory will be deleted from the target app
4. The content of the delete marker file is ignored (can be empty or contain comments)

#### Examples

**Delete a single file:**
```
template/
└── src/
    └── __delete__routes.tsx    # Deletes src/routes.tsx from target app
```

**Delete a directory:**
```
template/
└── src/
    └── __delete__pages/        # Deletes src/pages/ directory from target app
        └── .gitkeep            # Placeholder file (content ignored)
```

**Delete from nested path:**
```
template/
└── src/
    └── components/
        └── __delete__Footer.tsx  # Deletes src/components/Footer.tsx
```

**Delete a parent directory:**
```
template/
└── __delete__src/
    └── legacy/                 # Deletes src/legacy/ directory from target app
        └── .gitkeep
```

#### Validation

The CLI validates that you don't have conflicting paths:
- ❌ **Invalid**: Having both `routes.tsx` and `__delete__routes.tsx` in the same template
- ✅ **Valid**: Having only `__delete__routes.tsx` (to delete) or only `routes.tsx` (to add/update)

If a conflict is detected, the CLI will throw an error:
```
Path conflict detected: "src/routes.tsx" appears multiple times in the template.
This can happen when both a file and its __delete__ marker exist.
Please remove one of them.
```

#### Use Cases

- **Remove obsolete files**: Delete deprecated components or utilities that the feature replaces
- **Clean up after refactoring**: Remove files that are no longer needed with the new feature
- **Remove base app scaffolding**: Delete placeholder files from the base app that the feature supersedes

For example, the `vibe-coding-starter` feature deletes the base app's `routes.tsx` because it provides a single-page app in `index.tsx` instead.

### Inheriting Files from Base App

Features can maintain type-safe references to base app files without copying them using the `__inherit__` prefix. This is useful when your feature code needs to import and reference files from the base app while keeping TypeScript/IDE support in your feature directory.

#### How It Works

1. Create a file with the `__inherit__` prefix in your feature's template directory
2. Copy the base app file's contents to the `__inherit__` file (for type checking and IDE support)
3. When the feature is applied, the file is NOT copied to the target (the base app's file is used)
4. The feature gets type safety and autocomplete for the inherited file

#### Why Use `__inherit__`?

**Problem**: You want to import a base app file in your feature code, but:
- If you don't have the file in your feature, TypeScript shows errors and IDE autocomplete doesn't work
- If you copy the file to your feature, it will overwrite the base app file when applied

**Solution**: Use `__inherit__` to keep the file in your feature for development, but skip it during application.

#### Automatic Import Path Fixing

**Important**: The CLI automatically fixes import paths in your feature files!

When you import from `__inherit__` files in your feature code:
```typescript
// In your feature's template
import { routes } from './__inherit__routes';
import AppLayout from './__inherit__appLayout';
```

The CLI automatically removes the `__inherit__` prefix when applying the feature:
```typescript
// In the target app after applying
import { routes } from './routes';
import AppLayout from './appLayout';
```

**Supported patterns:**
- `import ... from './__inherit__file'` → `import ... from './file'`
- `export ... from './__inherit__file'` → `export ... from './file'`
- `require('./__inherit__file')` → `require('./file')`
- `import('./__inherit__file')` → `import('./file')`
- Works with single quotes (`'`), double quotes (`"`), and backticks (`` ` ``)
- Works with relative paths (`../`, `./`)

**Only processes JavaScript/TypeScript files:**
- `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`
- Other files (`.md`, `.json`, etc.) are not processed

This means you can freely import from `__inherit__` files in your feature code, and the imports will "just work" when the feature is applied!

#### Examples

**Inherit routes for type safety:**
```typescript
// Feature structure
template/
└── src/
    ├── __inherit__routes.tsx    // Copy of base app routes for types
    └── index.tsx                // Can import routes safely

// In your feature's index.tsx
import { routes } from './routes';  // TypeScript works!

// When applied:
// - routes.tsx from base app is used (not overwritten)
// - Your index.tsx can still import it
```

**Inherit shared layout:**
```
template/
└── src/
    ├── __inherit__appLayout.tsx  # Copy from base for types
    └── pages/
        └── MyPage.tsx            # Can import appLayout safely
```

#### Validation

The CLI validates `__inherit__` files:

1. **No conflicts**: You cannot have both `routes.tsx` and `__inherit__routes.tsx` in the same template
   - ❌ **Invalid**: Both `routes.tsx` and `__inherit__routes.tsx`
   - ✅ **Valid**: Only `__inherit__routes.tsx`

2. **Base file must exist**: The file must exist in the base app
   - ❌ **Invalid**: `__inherit__nonexistent.tsx` when file doesn't exist in base app
   - ✅ **Valid**: `__inherit__routes.tsx` when `routes.tsx` exists in base app

If validation fails, you'll see clear error messages:
```
Validation error: Cannot inherit file that doesn't exist!

File marked for inheritance: src/routes.tsx
Expected location in base app: /path/to/base/src/routes.tsx
The file doesn't exist in the base app.
```

#### Use Cases

- **Type-safe imports**: Import base app files in your feature with full TypeScript support
- **Shared layouts**: Reference base app layouts without overwriting them
- **Route definitions**: Import base routes to extend or reference them
- **Shared utilities**: Reference base app utility functions with autocomplete

#### Complete Example

```typescript
// Base app has: src/routes.tsx, src/appLayout.tsx

// Feature template structure:
template/
└── src/
    ├── __inherit__routes.tsx     # Copy from base (for types only)
    ├── __inherit__appLayout.tsx  # Copy from base (for types only)
    └── pages/
        └── Dashboard.tsx         # New page that imports both

// In template/src/pages/Dashboard.tsx (during development):
import { routes } from '../__inherit__routes';     // Import from __inherit__ file
import AppLayout from '../__inherit__appLayout';   // TypeScript works!

export default function Dashboard() {
  return <AppLayout>Dashboard using {routes.length} routes</AppLayout>;
}

// When applied, Dashboard.tsx is automatically transformed to:
import { routes } from '../routes';                // __inherit__ removed!
import AppLayout from '../appLayout';              // __inherit__ removed!

export default function Dashboard() {
  return <AppLayout>Dashboard using {routes.length} routes</AppLayout>;
}

// Final result in target app:
// - src/routes.tsx: inherited from base (not overwritten) ✓
// - src/appLayout.tsx: inherited from base (not overwritten) ✓
// - src/pages/Dashboard.tsx: added from feature with fixed imports ✓
```

### Prepending and Appending to Base Files

Features can add content to the beginning (`__prepend__`) or end (`__append__`) of existing base app files. This is useful for adding styles, imports, or configuration to base files without completely replacing them.

#### How It Works

1. **Prepend**: Content from the feature file is added **before** the base file's content
2. **Append**: Content from the feature file is added **after** the base file's content
3. The prefix can appear anywhere in the path
4. Import paths with `__inherit__` are automatically fixed in the content

#### Examples

**Append CSS to global styles:**
```
template/
└── src/
    └── styles/
        └── __append__global.css    # Adds content after base global.css
```

```css
/* Feature's __append__global.css */
:root {
  --feature-color: #066afe;
  --feature-background: #ffffff;
}

.feature-specific {
  color: var(--feature-color);
}
```

**Result in target app's `global.css`:**
```css
/* Base app's existing content */
@import "tailwindcss";

body {
  margin: 0;
}

/* Content appended from feature */
:root {
  --feature-color: #066afe;
  --feature-background: #ffffff;
}

.feature-specific {
  color: var(--feature-color);
}
```

**Prepend imports to a TypeScript file:**
```
template/
└── src/
    └── __prepend__index.tsx    # Adds imports before base index.tsx
```

```typescript
/* Feature's __prepend__index.tsx */
import { initializeFeature } from './feature-init';

initializeFeature();
```

**Result in target app's `index.tsx`:**
```typescript
/* Content prepended from feature */
import { initializeFeature } from './feature-init';

initializeFeature();

/* Base app's existing content */
import React from 'react';
import ReactDOM from 'react-dom';
// ... rest of base file
```

#### Validation

The CLI validates prepend/append operations:

1. **No conflicts**: You cannot target the same file with multiple operations
   - ❌ **Invalid**: Both `__prepend__global.css` and `__append__global.css`
   - ❌ **Invalid**: Both `global.css` and `__append__global.css`
   - ✅ **Valid**: Only `__append__global.css`

2. **Base file must exist**: The target file must exist in the base app
   - ❌ **Invalid**: `__append__nonexistent.css` when file doesn't exist
   - ✅ **Valid**: `__append__global.css` when `global.css` exists in base app

If validation fails, you'll see clear error messages:
```
Path conflict detected!

The following paths resolve to the same target file:
  1. src/styles/__append__global.css (append)
  2. src/styles/__prepend__global.css (prepend)
  → Both target: src/styles/global.css

You cannot have multiple files targeting the same path.
```

#### Automatic Import Fixing

When prepending or appending TypeScript/JavaScript files, import paths with `__inherit__` are automatically fixed:

```typescript
// In feature's __prepend__index.tsx
import { routes } from './__inherit__routes';

// After prepending to target app
import { routes } from './routes';  // __inherit__ removed!
```

This works the same as regular file operations (see "Automatic Import Path Fixing" above).

#### Use Cases

**Prepend:**
- Add initialization code at the start of entry files
- Add imports before existing code
- Add type declarations or interfaces

**Append:**
- Add CSS variables and styles to global stylesheets
- Add routes or configuration entries
- Extend existing files with additional functionality
- Add utility functions or helpers

#### Complete Example

```typescript
// Base app has: src/styles/global.css
// with Tailwind configuration

// Feature adds Salesforce Design Tokens by appending

// Feature structure:
template/
└── src/
    └── styles/
        └── __append__global.css

// Feature's __append__global.css:
:root {
  /* Salesforce Design Tokens */
  --electric-blue-50: #066afe;
  --constant-white: #ffffff;
}

button {
  background-color: var(--electric-blue-50);
  color: var(--constant-white);
}

// Final result in target app's global.css:
@import "tailwindcss";         // ← Base content

body {
  @apply antialiased;          // ← Base content
}

:root {
  /* Salesforce Design Tokens */
  --electric-blue-50: #066afe; // ← Appended content
  --constant-white: #ffffff;   // ← Appended content
}

button {
  background-color: var(--electric-blue-50); // ← Appended content
  color: var(--constant-white);              // ← Appended content
}
```

## Route Merging Strategy

The `merge` change type with `routes` strategy intelligently combines route definitions from features with base app routes.

### How It Works

**Replace-Matching with Deep Children Merge:**

1. **Top-Level Routes:**
   - Routes with the **same path** → Merge their children arrays
   - Routes with **different paths** → Add feature route to result

2. **Children Array Merging** (when parent paths match):
   - Index routes (`index: true`) → Feature replaces base if both exist
   - Named routes (`path: 'about'`) → Feature replaces base if paths match
   - **New routes** → Added to children array
   - **Base routes not in feature** → Preserved

3. **Route Deletion:**
   - Routes with path starting with `__delete__` → Remove matching route from result
   - Example: `path: '__delete__new'` removes the route with `path: 'new'`
   - Throws error if route to delete doesn't exist

4. **Recursion:**
   - Applies same logic to nested children arrays

### Example

**Base App Routes**:
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        index: true,
        element: <Home />,
        handle: { showInNavigation: true, label: 'Home' }
      }
    ]
  }
]
```

**Feature Routes** (in `template/digitalExperiences/webApplications/<feature-name>/src/routes.tsx`):
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        path: 'about',
        element: <About />,
        handle: { showInNavigation: true, label: 'About' }
      },
      {
        path: 'contact',
        element: <Contact />,
        handle: { showInNavigation: false }
      }
    ]
  }
]
```

**Merged Result** (after applying feature):
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,  // Uses feature's element
    children: [
      {
        index: true,
        element: <Home />,    // ✓ Preserved from base
        handle: { showInNavigation: true, label: 'Home' }
      },
      {
        path: 'about',
        element: <About />,   // ✓ Added from feature
        handle: { showInNavigation: true, label: 'About' }
      },
      {
        path: 'contact',
        element: <Contact />, // ✓ Added from feature
        handle: { showInNavigation: false }
      }
    ]
  }
]
```

### Key Benefits

- **Preserves existing routes**: Base app's Home route stays intact
- **Adds new routes**: Feature routes (About, Contact) are added
- **No duplication**: Routes with matching paths are replaced, not duplicated
- **Deep merging**: Works with nested route structures
- **Multiple features**: Apply multiple route-adding features sequentially

Route merging happens automatically for `routes.tsx` files.

### Deleting Routes

Features can delete routes from the base app or previously applied features by using the `__delete__` prefix in the route path. This is useful when a feature needs to remove routes that were added by dependencies or the base app.

#### How It Works

1. Add a route with `path: '__delete__<route-name>'` in your feature's routes file
2. The route with the matching path (without the prefix) will be removed during merging
3. If the route doesn't exist, an error will be thrown

#### Example

**Base/Previous Routes:**
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      { index: true, element: <Home /> },
      { path: 'about', element: <About /> },
      { path: 'new', element: <New /> }
    ]
  }
]
```

**Feature Routes (deleting 'new'):**
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    children: [
      {
        path: '__delete__new',
        element: <></>  // Element value is ignored for deletion markers
      }
    ]
  }
]
```

**Merged Result:**
```typescript
export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      { index: true, element: <Home /> },   // ✓ Preserved
      { path: 'about', element: <About /> } // ✓ Preserved
      // 'new' route deleted ✓
    ]
  }
]
```

#### Validation

- **Error if route doesn't exist**: The CLI will throw an error if you attempt to delete a route that doesn't exist in the current routes
- This prevents silent failures and ensures routes are being deleted as expected

#### Use Cases

- **Remove dependency routes**: Delete routes added by feature dependencies that aren't needed
- **Clean up base routes**: Remove placeholder or example routes from the base app
- **Override parent features**: Child features can remove routes added by parent features they depend on

For example, a feature that provides a single-page app might delete all routes from the base app to start fresh.

### Automatic Import Cleanup for Deleted Files

When using route deletion (or file deletion with `__delete__` prefix), imports from deleted files are automatically removed during route merging. This prevents broken import references in the final merged code.

#### How It Works

1. During route merging, after all imports are merged from the feature file
2. The import merger scans all import statements in the target file
3. Any imports with `__delete__` in the module specifier are automatically removed
4. This ensures deleted components don't leave broken import references

#### Example

**Feature Routes File:**
```typescript
import type { RouteObject } from "react-router";
import AppLayout from "./__inherit__appLayout";
import Home from ".";
import New from "./__delete__new";  // Import from deleted file

export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        index: true,
        element: <Home />,
        handle: { showInNavigation: true, label: 'Home' }
      },
      {
        path: '__delete__new',  // Delete the 'new' route
        element: <New />,
      }
    ]
  }
]
```

**Merged Result:**
```typescript
import type { RouteObject } from "react-router";
import AppLayout from "./appLayout";
import Home from ".";
// ✓ Import from ./__delete__new automatically removed

export const routes: RouteObject[] = [
  {
    path: '/',
    element: <AppLayout />,
    children: [
      {
        index: true,
        element: <Home />,
        handle: { showInNavigation: true, label: 'Home' }
      }
      // ✓ 'new' route deleted
    ]
  }
]
```

#### Key Benefits

- **No broken imports**: Automatically removes imports from deleted files
- **Clean merged code**: Final output doesn't reference non-existent files
- **Works with file deletion**: Applies to any file marked with `__delete__` prefix
- **Seamless integration**: Happens automatically during route merging, no manual cleanup needed

## Testing

The CLI has a comprehensive test suite using Vitest, including E2E tests with gold files and unit tests for critical utilities.

**Test Coverage:**
- ✅ 12 E2E test scenarios covering all major CLI workflows
- ✅ 46+ unit tests covering route merging, import merging, and file operations
- ✅ Gold file comparison for E2E validation
- ✅ No skipped tests - all functionality fully tested

### Running Tests

```bash
# Run all tests
npm test

# Run with UI
yarn test:ui

# Run E2E tests only
yarn test:e2e

# Run unit tests only
yarn test:unit

# Run with coverage
yarn test:coverage
```

### Test Structure

```
packages/cli/
├── test/
│   ├── e2e/                    # End-to-end tests
│   │   ├── fixtures/           # Test fixtures (base apps, features)
│   │   ├── gold/               # Expected outputs for E2E tests
│   │   └── apply-patches.spec.ts
│   ├── unit/                   # Unit tests
│   │   ├── route-merger.spec.ts
│   │   ├── import-merger.spec.ts
│   │   └── file-operations.spec.ts
│   └── helpers/                # Test utilities
│       ├── compare-directories.ts
│       ├── create-temp-dir.ts
│       └── fixtures.ts
```

### E2E Tests

E2E tests verify complete CLI workflows using gold files (expected outputs). Tests cover:

- Simple feature application (adding routes/files)
- File deletion with `__delete__` prefix
- Route deletion and import cleanup
- Feature dependency resolution
- Complex operations (`__inherit__`, `__prepend__`, `__append__`)
- Error handling and validation

### Unit Tests

Unit tests focus on individual modules:

- **route-merger**: Route merging logic and deletion
- **import-merger**: Import statement merging, deduplication, type imports, and formatting
- **file-operations**: File deletion, prepending, and appending

### Updating Gold Files

When intentionally changing CLI behavior, update gold files:

```bash
UPDATE_GOLD=1 npm test
```

⚠️ **Warning**: Only update gold files after verifying the new output is correct!

### Creating New Tests

**E2E Test Example**:
```typescript
it('should apply a simple feature correctly', async () => {
  const outputDir = copyFixture('base-app', join(tempDir, 'output'));
  const featurePath = getFixturePath('feature-simple');

  await applyPatchesCommand(featurePath, outputDir, {
    skipDependencyChanges: true
  });

  const goldDir = getGoldPath('simple-apply');
  const differences = compareOrUpdate(outputDir, goldDir);

  expect(differences).toEqual([]);
});
```

**Unit Test Example**:
```typescript
it('should merge routes correctly', () => {
  const project = new Project({ useInMemoryFileSystem: true });

  const targetFile = project.createSourceFile('target.tsx', `...`);
  const featureFile = project.createSourceFile('feature.tsx', `...`);

  const result = mergeRoutes('feature.tsx', 'target.tsx', project);

  expect(result).toContain('expected content');
});
```

## Development

```bash
# Build the CLI
yarn build

# Run without building (development)
yarn dev -- <feature> <base-app>
```