Skip to content

Commit f06efb4

Browse files
committed
feat(util): add new util library schematic
1 parent c7afff8 commit f06efb4

File tree

6 files changed

+287
-0
lines changed

6 files changed

+287
-0
lines changed

libs/ddd/collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"factory": "./src/schematics/ui/ui",
2222
"schema": "./src/schematics/ui/schema.json",
2323
"description": "adds a UI library to a domain or as a shared library"
24+
},
25+
"util": {
26+
"factory": "./src/schematics/util/util",
27+
"schema": "./src/schematics/util/schema.json",
28+
"description": "adds a utility library to a domain or as a shared library"
2429
}
2530
}
2631
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ toTypeScript
1313
toTypeScript
1414
.compileFromFile('libs/ddd/src/schematics/ui/schema.json')
1515
.then(ts => fs.writeFileSync('libs/ddd/src/schematics/ui/schema.ts', ts));
16+
17+
toTypeScript
18+
.compileFromFile('libs/ddd/src/schematics/util/schema.json')
19+
.then(ts => fs.writeFileSync('libs/ddd/src/schematics/util/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": "util-options",
4+
"type": "object",
5+
"properties": {
6+
"name": {
7+
"type": "string",
8+
"description": "Name of the utility 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+
}
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 UtilOptions {
9+
/**
10+
* Name of the utility 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+
}
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 { UtilOptions } from './schema';
7+
8+
describe('util', () => {
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 util lib is shared', async () => {
32+
const tree = await runSchematic<UtilOptions>(
33+
'util',
34+
{ name: 'form-validation', shared: true },
35+
appTree
36+
);
37+
38+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
39+
expect(nxJson.projects).toEqual({
40+
'shared-util-form-validation': {
41+
tags: ['domain:shared', 'type:util'],
42+
},
43+
});
44+
});
45+
46+
it('should add correct tags if util lib belongs to a domain', async () => {
47+
const tree = await runSchematic<UtilOptions>(
48+
'util',
49+
{ name: 'form-validation', domain: 'customer' },
50+
appTree
51+
);
52+
53+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
54+
expect(nxJson.projects).toEqual({
55+
'customer-util-form-validation': {
56+
tags: ['domain:customer', 'type:util'],
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<UtilOptions>('util', { name: 'form-validation' }, 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<UtilOptions>(
70+
'util',
71+
{ name: 'form-validation', 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<UtilOptions>(
79+
'util',
80+
{ name: 'form-validation', domain: 'customer', directory: 'forms' },
81+
appTree
82+
);
83+
84+
const workspaceJson = readJsonInTree(tree, '/workspace.json');
85+
expect(workspaceJson.projects).toHaveProperty(
86+
'customer-forms-util-form-validation'
87+
);
88+
expect(
89+
workspaceJson.projects['customer-forms-util-form-validation'].root
90+
).toEqual('libs/customer/forms/util-form-validation');
91+
});
92+
93+
it('should keep correct tags with a customized directory', async () => {
94+
const tree = await runSchematic<UtilOptions>(
95+
'util',
96+
{ name: 'form-validation', domain: 'customer', directory: 'forms' },
97+
appTree
98+
);
99+
100+
const nxJson = readJsonInTree<NxJson>(tree, '/nx.json');
101+
expect(nxJson.projects).toEqual({
102+
'customer-forms-util-form-validation': {
103+
tags: ['domain:customer', 'type:util'],
104+
},
105+
});
106+
});
107+
108+
it('should add valid import path to publishable lib', async () => {
109+
const tree = await runSchematic<UtilOptions>(
110+
'util',
111+
{ name: 'form-validation', shared: true, type: 'publishable' },
112+
appTree
113+
);
114+
115+
let ngPackage = readJsonInTree(
116+
tree,
117+
'libs/shared/util-form-validation/ng-package.json'
118+
);
119+
expect(ngPackage).toBeDefined();
120+
const packageJson = readJsonInTree(
121+
tree,
122+
'libs/shared/util-form-validation/package.json'
123+
);
124+
expect(packageJson.name).toEqual('@proj/shared-util-form-validation');
125+
});
126+
127+
it('should add valid import path to publishable lib with customized directory', async () => {
128+
const tree = await runSchematic<UtilOptions>(
129+
'util',
130+
{
131+
name: 'form-validation',
132+
shared: true,
133+
type: 'publishable',
134+
directory: 'forms',
135+
},
136+
appTree
137+
);
138+
139+
let ngPackage = readJsonInTree(
140+
tree,
141+
'libs/shared/forms/util-form-validation/ng-package.json'
142+
);
143+
expect(ngPackage).toBeDefined();
144+
const packageJson = readJsonInTree(
145+
tree,
146+
'libs/shared/forms/util-form-validation/package.json'
147+
);
148+
expect(packageJson.name).toEqual('@proj/shared-forms-util-form-validation');
149+
});
150+
});

libs/ddd/src/schematics/util/util.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 { UtilOptions } from './schema';
10+
11+
function validateInputs(options: UtilOptions): void {
12+
if (options.shared && options.domain) {
13+
throw new Error(`A utility library should either belong to a specific domain or be shared globally.
14+
If you want to share a utility 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 utilti 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: UtilOptions): Rule {
26+
return (host: Tree) => {
27+
validateInputs(options);
28+
29+
const libName = `util-${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:util`,
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)