@team-codeme/crud-forge 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  |___/
10
10
  Developed by TeamCodeMe
11
11
  Official Website: https://crudforge.teamcodeme.com/
12
- Documentation: https://crudforge-cli-doc.vercel.app
12
+ Documentation: https://docs-crudforgecli.teamcodeme.com/
13
13
  ```
14
14
 
15
15
  ![CRUDForge poster](https://lh3.googleusercontent.com/kTtVlTPXl9b8qEU7VXoz8u6eRtiAXjcnLIMVPlWnsJKUEI5Xx_EbjiAcH1afwmV-1sho3l_JPjNphGAC5Oszr1rA0V2uvdBs9mhLkK8Z=s960)
@@ -17,7 +17,7 @@
17
17
  <div align="center">
18
18
 
19
19
  [![Official Website](https://img.shields.io/badge/Official%20Website-crudforge.teamcodeme.com-blue?style=for-the-badge&logo=googlechrome&logoColor=white)](https://crudforge.teamcodeme.com/)
20
- [![Documentation](https://img.shields.io/badge/Documentation-crudforge--cli--doc.vercel.app-success?style=for-the-badge&logo=vercel&logoColor=white)](https://crudforge-cli-doc.vercel.app)
20
+ [![Documentation](https://img.shields.io/badge/Documentation-docs--crudforgecli.teamcodeme.com-success?style=for-the-badge&logo=vercel&logoColor=white)](https://docs-crudforgecli.teamcodeme.com/)
21
21
  [![npm version](https://img.shields.io/npm/v/@team-codeme/crud-forge?style=for-the-badge&color=cb3837&logo=npm)](https://www.npmjs.com/package/@team-codeme/crud-forge)
22
22
 
23
23
  </div>
@@ -25,10 +25,10 @@
25
25
  CRUDForge CLI is a high-performance, developer-configurable Angular Material CRUD code generator. It maps JDL (JHipster Domain Language) or GDL schemas directly into high-fidelity standalone Angular 17+ components, types, services, and routes.
26
26
 
27
27
  - 🌐 **Official Website:** [https://crudforge.teamcodeme.com/](https://crudforge.teamcodeme.com/)
28
- - 📖 **Documentation:** [https://crudforge-cli-doc.vercel.app](https://crudforge-cli-doc.vercel.app)
28
+ - 📖 **Documentation:** [https://docs-crudforgecli.teamcodeme.com/](https://docs-crudforgecli.teamcodeme.com/)
29
29
  - 🏢 **Organization:** [@team-codeme](https://www.npmjs.com/org/team-codeme)
30
30
 
31
- **Product docs (deployable Starlight site):** Available live at [crudforge-cli-doc.vercel.app](https://crudforge-cli-doc.vercel.app) (source in sibling repo [`crud-fordge-doc`](../crud-fordge-doc)) — splash uses the same poster; Features / Customization galleries with screenshots. Capture shots: `npm run e2e:serve` then `cd ../crud-fordge-doc && npm run shots:capture` (IAM: `e2e:serve:iam` + `shots:capture:iam`).
31
+ **Product docs (deployable Starlight site):** Available live at [docs-crudforgecli.teamcodeme.com](https://docs-crudforgecli.teamcodeme.com/) (source in sibling repo [`crud-fordge-doc`](../crud-fordge-doc)) — splash uses the same poster; Features / Customization galleries with screenshots. Capture shots: `npm run e2e:serve` then `cd ../crud-fordge-doc && npm run shots:capture` (IAM: `e2e:serve:iam` + `shots:capture:iam`).
32
32
 
33
33
  ---
34
34
 
package/Todo.md CHANGED
@@ -204,4 +204,4 @@ PMB still copies some of these into every entity `shared/`. Generate them **once
204
204
 
205
205
 
206
206
 
207
- generated fuse has 404 for user management.
207
+ generated fuse has 404 for user management. — **addressed in generator (ISSUE-001)**: route registration now preserves IAM `user-management`/`no-access` when entity routes are rewritten (including `fuse.cleanLayout`). Verify by regenerating an IAM-enabled Fuse fixture and opening `/admin/user-management`.
package/cli.js CHANGED
@@ -19,6 +19,7 @@ const argv = yargs
19
19
  .positional('jdlFile', { describe: 'JDL/GDL file (defaults to input.file in project config)', type: 'string' })
20
20
  .positional('outputFolder', { describe: 'Target output folder for the Angular files', type: 'string' })
21
21
  .option('microservice', { alias: 'm', describe: 'Name of the microservice for URL paths', type: 'string' })
22
+ .option('service', { alias: 's', describe: 'Target only the specified service from input.sources', type: 'string' })
22
23
  .option('preferences', { alias: 'p', describe: 'Path to custom crudforge config', type: 'string' })
23
24
  .option('json', { type: 'boolean', default: false, describe: 'JSON outcome/diagnostics on stdout; progress on stderr' })
24
25
  .option('dry-run', { alias: 'd', describe: 'Preview modifications without writing', type: 'boolean', default: false })
@@ -0,0 +1,17 @@
1
+ # Issues — CRUDForge Generator
2
+
3
+ This directory holds issues reported by developers, categorized by status:
4
+
5
+ - `issues/open/` — open / triaged issues awaiting fix
6
+ - `issues/closed/` — resolved issues (moved here once fixed)
7
+ - `issues/templates/` — issue report templates
8
+
9
+ ## Issue Report Template
10
+
11
+ Copy `issues/templates/issue.md` into `issues/open/` with a descriptive filename (e.g. `issue-001-fix-name.md`), fill in the fields, and link to any related PR/commit when resolved.
12
+
13
+ ## Workflow
14
+
15
+ 1. Developer submits an issue → create file in `issues/open/`
16
+ 2. Maintainer triages → adds labels/priority in the front matter
17
+ 3. Fix implemented → link PR/commit, move file to `issues/closed/`
@@ -0,0 +1,44 @@
1
+ ---
2
+ id: ISSUE-001
3
+ title: Generated Fuse app returns 404 for /admin/user-management
4
+ status: resolved
5
+ priority: high
6
+ reported-by: developer
7
+ reported-date: 2026-11-08
8
+ component: generator
9
+ labels:
10
+ - bug
11
+ - iam
12
+ - routes
13
+ related-pr:
14
+ related-commit:
15
+ ---
16
+
17
+ ## Description
18
+
19
+ When generating a Fuse app with Keycloak/IAM enabled (especially with `fuse.cleanLayout: true`), navigating to `/admin/user-management` can resolve to the SPA 404 route.
20
+
21
+ ## Root Cause
22
+
23
+ 1. `applyAuthPack` registers the IAM route (`path: 'user-management'` → `@/app/core/user-management/user-management.routes`) in `src/app/domains/admin/routes.ts`.
24
+ 2. Entity route registration in `src/generator/index.js` later rewrites admin routes:
25
+ - `fuse.cleanLayout: true` rebuilt the full `children` array and omitted the previously registered IAM route.
26
+ - The non-cleanLayout path replaced the `// cforge-routes-start/end` block with entity routes only, which can drop IAM routes inserted into that block.
27
+ 3. `patchLandingAndNoAccess` also injects `no-access`, which could be lost by the same rewrite.
28
+
29
+ ## Expected Behavior
30
+
31
+ `/admin/user-management` (and IAM child routes) remain reachable after entity route registration, regardless of `fuse.cleanLayout`.
32
+
33
+ ## Fix
34
+
35
+ - `src/generator/index.js`
36
+ - When rewriting routes, capture the existing `user-management` and `no-access` route objects before regeneration.
37
+ - Re-inject those routes into the generated entity route block (`cforge-routes-start/end`) so they survive both cleanLayout and non-cleanLayout rewrites.
38
+ - Avoid mutating the same `content` variable across cleanLayout branches.
39
+
40
+ ## Verification
41
+
42
+ - `node --check src/generator/index.js`
43
+ - `node --check src/generator/auth-pack.js`
44
+ - Pending: regenerate an IAM-enabled Fuse fixture (`e2e:prepare:iam` / project workflow) and confirm `/admin/user-management` loads UserListComponent.
@@ -0,0 +1,99 @@
1
+ ---
2
+ id: ISSUE-002
3
+ title: List UI Issues (Row Pinning, Page Size Reset, Search Clearing)
4
+ status: resolved
5
+ priority: medium
6
+ reported-by: developer
7
+ reported-date: 2026-11-09
8
+ component: templates
9
+ labels:
10
+ - bug
11
+ - ui
12
+ - list
13
+ - pagination
14
+ - search
15
+ related-pr:
16
+ related-commit:
17
+ ---
18
+
19
+ # List UI Issues
20
+
21
+ This document records three UI/UX issues identified and resolved in generated entity list presentation components.
22
+
23
+ ---
24
+
25
+ ## Issue 1: Record Pinning Position & Restoration on Unpin
26
+
27
+ ### Description
28
+ When a record row is pinned in the list/table view, it should immediately be pinned at the top of the table. When unpinning the record, it should return to its original place in the list rather than remaining displaced or at an arbitrary position.
29
+
30
+ ### Expected Behavior
31
+ 1. Clicking the pin icon on any row pins the record and immediately shifts it to the top of the current display table/cards.
32
+ 2. Clicking the pin icon again unpins the record and returns it to its original position within the current page's dataset.
33
+ 3. If multiple records are pinned, they remain grouped at the top, and all unpinned records preserve their original server/query order.
34
+
35
+ ### Root Cause
36
+ 1. `BaseListComponent.togglePin()` modified `this.pinnedIds` in place and called `cdr.markForCheck()`, but did not re-sort or re-assign `this.dataSource.data`.
37
+ 2. `sortRowsForDisplay()` used a simple `.sort()` comparison that returned `0` when both elements shared the same pinned state (`leftPinned === rightPinned`). Because JavaScript array sort is not guaranteed to restore previous indices when returning `0`, and because the original unsorted array was not preserved as a baseline, unpinning did not return the row to its original index.
38
+
39
+ ### Fix
40
+ - In `BaseListComponent` (`src/templates/angular/shared/base/base-list.component.ts.ejs`):
41
+ - Added `dataSource?: any` and `protected rawRows: T[] = []` to preserve the pristine dataset baseline.
42
+ - Rewrote `sortRowsForDisplay(rows)` into a stable two-bucket partition (`pinned` and `unpinned`), preserving exact baseline order.
43
+ - Updated `togglePin(element)` to re-sort `dataSource.data` from `this.rawRows` baseline immediately on pin/unpin toggles.
44
+ - In `ListComponent` (`src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs`):
45
+ - Preserved incoming query results in `this.rawRows = res.body ?? []` alongside `dataSource.data`.
46
+
47
+ ---
48
+
49
+ ## Issue 2: Pagination Page Size Reset on Page Navigation
50
+
51
+ ### Description
52
+ When a user increases the pagination page size (e.g. from 10 to 25, 50, or 100), navigates away to another page/entity, and returns, the page size remained set to the previously increased value rather than resetting to the default page size of 10.
53
+
54
+ ### Expected Behavior
55
+ - While interacting on the current list view, the user can change page size and paginate through records with that size.
56
+ - When the user navigates away to another screen and later returns to the list view, the page size must reset to default (`10`) by default.
57
+
58
+ ### Root Cause
59
+ - In `ngOnInit()`, the component loaded `savedPageSize` from `localStorage` under `storageKeyPageSize` (`${entityNameKebab}-page-size`) and set `this.itemsPerPage = Number(savedPageSize)`.
60
+ - In `loadData()`, any change in `this.paginator.pageSize` was being persisted into `localStorage`. This caused the increased size to persist indefinitely across navigations.
61
+
62
+ ### Fix
63
+ - In `ListComponent` (`src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs`):
64
+ - Removed `localStorage.getItem(this.storageKeyPageSize)` restoration from `ngOnInit()`, so the component consistently starts with default `itemsPerPage = 10`.
65
+ - Removed `localStorage.setItem(this.storageKeyPageSize, ...)` in `loadData()`, updating `this.itemsPerPage` in-memory only during interaction on the active screen.
66
+
67
+ ---
68
+
69
+ ## Issue 3: Search Bar Clearing on Search Preference Change
70
+
71
+ ### Description
72
+ When a search term is entered in the search bar and the user changes the search preference (the `searchBy` column dropdown, e.g. from "Fish Type Name" to "Fish Type Code"), the search bar retained the previous search term rather than clearing it.
73
+
74
+ ### Expected Behavior
75
+ When a search term is typed and the user selects a different search preference/field from the dropdown, the search bar input should be cleared automatically, and the filter should be reset.
76
+
77
+ ### Root Cause
78
+ - In `ActionsComponent.onSearchByChange(value)`, the component emitted `{ searchBy: value, searchTerm: this.searchTerm }`, preserving the existing `searchTerm`.
79
+ - In `ListComponent.handleSearchChanged(event)`, the incoming `searchTerm` was accepted directly without checking whether `event.searchBy !== this.searchBy`.
80
+
81
+ ### Fix
82
+ - In `ActionsComponent` (`src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.ts.ejs`):
83
+ - Updated `onSearchByChange(value)` to set `this.searchTerm = ''` and emit `{ searchBy: value, searchTerm: '' }`.
84
+ - In `ListComponent` (`src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs`):
85
+ - Updated `handleSearchChanged(event)` to detect when `event.searchBy !== this.searchBy` and reset `this.searchTerm = ''`, updating `localStorage` and calling `applySearchFilters()`.
86
+
87
+ ---
88
+
89
+ ## Verification
90
+
91
+ - Added unit test regressions in `test/list-state.js` verifying:
92
+ - Row pinning immediately moves rows to the top, grouping multiple pinned rows while preserving unpinned order.
93
+ - Row unpinning immediately returns items to their original baseline indices.
94
+ - Page size defaults to 10 on initialization, ignores stale localStorage entries, and updates in-memory without persisting across navigations.
95
+ - Changing searchBy in `ActionsComponent` clears `searchTerm` and emits empty query.
96
+ - Changing searchBy in `ListComponent.handleSearchChanged()` clears `searchTerm` and resets active filters.
97
+ - `npm run test:ui-state` passed.
98
+ - `npm test` passed.
99
+
@@ -0,0 +1,70 @@
1
+ ---
2
+ id: ISSUE-003
3
+ title: List UI Issues (Async Rows Blank until Pointer Move & Disappearing Header)
4
+ status: resolved
5
+ priority: high
6
+ reported-by: developer
7
+ reported-date: 2026-11-13
8
+ component: templates
9
+ labels:
10
+ - bug
11
+ - ui
12
+ - list
13
+ - change-detection
14
+ - responsive
15
+ related-pr:
16
+ related-commit:
17
+ ---
18
+
19
+ # List UI Issues
20
+
21
+ This document records two UI issues identified and resolved across the IAM user management screen and generated entity list presentation components.
22
+
23
+ ---
24
+
25
+ ## Issue A: Loaded rows stay hidden until the pointer moves
26
+
27
+ ### Description
28
+ On lists such as `/admin/user-management` and generated entity screens, data has loaded from the API but the table rows stay blank/hidden until the mouse moves or hovers over the browser window.
29
+
30
+ ### Expected Behavior
31
+ Once asynchronous data fetch finishes, rows immediately render into the table without requiring user interaction or pointer movement.
32
+
33
+ ### Root Cause
34
+ 1. `UserListComponent` lacked `ChangeDetectorRef` and `ChangeDetectionStrategy.OnPush`. In addition, its `tableRows` was implemented as a computed getter (`get tableRows()`). Angular did not evaluate the getter or mark the view dirty after async HTTP callbacks until a DOM event (e.g. mouse move) triggered Zone.js change detection.
35
+ 2. In `BaseListComponent`, `observeListWidth()` callback ran asynchronously inside a native `ResizeObserver`, which executed outside of Angular's Zone.js without re-entering the zone or running change detection.
36
+ 3. In `_entity_-list.component.ts.ejs`, setting `this.isLoading = true` and `isLoading = false` did not always trigger immediate view re-checks under OnPush when async observable subscriptions completed.
37
+
38
+ ### Fix
39
+ - In `UserListComponent` (`src/templates/angular/iam/user-list.component.ts.ejs`):
40
+ - Injected `ChangeDetectorRef` and enabled `changeDetection: ChangeDetectionStrategy.OnPush`.
41
+ - Added explicit `this.cdr.markForCheck()` calls in `load()` on API success/error, `onSortChange()`, `setViewMode()`, `togglePin()`, and `setFlagColor()`.
42
+ - Added host class `cforge-iam-list` for styling containment.
43
+ - In `BaseListComponent` (`src/templates/angular/shared/base/base-list.component.ts.ejs`):
44
+ - Injected `NgZone` and wrapped the `observeListWidth()` ResizeObserver callback in `this.ngZone.run()` to guarantee observer ticks trigger change detection inside Angular's zone.
45
+ - In `_entity_-list.component.ts.ejs` (`src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs`):
46
+ - Injected `ChangeDetectorRef` and called `this.cdr.markForCheck()` on load start and upon data arrival.
47
+
48
+ ---
49
+
50
+ ## Issue B: The page heading disappears at some window widths
51
+
52
+ ### Description
53
+ The page heading/title was visible at small screen sizes and large desktop widths, but completely vanished at intermediate widths (e.g. tablet / medium viewports between 768px and 1279px) while the action toolbar remained visible.
54
+
55
+ ### Expected Behavior
56
+ The page heading, subtitle, and breadcrumb remain visible at all viewport widths, wrapping gracefully alongside or above the actions toolbar.
57
+
58
+ ### Root Cause
59
+ 1. In `_entity_-actions.component.ts.ejs`, the actions component host was styled with `:host { @apply block w-full xl:w-auto; }`. On viewports between `md` (768px) and `xl` (1280px), `w-full` forced the actions component to request 100% width inside the flex header container.
60
+ 2. In `_entity_-list.component.html.ejs` and `user-list.component.html.ejs`, the header container was styled with `flex flex-col md:flex-row md:items-center justify-between` without `flex-wrap`. Combined with the actions bar occupying 100% width, `<div class="min-w-0 flex-1">` holding the heading was compressed down to 0 width.
61
+ 3. In `user-list.component.html.ejs`, the container used an invalid Tailwind utility class `pb-22`.
62
+
63
+ ### Fix
64
+ - In `_entity_-actions.component.ts.ejs`:
65
+ - Changed host class styling from `block w-full xl:w-auto` to `block w-full sm:w-auto md:shrink-0`.
66
+ - In `_entity_-list.component.html.ejs` and `user-list.component.html.ejs`:
67
+ - Updated header container classes to `flex flex-col sm:flex-row sm:items-center sm:justify-between sm:flex-wrap gap-4`.
68
+ - Added `shrink-0` to the title wrapper container so it cannot be shrunk to 0 width.
69
+ - Replaced invalid `pb-22` with valid Tailwind class `pb-20`.
70
+
@@ -0,0 +1,110 @@
1
+ ---
2
+ id: ISSUE-004
3
+ title: Generator Isolation, Safe Merging, and Preservation during Regeneration
4
+ status: resolved
5
+ priority: high
6
+ reported-by: developer
7
+ reported-date: 2026-11-13
8
+ component: generator
9
+ labels:
10
+ - bug
11
+ - generator
12
+ - multi-source
13
+ - overwrite-policy
14
+ - routing
15
+ - navigation
16
+ - access-control
17
+ related-pr:
18
+ related-commit:
19
+ ---
20
+
21
+ # Generator Issue Report: Service Isolation, Unintended File Overwrites, and Destructive Generation
22
+
23
+ ## 1. Overview
24
+
25
+ During incremental generation targeting a newly added schema (`GDL/proc.gdl`), running `crudforge generate` (both with global configuration and direct positional file arguments) previously produced side-effects across preexisting services, shared folders, and global application infrastructure:
26
+ 1. Re-parsed and re-generated all microservice modules rather than isolating execution.
27
+ 2. Overwrote existing files and wiped out un-bracketed modifications under `--overwrite-policy merge`.
28
+ 3. Created malformed nested directory structures (`micro-functions/micro-functions`).
29
+ 4. Stripped existing routes and navigation links from `routes.ts` and `navigation.ts`.
30
+ 5. Emptied preexisting service registrations from the screen catalog CSV and TS registry.
31
+ 6. Overwrote shared styles and components.
32
+
33
+ ---
34
+
35
+ ## 2. Issues and Resolutions
36
+
37
+ ### Issue 1: Lack of Schema / Service Isolation
38
+ * **Description**: Running `crudforge generate` iterated through all schemas under `input.sources` in `config/crudforge.config.yml`, re-generating all services even when targeting a specific service.
39
+ * **Root Cause**: The CLI generate command lacked service filtering support.
40
+ * **Fix**:
41
+ - Added `-s, --service <name>` option to `cli.js`.
42
+ - In `src/commands/generate.js`, filtered `inputSources` based on `--service` flag matching service name or microservice ID.
43
+ - When a positional schema file is passed, matched it against configured sources in `config.input.sources`.
44
+
45
+ ---
46
+
47
+ ### Issue 2: Destructive Overwrite Under `--overwrite-policy merge`
48
+ * **Description**: Under `--overwrite-policy merge`, files without zone markers (such as services, enums, and components) were completely replaced with fresh boilerplate, wiping out developer customizations.
49
+ * **Root Cause**: `mergeContent()` in `src/utils/merger.js` only checked for custom code block markers (`// cforge-custom-code-start`). When `oldZones` was empty, it returned raw `newContent`, making `merge` act like `overwrite`.
50
+ * **Fix**:
51
+ - In `src/utils/merger.js`, updated `mergeContent()` so that if `oldZones` is empty (`Object.keys(oldZones).length === 0`), it returns `oldContent`, protecting existing code from unintended overwrites.
52
+
53
+ ---
54
+
55
+ ### Issue 3: Pathing Anomaly with Positional File Argument (`micro-functions/micro-functions`)
56
+ * **Description**: Running `crudforge generate GDL/proc.gdl` created a double-nested path `src/app/domains/admin/modules/apps/micro-functions/micro-functions/`.
57
+ * **Root Cause**: In `src/commands/generate.js`, synthetic input sources defaulted `name` and `folder` to `'micro-functions'`, which `serviceFolder()` in `entity-layout.js` then prefixed with `micro-functions/`, producing `micro-functions/micro-functions`.
58
+ * **Fix**:
59
+ - In `src/commands/generate.js`, derived service name from `options.microservice || options.service || config.api?.microservice || fileBase`.
60
+ - In `src/generator/entity-layout.js`, ensured that when `folderName` or `module` equals `'micro-functions'`, it does not double-nest the folder.
61
+
62
+ ---
63
+
64
+ ### Issue 4: Global Route Truncation Under `cleanLayout: true`
65
+ * **Description**: In `src/app/domains/admin/routes.ts`, preexisting routes for other services were deleted, leaving only the routes processed in the current run.
66
+ * **Root Cause**: The generator replaced the children array with routes built solely from the current run's `entities` array.
67
+ * **Fix**:
68
+ - In `src/generator/index.js`, extracted existing entity routes from `routes.ts` (between `// cforge-routes-start` and `// cforge-routes-end` markers), merged them with the active run's entities by route path, and used bracket-depth balancing to replace the children block while preserving all existing service routes and IAM routes.
69
+ - Set `{ bypassMerge: true }` on `routes.ts` writes so the programmatically merged content is written directly.
70
+
71
+ ---
72
+
73
+ ### Issue 5: Navigation Item Purging Under `cleanLayout: true`
74
+ * **Description**: In `src/app/domains/admin/layout/data/navigation.ts`, preexisting service navigation groups were purged.
75
+ * **Root Cause**: `NAVIGATION` array content was replaced with items derived only from current `entities`.
76
+ * **Fix**:
77
+ - In `src/generator/index.js`, parsed existing top-level navigation groups from `navigation.ts` using balanced curly-brace tracking, preserving preexisting service groups and IAM navigation while updating or adding current run groups.
78
+ - Set `{ bypassMerge: true }` on `navigation.ts` writes.
79
+
80
+ ---
81
+
82
+ ### Issue 6: Screen Catalog & Access Control Purge
83
+ * **Description**: In `public/assets/screen-components.csv` and `shared/screen-catalog.ts`, screen entries for preexisting services were removed.
84
+ * **Root Cause**: `generateIamModule()` in `src/generator/auth-pack.js` generated the catalog from scratch based only on the current run's `entities`.
85
+ * **Fix**:
86
+ - In `src/generator/auth-pack.js`, parsed existing rows from `screen-components.csv`, retained screens from other services, deduplicated by `screenId`, and generated the merged CSV.
87
+ - Derived `finalScreens` from the merged CSV rows and updated `locals.managedScreens` so `shared/screen-catalog.ts` retains all screens across all services.
88
+ - Set `{ bypassMerge: true }` for CSV and screen catalog outputs.
89
+
90
+ ---
91
+
92
+ ### Issue 7: Shared Folder Modifications & Starter Pack Overwriting
93
+ * **Description**: Shared styles (`shared/styles/list-styles.css`), shared validators, and base entity configurations were rewritten during generation, disregarding changes made in those files.
94
+ * **Root Cause**:
95
+ 1. `fs.copySync(starterPackPath, outputDir)` ran on every generation pass without `{ overwrite: false }`, overwriting existing scaffolded files with pristine starter pack files.
96
+ 2. Under `--overwrite-policy merge`, files without zone markers were overwritten due to Issue 2.
97
+ * **Fix**:
98
+ - In `src/generator/index.js`, added `{ overwrite: false }` to `fs.copySync(starterPackPath, outputDir, { filter: filterFn, overwrite: false })`.
99
+ - Together with the `merger.js` protection, custom changes to shared styles, validators, and components are preserved under `merge`.
100
+
101
+ ---
102
+
103
+ ## 3. Verification
104
+
105
+ A dedicated regression test suite was added in `test/regeneration-isolation.js` and integrated into `npm test`:
106
+ - Validates merger preservation of un-bracketed developer code when no zones exist.
107
+ - Validates that `serviceFolder` does not create `micro-functions/micro-functions` nesting.
108
+ - Validates multi-pass incremental generation preserving routes, navigation, and CSV screen catalog across services.
109
+ - Verified with `npm test`, `npm run test:ui-state`, `npm run test:auth`, and `npm run test:field-preferences`.
110
+
File without changes
@@ -0,0 +1,50 @@
1
+ ---
2
+ id: ISSUE-000
3
+ title: Replace with short issue title
4
+ status: open | triaged | in-progress | resolved
5
+ priority: high | medium | low
6
+ reported-by: developer name or handle
7
+ reported-date: YYYY-MM-DD
8
+ component: generator | cli | parser | validator | templates | docs
9
+ labels:
10
+ - bug
11
+ - enhancement
12
+ related-pr: PR number or link
13
+ related-commit: commit sha or link
14
+ ---
15
+
16
+ ## Description
17
+
18
+ Brief description of the issue.
19
+
20
+ ## Steps to Reproduce
21
+
22
+ 1.
23
+ 2.
24
+ 3.
25
+
26
+ ## Expected Behavior
27
+
28
+ What should happen.
29
+
30
+ ## Actual Behavior
31
+
32
+ What actually happens.
33
+
34
+ ## Environment
35
+
36
+ - Node version:
37
+ - OS:
38
+ - CRUDForge version:
39
+
40
+ ## Additional Context
41
+
42
+ Screenshots, logs, or config snippets.
43
+
44
+ ## Fix
45
+
46
+ Summary of the fix applied and files changed.
47
+
48
+ ## Verification
49
+
50
+ How the fix was verified (tests run, manual check).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-codeme/crud-forge",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "A modern, configurable JDL/GDL-to-Angular code generator supporting Angular 17+ standalone architecture, centralized developer preferences, and strict type safety.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -13,10 +13,10 @@
13
13
  "homepage": "https://crudforge.teamcodeme.com/",
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "git+https://github.com/FayyazAbdulla/crudforge-cli.git"
16
+ "url": "git+https://github.com/teamcodemelk/crudforge-cli.git"
17
17
  },
