@salesforce/webapp-template-cli-experimental 1.3.3

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.
Files changed (71) hide show
  1. package/LICENSE.txt +82 -0
  2. package/README.md +1464 -0
  3. package/dist/commands/apply-patches.d.ts +23 -0
  4. package/dist/commands/apply-patches.d.ts.map +1 -0
  5. package/dist/commands/apply-patches.js +650 -0
  6. package/dist/commands/apply-patches.js.map +1 -0
  7. package/dist/commands/new-app-feature.d.ts +7 -0
  8. package/dist/commands/new-app-feature.d.ts.map +1 -0
  9. package/dist/commands/new-app-feature.js +208 -0
  10. package/dist/commands/new-app-feature.js.map +1 -0
  11. package/dist/commands/new-app.d.ts +6 -0
  12. package/dist/commands/new-app.d.ts.map +1 -0
  13. package/dist/commands/new-app.js +7 -0
  14. package/dist/commands/new-app.js.map +1 -0
  15. package/dist/commands/watch-patches.d.ts +4 -0
  16. package/dist/commands/watch-patches.d.ts.map +1 -0
  17. package/dist/commands/watch-patches.js +128 -0
  18. package/dist/commands/watch-patches.js.map +1 -0
  19. package/dist/core/dependency-resolver.d.ts +40 -0
  20. package/dist/core/dependency-resolver.d.ts.map +1 -0
  21. package/dist/core/dependency-resolver.js +126 -0
  22. package/dist/core/dependency-resolver.js.map +1 -0
  23. package/dist/core/file-operations.d.ts +55 -0
  24. package/dist/core/file-operations.d.ts.map +1 -0
  25. package/dist/core/file-operations.js +350 -0
  26. package/dist/core/file-operations.js.map +1 -0
  27. package/dist/core/package-json-merger.d.ts +31 -0
  28. package/dist/core/package-json-merger.d.ts.map +1 -0
  29. package/dist/core/package-json-merger.js +149 -0
  30. package/dist/core/package-json-merger.js.map +1 -0
  31. package/dist/core/patch-loader.d.ts +18 -0
  32. package/dist/core/patch-loader.d.ts.map +1 -0
  33. package/dist/core/patch-loader.js +115 -0
  34. package/dist/core/patch-loader.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +95 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/types.d.ts +28 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +2 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/utils/debounce.d.ts +9 -0
  44. package/dist/utils/debounce.d.ts.map +1 -0
  45. package/dist/utils/debounce.js +20 -0
  46. package/dist/utils/debounce.js.map +1 -0
  47. package/dist/utils/import-merger.d.ts +17 -0
  48. package/dist/utils/import-merger.d.ts.map +1 -0
  49. package/dist/utils/import-merger.js +244 -0
  50. package/dist/utils/import-merger.js.map +1 -0
  51. package/dist/utils/logger.d.ts +6 -0
  52. package/dist/utils/logger.d.ts.map +1 -0
  53. package/dist/utils/logger.js +22 -0
  54. package/dist/utils/logger.js.map +1 -0
  55. package/dist/utils/path-mappings.d.ts +94 -0
  56. package/dist/utils/path-mappings.d.ts.map +1 -0
  57. package/dist/utils/path-mappings.js +139 -0
  58. package/dist/utils/path-mappings.js.map +1 -0
  59. package/dist/utils/paths.d.ts +61 -0
  60. package/dist/utils/paths.d.ts.map +1 -0
  61. package/dist/utils/paths.js +178 -0
  62. package/dist/utils/paths.js.map +1 -0
  63. package/dist/utils/route-merger.d.ts +107 -0
  64. package/dist/utils/route-merger.d.ts.map +1 -0
  65. package/dist/utils/route-merger.js +358 -0
  66. package/dist/utils/route-merger.js.map +1 -0
  67. package/dist/utils/validation.d.ts +35 -0
  68. package/dist/utils/validation.d.ts.map +1 -0
  69. package/dist/utils/validation.js +137 -0
  70. package/dist/utils/validation.js.map +1 -0
  71. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,1464 @@
