Skip to content

Commit c7afff8

Browse files
Merge pull request #18 from pascalbe-dev/feature/15_add-ui-library
Feature/15 add ui library
2 parents 71ade1f + 421e5cd commit c7afff8

18 files changed

+3178
-11435
lines changed

.eslintrc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"parserOptions": {
55
"ecmaVersion": 2018,
66
"sourceType": "module",
7-
"project": "./tsconfig.json"
7+
"project": "./tsconfig.base.json"
88
},
99
"plugins": ["@typescript-eslint", "@nrwl/nx"],
1010
"extends": [
@@ -24,7 +24,10 @@
2424
"enforceBuildableLibDependency": true,
2525
"allow": [],
2626
"depConstraints": [
27-
{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }
27+
{
28+
"sourceTag": "*",
29+
"onlyDependOnLibsWithTags": ["*"]
30+
}
2831
]
2932
}
3033
]

apps/ddd-e2e/tsconfig.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
{
2-
"extends": "../../tsconfig.json",
2+
"extends": "../../tsconfig.base.json",
33
"compilerOptions": {
44
"types": ["node", "jest"]
55
},
6-
"include": ["**/*.ts"]
6+
"include": [],
7+
"files": [],
8+
"references": [
9+
{
10+
"path": "./tsconfig.spec.json"
11+
}
12+
]
713
}

libs/ddd/.eslintrc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
{ "extends": "../../.eslintrc", "rules": {} }
1+
{
2+
"extends": "../../.eslintrc",
3+
"rules": {},
4+
"ignorePatterns": ["!**/*"]
5+
}

libs/ddd/collection.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
"factory": "./src/schematics/feature/index",
1717
"schema": "./src/schematics/feature/schema.json",
1818
"description": "adds a feature lib to a domain"
19+
},
20+
"ui": {
21+
"factory": "./src/schematics/ui/ui",
22+
"schema": "./src/schematics/ui/schema.json",
23+
"description": "adds a UI library to a domain or as a shared library"
1924
}
20-
2125
}
22-
}
26+
}

libs/ddd/jest.config.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ module.exports = {
22
name: 'ddd',
33
preset: '../../jest.config.js',
44
transform: {
5-
'^.+\\.[tj]sx?$': 'ts-jest'
5+
'^.+\\.[tj]sx?$': 'ts-jest',
66
},
77
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
8-
coverageDirectory: '../../coverage/libs/ddd'
8+
coverageDirectory: '../../coverage/libs/ddd',
9+
globals: { 'ts-jest': { tsConfig: '<rootDir>/tsconfig.spec.json' } },
910
};

libs/ddd/src/schematics/json-schema-to-ts.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ toTypeScript
99
.compileFromFile('libs/ddd/src/schematics/feature/schema.json')
1010
.then(ts => fs.writeFileSync('libs/ddd/src/schematics/feature/schema.ts', ts));
1111

12+
13+
toTypeScript
14+
.compileFromFile('libs/ddd/src/schematics/ui/schema.json')
15+
.then(ts => fs.writeFileSync('libs/ddd/src/schematics/ui/schema.ts', ts));
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"id": "ui-options",
4+
"type": "object",
5+
"properties": {
6+
"name": {
7+
"type": "string",
8+
"description": "Name of the UI library",
9+
"$default": {
10+
"$source": "argv",
11+
"index": 0
12+
}
13+
},
14+
"shared": {
15+
"type": "boolean",
16+
"description": "Whether the library should be shared across all domains.",
17+
"default": false
18+
},
19+
"domain": {
20+
"type": "string",
21+
"description": "Domain name, if the library belongs to a certain domain."
22+
},
23+
"directory": {
24+
"type": "string",
25+
"description": "Subpath of the library beneath the domain or shared folder."
26+
},
27+
"type": {
28+
"type": "string",
29+
"enum": [
30+
"internal",
31+
"buildable",
32+
"publishable"
33+
],
34+
"description": "A type to determine if and how to build the library.",
35+
"default": "buildable"
36+
}
37+
},
38+
"required": [
39+
"name"
40+
]
41+
}