18
18
  "scripts": {
19
- "test": "node test/verify.js && node test/micro-functions-layout.js && node test/project-workflow.js && node test/pmb-preset.js && node test/quality-features.js",
19
+ "test": "node test/verify.js && node test/micro-functions-layout.js && node test/project-workflow.js && node test/pmb-preset.js && node test/quality-features.js && node test/regeneration-isolation.js",
20
20
  "test:field-preferences": "node test/field-preferences.js",
21
21
  "e2e:prepare:field-profile": "node scripts/e2e-prepare-field-profile.js",
22
22
  "test:ui-state": "node test/list-state.js",
@@ -21,24 +21,40 @@ async function generateCommand(jdlFile, outputFolder, options = {}) {
21
21
 
22
22
  // CLI positional file overrides config; otherwise use input.sources[] or input.file.
23
23
  let inputSources;
24
+ const configuredSources = Array.isArray(config.input?.sources) && config.input.sources.length > 0
25
+ ? resolveInputSources(config, projectRoot)
26
+ : [];
27
+
24
28
  if (jdlFile) {
25
29
  const resolved = path.resolve(cwd, jdlFile);
26
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
27
- throw new Error(`Input file not found: "${resolved}".`);
30
+ const matchedSource = configuredSources.find((s) =>
31
+ s.file === resolved ||
32
+ path.resolve(projectRoot, s.file) === resolved ||
33
+ s.name.toLowerCase() === jdlFile.toLowerCase() ||
34
+ (s.microservice && s.microservice.toLowerCase() === jdlFile.toLowerCase())
35
+ );
36
+
37
+ if (matchedSource) {
38
+ inputSources = [matchedSource];
39
+ } else {
40
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
41
+ throw new Error(`Input file not found: "${resolved}".`);
42
+ }
43
+ if (!/\.(gdl|jdl)$/i.test(resolved)) throw new Error(`Unsupported schema "${resolved}". Use .gdl or .jdl.`);
44
+ const type = path.extname(resolved).slice(1).toLowerCase();
45
+ const fileBase = path.basename(resolved, path.extname(resolved));
46
+ const serviceName = options.microservice || options.service || config.api?.microservice || fileBase;
47
+ const folder = String(config.fuse?.folderType || '').trim() || serviceName;
48
+ inputSources = [
49
+ {
50
+ name: serviceName,
51
+ file: resolved,
52
+ type,
53
+ microservice: serviceName,
54
+ folder,
55
+ },
56
+ ];
28
57
  }
29
- if (!/\.(gdl|jdl)$/i.test(resolved)) throw new Error(`Unsupported schema "${resolved}". Use .gdl or .jdl.`);
30
- const type = path.extname(resolved).slice(1).toLowerCase();
31
- const folder =
32
- String(config.fuse?.folderType || '').trim() || (type === 'gdl' ? 'micro-functions' : 'microservice');
33
- inputSources = [
34
- {
35
- name: folder,
36
- file: resolved,
37
- type,
38
- microservice: String(config.api?.microservice || '').trim(),
39
- folder,
40
- },
41
- ];
42
58
  } else {
43
59
  inputSources = resolveInputSources(config, projectRoot);
44
60
  if (!inputSources.length) {
@@ -48,6 +64,21 @@ async function generateCommand(jdlFile, outputFolder, options = {}) {
48
64
  }
49
65
  }
50
66
 
67
+ if (options.service) {
68
+ const target = String(options.service).trim().toLowerCase();
69
+ const filtered = inputSources.filter((s) =>
70
+ s.name.toLowerCase() === target ||
71
+ (s.microservice && s.microservice.toLowerCase() === target) ||
72
+ path.basename(s.file, path.extname(s.file)).toLowerCase() === target
73
+ );
74
+ if (!filtered.length) {
75
+ throw new Error(
76
+ `Service "${options.service}" not found in input sources. Available sources: ${inputSources.map((s) => s.name).join(', ')}`
77
+ );
78
+ }
79
+ inputSources = filtered;
80
+ }
81
+
51
82
  for (const source of inputSources) {
52
83
  if (!fs.existsSync(source.file) || !fs.statSync(source.file).isFile()) {
53
84
  throw new Error(