@ngflow/ng-architect 1.0.1 โ 1.0.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.
- package/README.md +31 -16
- package/package.json +1 -1
- package/src/ng-architect/files/src/app/features/__featureKebab__/__featureKebab__.routes.ts +9 -0
- package/src/ng-architect/files/src/app/features/__featureKebab__/data/__featureKebab__.service.ts +4 -0
- package/src/ng-architect/files/src/app/features/__featureKebab__/data/__featureKebab__.store.ts +13 -0
- package/src/ng-architect/files/src/app/features/__featureKebab__/models/__featureKebab__.model.ts +3 -0
- package/src/ng-architect/files/src/app/features/__featureKebab__/pages/__featureKebab__.page.ts +11 -0
- package/src/ng-architect/index.js +4 -3
- package/src/ng-architect/index.js.map +1 -1
- package/src/ng-architect/index.ts +76 -0
- package/src/ng-architect/index_spec.js +15 -6
- package/src/ng-architect/index_spec.js.map +1 -1
- package/src/ng-architect/index_spec.ts +24 -0
package/README.md
CHANGED
|
@@ -1,28 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@ngflow/ng-architect) [](https://opensource.org/licenses/MIT)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`ng-architect` is an **Angular schematic** that automates the creation of **complete features** while keeping a clean and consistent **project structure and architecture**.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
It generates **pages, services, models, store, and routes**, making your development faster and more organized.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ๐ฆ Installation
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
|
|
14
|
+
# Install as a dev dependency
|
|
15
|
+
npm install @ngflow/ng-architect --save-dev
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
### Publishing
|
|
18
|
+
## Usage
|
|
19
|
+
```bash
|
|
20
|
+
ng g @ngflow/ng-architect:ng-architect --name=<feature-name>
|
|
20
21
|
|
|
21
|
-
To publish, simply do:
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
npm publish
|
|
23
|
+
example:
|
|
24
|
+
ng g @ngflow/ng-architect:ng-architect --name=user-profile
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
```bash
|
|
28
|
+
This will create the following structure:
|
|
29
|
+
|
|
30
|
+
src/app/features/user-profile/
|
|
31
|
+
โโ data/
|
|
32
|
+
โ โโ user-profile.service.ts
|
|
33
|
+
โ โโ user-profile.store.ts
|
|
34
|
+
โโ models/
|
|
35
|
+
โ โโ user-profile.model.ts
|
|
36
|
+
โโ pages/
|
|
37
|
+
โโ user-profile.page.ts
|
|
38
|
+
โโ user-profile.page.html
|
|
39
|
+
โโ user-profile.page.scss
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
If app.routes.ts exists, the feature route will be added automatically.
|
|
43
|
+
```
|
package/package.json
CHANGED
package/src/ng-architect/files/src/app/features/__featureKebab__/data/__featureKebab__.store.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export interface <%= featurePascal %>State {
|
|
4
|
+
data: unknown;
|
|
5
|
+
loading: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const <%= featurePascal %>Store = {
|
|
9
|
+
state: signal<<%= featurePascal %>State>({
|
|
10
|
+
data: null,
|
|
11
|
+
loading: false,
|
|
12
|
+
}),
|
|
13
|
+
};
|
package/src/ng-architect/files/src/app/features/__featureKebab__/pages/__featureKebab__.page.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'app-<%= featureKebab %>',
|
|
5
|
+
standalone: true,
|
|
6
|
+
imports: [],
|
|
7
|
+
templateUrl: './<%= featureKebab %>.page.html',
|
|
8
|
+
styleUrl: './<%= featureKebab %>.page.scss',
|
|
9
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10
|
+
})
|
|
11
|
+
export class <%= featurePascal %>Page {}
|
|
@@ -9,7 +9,7 @@ const toKebabCase = (value) => value
|
|
|
9
9
|
.toLowerCase();
|
|
10
10
|
const toPascalCase = (value) => value
|
|
11
11
|
.split(/[-_]/)
|
|
12
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
12
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
13
13
|
.join('');
|
|
14
14
|
function default_1(options) {
|
|
15
15
|
const featureKebab = toKebabCase(options.name);
|
|
@@ -20,7 +20,7 @@ function default_1(options) {
|
|
|
20
20
|
},
|
|
21
21
|
// Generate files from templates
|
|
22
22
|
(0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./files'), [
|
|
23
|
-
(0, schematics_1.filter)(path => !path.endsWith('.swp')),
|
|
23
|
+
(0, schematics_1.filter)((path) => !path.endsWith('.swp')),
|
|
24
24
|
(0, schematics_1.template)({
|
|
25
25
|
featureKebab,
|
|
26
26
|
featurePascal,
|
|
@@ -28,9 +28,10 @@ function default_1(options) {
|
|
|
28
28
|
])),
|
|
29
29
|
// Update app.routes.ts if it exists
|
|
30
30
|
(tree, context) => {
|
|
31
|
+
var _a;
|
|
31
32
|
const appRoutesPath = 'src/app/app.routes.ts';
|
|
32
33
|
if (tree.exists(appRoutesPath)) {
|
|
33
|
-
let content = tree.read(appRoutesPath)
|
|
34
|
+
let content = ((_a = tree.read(appRoutesPath)) === null || _a === void 0 ? void 0 : _a.toString('utf-8')) || '';
|
|
34
35
|
const routePattern = new RegExp(`path:\\s*['"]${featureKebab}['"]`);
|
|
35
36
|
if (!routePattern.test(content)) {
|
|
36
37
|
const routeBlock = ` {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;AAyBA,4BAkDC;AA3ED,2DAUoC;AAEpC,mBAAmB;AACnB,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAC5C,KAAK;KACF,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC;KACnC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;KAClB,WAAW,EAAE,CAAC;AAEnB,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAC7C,KAAK;KACF,KAAK,CAAC,MAAM,CAAC;KACb,GAAG,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";;AAyBA,4BAkDC;AA3ED,2DAUoC;AAEpC,mBAAmB;AACnB,MAAM,WAAW,GAAG,CAAC,KAAa,EAAU,EAAE,CAC5C,KAAK;KACF,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC;KACnC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;KAClB,WAAW,EAAE,CAAC;AAEnB,MAAM,YAAY,GAAG,CAAC,KAAa,EAAU,EAAE,CAC7C,KAAK;KACF,KAAK,CAAC,MAAM,CAAC;KACb,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KAC3D,IAAI,CAAC,EAAE,CAAC,CAAC;AAEd,mBAAyB,OAAY;IACnC,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD,OAAO,IAAA,kBAAK,EAAC;QACX,CAAC,KAAW,EAAE,OAAyB,EAAE,EAAE;YACzC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,sBAAsB,aAAa,KAAK,YAAY,GAAG,CAAC,CAAC;QAC/E,CAAC;QAED,gCAAgC;QAChC,IAAA,sBAAS,EACP,IAAA,kBAAK,EAAC,IAAA,gBAAG,EAAC,SAAS,CAAC,EAAE;YACpB,IAAA,mBAAM,EAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACxC,IAAA,qBAAQ,EAAC;gBACP,YAAY;gBACZ,aAAa;aACd,CAAC;SACH,CAAC,CACH;QAED,oCAAoC;QACpC,CAAC,IAAU,EAAE,OAAyB,EAAE,EAAE;;YACxC,MAAM,aAAa,GAAG,uBAAuB,CAAC;YAE9C,IAAI,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC/B,IAAI,OAAO,GAAG,CAAA,MAAA,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,0CAAE,QAAQ,CAAC,OAAO,CAAC,KAAI,EAAE,CAAC;gBAEhE,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,gBAAgB,YAAY,MAAM,CAAC,CAAC;gBACpE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;oBAChC,MAAM,UAAU,GAAG;aAChB,YAAY;;2BAEE,YAAY,IAAI,YAAY;uBAChC,aAAa;KAC/B,CAAC;oBAEI,oCAAoC;oBACpC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,UAAU,MAAM,CAAC,CAAC;oBAC9D,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;oBACvC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,YAAY,8BAA8B,CAAC,CAAC;gBAC7E,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,YAAY,8BAA8B,CAAC,CAAC;gBAC9E,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;YAC7F,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Rule,
|
|
3
|
+
SchematicContext,
|
|
4
|
+
Tree,
|
|
5
|
+
apply,
|
|
6
|
+
chain,
|
|
7
|
+
filter,
|
|
8
|
+
mergeWith,
|
|
9
|
+
template,
|
|
10
|
+
url,
|
|
11
|
+
} from '@angular-devkit/schematics';
|
|
12
|
+
|
|
13
|
+
// Helper functions
|
|
14
|
+
const toKebabCase = (value: string): string =>
|
|
15
|
+
value
|
|
16
|
+
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
17
|
+
.replace(/_/g, '-')
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
|
|
20
|
+
const toPascalCase = (value: string): string =>
|
|
21
|
+
value
|
|
22
|
+
.split(/[-_]/)
|
|
23
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
24
|
+
.join('');
|
|
25
|
+
|
|
26
|
+
export default function (options: any): Rule {
|
|
27
|
+
const featureKebab = toKebabCase(options.name);
|
|
28
|
+
const featurePascal = toPascalCase(options.name);
|
|
29
|
+
|
|
30
|
+
return chain([
|
|
31
|
+
(_tree: Tree, context: SchematicContext) => {
|
|
32
|
+
context.logger.info(`โ
Criando feature: ${featurePascal} (${featureKebab})`);
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Generate files from templates
|
|
36
|
+
mergeWith(
|
|
37
|
+
apply(url('./files'), [
|
|
38
|
+
filter((path) => !path.endsWith('.swp')),
|
|
39
|
+
template({
|
|
40
|
+
featureKebab,
|
|
41
|
+
featurePascal,
|
|
42
|
+
}),
|
|
43
|
+
])
|
|
44
|
+
),
|
|
45
|
+
|
|
46
|
+
// Update app.routes.ts if it exists
|
|
47
|
+
(tree: Tree, context: SchematicContext) => {
|
|
48
|
+
const appRoutesPath = 'src/app/app.routes.ts';
|
|
49
|
+
|
|
50
|
+
if (tree.exists(appRoutesPath)) {
|
|
51
|
+
let content = tree.read(appRoutesPath)?.toString('utf-8') || '';
|
|
52
|
+
|
|
53
|
+
const routePattern = new RegExp(`path:\\s*['"]${featureKebab}['"]`);
|
|
54
|
+
if (!routePattern.test(content)) {
|
|
55
|
+
const routeBlock = ` {
|
|
56
|
+
path: '${featureKebab}',
|
|
57
|
+
loadChildren: () =>
|
|
58
|
+
import('./features/${featureKebab}/${featureKebab}.routes')
|
|
59
|
+
.then(m => m.${featurePascal}Routes),
|
|
60
|
+
},`;
|
|
61
|
+
|
|
62
|
+
// Insert before the closing bracket
|
|
63
|
+
content = content.replace(/\n\];\s*$/, `\n${routeBlock}\n];`);
|
|
64
|
+
tree.overwrite(appRoutesPath, content);
|
|
65
|
+
context.logger.info(`โ Rota "${featureKebab}" registada em app.routes.ts`);
|
|
66
|
+
} else {
|
|
67
|
+
context.logger.warn(`โน๏ธ Rota "${featureKebab}" jรก existe em app.routes.ts`);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
context.logger.warn('โ ๏ธ app.routes.ts nรฃo encontrado, rota nรฃo registada automaticamente');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return tree;
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
12
|
const schematics_1 = require("@angular-devkit/schematics");
|
|
4
13
|
const testing_1 = require("@angular-devkit/schematics/testing");
|
|
@@ -6,16 +15,16 @@ const path = require("path");
|
|
|
6
15
|
// SchematicTestRunner needs an absolute path to the collection to test.
|
|
7
16
|
const collectionPath = path.join(__dirname, '../collection.json');
|
|
8
17
|
describe('my-full-schematic', () => {
|
|
9
|
-
it('requires required option',
|
|
18
|
+
it('requires required option', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
10
19
|
// We test that
|
|
11
20
|
const runner = new testing_1.SchematicTestRunner('schematics', collectionPath);
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
it('works',
|
|
21
|
+
yield expectAsync(runner.runSchematic('my-full-schematic', {}, schematics_1.Tree.empty())).toBeRejected();
|
|
22
|
+
}));
|
|
23
|
+
it('works', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
15
24
|
const runner = new testing_1.SchematicTestRunner('schematics', collectionPath);
|
|
16
|
-
const tree =
|
|
25
|
+
const tree = yield runner.runSchematic('my-full-schematic', { name: 'str' }, schematics_1.Tree.empty());
|
|
17
26
|
// Listing files
|
|
18
27
|
expect(tree.files.sort()).toEqual(['/allo', '/hola', '/test1', '/test2']);
|
|
19
|
-
});
|
|
28
|
+
}));
|
|
20
29
|
});
|
|
21
30
|
//# sourceMappingURL=index_spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index_spec.js","sourceRoot":"","sources":["index_spec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index_spec.js","sourceRoot":"","sources":["index_spec.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,2DAAkD;AAClD,gEAAyE;AACzE,6BAA6B;AAE7B,wEAAwE;AACxE,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;AAElE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,EAAE,CAAC,0BAA0B,EAAE,GAAS,EAAE;QACxC,eAAe;QACf,MAAM,MAAM,GAAG,IAAI,6BAAmB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;QACrE,MAAM,WAAW,CACf,MAAM,CAAC,YAAY,CAAC,mBAAmB,EAAE,EAAE,EAAE,iBAAI,CAAC,KAAK,EAAE,CAAC,CAC3D,CAAC,YAAY,EAAE,CAAC;IACnB,CAAC,CAAA,CAAC,CAAC;IAEH,EAAE,CAAC,OAAO,EAAE,GAAS,EAAE;QACrB,MAAM,MAAM,GAAG,IAAI,6BAAmB,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAE3F,gBAAgB;QAChB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Tree } from '@angular-devkit/schematics';
|
|
2
|
+
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
// SchematicTestRunner needs an absolute path to the collection to test.
|
|
6
|
+
const collectionPath = path.join(__dirname, '../collection.json');
|
|
7
|
+
|
|
8
|
+
describe('my-full-schematic', () => {
|
|
9
|
+
it('requires required option', async () => {
|
|
10
|
+
// We test that
|
|
11
|
+
const runner = new SchematicTestRunner('schematics', collectionPath);
|
|
12
|
+
await expectAsync(
|
|
13
|
+
runner.runSchematic('my-full-schematic', {}, Tree.empty())
|
|
14
|
+
).toBeRejected();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('works', async () => {
|
|
18
|
+
const runner = new SchematicTestRunner('schematics', collectionPath);
|
|
19
|
+
const tree = await runner.runSchematic('my-full-schematic', { name: 'str' }, Tree.empty());
|
|
20
|
+
|
|
21
|
+
// Listing files
|
|
22
|
+
expect(tree.files.sort()).toEqual(['/allo', '/hola', '/test1', '/test2']);
|
|
23
|
+
});
|
|
24
|
+
});
|