libs/ddd/src/schematics/ui/schema.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/* tslint:disable */
2+
/**
3+
* This file was automatically generated by json-schema-to-typescript.
4+
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5+
* and run json-schema-to-typescript to regenerate this file.
6+
*/
7+
8+
export interface UiOptions {
9+
/**
10+
* Name of the UI library
11+
*/
12+
name: string;
13+
/**
14+
* Whether the library should be shared across all domains.
15+
*/
16+
shared?: boolean;
17+
/**
18+
* Domain name, if the library belongs to a certain domain.
19+
*/
20+
domain?: string;
21+
/**
22+
* Subpath of the library beneath the domain or shared folder.
23+
*/
24+
directory?: string;
25+
/**
26+
* A type to determine if and how to build the library.
27+
*/
28+
type?: "internal" | "buildable" | "publishable";
29+
[k: string]: any;
30+
}

libs/ddd/src/schematics/ui/ui.spec.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { Tree } from '@angular-devkit/schematics';
2+
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
3+
import { NxJson, readJsonInTree } from '@nrwl/workspace';
4+
import { createEmptyWorkspace } from '@nrwl/workspace/testing';
5+
import { join } from 'path';
6+
import { UiOptions } from './schema';
7+
8+
describe('ui', () => {
9+
let appTree: Tree;
10+
11+
const testRunner = new SchematicTestRunner(
12+
'@angular-architects/ddd',
13+
join(__dirname, '../../../collection.json')
14+
);
15+
16+
function runSchematic<SchemaOptions = any>(
17+
schematicName: string,
18+
options: SchemaOptions,
19+
tree: Tree
20+
) {
21+
return testRunner
22+
.runSchematicAsync(schematicName, options, tree)
23+
.toPromise();
24+
}
25+
26+
beforeEach(() => {
27+
appTree = Tree.empty();
28+
appTree = createEmptyWorkspace(appTree);
29+
});
30+
31+
it('should add correct tags if ui lib is shared', async () => {
32+
const tree = await runSchematic<UiOptions>(
33+
'ui',
34+
{ name: 'form-components', shared: true },
35+
appTree
36+
);
37+
38+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
39+
expect(nxJson.projects).toEqual({
40+
'shared-ui-form-components': {
41+
tags: ['domain:shared', 'type:ui'],
42+
},
43+
});
44+
});
45+
46+
it('should add correct tags if ui lib belongs to a domain', async () => {
47+
const tree = await runSchematic<UiOptions>(
48+
'ui',
49+
{ name: 'form-components', domain: 'customer' },
50+
appTree
51+
);
52+
53+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
54+
expect(nxJson.projects).toEqual({
55+
'customer-ui-form-components': {
56+
tags: ['domain:customer', 'type:ui'],
57+
},
58+
});
59+
});
60+
61+
it('should throw error if neither domain nor shared option is provided', async () => {
62+
const schematicFunc = async () =>
63+
await runSchematic<UiOptions>('ui', { name: 'form-components' }, appTree);
64+
await expect(schematicFunc()).rejects.toThrowError();
65+
});
66+
67+
it('should throw error if domain and shared option is provided', async () => {
68+
const schematicFunc = async () =>
69+
await runSchematic<UiOptions>(
70+
'ui',
71+
{ name: 'form-components', domain: 'customer', shared: true },
72+
appTree
73+
);
74+
await expect(schematicFunc()).rejects.toThrowError();
75+
});
76+
77+
it('should be able to customize the directory of the library within the domain / shared folder', async () => {
78+
const tree = await runSchematic<UiOptions>(
79+
'ui',
80+
{ name: 'form-components', domain: 'customer', directory: 'forms' },
81+
appTree
82+
);
83+
84+
const workspaceJson = readJsonInTree(tree, '/workspace.json');
85+
expect(workspaceJson.projects).toHaveProperty(
86+
'customer-forms-ui-form-components'
87+
);
88+
expect(
89+
workspaceJson.projects['customer-forms-ui-form-components'].root
90+
).toEqual('libs/customer/forms/ui-form-components');
91+
});
92+
93+
it('should keep correct tags with a customized directory', async () => {
94+
const tree = await runSchematic<UiOptions>(
95+
'ui',
96+
{ name: 'form-components', domain: 'customer', directory: 'forms' },
97+
appTree
98+
);
99+
100+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
101+
expect(nxJson.projects).toEqual({
102+
'customer-forms-ui-form-components': {
103+
tags: ['domain:customer', 'type:ui'],
104+
},
105+
});
106+
});
107+
108+
it('should add valid import path to publishable lib', async () => {
109+
const tree = await runSchematic<UiOptions>(
110+
'ui',
111+
{ name: 'form-components', shared: true, type: 'publishable' },
112+
appTree
113+
);
114+
115+
let ngPackage = readJsonInTree(
116+
tree,
117+
'libs/shared/ui-form-components/ng-package.json'
118+
);
119+
expect(ngPackage).toBeDefined();
120+
const packageJson = readJsonInTree(
121+
tree,
122+
'libs/shared/ui-form-components/package.json'
123+
);
124+
expect(packageJson.name).toEqual('@proj/shared-ui-form-components');
125+
});
126+
127+
it('should add valid import path to publishable lib with customized directory', async () => {
128+
const tree = await runSchematic<UiOptions>(
129+
'ui',
130+
{
131+
name: 'form-components',
132+
shared: true,
133+
type: 'publishable',
134+
directory: 'forms',
135+
},
136+
appTree
137+
);
138+
139+
let ngPackage = readJsonInTree(
140+
tree,
141+
'libs/shared/forms/ui-form-components/ng-package.json'
142+
);
143+
expect(ngPackage).toBeDefined();
144+
const packageJson = readJsonInTree(
145+
tree,
146+
'libs/shared/forms/ui-form-components/package.json'
147+
);
148+
expect(packageJson.name).toEqual('@proj/shared-forms-ui-form-components');
149+
});
150+
});

