@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 +4 -4
- package/Todo.md +1 -1
- package/cli.js +1 -0
- package/issues/README.md +17 -0
- package/issues/closed/ISSUE-001-fuse-user-management-404.md +44 -0
- package/issues/closed/ISSUE-002-list-UI-issues.md +99 -0
- package/issues/closed/ISSUE-003-list-UI-issues.md +70 -0
- package/issues/closed/ISSUE-004-re-generator-issue.md +110 -0
- package/issues/open/.gitkeep +0 -0
- package/issues/templates/issue.md +50 -0
- package/package.json +3 -3
- package/src/commands/generate.js +46 -15
- package/src/generator/auth-pack.js +111 -7
- package/src/generator/entity-layout.js +9 -5
- package/src/generator/index.js +125 -45
- package/src/generator/project-output.js +3 -2
- package/src/templates/angular/entity/components/interaction/actions/_entity_-actions.component.ts.ejs +3 -2
- package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.html.ejs +3 -3
- package/src/templates/angular/entity/components/presentation/list/_entity_-list.component.ts.ejs +13 -11
- package/src/templates/angular/iam/user-list.component.html.ejs +4 -4
- package/src/templates/angular/iam/user-list.component.ts.ejs +11 -2
- package/src/templates/angular/shared/base/base-list.component.ts.ejs +27 -11
- package/src/utils/merger.js +2 -1
- package/test/list-state.js +72 -1
- package/test/regeneration-isolation.js +94 -0
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://
|
|
12
|
+
Documentation: https://docs-crudforgecli.teamcodeme.com/
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|

|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div align="center">
|
|
18
18
|
|
|
19
19
|
[](https://crudforge.teamcodeme.com/)
|
|
20
|
-
[](https://docs-crudforgecli.teamcodeme.com/)
|
|
21
21
|
[](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://
|
|
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 [
|
|
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 })
|
package/issues/README.md
ADDED
|
@@ -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.
|
|
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/
|
|
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",
|
package/src/commands/generate.js
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
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(
|