@devmed555/angular-clean-architecture-cli 0.0.1 → 0.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/bin/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmed555/angular-clean-architecture-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "CLI generator for Angular Clean Architecture features using NgRx SignalStore",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -25,10 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@nx/devkit": "20.0.4",
|
|
28
|
-
"@nx/js": "20.0.4",
|
|
29
|
-
"@nx/angular": "20.0.4",
|
|
30
28
|
"tslib": "^2.3.0",
|
|
31
|
-
"inquirer": "^
|
|
29
|
+
"inquirer": "^8.2.6"
|
|
32
30
|
},
|
|
33
31
|
"type": "commonjs",
|
|
34
32
|
"main": "./src/index.js",
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.cleanFeatureGenerator = cleanFeatureGenerator;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const devkit_1 = require("@nx/devkit");
|
|
6
|
+
const inquirer = require("inquirer");
|
|
6
7
|
/**
|
|
7
8
|
* Capitalizes the first letter of a string
|
|
8
9
|
*/
|
|
@@ -15,31 +16,67 @@ function capitalizeFirst(str) {
|
|
|
15
16
|
function toPascalCase(str) {
|
|
16
17
|
return str
|
|
17
18
|
.split('-')
|
|
18
|
-
.map(part => capitalizeFirst(part))
|
|
19
|
+
.map((part) => capitalizeFirst(part))
|
|
19
20
|
.join('');
|
|
20
21
|
}
|
|
21
22
|
function cleanFeatureGenerator(tree, options) {
|
|
22
23
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
23
|
-
|
|
24
|
+
let name = options.name;
|
|
25
|
+
let attributes = [];
|
|
26
|
+
// Interactive mode if no name provided or explicit interactive flag (though we don't have a specific flag in schema, strict missing name is enough)
|
|
27
|
+
if (!name) {
|
|
28
|
+
const questions = [
|
|
29
|
+
{
|
|
30
|
+
type: 'input',
|
|
31
|
+
name: 'name',
|
|
32
|
+
message: 'What is the name of the feature (singular)?',
|
|
33
|
+
validate: (input) => input.length > 0 ? true : 'Name is required',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
const answers = yield inquirer.prompt(questions);
|
|
37
|
+
name = answers.name;
|
|
38
|
+
}
|
|
39
|
+
// Auto-pluralize: simply add 's' for now as requested
|
|
40
|
+
// Ideally use a pluralize library, but sticking to simple requirement
|
|
41
|
+
if (!name.endsWith('s')) {
|
|
42
|
+
name = name + 's';
|
|
43
|
+
}
|
|
24
44
|
const targetPath = (0, devkit_1.joinPathFragments)('apps/sandbox/src/app/features', name);
|
|
25
45
|
// Format names for templates
|
|
26
46
|
const pascalName = toPascalCase(name);
|
|
27
|
-
// Parse attributes
|
|
28
|
-
let attributes = [];
|
|
29
47
|
if (options.attributes) {
|
|
30
|
-
attributes = options.attributes.split(',').map(attr => {
|
|
31
|
-
const [
|
|
32
|
-
return { name, type:
|
|
48
|
+
attributes = options.attributes.split(',').map((attr) => {
|
|
49
|
+
const [n, t] = attr.split(':');
|
|
50
|
+
return { name: n, type: t || 'string' };
|
|
33
51
|
});
|
|
34
52
|
}
|
|
35
53
|
else {
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
{
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
// Interactive attribute prompting
|
|
55
|
+
console.log('Let\'s add some attributes (property fields).');
|
|
56
|
+
let addingAttributes = true;
|
|
57
|
+
while (addingAttributes) {
|
|
58
|
+
const { attrName } = yield inquirer.prompt([
|
|
59
|
+
{
|
|
60
|
+
type: 'input',
|
|
61
|
+
name: 'attrName',
|
|
62
|
+
message: 'Enter attribute name (or press enter to finish):',
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
if (!attrName || attrName.trim() === '') {
|
|
66
|
+
addingAttributes = false;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const { attrType } = yield inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'list',
|
|
72
|
+
name: 'attrType',
|
|
73
|
+
message: 'Select type:',
|
|
74
|
+
choices: ['string', 'number', 'boolean', 'Date', 'any'],
|
|
75
|
+
default: 'string',
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
attributes.push({ name: attrName, type: attrType });
|
|
79
|
+
}
|
|
43
80
|
}
|
|
44
81
|
// Generate files from templates
|
|
45
82
|
(0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files'), targetPath, Object.assign(Object.assign({}, options), { name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../apps/cli/src/generators/clean-feature/generator.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../apps/cli/src/generators/clean-feature/generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,14 +10,12 @@
|
|
|
10
10
|
"$default": {
|
|
11
11
|
"$source": "argv",
|
|
12
12
|
"index": 0
|
|
13
|
-
}
|
|
14
|
-
"x-prompt": "What name would you like to use?"
|
|
13
|
+
}
|
|
15
14
|
},
|
|
16
15
|
"attributes": {
|
|
17
16
|
"type": "string",
|
|
18
|
-
"description": "Comma-separated list of attributes (e.g., name:string,age:number)"
|
|
19
|
-
"x-prompt": "Enter attributes (format: name:string,age:number) or leave empty:"
|
|
17
|
+
"description": "Comma-separated list of attributes (e.g., name:string,age:number)"
|
|
20
18
|
}
|
|
21
19
|
},
|
|
22
|
-
"required": [
|
|
20
|
+
"required": []
|
|
23
21
|
}
|
package/README.md
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# CLI Documentation
|
|
2
|
-
|
|
3
|
-
The CLI is an Nx generator designed to scaffold clean architecture features within the `sandbox` application.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
To generate a new feature, run:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm run generate:feature -- --name=<feature-name>
|
|
11
|
-
|
|
12
|
-
# Examples:
|
|
13
|
-
npm run generate:feature -- --name=products
|
|
14
|
-
npm run generate:feature -- --name=user-profile
|
|
15
|
-
npm run generate:feature -- --name=shopping-cart
|
|
16
|
-
|
|
17
|
-
# Interactive mode with prompts:
|
|
18
|
-
npm run generate:feature:interactive
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Arguments
|
|
22
|
-
|
|
23
|
-
| Argument | Description | Required |
|
|
24
|
-
| --- | --- | --- |
|
|
25
|
-
| `--name` | The name of the feature to generate (kebab-case recommended). | Yes |
|
|
26
|
-
|
|
27
|
-
### Generated Structure
|
|
28
|
-
|
|
29
|
-
The generator will create the following structure in `apps/sandbox/src/app/features/<name>`:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
<feature-name>/
|
|
33
|
-
├── domain/
|
|
34
|
-
│ └── model.ts # Business entities (PascalCase interfaces)
|
|
35
|
-
├── infrastructure/
|
|
36
|
-
│ └── service.ts # HTTP services with CRUD operations
|
|
37
|
-
├── application/
|
|
38
|
-
│ └── store.ts # NgRx SignalStore for state management
|
|
39
|
-
└── ui/
|
|
40
|
-
└── component.ts # Standalone Angular component with store injection
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Naming Conventions
|
|
44
|
-
|
|
45
|
-
The generator automatically handles naming:
|
|
46
|
-
- **Input**: kebab-case (e.g., `user-profile`)
|
|
47
|
-
- **Classes/Interfaces**: PascalCase (e.g., `UserProfileComponent`, `UserProfile`)
|
|
48
|
-
- **Stores**: PascalCase with "Store" suffix (e.g., `UserProfileStore`)
|
|
49
|
-
- **Services**: PascalCase with "Service" suffix (e.g., `UserProfileService`)
|
|
50
|
-
|
|
51
|
-
### Example Generated Code
|
|
52
|
-
|
|
53
|
-
For `npm run generate:feature -- --name=product`:
|
|
54
|
-
|
|
55
|
-
**Domain Model** (`domain/model.ts`):
|
|
56
|
-
```typescript
|
|
57
|
-
export interface Product {
|
|
58
|
-
id: string;
|
|
59
|
-
createdAt: Date;
|
|
60
|
-
updatedAt: Date;
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Infrastructure Service** (`infrastructure/service.ts`):
|
|
65
|
-
```typescript
|
|
66
|
-
@Injectable({ providedIn: 'root' })
|
|
67
|
-
export class ProductService {
|
|
68
|
-
private readonly apiUrl = '/api/products';
|
|
69
|
-
|
|
70
|
-
getAll(): Observable<Product[]> { ... }
|
|
71
|
-
getById(id: string): Observable<Product> { ... }
|
|
72
|
-
create(data: Omit<Product, 'id' | 'createdAt' | 'updatedAt'>): Observable<Product> { ... }
|
|
73
|
-
update(id: string, data: Partial<Product>): Observable<Product> { ... }
|
|
74
|
-
delete(id: string): Observable<void> { ... }
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Application Store** (`application/store.ts`):
|
|
79
|
-
```typescript
|
|
80
|
-
export const ProductStore = signalStore(
|
|
81
|
-
{ providedIn: 'root' },
|
|
82
|
-
withState({ loading: false })
|
|
83
|
-
);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
**UI Component** (`ui/component.ts`):
|
|
87
|
-
```typescript
|
|
88
|
-
@Component({
|
|
89
|
-
selector: 'app-product-feature',
|
|
90
|
-
standalone: true,
|
|
91
|
-
imports: [CommonModule],
|
|
92
|
-
template: `
|
|
93
|
-
<div class="product-feature">
|
|
94
|
-
<h1>Product Feature</h1>
|
|
95
|
-
@if (store.loading()) {
|
|
96
|
-
<p>Loading...</p>
|
|
97
|
-
} @else {
|
|
98
|
-
<p>Ready to build your product feature!</p>
|
|
99
|
-
}
|
|
100
|
-
</div>
|
|
101
|
-
`,
|
|
102
|
-
})
|
|
103
|
-
export class ProductComponent {
|
|
104
|
-
protected readonly store = inject(ProductStore);
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Template Structure
|
|
109
|
-
|
|
110
|
-
Templates are located in `src/generators/clean-feature/files/`:
|
|
111
|
-
|
|
112
|
-
```
|
|
113
|
-
files/
|
|
114
|
-
├── application/
|
|
115
|
-
│ └── store.ts.template # SignalStore template
|
|
116
|
-
├── domain/
|
|
117
|
-
│ └── model.ts.template # Interface template
|
|
118
|
-
├── infrastructure/
|
|
119
|
-
│ └── service.ts.template # Service template with CRUD
|
|
120
|
-
└── ui/
|
|
121
|
-
└── component.ts.template # Component template with store
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Templates use EJS syntax:
|
|
125
|
-
- `<%= name %>` - Original kebab-case feature name
|
|
126
|
-
- `<%= pascalName %>` - PascalCase version of the name
|
|
127
|
-
|
|
128
|
-
## Development
|
|
129
|
-
|
|
130
|
-
### Build the CLI
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
npm run cli:build
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Test the CLI
|
|
137
|
-
|
|
138
|
-
```bash
|
|
139
|
-
npm run cli:test
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### Lint the CLI
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
npm run cli:lint
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### Development Workflow
|
|
149
|
-
|
|
150
|
-
1. **Modify templates** in `src/generators/clean-feature/files/`
|
|
151
|
-
2. **Update generator logic** in `generator.ts` if needed
|
|
152
|
-
3. **Build**: `npm run cli:build`
|
|
153
|
-
4. **Test**: Generate a feature with `npm run generate:feature -- --name=test`
|
|
154
|
-
5. **Verify**: Check generated code in `apps/sandbox/src/app/features/test`
|
|
155
|
-
6. **Clean up**: Delete test feature after verification
|
|
156
|
-
|
|
157
|
-
### Adding New Template Variables
|
|
158
|
-
|
|
159
|
-
To add new template variables:
|
|
160
|
-
|
|
161
|
-
1. Update `generator.ts`:
|
|
162
|
-
```typescript
|
|
163
|
-
generateFiles(tree, ..., targetPath, {
|
|
164
|
-
...options,
|
|
165
|
-
name,
|
|
166
|
-
pascalName,
|
|
167
|
-
yourNewVariable: computeValue(name), // Add here
|
|
168
|
-
tmpl: '',
|
|
169
|
-
});
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
2. Use in templates:
|
|
173
|
-
```typescript
|
|
174
|
-
// In any .template file
|
|
175
|
-
export class <%= yourNewVariable %>Something { }
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## Tips
|
|
179
|
-
|
|
180
|
-
- **Feature naming**: Use kebab-case for multi-word features (e.g., `user-profile`, not `userProfile`)
|
|
181
|
-
- **Testing**: Always test generated code in the sandbox app
|
|
182
|
-
- **Customization**: Modify templates to match your team's conventions
|
|
183
|
-
- **Validation**: Consider adding name validation in `generator.ts` to prevent duplicates
|