libs/ddd/src/schematics/ui/ui.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { strings } from '@angular-devkit/core';
2+
import {
3+
chain,
4+
externalSchematic,
5+
Rule,
6+
Tree
7+
} from '@angular-devkit/schematics';
8+
import { getNpmScope } from '@nrwl/workspace';
9+
import { UiOptions } from './schema';
10+
11+
function validateInputs(options: UiOptions): void {
12+
if (options.shared && options.domain) {
13+
throw new Error(`A UI library should either belong to a specific domain or be shared globally.
14+
If you want to share a UI library across multiple specific domains,
15+
consider using an API library. Hence, you should not provide the shared option in combination
16+
with the domain option.`);
17+
}
18+
19+
if (!options.shared && !options.domain) {
20+
throw new Error(`A UI library should either belong to a domain or be shared globally.
21+
Please provide either of these two options: --domain / --shared`);
22+
}
23+
}
24+
25+
export default function (options: UiOptions): Rule {
26+
return (host: Tree) => {
27+
validateInputs(options);
28+
29+
const libName = `ui-${strings.dasherize(options.name)}`;
30+
const domain = options.shared ? 'shared' : options.domain;
31+
const libDirectory = options.directory
32+
? `${domain}/${options.directory}`
33+
: domain;
34+
const isPublishableLib = options.type === 'publishable';
35+
const npmScope = getNpmScope(host);
36+
const projectName = `${libDirectory}-${libName}`.replace(
37+
new RegExp('/', 'g'),
38+
'-'
39+
);
40+
const importPath = isPublishableLib
41+
? `@${npmScope}/${projectName}`
42+
: undefined;
43+
44+
return chain([
45+
externalSchematic('@nrwl/angular', 'lib', {
46+
name: libName,
47+
tags: `domain:${domain},type:ui`,
48+
style: 'scss',
49+
prefix: options.name,
50+
publishable: isPublishableLib,
51+
buildable: options.type === 'buildable',
52+
directory: libDirectory,
53+
importPath,
54+
}),
55+
]);
56+
};
57+
}

0 commit comments

Comments
 (0)