1
+ # Feature Patch CLI
2
+
3
+ CLI tool for applying feature patches to apps in the webapps-templates monorepo.
4
+
5
+ ## Installation
6
+
7
+ Build the CLI from the monorepo root:
8
+
9
+ ```bash
10
+ npm run build --workspace=@salesforce/webapp-template-cli-experimental
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Apply patches
16
+
17
+ ```bash
18
+ # Apply patches to a target directory (base app is used as reference only)
19
+ npm run apply-patches -- <feature-path> <app-path> <target-dir>
20
+
21
+ # Examples:
22
+ # Apply navigation-menu patches to my-app (using base-react-app as reference)
23
+ npm run apply-patches -- packages/feature/feature-react-nav-menu packages/base-app/base-react-app my-app
24
+
25
+ # Skip dependency installation
26
+ npm run apply-patches -- packages/feature/feature-react-nav-menu packages/base-app/base-react-app my-app --skip-dependency-changes
27
+
28
+ # Reset target directory to base app state before applying (preserves node_modules)
29
+ npm run apply-patches -- packages/feature/feature-react-nav-menu packages/base-app/base-react-app my-app --reset
30
+ ```
31
+
32
+ ### Direct usage
33
+
34
+ ```bash
35
+ # From monorepo root
36
+ node packages/cli/dist/index.js <feature-path> <app-path> <target-dir>
37
+
38
+ # With flags
39
+ node packages/cli/dist/index.js <feature-path> <app-path> my-app --skip-dependency-changes --reset
40
+ ```
41
+
42
+ ### Create a new feature
43
+
44
+ Create a new feature package from the base-feature template:
45
+
46
+ ```bash
47
+ # Create a new feature (will be prefixed with "feature-")
48
+ npm run new-feature -- <feature-name>
49
+
50
+ # Examples:
51
+ npm run new-feature -- navigation # Creates packages/feature/feature-navigation
52
+ npm run new-feature -- user-dashboard # Creates packages/feature/feature-user-dashboard
53
+ npm run new-feature -- api-client # Creates packages/feature/feature-api-client
54
+ ```
55
+
56
+ #### Direct usage
57
+
58
+ ```bash
59
+ # From monorepo root
60
+ node packages/cli/dist/index.js new-feature <feature-name>
61
+ ```
62
+
63
+ #### Feature Name Requirements
64
+
65
+ - **Format**: Must be in kebab-case (lowercase with hyphens)
66
+ - **Characters**: Only alphanumeric characters and hyphens allowed
67
+ - **Cannot**: Start or end with hyphens, have consecutive hyphens
68
+ - **Reserved**: Cannot be "base" or "cli"
69
+ - **Auto-prefix**: Automatically prefixed with "feature-" (e.g., `nav-menu` → `feature-nav-menu`)
70
+
71
+ Examples:
72
+
73
+ - ✅ `navigation` → `feature-navigation`
74
+ - ✅ `user-auth` → `feature-user-auth`
75
+ - ✅ `dashboard-v2` → `feature-dashboard-v2`
76
+ - ❌ `Navigation` (uppercase)
77
+ - ❌ `user_auth` (underscore)
78
+ - ❌ `--menu` (starts with hyphen)
79
+ - ❌ `nav--menu` (consecutive hyphens)
80
+
81
+ #### What the command does
82
+
83
+ The `new-feature` command:
84
+
85
+ 1. **Validates** the feature name format
86
+ 2. **Checks** that the feature doesn't already exist
87
+ 3. **Copies** the template-feature template to `packages/feature/feature-{name}/`
88
+ 4. **Renames** the template directory from `template-feature` to `feature-{name}`
89
+ 5. **Updates** `package.json` with the new feature name
90
+ 6. **Updates** `tsconfig.app.json` with the new directory path
91
+ 7. **Creates** a ready-to-use feature package
92
+
93
+ The created feature will have:
94
+
95
+ - ✅ Correct `package.json` configuration
96
+ - ✅ TypeScript configuration
97
+ - ✅ Feature structure following conventions
98
+ - ✅ Ready for customization and development
99
+
100
+ #### Next steps after creation
101
+
102
+ ```bash
103
+ cd packages/feature/feature-{name}
104
+ # 1. Customize template files in template/ directory
105
+ # 2. Update feature.ts with feature configuration
106
+ # 3. Test with: npm run apply-patches -- packages/feature/feature-{name} packages/base-app/base-react-app test-app
107
+ ```
108
+
109
+ ### Options
110
+
111
+ - `<feature-path>`: Path to the feature directory (can be relative to monorepo root or absolute). The feature must contain a feature.ts file.
112
+ - `<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.
113
+ - `<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.
114
+ - `--skip-dependency-changes`: Skip installing dependencies from package.json. Only file changes will be applied.
115
+ - `--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).
116
+
117
+ ## What it does
118
+
119
+ The CLI tool:
120
+
121
+ 1. **Validates** that the specified feature path exists and contains a feature.ts file, and the base app path is valid
122
+ 2. **Prepares target directory**: Creates the target directory (or resets it to base app state if `--reset` flag is used, preserving node_modules)
123
+ 3. **Resolves dependencies**: Recursively resolves all feature dependencies, detecting circular dependencies and building an ordered list where dependencies are applied before dependent features
124
+ 4. **Loads** the feature definitions from each `feature.ts` file in dependency order
125
+ 5. **Discovers files** from each feature's template directory (defaults to `template`, configurable via `templateDir` in feature.ts)
126
+ 6. **Validates paths**: For each feature, ensures:
127
+ - No conflicting paths exist (e.g., both `routes.tsx` and `__delete__routes.tsx`, or `__prepend__global.css` and `__append__global.css`)
128
+ - Files marked with `__delete__`, `__inherit__`, `__prepend__`, or `__append__` exist in the base app
129
+ 7. **Applies file changes** from each feature's template directory in dependency order:
130
+ - **Delete operations**: Removes files/directories marked with `__delete__` prefix from the target app
131
+ - **Inherit operations**: Skips files marked with `__inherit__` (inherited from base app)
132
+ - **Prepend operations**: Adds content from feature file before the base file's content (for files marked with `__prepend__`)
133
+ - **Append operations**: Adds content from feature file after the base file's content (for files marked with `__append__`)
134
+ - **Import path fixing**: Automatically removes `__inherit__` prefix from import statements in JS/TS files
135
+ - **File changes**: Copies each file from the template directory to the target
136
+ - **Route merging**: Intelligently merges route files (`routes.tsx`), accumulating routes from all features
137
+ 8. **Aggregates and installs dependencies**: Collects all NPM dependencies from all features and installs them in a single `npm` command (unless `--skip-dependency-changes` is used). Detects and errors on version conflicts.
138
+
139
+ ## Examples
140
+
141
+ ### Example: Apply feature patches to create a new app
142
+
143
+ ```bash
144
+ $ npm run apply-patches -- packages/feature/feature-react-nav-menu packages/base-app/base-react-app my-app
145
+
146
+ Applying patches: packages/feature-react-nav-menu → my-app
147
+ ℹ Validating paths...
148
+ ✓ Validation passed
149
+ ℹ Creating target directory my-app...
150
+ ✓ Target directory created
151
+
152
+ Resolving Dependencies
153
+ ℹ No dependencies to resolve
154
+
155
+
156
+ Applying: packages/feature-react-nav-menu
157
+ ℹ Discovering files...
158
+ ℹ Found 6 file(s)
159
+ ℹ Validating paths...
160
+ ✓ Paths validated
161
+ ✓ Added webapplications/feature-react-nav-menu/src/navigationMenu.tsx
162
+ ✓ Added webapplications/feature-react-nav-menu/src/appLayout.tsx
163
+ ✓ Added webapplications/feature-react-nav-menu/src/routes.tsx
164
+ ✓ Added webapplications/feature-react-nav-menu/src/router-utils.tsx
165
+ ✓ Added webapplications/feature-react-nav-menu/src/about.tsx
166
+ ✓ Added webapplications/feature-react-nav-menu/src/new.tsx
167
+
168
+ Installing dependencies
169
+ ℹ Installing dependencies...
170
+ [npm output]
171
+ ✓ Dependencies installed
172
+
173
+ ✓ Success
174
+ ✓ Created: /path/to/monorepo/my-app
175
+ ```
176
+
177
+ ## Creating Features
178
+
179
+ **Quick Start:** Use the CLI to create a new feature from the template:
180
+
181
+ ```bash
182
+ npm run new-feature -- your-feature-name
183
+ ```
184
+
185
+ This creates a new feature at `packages/feature/feature-your-feature-name/` with all the necessary configuration files. Then customize the template files in the `template/` directory.
186
+
187
+ For manual setup or advanced configuration, see below...
188
+
189
+ ---
190
+
191
+ 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`.
192
+
193
+ ### Feature Structure
194
+
195
+ ```
196
+ packages/feature/feature-my-feature/
197
+ ├── feature.ts # Feature configuration
198
+ ├── package.json # NPM dependencies for development
199
+ └── template/ # Template files (default directory name)
200
+ ├── webApp/ # Web application files (mapped to webapplications/<feature-name>/)
201
+ │ └── src/
202
+ │ ├── routes.tsx
203
+ │ ├── component1.tsx
204
+ │ └── component2.tsx
205
+ └── classes/ # SFDX metadata (placed at root level in dist)
206
+ └── MyClass.cls
207
+ ```
208
+
209
+ **Note**: The CLI handles two types of files differently:
210
+
211
+ - **Web Application files** (under `webApp/`): Automatically mapped to `webapplications/<feature-name>/`
212
+ - **SFDX metadata files** (like `classes/`, `triggers/`, `objects/`, etc.): Placed at root level in the output directory
213
+
214
+ This structure ensures proper organization for both digital experience applications and Salesforce metadata.
215
+
216
+ ### Feature Configuration
217
+
218
+ The feature configuration file specifies:
219
+
220
+ - `templateDir`: Directory containing template files (defaults to `template`)
221
+ - `webAppName`: Name of the web application (defaults to the feature name extracted from the directory). This is used for constructing the default route path.
222
+ - `routeFilePath`: Path to the routes file for merging (defaults to `webapplications/<webAppName>/src/routes.tsx`)
223
+ - `packageJson`: NPM dependencies to install in the target app
224
+ - `dependencies`: Array of other features this feature depends on (applied first)
225
+
226
+ ### Basic Feature Example:
227
+
228
+ ```typescript
229
+ import type { Feature } from "../cli/src/types.js";
230
+
231
+ const feature: Feature = {
232
+ // All fields are optional with sensible defaults
233
+ templateDir: "template", // Optional, defaults to 'template'
234
+ webAppName: "my-feature", // Optional, defaults to feature directory name
235
+ // routeFilePath defaults to 'webapplications/<webAppName>/src/routes.tsx'
236
+ packageJson: {
237
+ dependencies: {
238
+ "react-router": "^7.10.1",
239
+ },
240
+ },
241
+ };
242
+
243
+ export default feature;
244
+ ```
245
+
246
+ ### Feature with Dependencies:
247
+
248
+ ```typescript
249
+ import type { Feature } from "../cli/src/types.js";
250
+
251
+ const feature: Feature = {
252
+ // This feature depends on navigation-menu feature
253
+ // navigation-menu will be applied first, then this feature
254
+ dependencies: ["packages/feature/feature-react-nav-menu"],
255
+ packageJson: {
256
+ dependencies: {
257
+ "some-package": "^1.0.0",
258
+ },
259
+ },
260
+ };
261
+
262
+ export default feature;
263
+ ```
264
+
265
+ ### Custom Configuration:
266
+
267
+ ```typescript
268
+ import type { Feature } from "../cli/src/types.js";
269
+
270
+ const feature: Feature = {
271
+ templateDir: "src", // Use 'src' instead of 'template'
272
+ webAppName: "custom-app-name", // Override default app name
273
+ routeFilePath: "custom/path/to/routes.tsx", // Custom route file path
274
+ packageJson: {
275
+ dependencies: {
276
+ "react-router": "^7.10.1",
277
+ },
278
+ },
279
+ };
280
+
281
+ export default feature;
282
+ ```
283
+
284
+ **Notes:**
285
+
286
+ - `templateDir`: All files in this directory will be discovered and applied to the target app
287
+ - `webAppName`: Used to construct the default route path and organize files. Defaults to the feature directory name (e.g., `feature-react-nav-menu` → `feature-react-nav-menu`)
288
+ - `routeFilePath`: Must be a path relative to `templateDir`. If not specified, defaults to `webapplications/<webAppName>/src/routes.tsx`
289
+
290
+ ## Path Mappings
291
+
292
+ 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.
293
+
294
+ ### Default Behavior (Enabled by Default)
295
+
296
+ By default, all features automatically get the `webApp` mapping, which transforms web application files into the proper nested structure. For example, in `feature-react-nav-menu`:
297
+
298
+ ```
299
+ template/webApp/src/app.tsx → dist/webapplications/feature-react-nav-menu/src/app.tsx
300
+ ```
301
+
302
+ This simplifies feature templates by removing the repetitive nested directory structure.
303
+
304
+ **Important**: Only files under `webApp/` get the nested structure. SFDX metadata types (like `classes/`, `triggers/`, `objects/`, `lwc/`, etc.) are placed at root level:
305
+
306
+ ```
307
+ feature-react-nav-menu/template/
308
+ ├── webApp/
309
+ │ └── src/
310
+ │ └── app.tsx → dist/webapplications/feature-react-nav-menu/src/app.tsx
311
+ └── classes/
312
+ └── NavMenu.cls → dist/classes/NavMenu.cls (root level)
313
+ ```
314
+
315
+ ### Using the Default Mapping
316
+
317
+ Simply organize your template files under `webApp/` for web application code, and at the root level for SFDX metadata:
318
+
319
+ ```typescript
320
+ // feature.ts - No configuration needed
321
+ export default {};
322
+ ```
323
+
324
+ ```
325
+ template/
326
+ ├── webApp/ # Web application files (automatically mapped)
327
+ │ └── src/
328
+ │ ├── routes.tsx
329
+ │ ├── app.tsx
330
+ │ └── components/
331
+ │ └── Header.tsx
332
+ └── classes/ # SFDX metadata (placed at root level)
333
+ └── MyClass.cls
334
+ ```
335
+
336
+ **Result**:
337
+
338
+ - Web app files go to `dist/webapplications/feature-name/src/`
339
+ - SFDX metadata stays at root: `dist/classes/MyClass.cls`
340
+
341
+ ### Disabling Path Mappings (Opt-Out)
342
+
343
+ Use full paths when you need precise control or for backwards compatibility:
344
+
345
+ ```typescript
346
+ // feature.ts
347
+ export default {
348
+ pathMappings: {
349
+ enabled: false, // Disable automatic mapping
350
+ },
351
+ };
352
+ ```
353
+
354
+ ```
355
+ template/
356
+ └── digitalExperiences/ # Use full structure
357
+ └── webapplications/
358
+ └── <feature-name>/
359
+ └── src/
360
+ └── routes.tsx
361
+ ```
362
+
363
+ ### Custom Path Mappings
364
+
365
+ Define custom mappings for non-standard structures:
366
+
367
+ ```typescript
368
+ // feature.ts
369
+ export default {
370
+ pathMappings: {
371
+ mappings: [
372
+ {
373
+ from: "web",
374
+ to: "webapplications/custom-app-name",
375
+ },
376
+ ],
377
+ },
378
+ };
379
+ ```
380
+
381
+ ```
382
+ template/
383
+ └── web/ # Custom prefix
384
+ └── src/
385
+ └── app.tsx
386
+ ```
387
+
388
+ **Result**: Maps `web/src/app.tsx` → `webapplications/custom-app-name/src/app.tsx`
389
+
390
+ ### Multiple Mappings
391
+
392
+ You can define multiple mappings in one feature:
393
+
394
+ ```typescript
395
+ export default {
396
+ pathMappings: {
397
+ mappings: [
398
+ { from: "webApp", to: "webapplications/my-app" },
399
+ { from: "shared", to: "digitalExperiences/shared-resources" },
400
+ ],
401
+ },
402
+ };
403
+ ```
404
+
405
+ ### Mixed Path Formats
406
+
407
+ You can mix web application files, SFDX metadata, and full paths in the same feature:
408
+
409
+ ```
410
+ template/
411
+ ├── webApp/ # Mapped to webapplications/<feature-name>/
412
+ │ └── src/
413
+ │ └── app.tsx
414
+ ├── classes/ # SFDX metadata (placed at root)
415
+ │ └── MyClass.cls
416
+ ├── triggers/ # SFDX metadata (placed at root)
417
+ │ └── MyTrigger.trigger
418
+ └── digitalExperiences/ # Full paths (passed through)
419
+ └── siteAssets/
420
+ └── logo.png
421
+ ```
422
+
423
+ All formats work together seamlessly:
424
+
425
+ - `webApp/` files → `webapplications/<feature-name>/`
426
+ - SFDX metadata (`classes/`, `triggers/`, `objects/`, `lwc/`, etc.) → Root level
427
+ - Full paths (already containing `digitalExperiences/`) → Used as-is
428
+
429
+ ### Path Mapping Examples
430
+
431
+ **Example 1: Default mapping (recommended)**
432
+
433
+ ```typescript
434
+ export default {}; // That's it!
435
+ ```
436
+
437
+ **Example 2: Opt-out for backwards compatibility**
438
+
439
+ ```typescript
440
+ export default {
441
+ pathMappings: { enabled: false },
442
+ };
443
+ ```
444
+
445
+ **Example 3: Custom app name**
446
+
447
+ ```typescript
448
+ export default {
449
+ pathMappings: {
450
+ mappings: [{ from: "webApp", to: "webapplications/custom-name" }],
451
+ },
452
+ };
453
+ ```
454
+
455
+ **Example 4: Completely custom structure**
456
+
457
+ ```typescript
458
+ export default {
459
+ pathMappings: {
460
+ mappings: [
461
+ { from: "src", to: "app/sources" },
462
+ { from: "assets", to: "public/static" },
463
+ ],
464
+ },
465
+ };
466
+ ```
467
+
468
+ ### How Path Mappings Work
469
+
470
+ 1. **Discovery**: CLI discovers all files in your template directory
471
+ 2. **Mapping**: Each file path is checked against mapping rules (first match wins)
472
+ 3. **Transformation**: Matching prefix is replaced with target prefix
473
+ 4. **Pass-Through**: Paths that don't match any mapping are used as-is
474
+ 5. **Application**: Transformed paths are used for file operations
475
+
476
+ This ensures:
477
+
478
+ - ✅ Backwards compatibility (old features still work)
479
+ - ✅ Simplified templates (new features are easier to create)
480
+ - ✅ Flexibility (custom mappings for special cases)
481
+ - ✅ No breaking changes (opt-in for existing features, opt-out available)
482
+
483
+ ## Feature Dependencies
484
+
485
+ Features can depend on other features. Dependencies are automatically resolved and applied in the correct order.
486
+
487
+ ### How Dependencies Work
488
+
489
+ 1. **Declaration**: Specify dependencies in your `feature.ts` file
490
+ 2. **Resolution**: CLI recursively resolves all dependencies (including nested dependencies)
491
+ 3. **Ordering**: Dependencies are always applied before the feature that depends on them
492
+ 4. **Circular Detection**: CLI detects and prevents circular dependencies
493
+ 5. **File Layering**: Files from dependencies can be overridden by dependent features (main feature wins)
494
+ 6. **Route Accumulation**: Routes from all features are merged together
495
+
496
+ ### Dependency Resolution Order
497
+
498
+ When you apply a feature with dependencies:
499
+
500
+ ```
501
+ Feature A depends on Feature B
502
+ Feature B depends on Feature C
503
+
504
+ Application order: C → B → A (dependencies first)
505
+ ```
506
+
507
+ The CLI builds a complete dependency graph and applies features in topological order.
508
+
509
+ ### Example: Building on Navigation Features
510
+
511
+ ```typescript
512
+ // packages/feature/feature-react-nav-menu/feature.ts
513
+ import type { Feature } from "../../../cli/src/types.js";
514
+
515
+ const feature: Feature = {
516
+ // Navigation menu has no dependencies
517
+ };
518
+
519
+ export default feature;
520
+ ```
521
+
522
+ ```typescript
523
+ // packages/feature/feature-admin-dashboard/feature.ts
524
+ import type { Feature } from "../../../cli/src/types.js";
525
+
526
+ const feature: Feature = {
527
+ // Admin dashboard builds on top of navigation menu
528
+ dependencies: ["packages/feature/feature-react-nav-menu"],
529
+ };
530
+
531
+ export default feature;
532
+ ```
533
+
534
+ When you apply `feature-admin-dashboard`:
535
+
536
+ 1. CLI resolves `feature-react-nav-menu` as a dependency
537
+ 2. Applies `feature-react-nav-menu` first (navigation menu files and routes)
538
+ 3. Applies `feature-admin-dashboard` second (dashboard files and routes)
539
+ 4. Result: App has both navigation menu and admin dashboard
540
+
541
+ ### Nested Dependencies
542
+
543
+ Dependencies can have their own dependencies. The CLI resolves them recursively:
544
+
545
+ ```
546
+ Feature App depends on Feature Dashboard
547
+ Feature Dashboard depends on Feature Navigation
548
+ Feature Navigation depends on Feature Auth
549
+
550
+ Application order: Auth → Navigation → Dashboard → App
551
+ ```
552
+
553
+ ### Circular Dependency Detection
554
+
555
+ The CLI detects and prevents circular dependencies:
556
+
557
+ ```typescript
558
+ // Feature A depends on B
559
+ // Feature B depends on C
560
+ // Feature C depends on A
561
+
562
+ // CLI will error:
563
+ // "Circular dependency detected:
564
+ // packages/feature-a → packages/feature-b → packages/feature-c → packages/feature-a"
565
+ ```
566
+
567
+ **Fix**: Remove one of the dependencies to break the cycle.
568
+
569
+ ### Main Feature Wins
570
+
571
+ When multiple features modify the same file:
572
+
573
+ - **Dependencies applied first**: Their files are written to the target
574
+ - **Main feature applied last**: Its files overwrite dependency files
575
+ - **Result**: Main feature can customize/override dependency behavior
576
+
577
+ Example:
578
+
579
+ ```
580
+ feature-react-nav-menu provides: src/appLayout.tsx
581
+ feature-custom-app also provides: src/appLayout.tsx
582
+
583
+ When applying feature-custom-app:
584
+ 1. navigation-menu's appLayout.tsx is applied
585
+ 2. custom-app's appLayout.tsx overwrites it
586
+ 3. Final result: custom-app's version is used
587
+ ```
588
+
589
+ ### Route Merging with Dependencies
590
+
591
+ Routes from all features are merged together:
592
+
593
+ **Base App Routes:**
594
+
595
+ ```typescript
596
+ export const routes = [
597
+ {
598
+ path: '/',
599
+ children: [
600
+ { index: true, element: <Home /> }
601
+ ]
602
+ }
603
+ ];
604
+ ```
605
+
606
+ **navigation-menu Routes:**
607
+
608
+ ```typescript
609
+ export const routes = [
610
+ {
611
+ path: '/',
612
+ children: [
613
+ { path: 'about', element: <About /> },
614
+ { path: 'contact', element: <Contact /> }
615
+ ]
616
+ }
617
+ ];
618
+ ```
619
+
620
+ **Final Merged Routes:**
621
+
622
+ ```typescript
623
+ export const routes = [
624
+ {
625
+ path: '/',
626
+ children: [
627
+ { index: true, element: <Home /> }, // From base
628
+ { path: 'about', element: <About /> }, // From navigation-menu
629
+ { path: 'contact', element: <Contact /> } // From navigation-menu
630
+ ]
631
+ }
632
+ ];
633
+ ```
634
+
635
+ Routes accumulate across all features, preserving routes from base app and all dependencies.
636
+
637
+ ### Dependency Paths
638
+
639
+ Dependency paths can be:
640
+
641
+ - **Relative to monorepo root**: `'packages/feature/feature-react-nav-menu'`
642
+ - **Absolute paths**: `'/absolute/path/to/feature'`
643
+
644
+ The CLI normalizes and resolves all paths consistently.
645
+
646
+ ### Diamond Dependencies
647
+
648
+ When multiple features depend on the same feature:
649
+
650
+ ```
651
+ Feature A depends on Feature C
652
+ Feature B depends on Feature C
653
+ Feature Main depends on A and B
654
+
655
+ Dependency graph:
656
+ Main
657
+ / \
658
+ A B
659
+ \ /
660
+ C
661
+ ```
662
+
663
+ **Resolution**: Feature C is applied once (not duplicated).
664
+
665
+ **Application order**: `C → A → B → Main`
666
+
667
+ ### Watch Mode with Dependencies
668
+
669
+ When using watch mode, dependencies are:
670
+
671
+ - Applied once on initial startup with reset to ensure clean state
672
+ - Re-applied when template files change (always resets to base app state, preserving node_modules)
673
+ - Not watched for changes (only the main feature is watched)
674
+
675
+ 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.
676
+
677
+ ## File Application
678
+
679
+ The CLI discovers all files in your feature's template directory and applies them to the target app:
680
+
681
+ 1. **Standard Files**: Copied directly to the target, creating directories as needed
682
+ 2. **Delete Markers**: Files/directories prefixed with `__delete__` mark files for deletion from the target app
683
+ 3. **Inherit Markers**: Files prefixed with `__inherit__` are kept in the feature for type safety but not copied (inherited from base app)
684
+ 4. **Prepend Markers**: Files prefixed with `__prepend__` add their content before the base file's content
685
+ 5. **Append Markers**: Files prefixed with `__append__` add their content after the base file's content
686
+ 6. **Route Files** (`routes.tsx`): Intelligently merged with existing routes to preserve base app routes
687
+
688
+ ### Deleting Files and Directories
689
+
690
+ Features can delete files or directories from the target app by using the `__delete__` prefix in the template directory.
691
+
692
+ #### How It Works
693
+
694
+ 1. Create a file with the `__delete__` prefix in your feature's template directory
695
+ 2. The prefix can appear anywhere in the path
696
+ 3. When the feature is applied, the corresponding file/directory will be deleted from the target app
697
+ 4. The content of the delete marker file is ignored (can be empty or contain comments)
698
+
699
+ #### Examples
700
+
701
+ **Delete a single file:**
702
+
703
+ ```
704
+ template/
705
+ └── src/
706
+ └── __delete__routes.tsx # Deletes src/routes.tsx from target app
707
+ ```
708
+
709
+ **Delete a directory:**
710
+
711
+ ```
712
+ template/
713
+ └── src/
714
+ └── __delete__pages/ # Deletes src/pages/ directory from target app
715
+ └── .gitkeep # Placeholder file (content ignored)
716
+ ```
717
+
718
+ **Delete from nested path:**
719
+
720
+ ```
721
+ template/
722
+ └── src/
723
+ └── components/
724
+ └── __delete__Footer.tsx # Deletes src/components/Footer.tsx
725
+ ```
726
+
727
+ **Delete a parent directory:**
728
+
729
+ ```
730
+ template/
731
+ └── __delete__src/
732
+ └── legacy/ # Deletes src/legacy/ directory from target app
733
+ └── .gitkeep
734
+ ```
735
+
736
+ #### Validation
737
+
738
+ The CLI validates that you don't have conflicting paths:
739
+
740
+ - ❌ **Invalid**: Having both `routes.tsx` and `__delete__routes.tsx` in the same template
741
+ - ✅ **Valid**: Having only `__delete__routes.tsx` (to delete) or only `routes.tsx` (to add/update)
742
+
743
+ If a conflict is detected, the CLI will throw an error:
744
+
745
+ ```
746
+ Path conflict detected: "src/routes.tsx" appears multiple times in the template.
747
+ This can happen when both a file and its __delete__ marker exist.
748
+ Please remove one of them.
749
+ ```
750
+
751
+ #### Use Cases
752
+
753
+ - **Remove obsolete files**: Delete deprecated components or utilities that the feature replaces
754
+ - **Clean up after refactoring**: Remove files that are no longer needed with the new feature
755
+ - **Remove base app scaffolding**: Delete placeholder files from the base app that the feature supersedes
756
+
757
+ 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.
758
+
759
+ ### Inheriting Files from Base App
760
+
761
+ 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.
762
+
763
+ #### How It Works
764
+
765
+ 1. Create a file with the `__inherit__` prefix in your feature's template directory
766
+ 2. Copy the base app file's contents to the `__inherit__` file (for type checking and IDE support)
767
+ 3. When the feature is applied, the file is NOT copied to the target (the base app's file is used)
768
+ 4. The feature gets type safety and autocomplete for the inherited file
769
+
770
+ #### Why Use `__inherit__`?
771
+
772
+ **Problem**: You want to import a base app file in your feature code, but:
773
+
774
+ - If you don't have the file in your feature, TypeScript shows errors and IDE autocomplete doesn't work
775
+ - If you copy the file to your feature, it will overwrite the base app file when applied
776
+
777
+ **Solution**: Use `__inherit__` to keep the file in your feature for development, but skip it during application.
778
+
779
+ #### Automatic Import Path Fixing
780
+
781
+ **Important**: The CLI automatically fixes import paths in your feature files!
782
+
783
+ When you import from `__inherit__` files in your feature code:
784
+
785
+ ```typescript
786
+ // In your feature's template
787
+ import { routes } from "./__inherit__routes";
788
+ import AppLayout from "./__inherit__appLayout";
789
+ ```
790
+
791
+ The CLI automatically removes the `__inherit__` prefix when applying the feature:
792
+
793
+ ```typescript
794
+ // In the target app after applying
795
+ import { routes } from "./routes";
796
+ import AppLayout from "./appLayout";
797
+ ```
798
+
799
+ **Supported patterns:**
800
+
801
+ - `import ... from './__inherit__file'` → `import ... from './file'`
802
+ - `export ... from './__inherit__file'` → `export ... from './file'`
803
+ - `require('./__inherit__file')` → `require('./file')`
804
+ - `import('./__inherit__file')` → `import('./file')`
805
+ - Works with single quotes (`'`), double quotes (`"`), and backticks (`` ` ``)
806
+ - Works with relative paths (`../`, `./`)
807
+
808
+ **Only processes JavaScript/TypeScript files:**
809
+
810
+ - `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`
811
+ - Other files (`.md`, `.json`, etc.) are not processed
812
+
813
+ This means you can freely import from `__inherit__` files in your feature code, and the imports will "just work" when the feature is applied!
814
+
815
+ #### Examples
816
+
817
+ **Inherit routes for type safety:**
818
+
819
+ ```typescript
820
+ // Feature structure
821
+ template/
822
+ └── src/
823
+ ├── __inherit__routes.tsx // Copy of base app routes for types
824
+ └── index.tsx // Can import routes safely
825
+
826
+ // In your feature's index.tsx
827
+ import { routes } from './routes'; // TypeScript works!
828
+
829
+ // When applied:
830
+ // - routes.tsx from base app is used (not overwritten)
831
+ // - Your index.tsx can still import it
832
+ ```
833
+
834
+ **Inherit shared layout:**
835
+
836
+ ```
837
+ template/
838
+ └── src/
839
+ ├── __inherit__appLayout.tsx # Copy from base for types
840
+ └── pages/
841
+ └── MyPage.tsx # Can import appLayout safely
842
+ ```
843
+
844
+ #### Validation
845
+
846
+ The CLI validates `__inherit__` files:
847
+
848
+ 1. **No conflicts**: You cannot have both `routes.tsx` and `__inherit__routes.tsx` in the same template
849
+ - ❌ **Invalid**: Both `routes.tsx` and `__inherit__routes.tsx`
850
+ - ✅ **Valid**: Only `__inherit__routes.tsx`
851
+
852
+ 2. **Base file must exist**: The file must exist in the base app
853
+ - ❌ **Invalid**: `__inherit__nonexistent.tsx` when file doesn't exist in base app
854
+ - ✅ **Valid**: `__inherit__routes.tsx` when `routes.tsx` exists in base app
855
+
856
+ If validation fails, you'll see clear error messages:
857
+
858
+ ```
859
+ Validation error: Cannot inherit file that doesn't exist!
860
+
861
+ File marked for inheritance: src/routes.tsx
862
+ Expected location in base app: /path/to/base/src/routes.tsx
863
+ The file doesn't exist in the base app.
864
+ ```
865
+
866
+ #### Use Cases
867
+
868
+ - **Type-safe imports**: Import base app files in your feature with full TypeScript support
869
+ - **Shared layouts**: Reference base app layouts without overwriting them
870
+ - **Route definitions**: Import base routes to extend or reference them
871
+ - **Shared utilities**: Reference base app utility functions with autocomplete
872
+
873
+ #### Complete Example
874
+
875
+ ```typescript
876
+ // Base app has: src/routes.tsx, src/appLayout.tsx
877
+
878
+ // Feature template structure:
879
+ template/
880
+ └── src/
881
+ ├── __inherit__routes.tsx # Copy from base (for types only)
882
+ ├── __inherit__appLayout.tsx # Copy from base (for types only)
883
+ └── pages/
884
+ └── Dashboard.tsx # New page that imports both
885
+
886
+ // In template/src/pages/Dashboard.tsx (during development):
887
+ import { routes } from '../__inherit__routes'; // Import from __inherit__ file
888
+ import AppLayout from '../__inherit__appLayout'; // TypeScript works!
889
+
890
+ export default function Dashboard() {
891
+ return <AppLayout>Dashboard using {routes.length} routes</AppLayout>;
892
+ }
893
+
894
+ // When applied, Dashboard.tsx is automatically transformed to:
895
+ import { routes } from '../routes'; // __inherit__ removed!
896
+ import AppLayout from '../appLayout'; // __inherit__ removed!
897
+
898
+ export default function Dashboard() {
899
+ return <AppLayout>Dashboard using {routes.length} routes</AppLayout>;
900
+ }
901
+
902
+ // Final result in target app:
903
+ // - src/routes.tsx: inherited from base (not overwritten) ✓
904
+ // - src/appLayout.tsx: inherited from base (not overwritten) ✓
905
+ // - src/pages/Dashboard.tsx: added from feature with fixed imports ✓
906
+ ```
907
+
908
+ ### Prepending and Appending to Base Files
909
+
910
+ 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.
911
+
912
+ #### How It Works
913
+
914
+ 1. **Prepend**: Content from the feature file is added **before** the base file's content
915
+ 2. **Append**: Content from the feature file is added **after** the base file's content
916
+ 3. The prefix can appear anywhere in the path
917
+ 4. Import paths with `__inherit__` are automatically fixed in the content
918
+
919
+ #### Examples
920
+
921
+ **Append CSS to global styles:**
922
+
923
+ ```
924
+ template/
925
+ └── src/
926
+ └── styles/
927
+ └── __append__global.css # Adds content after base global.css
928
+ ```
929
+
930
+ ```css
931
+ /* Feature's __append__global.css */
932
+ :root {
933
+ --feature-color: #066afe;
934
+ --feature-background: #ffffff;
935
+ }
936
+
937
+ .feature-specific {
938
+ color: var(--feature-color);
939
+ }
940
+ ```
941
+
942
+ **Result in target app's `global.css`:**
943
+
944
+ ```css
945
+ /* Base app's existing content */
946
+ @import "tailwindcss";
947
+
948
+ body {
949
+ margin: 0;
950
+ }
951
+
952
+ /* Content appended from feature */
953
+ :root {
954
+ --feature-color: #066afe;
955
+ --feature-background: #ffffff;
956
+ }
957
+
958
+ .feature-specific {
959
+ color: var(--feature-color);
960
+ }
961
+ ```
962
+
963
+ **Prepend imports to a TypeScript file:**
964
+
965
+ ```
966
+ template/
967
+ └── src/
968
+ └── __prepend__index.tsx # Adds imports before base index.tsx
969
+ ```
970
+
971
+ ```typescript
972
+ /* Feature's __prepend__index.tsx */
973
+ import { initializeFeature } from "./feature-init";
974
+
975
+ initializeFeature();
976
+ ```
977
+
978
+ **Result in target app's `index.tsx`:**
979
+
980
+ ```typescript
981
+ /* Content prepended from feature */
982
+ import { initializeFeature } from "./feature-init";
983
+
984
+ initializeFeature();
985
+
986
+ /* Base app's existing content */
987
+ import React from "react";
988
+ import ReactDOM from "react-dom";
989
+ // ... rest of base file
990
+ ```
991
+
992
+ #### Validation
993
+
994
+ The CLI validates prepend/append operations:
995
+
996
+ 1. **No conflicts**: You cannot target the same file with multiple operations
997
+ - ❌ **Invalid**: Both `__prepend__global.css` and `__append__global.css`
998
+ - ❌ **Invalid**: Both `global.css` and `__append__global.css`
999
+ - ✅ **Valid**: Only `__append__global.css`
1000
+
1001
+ 2. **Base file must exist**: The target file must exist in the base app
1002
+ - ❌ **Invalid**: `__append__nonexistent.css` when file doesn't exist
1003
+ - ✅ **Valid**: `__append__global.css` when `global.css` exists in base app
1004
+
1005
+ If validation fails, you'll see clear error messages:
1006
+
1007
+ ```
1008
+ Path conflict detected!
1009
+
1010
+ The following paths resolve to the same target file:
1011
+ 1. src/styles/__append__global.css (append)
1012
+ 2. src/styles/__prepend__global.css (prepend)
1013
+ → Both target: src/styles/global.css
1014
+
1015
+ You cannot have multiple files targeting the same path.
1016
+ ```
1017
+
1018
+ #### Automatic Import Fixing
1019
+
1020
+ When prepending or appending TypeScript/JavaScript files, import paths with `__inherit__` are automatically fixed:
1021
+
1022
+ ```typescript
1023
+ // In feature's __prepend__index.tsx
1024
+ import { routes } from "./__inherit__routes";
1025
+
1026
+ // After prepending to target app
1027
+ import { routes } from "./routes"; // __inherit__ removed!
1028
+ ```
1029
+
1030
+ This works the same as regular file operations (see "Automatic Import Path Fixing" above).
1031
+
1032
+ #### Use Cases
1033
+
1034
+ **Prepend:**
1035
+
1036
+ - Add initialization code at the start of entry files
1037
+ - Add imports before existing code
1038
+ - Add type declarations or interfaces
1039
+
1040
+ **Append:**
1041
+
1042
+ - Add CSS variables and styles to global stylesheets
1043
+ - Add routes or configuration entries
1044
+ - Extend existing files with additional functionality
1045
+ - Add utility functions or helpers
1046
+
1047
+ #### Complete Example
1048
+
1049
+ ```typescript
1050
+ // Base app has: src/styles/global.css
1051
+ // with Tailwind configuration
1052
+
1053
+ // Feature adds Salesforce Design Tokens by appending
1054
+
1055
+ // Feature structure:
1056
+ template/
1057
+ └── src/
1058
+ └── styles/
1059
+ └── __append__global.css
1060
+
1061
+ // Feature's __append__global.css:
1062
+ :root {
1063
+ /* Salesforce Design Tokens */
1064
+ --electric-blue-50: #066afe;
1065
+ --constant-white: #ffffff;
1066
+ }
1067
+
1068
+ button {
1069
+ background-color: var(--electric-blue-50);
1070
+ color: var(--constant-white);
1071
+ }
1072
+
1073
+ // Final result in target app's global.css:
1074
+ @import "tailwindcss"; // ← Base content
1075
+
1076
+ body {
1077
+ @apply antialiased; // ← Base content
1078
+ }
1079
+
1080
+ :root {
1081
+ /* Salesforce Design Tokens */
1082
+ --electric-blue-50: #066afe; // ← Appended content
1083
+ --constant-white: #ffffff; // ← Appended content
1084
+ }
1085
+
1086
+ button {
1087
+ background-color: var(--electric-blue-50); // ← Appended content
1088
+ color: var(--constant-white); // ← Appended content
1089
+ }
1090
+ ```
1091
+
1092
+ ## Route Merging Strategy
1093
+
1094
+ The `merge` change type with `routes` strategy intelligently combines route definitions from features with base app routes.
1095
+
1096
+ ### How It Works
1097
+
1098
+ **Replace-Matching with Deep Children Merge:**
1099
+
1100
+ 1. **Top-Level Routes:**
1101
+ - Routes with the **same path** → Merge their children arrays
1102
+ - Routes with **different paths** → Add feature route to result
1103
+
1104
+ 2. **Children Array Merging** (when parent paths match):
1105
+ - Index routes (`index: true`) → Feature replaces base if both exist
1106
+ - Named routes (`path: 'about'`) → Feature replaces base if paths match
1107
+ - **New routes** → Added to children array
1108
+ - **Base routes not in feature** → Preserved
1109
+
1110
+ 3. **Route Deletion:**
1111
+ - Routes with path starting with `__delete__` → Remove matching route from result
1112
+ - Example: `path: '__delete__new'` removes the route with `path: 'new'`
1113
+ - Throws error if route to delete doesn't exist
1114
+
1115
+ 4. **Recursion:**
1116
+ - Applies same logic to nested children arrays
1117
+
1118
+ ### Example
1119
+
1120
+ **Base App Routes**:
1121
+
1122
+ ```typescript
1123
+ export const routes: RouteObject[] = [
1124
+ {
1125
+ path: '/',
1126
+ element: <AppLayout />,
1127
+ children: [
1128
+ {
1129
+ index: true,
1130
+ element: <Home />,
1131
+ handle: { showInNavigation: true, label: 'Home' }
1132
+ }
1133
+ ]
1134
+ }
1135
+ ]
1136
+ ```
1137
+
1138
+ **Feature Routes** (in `template/webapplications/<feature-name>/src/routes.tsx`):
1139
+
1140
+ ```typescript
1141
+ export const routes: RouteObject[] = [
1142
+ {
1143
+ path: '/',
1144
+ element: <AppLayout />,
1145
+ children: [
1146
+ {
1147
+ path: 'about',
1148
+ element: <About />,
1149
+ handle: { showInNavigation: true, label: 'About' }
1150
+ },
1151
+ {
1152
+ path: 'contact',
1153
+ element: <Contact />,
1154
+ handle: { showInNavigation: false }
1155
+ }
1156
+ ]
1157
+ }
1158
+ ]
1159
+ ```
1160
+
1161
+ **Merged Result** (after applying feature):
1162
+
1163
+ ```typescript
1164
+ export const routes: RouteObject[] = [
1165
+ {
1166
+ path: '/',
1167
+ element: <AppLayout />, // Uses feature's element
1168
+ children: [
1169
+ {
1170
+ index: true,
1171
+ element: <Home />, // ✓ Preserved from base
1172
+ handle: { showInNavigation: true, label: 'Home' }
1173
+ },
1174
+ {
1175
+ path: 'about',
1176
+ element: <About />, // ✓ Added from feature
1177
+ handle: { showInNavigation: true, label: 'About' }
1178
+ },
1179
+ {
1180
+ path: 'contact',
1181
+ element: <Contact />, // ✓ Added from feature
1182
+ handle: { showInNavigation: false }
1183
+ }
1184
+ ]
1185
+ }
1186
+ ]
1187
+ ```
1188
+
1189
+ ### Key Benefits
1190
+
1191
+ - **Preserves existing routes**: Base app's Home route stays intact
1192
+ - **Adds new routes**: Feature routes (About, Contact) are added
1193
+ - **No duplication**: Routes with matching paths are replaced, not duplicated
1194
+ - **Deep merging**: Works with nested route structures
1195
+ - **Multiple features**: Apply multiple route-adding features sequentially
1196
+
1197
+ Route merging happens automatically for `routes.tsx` files.
1198
+
1199
+ ### Deleting Routes
1200
+
1201
+ 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.
1202
+
1203
+ #### How It Works
1204
+
1205
+ 1. Add a route with `path: '__delete__<route-name>'` in your feature's routes file
1206
+ 2. The route with the matching path (without the prefix) will be removed during merging
1207
+ 3. If the route doesn't exist, an error will be thrown
1208
+
1209
+ #### Example
1210
+
1211
+ **Base/Previous Routes:**
1212
+
1213
+ ```typescript
1214
+ export const routes: RouteObject[] = [
1215
+ {
1216
+ path: '/',
1217
+ element: <AppLayout />,
1218
+ children: [
1219
+ { index: true, element: <Home /> },
1220
+ { path: 'about', element: <About /> },
1221
+ { path: 'new', element: <New /> }
1222
+ ]
1223
+ }
1224
+ ]
1225
+ ```
1226
+
1227
+ **Feature Routes (deleting 'new'):**
1228
+
1229
+ ```typescript
1230
+ export const routes: RouteObject[] = [
1231
+ {
1232
+ path: '/',
1233
+ children: [
1234
+ {
1235
+ path: '__delete__new',
1236
+ element: <></> // Element value is ignored for deletion markers
1237
+ }
1238
+ ]
1239
+ }
1240
+ ]
1241
+ ```
1242
+
1243
+ **Merged Result:**
1244
+
1245
+ ```typescript
1246
+ export const routes: RouteObject[] = [
1247
+ {
1248
+ path: '/',
1249
+ element: <AppLayout />,
1250
+ children: [
1251
+ { index: true, element: <Home /> }, // ✓ Preserved
1252
+ { path: 'about', element: <About /> } // ✓ Preserved
1253
+ // 'new' route deleted ✓
1254
+ ]
1255
+ }
1256
+ ]
1257
+ ```
1258
+
1259
+ #### Validation
1260
+
1261
+ - **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
1262
+ - This prevents silent failures and ensures routes are being deleted as expected
1263
+
1264
+ #### Use Cases
1265
+
1266
+ - **Remove dependency routes**: Delete routes added by feature dependencies that aren't needed
1267
+ - **Clean up base routes**: Remove placeholder or example routes from the base app
1268
+ - **Override parent features**: Child features can remove routes added by parent features they depend on
1269
+
1270
+ For example, a feature that provides a single-page app might delete all routes from the base app to start fresh.
1271
+
1272
+ ### Automatic Import Cleanup for Deleted Files
1273
+
1274
+ 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.
1275
+
1276
+ #### How It Works
1277
+
1278
+ 1. During route merging, after all imports are merged from the feature file
1279
+ 2. The import merger scans all import statements in the target file
1280
+ 3. Any imports with `__delete__` in the module specifier are automatically removed
1281
+ 4. This ensures deleted components don't leave broken import references
1282
+
1283
+ #### Example
1284
+
1285
+ **Feature Routes File:**
1286
+
1287
+ ```typescript
1288
+ import type { RouteObject } from "react-router";
1289
+ import AppLayout from "./__inherit__appLayout";
1290
+ import Home from ".";
1291
+ import New from "./__delete__new"; // Import from deleted file
1292
+
1293
+ export const routes: RouteObject[] = [
1294
+ {
1295
+ path: '/',
1296
+ element: <AppLayout />,
1297
+ children: [
1298
+ {
1299
+ index: true,
1300
+ element: <Home />,
1301
+ handle: { showInNavigation: true, label: 'Home' }
1302
+ },
1303
+ {
1304
+ path: '__delete__new', // Delete the 'new' route
1305
+ element: <New />,
1306
+ }
1307
+ ]
1308
+ }
1309
+ ]
1310
+ ```
1311
+
1312
+ **Merged Result:**
1313
+
1314
+ ```typescript
1315
+ import type { RouteObject } from "react-router";
1316
+ import AppLayout from "./appLayout";
1317
+ import Home from ".";
1318
+ // ✓ Import from ./__delete__new automatically removed
1319
+
1320
+ export const routes: RouteObject[] = [
1321
+ {
1322
+ path: '/',
1323
+ element: <AppLayout />,
1324
+ children: [
1325
+ {
1326
+ index: true,
1327
+ element: <Home />,
1328
+ handle: { showInNavigation: true, label: 'Home' }
1329
+ }
1330
+ // ✓ 'new' route deleted
1331
+ ]
1332
+ }
1333
+ ]
1334
+ ```
1335
+
1336
+ #### Key Benefits
1337
+
1338
+ - **No broken imports**: Automatically removes imports from deleted files
1339
+ - **Clean merged code**: Final output doesn't reference non-existent files
1340
+ - **Works with file deletion**: Applies to any file marked with `__delete__` prefix
1341
+ - **Seamless integration**: Happens automatically during route merging, no manual cleanup needed
1342
+
1343
+ ## Testing
1344
+
1345
+ The CLI has a comprehensive test suite using Vitest, including E2E tests with gold files and unit tests for critical utilities.
1346
+
1347
+ **Test Coverage:**
1348
+
1349
+ - ✅ 12 E2E test scenarios covering all major CLI workflows
1350
+ - ✅ 46+ unit tests covering route merging, import merging, and file operations
1351
+ - ✅ Gold file comparison for E2E validation
1352
+ - ✅ No skipped tests - all functionality fully tested
1353
+
1354
+ ### Running Tests
1355
+
1356
+ ```bash
1357
+ # Run all tests
1358
+ npm test
1359
+
1360
+ # Run with UI
1361
+ npm run test:ui
1362
+
1363
+ # Run E2E tests only
1364
+ npm run test:e2e
1365
+
1366
+ # Run unit tests only
1367
+ npm run test:unit
1368
+
1369
+ # Run with coverage
1370
+ npm run test:coverage
1371
+ ```
1372
+
1373
+ ### Test Structure
1374
+
1375
+ ```
1376
+ packages/cli/
1377
+ ├── test/
1378
+ │ ├── e2e/ # End-to-end tests
1379
+ │ │ ├── fixtures/ # Test fixtures (base apps, features)
1380
+ │ │ ├── gold/ # Expected outputs for E2E tests
1381
+ │ │ └── apply-patches.spec.ts
1382
+ │ ├── unit/ # Unit tests
1383
+ │ │ ├── route-merger.spec.ts
1384
+ │ │ ├── import-merger.spec.ts
1385
+ │ │ └── file-operations.spec.ts
1386
+ │ └── helpers/ # Test utilities
1387
+ │ ├── compare-directories.ts
1388
+ │ ├── create-temp-dir.ts
1389
+ │ └── fixtures.ts
1390
+ ```
1391
+
1392
+ ### E2E Tests
1393
+
1394
+ E2E tests verify complete CLI workflows using gold files (expected outputs). Tests cover:
1395
+
1396
+ - Simple feature application (adding routes/files)
1397
+ - File deletion with `__delete__` prefix
1398
+ - Route deletion and import cleanup
1399
+ - Feature dependency resolution
1400
+ - Complex operations (`__inherit__`, `__prepend__`, `__append__`)
1401
+ - Error handling and validation
1402
+
1403
+ ### Unit Tests
1404
+
1405
+ Unit tests focus on individual modules:
1406
+
1407
+ - **route-merger**: Route merging logic and deletion
1408
+ - **import-merger**: Import statement merging, deduplication, type imports, and formatting
1409
+ - **file-operations**: File deletion, prepending, and appending
1410
+
1411
+ ### Updating Gold Files
1412
+
1413
+ When intentionally changing CLI behavior, update gold files:
1414
+
1415
+ ```bash
1416
+ UPDATE_GOLD=1 npm test
1417
+ ```
1418
+
1419
+ ⚠️ **Warning**: Only update gold files after verifying the new output is correct!
1420
+
1421
+ ### Creating New Tests
1422
+
1423
+ **E2E Test Example**:
1424
+
1425
+ ```typescript
1426
+ it("should apply a simple feature correctly", async () => {
1427
+ const outputDir = copyFixture("base-app", join(tempDir, "output"));
1428
+ const featurePath = getFixturePath("feature-simple");
1429
+
1430
+ await applyPatchesCommand(featurePath, outputDir, {
1431
+ skipDependencyChanges: true,
1432
+ });
1433
+
1434
+ const goldDir = getGoldPath("simple-apply");
1435
+ const differences = compareOrUpdate(outputDir, goldDir);
1436
+
1437
+ expect(differences).toEqual([]);
1438
+ });
1439
+ ```
1440
+
1441
+ **Unit Test Example**:
1442
+
1443
+ ```typescript
1444
+ it("should merge routes correctly", () => {
1445
+ const project = new Project({ useInMemoryFileSystem: true });
1446
+
1447
+ const targetFile = project.createSourceFile("target.tsx", `...`);
1448
+ const featureFile = project.createSourceFile("feature.tsx", `...`);
1449
+
1450
+ const result = mergeRoutes("feature.tsx", "target.tsx", project);
1451
+
1452
+ expect(result).toContain("expected content");
1453
+ });
1454
+ ```
1455
+
1456
+ ## Development
1457
+
1458
+ ```bash
1459
+ # Build the CLI
1460
+ npm run build
1461
+
1462
+ # Run without building (development)
1463
+ npm run dev -- <feature> <base-app>
1464
+ ```